Skip to content

Commit

Permalink
Merge pull request #1626 from ag-grid/AG-11133-annotations-deletion
Browse files Browse the repository at this point in the history
AG-11133 Add annotation delete on floating toolbar
  • Loading branch information
alantreadway committed May 30, 2024
2 parents 1c03f03 + 12f2277 commit 4695d07
Show file tree
Hide file tree
Showing 25 changed files with 693 additions and 241 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import type { AgToolbarOptions } from '../../options/chart/toolbarOptions';
import { BaseManager } from '../baseManager';
import type { ToolbarGroup } from '../toolbar/toolbarTypes';

type EventTypes = ToolbarButtonPressed | ToolbarButtonToggled | ToolbarGroupToggled | ToolbarProxyGroupOptions;
type ToolbarButtonPressed = 'button-pressed';
type ToolbarButtonToggled = 'button-toggled';
type ToolbarGroupToggled = 'group-toggled';
type ToolbarProxyGroupOptions = 'proxy-group-options';

type EventTypes =
| 'button-pressed'
| 'button-toggled'
| 'floating-anchor-changed'
| 'group-toggled'
| 'proxy-group-options';
type ToolbarEvent =
| ToolbarButtonPressedEvent
| ToolbarButtonToggledEvent
| ToolbarFloatingAnchorChangedEvent
| ToolbarGroupToggledEvent
| ToolbarProxyGroupOptionsEvent;
type ToolbarEventButtonValue<T extends ToolbarGroup> = NonNullable<
Expand All @@ -19,26 +20,29 @@ type ToolbarEventButtonValue<T extends ToolbarGroup> = NonNullable<

interface Event<T extends EventTypes> {
type: T;
group: ToolbarGroup;
}

