UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

174 lines (173 loc) 9.1 kB
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 LayerProps = { canvas?: LayerPropsElementProps & { respectPixelRatio?: boolean; }; html?: LayerPropsElementProps; 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; /** * 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; protected onCameraChange(camera: TCameraState): void; 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; }