Skip to content

Commit

Permalink
Merge pull request #1570 from ag-grid/AG-11385/initial_proxyInteracti…
Browse files Browse the repository at this point in the history
…onService

AG-11385 Initial ProxyInteractionService
  • Loading branch information
alantreadway committed May 13, 2024
2 parents 85eb31e + 037c39c commit 339ee1b
Show file tree
Hide file tree
Showing 20 changed files with 139 additions and 38 deletions.
10 changes: 9 additions & 1 deletion packages/ag-charts-community/src/chart/chartContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { AnnotationManager } from './annotation/annotationManager';
import type { ChartService } from './chartService';
import { DataService } from './data/dataService';
import { DOMManager } from './dom/domManager';
import { FocusIndicator } from './dom/focusIndicator';
import { ProxyInteractionService } from './dom/proxyInteractionService';
import { AnimationManager } from './interaction/animationManager';
import { AriaAnnouncementService } from './interaction/ariaAnnouncementServices';
import { ChartEventManager } from './interaction/chartEventManager';
Expand Down Expand Up @@ -45,9 +47,11 @@ export class ChartContext implements ModuleContext {
contextMenuRegistry: ContextMenuRegistry;
cursorManager: CursorManager;
domManager: DOMManager;
focusIndicator: FocusIndicator;
highlightManager: HighlightManager;
interactionManager: InteractionManager;
keyNavManager: KeyNavManager;
proxyInteractionService: ProxyInteractionService;
regionManager: RegionManager;
seriesStateManager: SeriesStateManager;
syncManager: SyncManager;
Expand Down Expand Up @@ -82,11 +86,13 @@ export class ChartContext implements ModuleContext {
this.highlightManager = new HighlightManager();
this.interactionManager = new InteractionManager(chart.keyboard, this.domManager);
this.keyNavManager = new KeyNavManager(this.interactionManager, this.domManager);
this.regionManager = new RegionManager(this.interactionManager, this.keyNavManager, this.domManager);
this.focusIndicator = new FocusIndicator(this.domManager);
this.regionManager = new RegionManager(this.interactionManager, this.keyNavManager, this.focusIndicator);
this.toolbarManager = new ToolbarManager();
this.gestureDetector = new GestureDetector(this.domManager);
this.layoutService = new LayoutService();
this.updateService = new UpdateService(updateCallback);
this.proxyInteractionService = new ProxyInteractionService(this.updateService, this.focusIndicator);
this.seriesStateManager = new SeriesStateManager();
this.callbackCache = new CallbackCache();

Expand All @@ -101,7 +107,9 @@ export class ChartContext implements ModuleContext {
destroy() {
// chart.ts handles the destruction of the scene and zoomManager.
this.tooltipManager.destroy();
this.proxyInteractionService.destroy();
this.regionManager.destroy();
this.focusIndicator.destroy();
this.keyNavManager.destroy();
this.interactionManager.destroy();
this.animationManager.stop();
Expand Down
9 changes: 4 additions & 5 deletions packages/ag-charts-community/src/chart/dom/focusIndicator.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { BBoxValues } from '../../util/bboxinterface';
import { setElementBBox } from '../../util/dom';
import type { DOMManager } from './domManager';
import * as focusStyles from './focusStyles';

Expand All @@ -16,16 +18,13 @@ export class FocusIndicator {
this.domManager.removeChild('canvas-overlay', focusStyles.block);
}

public updateBBox(rect?: { x: number; y: number; width: number; height: number }) {
public updateBBox(rect: BBoxValues | undefined) {
if (rect == null) {
this.element.classList.add(focusStyles.modifiers.hidden);
return;
}

this.element.classList.remove(focusStyles.modifiers.hidden);
this.element.style.width = `${rect.width}px`;
this.element.style.height = `${rect.height}px`;
this.element.style.left = `${rect.x}px`;
this.element.style.top = `${rect.y}px`;
setElementBBox(this.element, rect);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { BBoxProvider, BBoxValues } from '../../util/bboxinterface';
import { Debug } from '../../util/debug';
import { createElement, setElementBBox } from '../../util/dom';
import type { UpdateService } from '../updateService';
import type { FocusIndicator } from './focusIndicator';

type ProxyType = 'button';

type ProxyParams<T extends ProxyType> = {
type: T;
id: string;
textContext: string;
parent: HTMLElement;
bboxprovider: BBoxProvider<BBoxValues>;
onclick?: (ev: MouseEvent) => void;
};

type ProxyReturnMap = {
button: HTMLButtonElement;
};

type ProxyNode = {
element: HTMLElement;
bboxprovider: BBoxProvider<BBoxValues>;
};

export class ProxyInteractionService {
// This debug option make the proxies button partially transparent instead of fully transparent.
// To enabled this option, set window.agChartsDebug = ['showDOMProxies'].
private readonly debugShowDOMProxies: boolean = Debug.check('showDOMProxies');

private readonly nodes: ProxyNode[] = [];
private readonly destroyFns: (() => void)[];

constructor(
updateService: UpdateService,
private readonly focusIndicator: FocusIndicator
) {
this.destroyFns = [updateService.addListener('update-complete', () => this.update())];
}

public destroy() {
this.destroyFns.forEach((d) => d());
}

private update() {
for (const { element, bboxprovider } of this.nodes) {
setElementBBox(element, bboxprovider.getCachedBBox());
}
}

createProxyElement<T extends ProxyType>(params: ProxyParams<T>): ProxyReturnMap[T] | undefined {
const { type, id, parent, bboxprovider, textContext, onclick } = params;
if (type === 'button') {
const newButton = createElement('button');

newButton.id = id;
newButton.textContent = textContext;
newButton.style.pointerEvents = 'none';
newButton.style.opacity = this.debugShowDOMProxies ? '0.25' : '0';
newButton.addEventListener('focus', (_event: FocusEvent): any => {
newButton.style.setProperty('pointerEvents', null);
this.focusIndicator.updateBBox(bboxprovider.getCachedBBox());
});
newButton.addEventListener('blur', (_event: FocusEvent): any => {
newButton.style.pointerEvents = 'none';
this.focusIndicator.updateBBox(undefined);
});
if (onclick) {
newButton.addEventListener('click', onclick);
}

this.nodes.push({ element: newButton, bboxprovider });
parent.appendChild(newButton);
return newButton;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { BBox } from '../../scene/bbox';
import type { BBoxContainsTester, BBoxProvider, BBoxValues } from '../../util/bboxinterface';
import { Listeners } from '../../util/listeners';
import type { DOMManager } from '../dom/domManager';
import { FocusIndicator } from '../dom/focusIndicator';
import type { FocusIndicator } from '../dom/focusIndicator';
import { buildConsumable } from './consumableEvent';
import type { InteractionManager, PointerInteractionEvent, PointerInteractionTypes } from './interactionManager';
import { InteractionState, POINTER_INTERACTION_TYPES } from './interactionManager';
Expand All @@ -28,21 +27,18 @@ type TypeInfo = { [K in PointerInteractionTypes]: PointerInteractionEvent<K> } &

type RegionEvent = PointerInteractionEvent | KeyNavEvent;
type RegionHandler = (event: RegionEvent) => void;
type RegionBBoxProvider = BBoxProvider<BBoxContainsTester & { width: number; height: number }>;

class RegionListeners extends Listeners<RegionEvent['type'], RegionHandler> {}

interface BBoxProvider {
getCachedBBox(): BBox;
}

type Region = {
readonly properties: RegionProperties;
readonly listeners: RegionListeners;
};

export interface RegionProperties {
readonly name: RegionName;
readonly bboxproviders: BBoxProvider[];
readonly bboxproviders: RegionBBoxProvider[];
canInteraction(): boolean;
}

Expand All @@ -67,7 +63,6 @@ function addHandler<T extends RegionEvent['type']>(

export class RegionManager {
private currentTabIndex = 0;
public readonly focusIndicator: FocusIndicator;

private currentRegion?: Region;
private isDragging = false;
Expand All @@ -80,7 +75,7 @@ export class RegionManager {
constructor(
private readonly interactionManager: InteractionManager,
private readonly keyNavManager: KeyNavManager,
domManager: DOMManager
private readonly focusIndicator: FocusIndicator
) {
this.destroyFns.push(
...POINTER_INTERACTION_TYPES.map((eventName) =>
Expand All @@ -93,8 +88,6 @@ export class RegionManager {
this.keyNavManager.addListener('nav-hori', this.onNav.bind(this)),
this.keyNavManager.addListener('submit', this.onNav.bind(this))
);

this.focusIndicator = new FocusIndicator(domManager);
}

public destroy() {
Expand All @@ -115,7 +108,7 @@ export class RegionManager {
return this.makeObserver(region);
}

public addRegion(name: RegionName, bboxprovider: BBoxProvider, ...extraProviders: BBoxProvider[]) {
public addRegion(name: RegionName, bboxprovider: RegionBBoxProvider, ...extraProviders: RegionBBoxProvider[]) {
return this.addRegionFromProperties({
name,
bboxproviders: [bboxprovider, ...extraProviders],
Expand Down Expand Up @@ -334,7 +327,7 @@ export class RegionManager {
this.dispatch(focusedRegion, event);
}

public updateFocusIndicatorRect(rect?: { x: number; y: number; width: number; height: number }) {
public updateFocusIndicatorRect(rect?: BBoxValues) {
this.focusIndicator.updateBBox(rect);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { Group } from '../../../scene/group';
import type { DistantObject } from '../../../scene/nearest';
import type { Node } from '../../../scene/node';
import type { Point } from '../../../scene/point';
import type { QuadtreeNearest } from '../../../scene/util/quadtree';
import { Logger } from '../../../util/logger';
import type { DistantObject } from '../../../util/nearest';
import type { SeriesNodePickMatch } from '../series';
import type { SeriesNodeDatum } from '../seriesTypes';

Expand Down
2 changes: 1 addition & 1 deletion packages/ag-charts-community/src/chart/series/series.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import type { ScaleType } from '../../scale/scale';
import type { BBox } from '../../scene/bbox';
import { Group } from '../../scene/group';
import type { ZIndexSubOrder } from '../../scene/layersManager';
import { type DistantObject, nearestSquared } from '../../scene/nearest';
import type { Node } from '../../scene/node';
import type { Point } from '../../scene/point';
import type { PlacedLabel, PointLabelDatum } from '../../scene/util/labelPlacement';
import { createId } from '../../util/id';
import { jsonDiff } from '../../util/json';
import { Listeners } from '../../util/listeners';
import { type DistantObject, nearestSquared } from '../../util/nearest';
import { clamp } from '../../util/number';
import { mergeDefaults } from '../../util/object';
import type { TypedEvent } from '../../util/observable';
Expand Down
2 changes: 0 additions & 2 deletions packages/ag-charts-community/src/integrated-charts-scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ export { Tooltip, toTooltipHtml } from './chart/tooltip/tooltip';
export type { TooltipMeta } from './chart/tooltip/tooltip';
export { BBox } from './scene/bbox';
export { SectorBox } from './scene/sectorBox';
export type { DistantObject, NearestResult } from './scene/nearest';
export { nearestSquared, nearestSquaredInContainer } from './scene/nearest';
export { HdpiCanvas } from './scene/canvas/hdpiCanvas';
export { Image } from './scene/image';
export { ExtendedPath2D } from './scene/extendedPath2D';
Expand Down
1 change: 1 addition & 0 deletions packages/ag-charts-community/src/module-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export { extractDecoratedProperties, isDecoratedObject, listDecoratedProperties
export * from './util/dom';
export * from './util/deprecation';
export * from './util/json';
export * from './util/nearest';
export * from './util/number';
export * from './util/object';
export * from './util/properties';
Expand Down
7 changes: 4 additions & 3 deletions packages/ag-charts-community/src/scene/bbox.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { BBoxContainsTester, BBoxValues } from '../util/bboxinterface';
import { type Interpolating, interpolate } from '../util/interpolating';
import type { DistantObject, NearestResult } from '../util/nearest';
import { nearestSquared } from '../util/nearest';
import { clamp } from '../util/number';
import type { DistantObject, NearestResult } from './nearest';
import { nearestSquared } from './nearest';

// For small data structs like a bounding box, objects are superior to arrays
// in terms of performance (by 3-4% in Chrome 71, Safari 12 and by 20% in Firefox 64).
Expand All @@ -20,7 +21,7 @@ type Padding = {

type ShrinkOrGrowPosition = 'top' | 'left' | 'bottom' | 'right' | 'vertical' | 'horizontal';

export class BBox implements DistantObject, Interpolating<BBox> {
export class BBox implements BBoxValues, BBoxContainsTester, DistantObject, Interpolating<BBox> {
x: number;
y: number;
width: number;
Expand Down
2 changes: 1 addition & 1 deletion packages/ag-charts-community/src/scene/shape/line.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { lineDistanceSquared } from '../../util/distance';
import type { DistantObject } from '../../util/nearest';
import { BBox } from '../bbox';
import type { DistantObject } from '../nearest';
import type { NodeOptions, RenderContext } from '../node';
import { RedrawType, SceneChangeDetection } from '../node';
import { Shape } from './shape';
Expand Down
2 changes: 1 addition & 1 deletion packages/ag-charts-community/src/scene/shape/path.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DistantObject } from '../../util//nearest';
import { ExtendedPath2D } from '../extendedPath2D';
import type { DistantObject } from '../nearest';
import type { RenderContext } from '../node';
import { RedrawType, SceneChangeDetection } from '../node';
import { Shape } from './shape';
Expand Down
2 changes: 1 addition & 1 deletion packages/ag-charts-community/src/scene/shape/rect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { DistantObject } from '../../util/nearest';
import { BBox } from '../bbox';
import { ExtendedPath2D } from '../extendedPath2D';
import type { DistantObject } from '../nearest';
import { Path, ScenePathChangeDetection } from './path';
import { Shape } from './shape';

Expand Down
2 changes: 1 addition & 1 deletion packages/ag-charts-community/src/scene/util/quadtree.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type DistantObject, type NearestResult, nearestSquared } from '../../util/nearest';
import { BBox } from '../bbox';
import { type DistantObject, type NearestResult, nearestSquared } from '../nearest';

type QuadtreeNearestResult<V> = NearestResult<QuadtreeElem<HitTesterNearest, V>>;

Expand Down
14 changes: 14 additions & 0 deletions packages/ag-charts-community/src/util/bboxinterface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface BBoxValues {
x: number;
y: number;
width: number;
height: number;
}

export interface BBoxContainsTester {
containsPoint(x: number, y: number): boolean;
}

export interface BBoxProvider<T = BBoxValues> {
getCachedBBox(): T;
}
9 changes: 9 additions & 0 deletions packages/ag-charts-community/src/util/dom.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { BBoxValues } from './bboxinterface';

const verifiedGlobals = {} as { document: Document; window: Window };

if (typeof window !== 'undefined') {
Expand Down Expand Up @@ -70,3 +72,10 @@ export function setDocument(document: Document) {
export function setWindow(window: Window) {
verifiedGlobals.window = window;
}

export function setElementBBox(element: HTMLElement, bbox: BBoxValues) {
element.style.width = `${bbox.width}px`;
element.style.height = `${bbox.height}px`;
element.style.left = `${bbox.x}px`;
element.style.top = `${bbox.y}px`;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { AgErrorBarFormatterParams, AgErrorBarOptions, AgErrorBarThemeableOptions } from 'ag-charts-community';
import { _ModuleSupport, _Scene } from 'ag-charts-community';

const { partialAssign, mergeDefaults } = _ModuleSupport;
const { nearestSquaredInContainer, partialAssign, mergeDefaults } = _ModuleSupport;
const { BBox } = _Scene;
type BBox = _Scene.BBox;
type NearestResult<T> = _Scene.NearestResult<T>;
type NearestResult<T> = _ModuleSupport.NearestResult<T>;

export type ErrorBarNodeDatum = _ModuleSupport.CartesianSeriesNodeDatum & _ModuleSupport.ErrorBoundSeriesNodeDatum;
export type ErrorBarStylingOptions = Omit<AgErrorBarThemeableOptions, 'cap'>;
Expand Down Expand Up @@ -243,7 +243,7 @@ export class ErrorBarGroup extends _Scene.Group {
}

nearestSquared(x: number, y: number): _ModuleSupport.PickNodeDatumResult {
const { nearest, distanceSquared } = _Scene.nearestSquaredInContainer(x, y, this);
const { nearest, distanceSquared } = nearestSquaredInContainer(x, y, this);
if (nearest !== undefined && !isNaN(distanceSquared)) {
return { datum: nearest.datum, distanceSquared };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ enum GroupTags {
Cap,
}

export class BoxPlotGroup extends Group implements _Scene.DistantObject {
export class BoxPlotGroup extends Group implements _ModuleSupport.DistantObject {
constructor() {
super();
this.append([
Expand Down Expand Up @@ -163,7 +163,7 @@ export class BoxPlotGroup extends Group implements _Scene.DistantObject {

distanceSquared(x: number, y: number): number {
const nodes = Selection.selectByClass<_Scene.Rect | _Scene.Line>(this, Rect, Line);
return _Scene.nearestSquared(x, y, nodes).distanceSquared;
return _ModuleSupport.nearestSquared(x, y, nodes).distanceSquared;
}

get midPoint(): { x: number; y: number } {
Expand Down

0 comments on commit 339ee1b

Please sign in to comment.