diff --git a/src/canvas/FloatingRenderLink.ts b/src/canvas/FloatingRenderLink.ts index d5315faddd..1e41984c19 100644 --- a/src/canvas/FloatingRenderLink.ts +++ b/src/canvas/FloatingRenderLink.ts @@ -1,5 +1,6 @@ import type { RenderLink } from "./RenderLink" -import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget" +import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget" +import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap" import type { INodeOutputSlot, LinkNetwork } from "@/interfaces" import type { INodeInputSlot } from "@/interfaces" import type { Point } from "@/interfaces" @@ -113,7 +114,7 @@ export class FloatingRenderLink implements RenderLink { return true } - connectToInput(node: LGraphNode, input: INodeInputSlot, _events?: LinkConnectorEventTarget): void { + connectToInput(node: LGraphNode, input: INodeInputSlot, _events?: CustomEventTarget): void { const floatingLink = this.link floatingLink.target_id = node.id floatingLink.target_slot = node.inputs.indexOf(input) @@ -125,7 +126,7 @@ export class FloatingRenderLink implements RenderLink { input._floatingLinks.add(floatingLink) } - connectToOutput(node: LGraphNode, output: INodeOutputSlot, _events?: LinkConnectorEventTarget): void { + connectToOutput(node: LGraphNode, output: INodeOutputSlot, _events?: CustomEventTarget): void { const floatingLink = this.link floatingLink.origin_id = node.id floatingLink.origin_slot = node.outputs.indexOf(output) @@ -138,7 +139,7 @@ export class FloatingRenderLink implements RenderLink { connectToRerouteInput( reroute: Reroute, { node: inputNode, input }: { node: LGraphNode, input: INodeInputSlot }, - events: LinkConnectorEventTarget, + events: CustomEventTarget, ) { const floatingLink = this.link floatingLink.target_id = inputNode.id @@ -155,7 +156,7 @@ export class FloatingRenderLink implements RenderLink { reroute: Reroute, outputNode: LGraphNode, output: INodeOutputSlot, - events: LinkConnectorEventTarget, + events: CustomEventTarget, ) { const floatingLink = this.link floatingLink.origin_id = outputNode.id diff --git a/src/canvas/LinkConnector.ts b/src/canvas/LinkConnector.ts index 775b0c76c9..fb7b97d809 100644 --- a/src/canvas/LinkConnector.ts +++ b/src/canvas/LinkConnector.ts @@ -6,7 +6,8 @@ import type { Reroute } from "@/Reroute" import type { CanvasPointerEvent } from "@/types/events" import type { IWidget } from "@/types/widgets" -import { LinkConnectorEventMap, LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget" +import { CustomEventTarget } from "@/infrastructure/CustomEventTarget" +import { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap" import { LLink } from "@/LLink" import { LinkDirection } from "@/types/globalEnums" @@ -67,7 +68,7 @@ export class LinkConnector { snapLinksPos: undefined, } - readonly events = new LinkConnectorEventTarget() + readonly events = new CustomEventTarget() /** Contains information for rendering purposes only. */ readonly renderLinks: RenderLinkUnion[] = [] diff --git a/src/canvas/MovingInputLink.ts b/src/canvas/MovingInputLink.ts index 02cf4229bd..f667212412 100644 --- a/src/canvas/MovingInputLink.ts +++ b/src/canvas/MovingInputLink.ts @@ -1,4 +1,5 @@ -import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget" +import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget" +import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap" import type { INodeInputSlot, INodeOutputSlot, LinkNetwork, Point } from "@/interfaces" import type { LGraphNode } from "@/LGraphNode" import type { LLink } from "@/LLink" @@ -39,7 +40,7 @@ export class MovingInputLink extends MovingLinkBase { return reroute.origin_id !== this.inputNode.id } - connectToInput(inputNode: LGraphNode, input: INodeInputSlot, events: LinkConnectorEventTarget): LLink | null | undefined { + connectToInput(inputNode: LGraphNode, input: INodeInputSlot, events: CustomEventTarget): LLink | null | undefined { if (input === this.inputSlot) return this.inputNode.disconnectInput(this.inputIndex, true) @@ -55,7 +56,7 @@ export class MovingInputLink extends MovingLinkBase { connectToRerouteInput( reroute: Reroute, { node: inputNode, input, link: existingLink }: { node: LGraphNode, input: INodeInputSlot, link: LLink }, - events: LinkConnectorEventTarget, + events: CustomEventTarget, originalReroutes: Reroute[], ): void { const { outputNode, outputSlot, fromReroute } = this diff --git a/src/canvas/MovingLinkBase.ts b/src/canvas/MovingLinkBase.ts index 9e79c6e318..062d968c08 100644 --- a/src/canvas/MovingLinkBase.ts +++ b/src/canvas/MovingLinkBase.ts @@ -1,5 +1,6 @@ import type { RenderLink } from "./RenderLink" -import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget" +import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget" +import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap" import type { INodeInputSlot, INodeOutputSlot, LinkNetwork, Point } from "@/interfaces" import type { LGraphNode, NodeId } from "@/LGraphNode" import type { LLink } from "@/LLink" @@ -79,10 +80,10 @@ export abstract class MovingLinkBase implements RenderLink { this.inputPos = inputNode.getInputPos(inputIndex) } - abstract connectToInput(node: LGraphNode, input: INodeInputSlot, events?: LinkConnectorEventTarget): void - abstract connectToOutput(node: LGraphNode, output: INodeOutputSlot, events?: LinkConnectorEventTarget): void - abstract connectToRerouteInput(reroute: Reroute, { node, input, link }: { node: LGraphNode, input: INodeInputSlot, link: LLink }, events: LinkConnectorEventTarget, originalReroutes: Reroute[]): void - abstract connectToRerouteOutput(reroute: Reroute, outputNode: LGraphNode, output: INodeOutputSlot, events: LinkConnectorEventTarget): void + abstract connectToInput(node: LGraphNode, input: INodeInputSlot, events?: CustomEventTarget): void + abstract connectToOutput(node: LGraphNode, output: INodeOutputSlot, events?: CustomEventTarget): void + abstract connectToRerouteInput(reroute: Reroute, { node, input, link }: { node: LGraphNode, input: INodeInputSlot, link: LLink }, events: CustomEventTarget, originalReroutes: Reroute[]): void + abstract connectToRerouteOutput(reroute: Reroute, outputNode: LGraphNode, output: INodeOutputSlot, events: CustomEventTarget): void abstract disconnect(): boolean } diff --git a/src/canvas/MovingOutputLink.ts b/src/canvas/MovingOutputLink.ts index a10fee79d4..01d15858da 100644 --- a/src/canvas/MovingOutputLink.ts +++ b/src/canvas/MovingOutputLink.ts @@ -1,4 +1,5 @@ -import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget" +import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget" +import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap" import type { INodeInputSlot, INodeOutputSlot, LinkNetwork, Point } from "@/interfaces" import type { LGraphNode } from "@/LGraphNode" import type { LLink } from "@/LLink" @@ -43,7 +44,7 @@ export class MovingOutputLink extends MovingLinkBase { throw new Error("MovingOutputLink cannot connect to an input.") } - connectToOutput(outputNode: LGraphNode, output: INodeOutputSlot, events: LinkConnectorEventTarget): LLink | null | undefined { + connectToOutput(outputNode: LGraphNode, output: INodeOutputSlot, events: CustomEventTarget): LLink | null | undefined { if (output === this.outputSlot) return const link = outputNode.connectSlots(output, this.inputNode, this.inputSlot, this.link.parentId) @@ -59,7 +60,7 @@ export class MovingOutputLink extends MovingLinkBase { reroute: Reroute, outputNode: LGraphNode, output: INodeOutputSlot, - events: LinkConnectorEventTarget, + events: CustomEventTarget, ): void { // Moving output side of links const { inputNode, inputSlot, fromReroute } = this diff --git a/src/canvas/RenderLink.ts b/src/canvas/RenderLink.ts index c154887acf..34dd4c1c55 100644 --- a/src/canvas/RenderLink.ts +++ b/src/canvas/RenderLink.ts @@ -1,4 +1,5 @@ -import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget" +import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget" +import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap" import type { LinkNetwork, Point } from "@/interfaces" import type { LGraphNode } from "@/LGraphNode" import type { INodeInputSlot, INodeOutputSlot, LLink, Reroute } from "@/litegraph" @@ -25,13 +26,13 @@ export interface RenderLink { /** The reroute that the link is being connected from. */ readonly fromReroute?: Reroute - connectToInput(node: LGraphNode, input: INodeInputSlot, events?: LinkConnectorEventTarget): void - connectToOutput(node: LGraphNode, output: INodeOutputSlot, events?: LinkConnectorEventTarget): void + connectToInput(node: LGraphNode, input: INodeInputSlot, events?: CustomEventTarget): void + connectToOutput(node: LGraphNode, output: INodeOutputSlot, events?: CustomEventTarget): void connectToRerouteInput( reroute: Reroute, { node, input, link }: { node: LGraphNode, input: INodeInputSlot, link: LLink }, - events: LinkConnectorEventTarget, + events: CustomEventTarget, originalReroutes: Reroute[], ): void @@ -39,6 +40,6 @@ export interface RenderLink { reroute: Reroute, outputNode: LGraphNode, output: INodeOutputSlot, - events: LinkConnectorEventTarget, + events: CustomEventTarget, ): void } diff --git a/src/canvas/ToInputRenderLink.ts b/src/canvas/ToInputRenderLink.ts index a95815f6bf..7fcab80c0c 100644 --- a/src/canvas/ToInputRenderLink.ts +++ b/src/canvas/ToInputRenderLink.ts @@ -1,5 +1,6 @@ import type { RenderLink } from "./RenderLink" -import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget" +import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget" +import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap" import type { INodeInputSlot, INodeOutputSlot, LinkNetwork, Point } from "@/interfaces" import type { LGraphNode } from "@/LGraphNode" import type { LLink } from "@/LLink" @@ -39,7 +40,7 @@ export class ToInputRenderLink implements RenderLink { return false } - connectToInput(node: LGraphNode, input: INodeInputSlot, events: LinkConnectorEventTarget) { + connectToInput(node: LGraphNode, input: INodeInputSlot, events: CustomEventTarget) { const { node: outputNode, fromSlot, fromReroute } = this if (node === outputNode) return @@ -54,7 +55,7 @@ export class ToInputRenderLink implements RenderLink { input, link, }: { node: LGraphNode, input: INodeInputSlot, link: LLink }, - events: LinkConnectorEventTarget, + events: CustomEventTarget, originalReroutes: Reroute[], ) { const { node: outputNode, fromSlot, fromReroute } = this diff --git a/src/canvas/ToOutputRenderLink.ts b/src/canvas/ToOutputRenderLink.ts index 8130a75f3a..a00a601722 100644 --- a/src/canvas/ToOutputRenderLink.ts +++ b/src/canvas/ToOutputRenderLink.ts @@ -1,5 +1,6 @@ import type { RenderLink } from "./RenderLink" -import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget" +import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget" +import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap" import type { INodeInputSlot, INodeOutputSlot, LinkNetwork, Point } from "@/interfaces" import type { LGraphNode } from "@/LGraphNode" import type { Reroute } from "@/Reroute" @@ -43,7 +44,7 @@ export class ToOutputRenderLink implements RenderLink { return true } - connectToOutput(node: LGraphNode, output: INodeOutputSlot, events: LinkConnectorEventTarget) { + connectToOutput(node: LGraphNode, output: INodeOutputSlot, events: CustomEventTarget) { const { node: inputNode, fromSlot, fromReroute } = this if (!inputNode) return @@ -55,7 +56,7 @@ export class ToOutputRenderLink implements RenderLink { reroute: Reroute, outputNode: LGraphNode, output: INodeOutputSlot, - events: LinkConnectorEventTarget, + events: CustomEventTarget, ): void { const { node: inputNode, fromSlot } = this const newLink = outputNode.connectSlots(output, inputNode, fromSlot, reroute?.id) diff --git a/src/infrastructure/CustomEventTarget.ts b/src/infrastructure/CustomEventTarget.ts new file mode 100644 index 0000000000..d660657fa1 --- /dev/null +++ b/src/infrastructure/CustomEventTarget.ts @@ -0,0 +1,55 @@ +/** {@link Omit} all properties that evaluate to `never`. */ +type NeverNever = { + [K in keyof T as T[K] extends never ? never : K]: T[K] +} + +/** {@link Pick} only properties that evaluate to `never`. */ +type PickNevers = { + [K in keyof T as T[K] extends never ? K : never]: T[K] +} + +type EventListeners = { + readonly [K in keyof T]: ((this: EventTarget, ev: CustomEvent) => any) | EventListenerObject | null +} + +export class CustomEventTarget< + EventMap extends Record, + Keys extends keyof EventMap & string = keyof EventMap & string, +> extends EventTarget { + /** + * Type-safe event dispatching. + * @see {@link EventTarget.dispatchEvent} + * @param type Name of the event to dispatch + * @param detail A custom object to send with the event + * @returns `true` if the event was dispatched successfully, otherwise `false`. + */ + dispatch>(type: T, detail: EventMap[T]): boolean + dispatch>(type: T): boolean + dispatch(type: T, detail?: EventMap[T]) { + const event = new CustomEvent(type as string, { detail, cancelable: true }) + return super.dispatchEvent(event) + } + + override addEventListener( + type: K, + listener: EventListeners[K], + options?: boolean | AddEventListenerOptions, + ): void { + // Assertion: Contravariance on CustomEvent => Event + super.addEventListener(type as string, listener as EventListener, options) + } + + override removeEventListener( + type: K, + listener: EventListeners[K], + options?: boolean | EventListenerOptions, + ): void { + // Assertion: Contravariance on CustomEvent => Event + super.removeEventListener(type as string, listener as EventListener, options) + } + + /** @deprecated Use {@link dispatch}. */ + override dispatchEvent(event: never): boolean { + return super.dispatchEvent(event) + } +} diff --git a/src/infrastructure/LinkConnectorEventMap.ts b/src/infrastructure/LinkConnectorEventMap.ts new file mode 100644 index 0000000000..8264c227ca --- /dev/null +++ b/src/infrastructure/LinkConnectorEventMap.ts @@ -0,0 +1,47 @@ +import type { FloatingRenderLink } from "@/canvas/FloatingRenderLink" +import type { MovingInputLink } from "@/canvas/MovingInputLink" +import type { MovingOutputLink } from "@/canvas/MovingOutputLink" +import type { RenderLink } from "@/canvas/RenderLink" +import type { ToInputRenderLink } from "@/canvas/ToInputRenderLink" +import type { LGraphNode } from "@/LGraphNode" +import type { LLink } from "@/LLink" +import type { Reroute } from "@/Reroute" +import type { CanvasPointerEvent } from "@/types/events" +import type { IWidget } from "@/types/widgets" + +export interface LinkConnectorEventMap { + "reset": boolean + + "before-drop-links": { + renderLinks: RenderLink[] + event: CanvasPointerEvent + } + "after-drop-links": { + renderLinks: RenderLink[] + event: CanvasPointerEvent + } + + "before-move-input": MovingInputLink | FloatingRenderLink + "before-move-output": MovingOutputLink | FloatingRenderLink + + "input-moved": MovingInputLink | FloatingRenderLink + "output-moved": MovingOutputLink | FloatingRenderLink + + "link-created": LLink | null | undefined + + "dropped-on-reroute": { + reroute: Reroute + event: CanvasPointerEvent + } + "dropped-on-node": { + node: LGraphNode + event: CanvasPointerEvent + } + "dropped-on-canvas": CanvasPointerEvent + + "dropped-on-widget": { + link: ToInputRenderLink + node: LGraphNode + widget: IWidget + } +} diff --git a/src/infrastructure/LinkConnectorEventTarget.ts b/src/infrastructure/LinkConnectorEventTarget.ts deleted file mode 100644 index d5a91061cc..0000000000 --- a/src/infrastructure/LinkConnectorEventTarget.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { FloatingRenderLink } from "@/canvas/FloatingRenderLink" -import type { MovingInputLink } from "@/canvas/MovingInputLink" -import type { MovingOutputLink } from "@/canvas/MovingOutputLink" -import type { RenderLink } from "@/canvas/RenderLink" -import type { ToInputRenderLink } from "@/canvas/ToInputRenderLink" -import type { LGraphNode } from "@/LGraphNode" -import type { LLink } from "@/LLink" -import type { Reroute } from "@/Reroute" -import type { CanvasPointerEvent } from "@/types/events" -import type { IWidget } from "@/types/widgets" - -export interface LinkConnectorEventMap { - "reset": boolean - - "before-drop-links": { - renderLinks: RenderLink[] - event: CanvasPointerEvent - } - "after-drop-links": { - renderLinks: RenderLink[] - event: CanvasPointerEvent - } - - "before-move-input": MovingInputLink | FloatingRenderLink - "before-move-output": MovingOutputLink | FloatingRenderLink - - "input-moved": MovingInputLink | FloatingRenderLink - "output-moved": MovingOutputLink | FloatingRenderLink - - "link-created": LLink | null | undefined - - "dropped-on-reroute": { - reroute: Reroute - event: CanvasPointerEvent - } - "dropped-on-node": { - node: LGraphNode - event: CanvasPointerEvent - } - "dropped-on-canvas": CanvasPointerEvent - - "dropped-on-widget": { - link: ToInputRenderLink - node: LGraphNode - widget: IWidget - } -} - -/** {@link Omit} all properties that evaluate to `never`. */ -type NeverNever = { - [K in keyof T as T[K] extends never ? never : K]: T[K] -} - -/** {@link Pick} only properties that evaluate to `never`. */ -type PickNevers = { - [K in keyof T as T[K] extends never ? K : never]: T[K] -} - -type LinkConnectorEventListeners = { - readonly [K in keyof LinkConnectorEventMap]: ((this: EventTarget, ev: CustomEvent) => any) | EventListenerObject | null -} - -/** Events that _do not_ pass a {@link CustomEvent} `detail` object. */ -type SimpleEvents = keyof PickNevers - -/** Events that pass a {@link CustomEvent} `detail` object. */ -type ComplexEvents = keyof NeverNever - -export class LinkConnectorEventTarget extends EventTarget { - /** - * Type-safe event dispatching. - * @see {@link EventTarget.dispatchEvent} - * @param type Name of the event to dispatch - * @param detail A custom object to send with the event - * @returns `true` if the event was dispatched successfully, otherwise `false`. - */ - dispatch(type: T, detail: LinkConnectorEventMap[T]): boolean - dispatch(type: T): boolean - dispatch(type: T, detail?: LinkConnectorEventMap[T]) { - const event = new CustomEvent(type, { detail, cancelable: true }) - return super.dispatchEvent(event) - } - - override addEventListener( - type: K, - listener: LinkConnectorEventListeners[K], - options?: boolean | AddEventListenerOptions, - ): void { - // Assertion: Contravariance on CustomEvent => Event - super.addEventListener(type, listener as EventListener, options) - } - - override removeEventListener( - type: K, - listener: LinkConnectorEventListeners[K], - options?: boolean | EventListenerOptions, - ): void { - // Assertion: Contravariance on CustomEvent => Event - super.removeEventListener(type, listener as EventListener, options) - } - - /** @deprecated Use {@link dispatch}. */ - override dispatchEvent(event: never): boolean { - return super.dispatchEvent(event) - } -}