Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sequenced clicks / dblclicks #3247

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { Debug } from '../../util/debug';
import { Listeners } from '../../util/listeners';
import type { Widget } from '../../widget/widget';
import type { DragWidgetEvent, MouseWidgetEvent, WheelWidgetEvent } from '../../widget/widgetEvents';
import type {
DragWidgetEvent,
MouseWidgetEvent,
SequencedClickEvent,
WheelWidgetEvent,
} from '../../widget/widgetEvents';
import { InteractionManager } from './interactionManager';
import type { PointerInteractionTypes } from './interactionManager';
import { InteractionState } from './interactionManager';
import { type PreventableEvent, buildPreventable } from './preventableEvent';

type RegionName = 'root' | 'series';
type RegionInteractionTypes = PointerInteractionTypes | 'drag-start' | 'drag' | 'drag-end';
type RegionInteractionTypes = PointerInteractionTypes | 'drag-start' | 'drag' | 'drag-end' | 'sequenced-click';

export type RegionEvent<T extends RegionInteractionTypes = RegionInteractionTypes> = PreventableEvent & {
type: T;
Expand All @@ -20,7 +25,7 @@ export type RegionEvent<T extends RegionInteractionTypes = RegionInteractionType
deltaX: T extends 'wheel' ? number : never;
deltaY: T extends 'wheel' ? number : never;
sourceEvent: Event;
timestamp: number;
sequence: SequencedClickEvent['sequence'];
};

