@gravity-ui/graph
Version:
Modern graph editor component
222 lines (221 loc) • 11 kB
TypeScript
import { Graph } from "../graph";
import { TGraphColors, TGraphConstants } from "../graphConfig";
import { GraphEventsDefinitions } from "../graphEvents";
import { CoreComponent } from "../lib";
import { Component, TComponentState } from "../lib/Component";
import { ICamera, TCameraState } from "./camera/CameraService";
import "./Layer.css";
export type LayerPropsElementProps = {
zIndex: number;
classNames?: string[];
root?: HTMLElement;
transformByCameraPosition?: boolean;
};
export type LayerPropsHtmlElementProps = LayerPropsElementProps & {
/**
* Minimum camera scale at which the HTML layer becomes active.
* When camera scale is below this value, the HTML layer is disabled
* and won't receive updates (improving performance when zoomed out).
*
* @example
* ```typescript
* // HTML layer only active when zoomed in (scale >= 0.5)
* html: { zIndex: 1, activationScale: 0.5 }
* ```
*/
activationScale?: number;
};
export type LayerProps = {
canvas?: LayerPropsElementProps & {
respectPixelRatio?: boolean;
alpha?: boolean;
desynchronized?: boolean;
willReadFrequently?: boolean;
};
html?: LayerPropsHtmlElementProps;
root?: HTMLElement;
camera: ICamera;
graph: Graph;
};
export type LayerContext = {
graph: Graph;
camera: ICamera;
constants: TGraphConstants;
colors: TGraphColors;
graphCanvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
layer: Layer;
};
/**
* Utility type to extract public props for a Layer constructor.
* Excludes internal props that are provided by the graph instance:
* - root: managed by the layers service
* - camera: provided by the graph's camera service
* - graph: provided by the graph instance
*
* The root prop is made optional as it can be overridden by the user.
*
* @template T - Layer constructor type
*/
export type LayerPublicProps<T extends Constructor<Layer>> = T extends Constructor<Layer<infer Props>> ? Omit<Props, "root" | "camera" | "graph"> & {
root?: Props["root"];
} : never;
export type LayerConstructor<T extends Constructor<Layer>> = T extends Constructor<Layer> ? T : never;
export declare class Layer<Props extends LayerProps = LayerProps, Context extends LayerContext = LayerContext, State extends TComponentState = TComponentState> extends Component<Props, State, Context> {
static id?: string;
protected canvas: HTMLCanvasElement;
protected html: HTMLElement;
protected root?: HTMLDivElement;
protected attached: boolean;
/**
* Indicates whether the HTML layer is currently active based on camera scale.
* When false, the HTML layer is hidden and won't receive updates.
*/
protected htmlActive: boolean;
/**
* AbortController used to manage event listeners.
* All event listeners (both graph.on and DOM addEventListener) are registered with this controller's signal.
* When the layer is unmounted, the controller is aborted, which automatically removes all event listeners.
*/
protected eventAbortController: AbortController;
/**
* A wrapper for this.props.graph.on that automatically includes the AbortController signal.
* The method is named onGraphEvent to indicate it's specifically for graph events.
* This simplifies event subscription and ensures proper cleanup when the layer is unmounted.
*
* IMPORTANT: Always use this method in the afterInit() method, NOT in the constructor.
* This ensures that event subscriptions are properly set up when the layer is reattached.
* When a layer is unmounted, the AbortController is aborted and a new one is created.
* When the layer is reattached, afterInit() is called again, which sets up new subscriptions
* with the new AbortController.
*
* @param eventName - The name of the event to subscribe to
* @param handler - The event handler function
* @param options - Additional options (optional)
* @returns The result of graph.on call (an unsubscribe function)
*/
protected onGraphEvent<EventName extends keyof GraphEventsDefinitions, Cb extends GraphEventsDefinitions[EventName]>(eventName: EventName, handler: Cb, options?: Omit<AddEventListenerOptions, "signal">): () => void;
hide(): void;
isHidden(): boolean;
show(): void;
/**
* A wrapper for HTMLElement.addEventListener that automatically includes the AbortController signal.
* This method is for adding event listeners to the HTML element of the layer.
* It simplifies event subscription and ensures proper cleanup when the layer is unmounted.
*
* IMPORTANT: Always use this method in the afterInit() method, NOT in the constructor.
* This ensures that event subscriptions are properly set up when the layer is reattached.
* When a layer is unmounted, the AbortController is aborted and a new one is created.
* When the layer is reattached, afterInit() is called again, which sets up new subscriptions
* with the new AbortController.
*
* @param eventName - The name of the DOM event to subscribe to
* @param handler - The event handler function
* @param options - Additional options (optional)
*/
protected onHtmlEvent<K extends keyof HTMLElementEventMap>(eventName: K, handler: ((this: HTMLElement, ev: HTMLElementEventMap[K]) => void) | EventListenerObject, options?: Omit<AddEventListenerOptions, "signal">): void;
/**
* A wrapper for HTMLCanvasElement.addEventListener that automatically includes the AbortController signal.
* This method is for adding event listeners to the canvas element of the layer.
* It simplifies event subscription and ensures proper cleanup when the layer is unmounted.
*
* IMPORTANT: Always use this method in the afterInit() method, NOT in the constructor.
* This ensures that event subscriptions are properly set up when the layer is reattached.
* When a layer is unmounted, the AbortController is aborted and a new one is created.
* When the layer is reattached, afterInit() is called again, which sets up new subscriptions
* with the new AbortController.
*
* @param eventName - The name of the DOM event to subscribe to
* @param handler - The event handler function
* @param options - Additional options (optional)
*/
protected onCanvasEvent<K extends keyof HTMLElementEventMap>(eventName: K, handler: ((this: HTMLCanvasElement, ev: HTMLElementEventMap[K]) => void) | EventListenerObject, options?: Omit<AddEventListenerOptions, "signal">): void;
/**
* A wrapper for HTMLElement.addEventListener that automatically includes the AbortController signal.
* This method is for adding event listeners to the root element of the layer.
* It simplifies event subscription and ensures proper cleanup when the layer is unmounted.
*
* IMPORTANT: Always use this method in the afterInit() method, NOT in the constructor.
* This ensures that event subscriptions are properly set up when the layer is reattached.
* When a layer is unmounted, the AbortController is aborted and a new one is created.
* When the layer is reattached, afterInit() is called again, which sets up new subscriptions
* with the new AbortController.
*
* @param eventName - The name of the DOM event to subscribe to
* @param handler - The event handler function
* @param options - Additional options (optional)
*/
protected onRootEvent<K extends keyof HTMLElementEventMap>(eventName: K, handler: ((this: HTMLElement, ev: HTMLElementEventMap[K]) => void) | EventListenerObject, options?: Omit<AddEventListenerOptions, "signal">): void;
/**
* Subscribes to a signal (with .subscribe) and automatically unsubscribes when the layer's AbortController is aborted.
*
* Usage:
* this.onSignal(signal, handler)
*
* @template S - Signal type (must have .subscribe method)
* @template T - Value type of the signal
* @param signal - Signal with .subscribe method (returns unsubscribe function)
* @param handler - Handler function to call on signal change
* @returns The unsubscribe function (called automatically on abort)
*/
protected onSignal<S extends {
subscribe: (handler: (value: T) => void) => () => void;
}, T = S extends {
subscribe: (handler: (value: infer U) => void) => () => void;
} ? U : unknown>(signal: S, handler: (value: T) => void): () => void;
constructor(props: Props, parent?: CoreComponent);
protected sizeTouched: boolean;
updateSize: () => void;
/**
* Called after initialization and when the layer is reattached.
* This is the proper place to set up event subscriptions using onGraphEvent().
*
* When a layer is unmounted, the AbortController is aborted and a new one is created.
* When the layer is reattached, this method is called again, which sets up new subscriptions
* with the new AbortController.
*
* All derived Layer classes should call super.afterInit() at the end of their afterInit method.
*/
protected afterInit(): void;
private readonly stopCameraMoving;
protected moving: boolean;
protected scheduleCameraChange(camera: TCameraState): void;
private handleCommittedCameraChange;
private applyCameraTransform;
/**
* Called when committed camera state changes (`graph.$camera` signal).
* Override in derived layers to react to pan, zoom, and resize.
*
* Built-in HTML/canvas transforms (`transformByCameraPosition`, `activationScale`)
* are applied before this hook via `applyCameraTransform`.
*
* @param camera - Committed camera state (same as `graph.$camera.value`)
*/
protected onCameraChange(_camera: TCameraState): void;
/**
* Called when the HTML layer's active state changes based on camera scale.
* Override this method to implement custom behavior when the layer activates/deactivates.
*
* @param active - Whether the HTML layer is now active
*/
protected onHtmlActiveChange(active: boolean): void;
/**
* Returns whether the HTML layer is currently active.
* The layer is inactive when camera scale is below the activationScale threshold.
*/
isHtmlActive(): boolean;
protected init(): void;
protected unmountLayer(): void;
protected unmount(): void;
getCanvas(): HTMLCanvasElement;
getHTML(): HTMLElement;
attachLayer(root: HTMLElement): void;
detachLayer(): void;
protected createCanvas(params: LayerProps["canvas"]): HTMLCanvasElement;
protected createHTML(params: LayerProps["html"]): HTMLDivElement;
getDRP(): number;
protected applyTransform(x: number, y: number, scale: number, respectPixelRatio?: boolean): void;
protected updateCanvasSize(): void;
resetTransform(): void;
protected render(): void;
}