export interface ToolbarGroupToggledEvent extends Event<ToolbarGroupToggled> {
group: ToolbarGroup;
export interface ToolbarGroupToggledEvent extends Event<'group-toggled'> {
caller: string;
visible: boolean;
}

export interface ToolbarButtonPressedEvent<T = any> extends Event<ToolbarButtonPressed> {
group: ToolbarGroup;
export interface ToolbarFloatingAnchorChangedEvent extends Event<'floating-anchor-changed'> {
anchor: { x: number; y: number };
}

export interface ToolbarButtonPressedEvent<T = any> extends Event<'button-pressed'> {
value: T;
}

export interface ToolbarButtonToggledEvent<T = any> extends Event<ToolbarButtonToggled> {
group: ToolbarGroup;
export interface ToolbarButtonToggledEvent<T = any> extends Event<'button-toggled'> {
value: T;
enabled: boolean;
}

export interface ToolbarProxyGroupOptionsEvent extends Event<ToolbarProxyGroupOptions> {
group: ToolbarGroup;
export interface ToolbarProxyGroupOptionsEvent extends Event<'proxy-group-options'> {
caller: string;
options: Partial<NonNullable<AgToolbarOptions[ToolbarGroup]>>;
}

Expand All @@ -54,15 +58,19 @@ export class ToolbarManager extends BaseManager<EventTypes, ToolbarEvent> {
this.listeners.dispatch('button-pressed', { type: 'button-pressed', group, value });
}

toggleGroup(group: ToolbarGroup, visible: boolean) {
this.listeners.dispatch('group-toggled', { type: 'group-toggled', group, visible });
toggleGroup(caller: string, group: ToolbarGroup, visible: boolean) {
this.listeners.dispatch('group-toggled', { type: 'group-toggled', caller, group, visible });
}

changeFloatingAnchor(group: ToolbarGroup, anchor: { x: number; y: number }) {
this.listeners.dispatch('floating-anchor-changed', { type: 'floating-anchor-changed', group, anchor });
}

toggleButton<T extends ToolbarGroup>(group: T, value: ToolbarEventButtonValue<T>, enabled: boolean) {
this.listeners.dispatch('button-toggled', { type: 'button-toggled', group, value, enabled });
}

proxyGroupOptions<T extends ToolbarGroup>(group: T, options: Partial<AgToolbarOptions[T]>) {
this.listeners.dispatch('proxy-group-options', { type: 'proxy-group-options', group, options });
proxyGroupOptions<T extends ToolbarGroup>(caller: string, group: T, options: Partial<AgToolbarOptions[T]>) {
this.listeners.dispatch('proxy-group-options', { type: 'proxy-group-options', caller, group, options });
}
}
111 changes: 62 additions & 49 deletions packages/ag-charts-community/src/chart/toolbar/toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { BOOLEAN, Validate } from '../../util/validation';
import { InteractionState, type PointerInteractionEvent } from '../interaction/interactionManager';
import type {
ToolbarButtonToggledEvent,
ToolbarFloatingAnchorChangedEvent,
ToolbarGroupToggledEvent,
ToolbarProxyGroupOptionsEvent,
} from '../interaction/toolbarManager';
Expand All @@ -22,7 +23,7 @@ import {
type ToolbarButton,
type ToolbarGroup,
ToolbarPosition,
isFloatingPosition,
isAnimatingFloatingPosition,
} from './toolbarTypes';
import { initToolbarKeyNav } from './toolbarUtil';

Expand All @@ -38,6 +39,10 @@ export class Toolbar extends BaseModuleInstance implements ModuleInstance {
this.onGroupChanged.bind(this, 'annotations'),
this.onGroupButtonsChanged.bind(this, 'annotations')
);
public annotationOptions = new ToolbarGroupProperties(
this.onGroupChanged.bind(this, 'annotationOptions'),
this.onGroupButtonsChanged.bind(this, 'annotationOptions')
);
public ranges = new ToolbarGroupProperties(
this.onGroupChanged.bind(this, 'ranges'),
this.onGroupButtonsChanged.bind(this, 'ranges')
Expand All @@ -57,6 +62,7 @@ export class Toolbar extends BaseModuleInstance implements ModuleInstance {
[ToolbarPosition.Right]: new Set(),
[ToolbarPosition.Bottom]: new Set(),
[ToolbarPosition.Left]: new Set(),
[ToolbarPosition.Floating]: new Set(),
[ToolbarPosition.FloatingTop]: new Set(),
[ToolbarPosition.FloatingBottom]: new Set(),
};
Expand All @@ -66,24 +72,28 @@ export class Toolbar extends BaseModuleInstance implements ModuleInstance {
[ToolbarPosition.Right]: {},
[ToolbarPosition.Bottom]: {},
[ToolbarPosition.Left]: {},
[ToolbarPosition.Floating]: {},
[ToolbarPosition.FloatingTop]: {},
[ToolbarPosition.FloatingBottom]: {},
};

private groupCallers: Record<ToolbarGroup, number> = {
annotations: 0,
ranges: 0,
zoom: 0,
private readonly groupCallers: Record<ToolbarGroup, Set<string>> = {
annotations: new Set(),
annotationOptions: new Set(),
ranges: new Set(),
zoom: new Set(),
};

private groupButtons: Record<ToolbarGroup, Array<HTMLButtonElement>> = {
annotations: [],
annotationOptions: [],
ranges: [],
zoom: [],
};

private groupDestroyFns: Record<ToolbarGroup, (() => void)[]> = {
private groupDestroyFns: Record<ToolbarGroup, Array<() => void>> = {
annotations: [],
annotationOptions: [],
ranges: [],
zoom: [],
};
Expand All @@ -96,30 +106,20 @@ export class Toolbar extends BaseModuleInstance implements ModuleInstance {
super();

ctx.domManager.addStyles(styles.block, styles.css);
this.elements = Object.values(ToolbarPosition)
.filter((v) => typeof v === 'string')
.reduce(
(r, n) => {
r[n] = ctx.domManager.addChild('canvas-overlay', `toolbar-${n}`);
return r;
},
{} as Record<ToolbarPosition, HTMLElement>
);

this.renderToolbar(ToolbarPosition.Top);
this.renderToolbar(ToolbarPosition.Right);
this.renderToolbar(ToolbarPosition.Bottom);
this.renderToolbar(ToolbarPosition.Left);
this.renderToolbar(ToolbarPosition.FloatingTop);
this.renderToolbar(ToolbarPosition.FloatingBottom);

this.elements = {} as Record<ToolbarPosition, HTMLElement>;
for (const position of TOOLBAR_POSITIONS) {
this.elements[position] = ctx.domManager.addChild('canvas-overlay', `toolbar-${position}`);
this.renderToolbar(position);
}
this.toggleVisibilities();

this.destroyFns.push(
ctx.interactionManager.addListener('hover', this.onHover.bind(this), InteractionState.All),
ctx.interactionManager.addListener('leave', this.onLeave.bind(this), InteractionState.All),
ctx.toolbarManager.addListener('button-toggled', this.onButtonToggled.bind(this)),
ctx.toolbarManager.addListener('group-toggled', this.onGroupToggled.bind(this)),
ctx.toolbarManager.addListener('floating-anchor-changed', this.onFloatingAnchorChanged.bind(this)),
ctx.toolbarManager.addListener('proxy-group-options', this.onProxyGroupOptions.bind(this)),
ctx.layoutService.addListener('layout-complete', this.onLayoutComplete.bind(this)),
() => this.destroyElements()
Expand Down Expand Up @@ -216,20 +216,30 @@ export class Toolbar extends BaseModuleInstance implements ModuleInstance {
}

private onGroupToggled(event: ToolbarGroupToggledEvent) {
const { group, visible } = event;
const { caller, group, visible } = event;

this.toggleGroup(group, visible);
this.toggleGroup(caller, group, visible);
this.toggleVisibilities();
}

private onFloatingAnchorChanged(event: ToolbarFloatingAnchorChangedEvent) {
const { group, anchor } = event;

if (!this.positions[ToolbarPosition.Floating].has(group)) return;

const element = this.elements[ToolbarPosition.Floating];
element.style.top = `${anchor.y - element.offsetHeight - this.margin}px`;
element.style.left = `${anchor.x - element.offsetWidth / 2}px`;
}

private onProxyGroupOptions(event: ToolbarProxyGroupOptionsEvent) {
const { group, options } = event;
const { caller, group, options } = event;

this.groupProxied.add(group);

this.createGroup(group, options.enabled, options.position);
this.createGroupButtons(group, options.buttons);
this.toggleGroup(group, options.enabled);
this.toggleGroup(caller, group, options.enabled);

this[group].set(options);
}
Expand Down Expand Up @@ -259,33 +269,36 @@ export class Toolbar extends BaseModuleInstance implements ModuleInstance {
const position = this[group].position ?? 'top';
const parent = this.positionAlignments[position][align];

if (!parent) return;

for (const options of buttons ?? []) {
const button = this.createButtonElement(group, options);
parent?.appendChild(button);
parent.appendChild(button);
this.groupButtons[group].push(button);
}
if (parent) {
let onfocus: ((ev: FocusEvent) => void) | undefined;
let onblur: ((ev: FocusEvent) => void) | undefined;
if (isFloatingPosition(position)) {
onfocus = () => this.translateFloatingElements(position, true);
onblur = () => this.translateFloatingElements(position, false);
}
this.groupDestroyFns[group] = initToolbarKeyNav({
orientation: 'horizontal',
toolbar: parent,
buttons: this.groupButtons[group],
onfocus,
onblur,
});

let onFocus;
let onBlur;

if (isAnimatingFloatingPosition(position)) {
onFocus = () => this.translateFloatingElements(position, true);
onBlur = () => this.translateFloatingElements(position, false);
}

this.groupDestroyFns[group] = initToolbarKeyNav({
orientation: 'horizontal',
toolbar: parent,
buttons: this.groupButtons[group],
onFocus,
onBlur,
});
}

private toggleGroup(group: ToolbarGroup, enabled?: boolean) {
private toggleGroup(caller: string, group: ToolbarGroup, enabled?: boolean) {
if (enabled) {
this.groupCallers[group] += 1;
this.groupCallers[group].add(caller);
} else {
this.groupCallers[group] = Math.max(0, this.groupCallers[group] - 1);
this.groupCallers[group].delete(caller);
}
}

Expand Down Expand Up @@ -343,17 +356,15 @@ export class Toolbar extends BaseModuleInstance implements ModuleInstance {
elements.left.style.height = `calc(100% - ${seriesRect.y + margin * 2}px)`;

elements[FloatingTop].style.top = `${seriesRect.y}px`;
elements[FloatingTop].style.paddingTop = `${margin}px`;

elements[FloatingBottom].style.top =
`${seriesRect.y + seriesRect.height - elements[FloatingBottom].offsetHeight}px`;
elements[FloatingBottom].style.paddingBottom = `${margin}px`;
}

private toggleVisibilities() {
if (this.elements == null) return;

const isGroupVisible = (group: ToolbarGroup) => this[group].enabled && this.groupCallers[group] > 0;
const isGroupVisible = (group: ToolbarGroup) => this[group].enabled && this.groupCallers[group].size > 0;
const isButtonVisible = (element: HTMLButtonElement) => (button: ToolbarButton) =>
(typeof button.value !== 'string' && typeof button.value !== 'number') ||
`${button.value}` === element.dataset.toolbarValue;
Expand Down Expand Up @@ -384,19 +395,21 @@ export class Toolbar extends BaseModuleInstance implements ModuleInstance {
const alignments = Object.values(positionAlignments[position]);
element.classList.toggle(styles.modifiers.floatingHidden, !visible);

const dir = position === ToolbarPosition.FloatingBottom ? 1 : -1;

for (const align of alignments) {
align.style.transform =
visible && align.style.transform !== ''
? 'translateY(0)'
: `translateY(${element.offsetHeight + margin}px)`;
: `translateY(${(element.offsetHeight + margin) * dir}px)`;
}
}

private renderToolbar(position = ToolbarPosition.Top) {
const element = this.elements[position];
element.classList.add(styles.block, styles.modifiers[position], styles.modifiers.preventFlash);

if (position === ToolbarPosition.FloatingTop || position === ToolbarPosition.FloatingBottom) {
if (isAnimatingFloatingPosition(position)) {
element.classList.add(styles.modifiers.floatingHidden);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ const annotations: AgToolbarOptions['annotations'] = {
],
};

const annotationOptions: AgToolbarOptions['annotationOptions'] = {
enabled: false,
position: 'floating',
align: 'start',
buttons: [{ icon: 'delete', tooltip: 'Delete Annotation', value: 'delete' }],
};

const ranges: AgToolbarOptions['ranges'] = {
enabled: false,
position: 'top',
Expand Down Expand Up @@ -84,6 +91,7 @@ export const ToolbarModule: Module = {
toolbar: {
enabled: true,
annotations,
annotationOptions,
ranges,
zoom,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { BaseProperties } from '../../util/properties';
import { ObserveChanges } from '../../util/proxy';
import { ARRAY, BOOLEAN, UNION, Validate } from '../../util/validation';
import { type ToolbarAlignment, type ToolbarButton, ToolbarPosition } from './toolbarTypes';
import {
TOOLBAR_ALIGNMENTS,
TOOLBAR_POSITIONS,
type ToolbarAlignment,
type ToolbarButton,
ToolbarPosition,
} from './toolbarTypes';

export class ToolbarGroupProperties extends BaseProperties {
@ObserveChanges<ToolbarGroupProperties>((target) => {
Expand All @@ -13,13 +19,13 @@ export class ToolbarGroupProperties extends BaseProperties {
@ObserveChanges<ToolbarGroupProperties>((target) => {
target.onChange(target.enabled);
})
@Validate(UNION(['start', 'center', 'end']), { optional: true })
@Validate(UNION([...TOOLBAR_ALIGNMENTS]), { optional: true })
align: ToolbarAlignment = 'start';

@ObserveChanges<ToolbarGroupProperties>((target) => {
target.onChange(target.enabled);
})
@Validate(UNION(['top', 'right', 'bottom', 'left', 'floating-top', 'floating-bottom']), { optional: true })
@Validate(UNION(TOOLBAR_POSITIONS), { optional: true })
position: ToolbarPosition = ToolbarPosition.Top;

@ObserveChanges<ToolbarGroupProperties>((target) => {
Expand Down
Loading

0 comments on commit 4695d07

Please sign in to comment.