export type MockEvent = {
Expand All @@ -30,7 +35,7 @@ export type MockEvent = {
mockRegion?: Pick<RegionEvent, 'region' | 'canvasX' | 'canvasY' | 'regionX' | 'regionY'>;
};

type TWidgetEvent = DragWidgetEvent | MouseWidgetEvent | WheelWidgetEvent;
type TWidgetEvent = DragWidgetEvent | MouseWidgetEvent | WheelWidgetEvent | SequencedClickEvent;

// This type-map allows the compiler to automatically figure out the parameter type of handlers
// specifies through the `addListener` method (see the `makeObserver` method).
Expand All @@ -50,9 +55,6 @@ export interface RegionProperties {
widget?: Widget;
}

const DRAG_THRESHOLD_MS = 300;
const DRAG_THRESHOLD_PX = 3;

function addHandler<T extends RegionEvent['type']>(
listeners: RegionListeners | undefined,
interactionManager: InteractionManager,
Expand Down Expand Up @@ -116,9 +118,7 @@ export class RegionManager {
};
private readonly destroyFns: (() => void)[] = [];
private readonly allRegionsListeners = new RegionListeners();
private deferredDragStart?: RegionEvent<'drag-start'>;
private isDragMoving = false;
private blockNextClickEvent = false;

constructor(private readonly interactionManager: InteractionManager) {}

Expand All @@ -143,6 +143,7 @@ export class RegionManager {
root.addListener('drag-start', this.processPointerEvent);
root.addListener('drag-move', this.processPointerEvent);
root.addListener('drag-end', this.processPointerEvent);
root.addListener('sequenced-click', this.processPointerEvent);
}

getRegion(name: RegionName) {
Expand Down Expand Up @@ -186,6 +187,7 @@ export class RegionManager {
'drag-start': 'drag-start',
'drag-move': 'drag',
'drag-end': 'drag-end',
'sequenced-click': 'sequenced-click',
} as const;
return map[widgetEvent.type];
}
Expand Down Expand Up @@ -223,62 +225,19 @@ export class RegionManager {
sourceEvent: widgetEvent.sourceEvent,
type: this.widgetEventTypeToRegionEventType(widgetEvent, regionEventType),
region: current.properties.name,
timestamp: Date.now(),
sequence: [],
});

switch (event.type) {
case 'drag-start': {
this.deferredDragStart = event as RegionEvent<'drag-start'>;
break;
}
case 'drag': {
if (this.deferredDragStart) {
if (this.canStartDrag(event as RegionEvent<'drag'>)) {
this.dispatchEvent(current, this.deferredDragStart);
} else {
return;
}
}
this.dispatchEvent(current, event);
this.deferredDragStart = undefined;
this.isDragMoving = true;
this.blockNextClickEvent = true;
break;
}
case 'drag-end': {
if (this.isDragMoving) {
this.dispatchEvent(current, event);
}
this.deferredDragStart = undefined;
this.isDragMoving = false;
break;
}
case 'click': {
if (!this.blockNextClickEvent) {
this.dispatchEvent(current, event);
}
this.blockNextClickEvent = false;
break;
}
case 'leave':
case 'enter':
if (!this.isDragMoving || this.deferredDragStart != null) {
this.dispatchEvent(current, event);
}
break;
case 'hover': {
this.blockNextClickEvent = false;
this.dispatchEvent(current, event);
break;
}
default: {
this.dispatchEvent(current, event);
break;
}
if (widgetEvent.type === 'sequenced-click') {
event.sequence = widgetEvent.sequence;
}

if (event.type === 'drag-start') {
this.isDragMoving = true;
} else if (event.type === 'drag-end') {
this.isDragMoving = false;
}
}

private dispatchEvent(current: Region, event: RegionEvent) {
this.debug('Dispatching region event: ', event);
this.allRegionsListeners.dispatch(event.type, event);
current.listeners.dispatch(event.type, event);
Expand All @@ -289,7 +248,7 @@ export class RegionManager {
const { current } = this;

let newCurrent: Region | undefined = current;
if (!this.isDragMoving && this.deferredDragStart == null) {
if (!this.isDragMoving) {
switch (ignore) {
case 'wait':
return;
Expand Down Expand Up @@ -326,18 +285,4 @@ export class RegionManager {
}
return this.regions.series;
}

private canStartDrag(event: RegionEvent<'drag'>) {
if (!this.deferredDragStart) return false;

const time = event.timestamp - this.deferredDragStart.timestamp;
if (time > DRAG_THRESHOLD_MS) {
return true;
}

const distanceApproximation =
Math.abs(event.canvasX - this.deferredDragStart.canvasX) +
Math.abs(event.canvasY - this.deferredDragStart.canvasY);
return distanceApproximation > DRAG_THRESHOLD_PX;
}
}
30 changes: 30 additions & 0 deletions packages/ag-charts-community/src/widget/widgetEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,40 @@ export type DragWidgetEvent<T extends DragWidgetEventType = DragWidgetEventType>
sourceEvent: MouseEvent | TouchEvent;
};

export type SequenceEvent<T extends 'mouseup' | 'mousedown'> = {
type: T;
clientX: number;
clientY: number;
};

export type SequencedClickEvent = {
type: 'sequenced-click';
offsetX: number;
offsetY: number;
clientX: number;
clientY: number;
sourceEvent: MouseEvent;
sequence: [] | [SequenceEvent<'mousedown'>, SequenceEvent<'mouseup'>];
};

export type SequencedDblClickEvent = {
type: 'sequenced-dblclick';
offsetX: number;
offsetY: number;
clientX: number;
clientY: number;
sourceEvent: MouseEvent;
sequence:
| []
| [SequenceEvent<'mousedown'>, SequenceEvent<'mouseup'>, SequenceEvent<'mousedown'>, SequenceEvent<'mouseup'>];
};

export type WidgetEventMap = {
'drag-start': DragWidgetEvent<'drag-start'>;
'drag-move': DragWidgetEvent<'drag-move'>;
'drag-end': DragWidgetEvent<'drag-end'>;
'sequenced-click': SequencedClickEvent;
'sequenced-dblclick': SequencedDblClickEvent;
blur: FocusWidgetEvent<'blur'>;
change: WidgetEvent;
contextmenu: MouseWidgetEvent<'contextmenu'>;
Expand Down
Loading
Loading