UNPKG

@foblex/flow

Version:

Angular-native node-based UI library for building node editors, workflow builders, and interactive graph interfaces.

1,332 lines (1,262 loc) 908 kB
import * as i0 from '@angular/core'; import { InjectionToken, inject, Injectable, ElementRef, DestroyRef, Injector, input, effect, untracked, Directive, signal, numberAttribute, model, booleanAttribute, ChangeDetectionStrategy, Component, viewChild, Input, computed, contentChildren, contentChild, NgZone, Renderer2, output, ViewContainerRef, TemplateRef, runInInjectionContext, EventEmitter, Output, afterNextRender, NgModule } from '@angular/core'; import { TransformModelExtensions, PointExtensions, RectExtensions, GetIntersections, RoundedRect, Point, LineExtensions, SizeExtensions, setRectToElement, adjustRectToMinSize, setRectToViewBox } from '@foblex/2d'; import { __decorate } from 'tslib'; import { FExecutionRegister, FMediator } from '@foblex/mediator'; import { BrowserService, PlatformService, EOperationSystem } from '@foblex/platform'; import { castToEnum, normalizeDomElementId, isClosestElementHasClass, flatMap, getOrCreateRootNodeForViewRef, deepCloneNode, disableDragInteractions, extendStyles, getDataAttrValueFromClosestElementWithClass } from '@foblex/utils'; import { DOCUMENT, CommonModule } from '@angular/common'; const F_BACKGROUND_PATTERN = new InjectionToken('F_BACKGROUND_PATTERN'); class AddPatternToBackgroundRequest { fPattern; static fToken = Symbol('AddPatternToBackgroundRequest'); constructor(fPattern) { this.fPattern = fPattern; } } /** * Stores instances indexed by `fId()` + preserves insertion order. * * - `get(id)` returns `undefined` * - `require(id)` throws with a readable message that includes `kind` */ class FIdRegistryBase { _items = []; _byId = new Map(); get(id) { return this._byId.get(id); } require(id) { const found = this._byId.get(id); if (!found) { throw new Error(`${this.kind} not found: ${id}`); } return found; } has(id) { return this._byId.has(id); } getAll() { return this._items; } size() { return this._items.length; } /** * Adds an instance. * If instance with the same id already exists — throws (keeps data consistent). */ add(instance) { const id = instance.fId(); if (this._byId.has(id)) { throw new Error(`${this.kind} already exists: ${id}`); } this._items.push(instance); this._byId.set(id, instance); } addMany(instances) { for (const instance of instances) { this.add(instance); } } /** * Removes by instance reference (fast path). * Returns `true` if removed, `false` if not found. */ remove(instance) { const id = instance.fId(); const existing = this._byId.get(id); if (!existing) { return false; } // Defensive: if another instance with same id is in registry (shouldn't happen), // we remove by id anyway. this._byId.delete(id); const index = this._items.indexOf(existing); if (index >= 0) { this._items.splice(index, 1); } return true; } /** * Removes by id. * Returns removed instance (if existed). */ removeById(id) { const existing = this._byId.get(id); if (!existing) { return undefined; } this._byId.delete(id); const index = this._items.indexOf(existing); if (index >= 0) { this._items.splice(index, 1); } return existing; } /** * Clears registry (does NOT touch other state outside). */ clear() { this._items.length = 0; this._byId.clear(); } } function fInstanceKey(name) { return { name }; } class FSingleRegistryBase { _instances = new Map(); get(key) { return this._instances.get(key.name); } require(key) { const instance = this.get(key); if (!instance) { throw new Error(`Instance not found: ${key.name}`); } return instance; } has(key) { return this._instances.has(key.name); } add(key, instance) { if (this._instances.has(key.name)) { throw new Error(`${key.name} already exists`); } this._instances.set(key.name, instance); } remove(key) { const existed = this._instances.has(key.name); if (!existed) { throw new Error(`${key.name} does not exist`); } this._instances.delete(key.name); return true; } clear() { this._instances.clear(); } } class FConnectorRegistry extends FIdRegistryBase { kind; constructor(kind) { super(); this.kind = kind; } } class FConnectionMarkerRegistry extends FIdRegistryBase { kind = 'Connection Marker'; } class FConnectionRegistry extends FIdRegistryBase { kind = 'Connection'; _instancesForCreate; _instancesForSnap; getForSnap() { return this._instancesForSnap; } getForCreate() { return this._instancesForCreate; } addForCreate(instance) { this._instancesForCreate = instance; } addForSnap(instance) { this._instancesForSnap = instance; } removeInstanceForCreate() { this._instancesForCreate = undefined; } removeInstanceForSnap() { this._instancesForSnap = undefined; } } class FNodeRegistry extends FIdRegistryBase { kind = 'Node'; } class ListenConnectionsChangesRequest { notifyOnSubscribe; static fToken = Symbol('ListenConnectionsChangesRequest'); constructor(notifyOnSubscribe = true) { this.notifyOnSubscribe = notifyOnSubscribe; } } function afterNextPaint() { return (callback) => { let raf1 = null; let raf2 = null; const cancel = () => { if (raf1 !== null) cancelAnimationFrame(raf1); if (raf2 !== null) cancelAnimationFrame(raf2); raf1 = raf2 = null; }; return { callback: () => { cancel(); raf1 = requestAnimationFrame(() => { raf1 = null; raf2 = requestAnimationFrame(() => { raf2 = null; callback(); }); }); }, cleanup: cancel, }; }; } function debounceAnimationFrame() { return (callback) => { let rafId = null; return { callback: () => { if (rafId !== null) cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => { rafId = null; callback(); }); }, cleanup: () => { if (rafId !== null) cancelAnimationFrame(rafId); rafId = null; }, }; }; } function debounceMicrotask() { return (callback) => { let scheduled = false; return { callback: () => { if (scheduled) return; scheduled = true; queueMicrotask(() => { scheduled = false; callback(); }); }, cleanup: () => { scheduled = false; }, }; }; } function debounceTime(delay) { return (callback) => { let timeoutId = null; return { callback: () => { if (timeoutId !== null) clearTimeout(timeoutId); timeoutId = setTimeout(() => { timeoutId = null; callback(); }, delay); }, cleanup: () => { if (timeoutId !== null) clearTimeout(timeoutId); timeoutId = null; }, }; }; } class FChannel { _listeners = new Set(); notify() { this._listeners.forEach((callback) => callback()); } listen(callback) { this._listeners.add(callback); return () => this.stop(callback); } stop(callback) { this._listeners.delete(callback); } } class FResizeChannel extends FChannel { _htmlElement; _observer = new ResizeObserver(() => this.notify()); _isObserving = false; constructor(_htmlElement) { super(); this._htmlElement = _htmlElement; } listen(callback) { if (!this._isObserving) { this._observer.observe(this._htmlElement); this._isObserving = true; } return super.listen(callback); } stop(callback) { super.stop(callback); if (this._listeners.size === 0) { this._disconnect(); } } _disconnect() { this._observer.unobserve(this._htmlElement); this._observer.disconnect(); this._isObserving = false; } } class FChannelHub { _channels = []; _operators = []; constructor(...channels) { this._channels = [...channels]; } pipe(...operators) { const result = new FChannelHub(...this._channels); result._operators = [...this._operators, ...operators]; return result; } listen(destroyRef, callback) { let current = callback; const cleanups = []; const onSubscribes = []; const teardownSetters = []; for (const operator of [...this._operators].reverse()) { const res = operator(current); current = res.callback; if (res.cleanup) cleanups.push(res.cleanup); if (res.onSubscribe) onSubscribes.push(res.onSubscribe); if (res.setTeardown) teardownSetters.push(res.setTeardown); } const unsubs = this._channels.map((ch) => ch.listen(() => current())); let unregisterOnDestroy = null; let tornDown = false; const teardown = () => { if (tornDown) return; tornDown = true; unregisterOnDestroy?.(); unregisterOnDestroy = null; unsubs.forEach((u) => u()); cleanups.forEach((c) => c()); }; teardownSetters .slice() .reverse() .forEach((set) => set(teardown)); onSubscribes .slice() .reverse() .forEach((fn) => fn(current)); unregisterOnDestroy = destroyRef.onDestroy(teardown); } } // export function notifyOnStart(): FChannelOperator { // return (callback) => { // let teardown: (() => void) | null = null; // // return { // setTeardown: (t) => (teardown = t), // callback, // onSubscribe: (finalCallback) => { // if (!teardown) return; // finalCallback(); // }, // }; // }; // } function notifyOnStart() { return (callback) => { let active = true; return { callback, onSubscribe: (finalCallback) => { queueMicrotask(() => { if (!active) return; finalCallback(); }); }, cleanup: () => { active = false; }, }; }; } function takeOne() { return (callback) => { let taken = false; let teardown = null; return { setTeardown: (t) => (teardown = t), callback: () => { if (taken) return; taken = true; try { callback(); } finally { teardown?.(); } }, }; }; } let ListenConnectionsChanges = class ListenConnectionsChanges { _store = inject(FComponentsStore); handle({ notifyOnSubscribe }) { return notifyOnSubscribe ? new FChannelHub(this._store.connectionsChanges$).pipe(notifyOnStart(), debounceTime(1)) : new FChannelHub(this._store.connectionsChanges$).pipe(debounceTime(1)); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ListenConnectionsChanges, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ListenConnectionsChanges }); }; ListenConnectionsChanges = __decorate([ FExecutionRegister(ListenConnectionsChangesRequest) ], ListenConnectionsChanges); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ListenConnectionsChanges, decorators: [{ type: Injectable }] }); class ListenNodesChangesRequest { static fToken = Symbol('ListenNodesChangesRequest'); } let ListenNodesChanges = class ListenNodesChanges { _store = inject(FComponentsStore); handle(_) { return new FChannelHub(this._store.nodesChanges$); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ListenNodesChanges, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ListenNodesChanges }); }; ListenNodesChanges = __decorate([ FExecutionRegister(ListenNodesChangesRequest) ], ListenNodesChanges); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ListenNodesChanges, decorators: [{ type: Injectable }] }); class ListenTransformChangesRequest { static fToken = Symbol('ListenTransformChangesRequest'); } let ListenTransformChanges = class ListenTransformChanges { _store = inject(FComponentsStore); handle(_) { return new FChannelHub(this._store.transformChanges$, this._store.connectionsChanges$, this._store.nodesChanges$); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ListenTransformChanges, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ListenTransformChanges }); }; ListenTransformChanges = __decorate([ FExecutionRegister(ListenTransformChangesRequest) ], ListenTransformChanges); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ListenTransformChanges, decorators: [{ type: Injectable }] }); class EmitConnectionsChangesRequest { static fToken = Symbol('EmitConnectionsChangesRequest'); } let EmitConnectionsChanges = class EmitConnectionsChanges { _store = inject(FComponentsStore); handle(_) { this._store.emitConnectionChanges(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: EmitConnectionsChanges, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: EmitConnectionsChanges }); }; EmitConnectionsChanges = __decorate([ FExecutionRegister(EmitConnectionsChangesRequest) ], EmitConnectionsChanges); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: EmitConnectionsChanges, decorators: [{ type: Injectable }] }); class NotifyTransformChangedRequest { static fToken = Symbol('NotifyTransformChangedRequest'); } class FComponentsStore { transformChanges$ = new FChannel(); viewportAnimationChanges$ = new FChannel(); connectionsChanges$ = new FChannel(); _connectionsRevision = 0; connectionsRenderedChanges$ = new FChannel(); _connectionsRenderedRevision = 0; _connectionsRenderedNodesRevision = 0; _viewportAnimationCount = 0; nodesChanges$ = new FChannel(); _nodesRevision = 0; progressiveRenderChanges$ = new FChannel(); _pendingProgressiveRenderCount = 0; get connectionsRevision() { return this._connectionsRevision; } get connectionsRenderedRevision() { return this._connectionsRenderedRevision; } get connectionsRenderedNodesRevision() { return this._connectionsRenderedNodesRevision; } get nodesRevision() { return this._nodesRevision; } get hasPendingProgressiveRender() { return this._pendingProgressiveRenderCount > 0; } get isViewportAnimating() { return this._viewportAnimationCount > 0; } get flowHost() { return this.fFlow?.hostElement; } fFlow; fCanvas; get transform() { return this.fCanvas?.transform; } nodes = new FNodeRegistry(); connections = new FConnectionRegistry(); connectionMarkers = new FConnectionMarkerRegistry(); outputs = new FConnectorRegistry('Output'); inputs = new FConnectorRegistry('Input'); outlets = new FConnectorRegistry('Outlet'); instances = new FSingleRegistryBase(); fDraggable; emitNodeChanges() { this._nodesRevision++; this.nodesChanges$.notify(); } emitConnectionChanges() { this._connectionsRevision++; this.connectionsChanges$.notify(); } completeConnectionsRender(revision, nodesRevision) { if (revision < this._connectionsRenderedRevision || (revision === this._connectionsRenderedRevision && nodesRevision <= this._connectionsRenderedNodesRevision)) { return; } this._connectionsRenderedRevision = Math.min(revision, this._connectionsRevision); this._connectionsRenderedNodesRevision = nodesRevision; this.connectionsRenderedChanges$.notify(); } beginProgressiveRender() { this._pendingProgressiveRenderCount++; this.progressiveRenderChanges$.notify(); } endProgressiveRender() { if (!this._pendingProgressiveRenderCount) { return; } this._pendingProgressiveRenderCount--; this.progressiveRenderChanges$.notify(); } transformChanged() { this.transformChanges$.notify(); } beginViewportAnimation() { this._viewportAnimationCount++; this.viewportAnimationChanges$.notify(); } endViewportAnimation() { if (!this._viewportAnimationCount) { return; } this._viewportAnimationCount--; this.viewportAnimationChanges$.notify(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: FComponentsStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: FComponentsStore }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: FComponentsStore, decorators: [{ type: Injectable }] }); const INSTANCES = { // eslint-disable-next-line @typescript-eslint/naming-convention MAGNETIC_LINES: fInstanceKey('magnetic-lines'), // eslint-disable-next-line @typescript-eslint/naming-convention MAGNETIC_RECTS: fInstanceKey('magnetic-rects'), // eslint-disable-next-line @typescript-eslint/naming-convention ZOOM: fInstanceKey('zoom-controls'), // eslint-disable-next-line @typescript-eslint/naming-convention BACKGROUND: fInstanceKey('background'), // eslint-disable-next-line @typescript-eslint/naming-convention SELECTION_AREA: fInstanceKey('selection-area'), // eslint-disable-next-line @typescript-eslint/naming-convention MINIMAP: fInstanceKey('minimap'), // eslint-disable-next-line @typescript-eslint/naming-convention AUTO_PAN: fInstanceKey('auto-pan'), }; let NotifyTransformChanged = class NotifyTransformChanged { _store = inject(FComponentsStore); handle(_request) { this._store.transformChanged(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NotifyTransformChanged, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NotifyTransformChanged }); }; NotifyTransformChanged = __decorate([ FExecutionRegister(NotifyTransformChangedRequest) ], NotifyTransformChanged); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NotifyTransformChanged, decorators: [{ type: Injectable }] }); class RegisterPluginInstanceRequest { key; instance; static fToken = Symbol('RegisterPluginInstanceRequest'); constructor(key, instance) { this.key = key; this.instance = instance; } } /** * Execution that adds a line alignment to the FComponentsStore. */ let RegisterPluginInstance = class RegisterPluginInstance { _store = inject(FComponentsStore); handle({ key, instance }) { this._store.instances.add(key, instance); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RegisterPluginInstance, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RegisterPluginInstance }); }; RegisterPluginInstance = __decorate([ FExecutionRegister(RegisterPluginInstanceRequest) ], RegisterPluginInstance); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RegisterPluginInstance, decorators: [{ type: Injectable }] }); class RemovePluginInstanceRequest { key; static fToken = Symbol('RemovePluginInstanceRequest'); constructor(key) { this.key = key; } } /** * Execution that removes a line alignment from the FComponentsStore. */ let RemovePluginInstance = class RemovePluginInstance { _store = inject(FComponentsStore); handle({ key }) { this._store.instances.remove(key); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RemovePluginInstance, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RemovePluginInstance }); }; RemovePluginInstance = __decorate([ FExecutionRegister(RemovePluginInstanceRequest) ], RemovePluginInstance); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RemovePluginInstance, decorators: [{ type: Injectable }] }); const F_STORAGE_PROVIDERS = [ FComponentsStore, ListenNodesChanges, ListenConnectionsChanges, ListenTransformChanges, EmitConnectionsChanges, NotifyTransformChanged, RegisterPluginInstance, RemovePluginInstance, ]; let uniqueId$d = 0; /** * Execution that adds a pattern to the background in the FComponentsStore. */ let AddPatternToBackground = class AddPatternToBackground { _store = inject(FComponentsStore); _browser = inject(BrowserService); get _backgroundElement() { return this._store.instances.get(INSTANCES.BACKGROUND)?.hostElement; } handle(request) { const patterns = this._getPatterns(request.fPattern?.hostElement); if (!patterns?.length) { return; } const defs = createSVGElement('defs', this._browser); request.fPattern?.hostElement.remove(); patterns.forEach((pattern) => { defs.append(pattern); }); if (patterns.length) { this._backgroundElement?.firstChild?.appendChild(defs); patterns[patterns.length - 1].id = 'f-background-pattern-' + uniqueId$d++; const lastPatternId = patterns[patterns.length - 1].id; const rect = createSVGElement('rect', this._browser); rect.setAttribute('fill', 'url(#' + lastPatternId + ')'); rect.setAttribute('width', '100%'); rect.setAttribute('height', '100%'); this._backgroundElement?.firstChild?.appendChild(rect); const transform = this._store.fCanvas?.transform || TransformModelExtensions.default(); request.fPattern?.setTransform(transform); } } _getPatterns(element) { return Array.from(element?.getElementsByTagName('pattern') || []); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddPatternToBackground, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddPatternToBackground }); }; AddPatternToBackground = __decorate([ FExecutionRegister(AddPatternToBackgroundRequest) ], AddPatternToBackground); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddPatternToBackground, decorators: [{ type: Injectable }] }); class SetBackgroundTransformRequest { fTransform; static fToken = Symbol('SetBackgroundTransformRequest'); constructor(fTransform) { this.fTransform = fTransform; } } /** * Execution that sets the transform for the background when canvas is transformed. */ let SetBackgroundTransform = class SetBackgroundTransform { _store = inject(FComponentsStore); handle(request) { this._store.instances.get(INSTANCES.BACKGROUND)?.setTransform(request.fTransform); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: SetBackgroundTransform, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: SetBackgroundTransform }); }; SetBackgroundTransform = __decorate([ FExecutionRegister(SetBackgroundTransformRequest) ], SetBackgroundTransform); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: SetBackgroundTransform, decorators: [{ type: Injectable }] }); /** * This file exports all the background-related features for the F-Flow domain. * It includes executions for adding, removing, and setting the background in the FComponentsStore. */ const F_BACKGROUND_FEATURES = [ AddPatternToBackground, SetBackgroundTransform, ]; class AddCanvasToStoreRequest { instance; static fToken = Symbol('AddCanvasToStoreRequest'); constructor(instance) { this.instance = instance; } } /** * Execution that adds a canvas to the FComponentsStore. */ let AddCanvasToStore = class AddCanvasToStore { _store = inject(FComponentsStore); handle({ instance }) { this._store.fCanvas = instance; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddCanvasToStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddCanvasToStore }); }; AddCanvasToStore = __decorate([ FExecutionRegister(AddCanvasToStoreRequest) ], AddCanvasToStore); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddCanvasToStore, decorators: [{ type: Injectable }] }); class CenterGroupOrNodeRequest { id; animated; static fToken = Symbol('CenterGroupOrNodeRequest'); constructor(id, animated) { this.id = id; this.animated = animated; } } /** * Execution that centers a group or a node inside the flow. */ let CenterGroupOrNode = class CenterGroupOrNode { _store = inject(FComponentsStore); _mediator = inject(FMediator); get _transform() { return this._store.transform; } handle({ id, animated }) { const node = this._store.nodes.get(id); if (!node) { return; } this._toCenter(this._getNodeRect(node), this._getFlowRect(), node._position); this._mediator.execute(new RedrawCanvasWithAnimationRequest(animated, ECanvasRedrawContext.VIEWPORT_ONLY)); } _toCenter(fNodeRect, fFlowRect, position) { this._transform.scaledPosition = PointExtensions.initialize(); this._transform.position = PointExtensions.initialize((fFlowRect.width - fNodeRect.width) / 2 - position.x * this._transform.scale, (fFlowRect.height - fNodeRect.height) / 2 - position.y * this._transform.scale); } _getNodeRect(fNode) { return RectExtensions.fromElement(fNode.hostElement); } _getFlowRect() { return RectExtensions.fromElement(this._store.flowHost); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: CenterGroupOrNode, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: CenterGroupOrNode }); }; CenterGroupOrNode = __decorate([ FExecutionRegister(CenterGroupOrNodeRequest) ], CenterGroupOrNode); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: CenterGroupOrNode, decorators: [{ type: Injectable }] }); class FitToFlowRequest { toCenter; animated; static fToken = Symbol('FitToFlowRequest'); constructor(toCenter, animated) { this.toCenter = toCenter; this.animated = animated; } } /** * Fits all nodes and groups to the flow by scaling and positioning them */ let FitToFlow = class FitToFlow { _store = inject(FComponentsStore); get _transform() { return this._store.transform; } _mediator = inject(FMediator); handle({ toCenter, animated }) { const fNodesRect = this._mediator.execute(new CalculateNodesBoundingBoxRequest()) || RectExtensions.initialize(); if (fNodesRect.width === 0 || fNodesRect.height === 0) { return; } this.fitToParent(fNodesRect, RectExtensions.fromElement(this._store.flowHost), this._store.nodes.getAll().map((x) => x._position), toCenter); this._mediator.execute(new RedrawCanvasWithAnimationRequest(animated, ECanvasRedrawContext.VIEWPORT_ONLY)); } fitToParent(rect, parentRect, points, toCenter) { this._transform.scaledPosition = PointExtensions.initialize(); this._transform.position = this._getZeroPositionWithoutScale(points); const itemsContainerWidth = rect.width / this._transform.scale + toCenter.x; const itemsContainerHeight = rect.height / this._transform.scale + toCenter.y; if (itemsContainerWidth > parentRect.width || itemsContainerHeight > parentRect.height || (itemsContainerWidth < parentRect.width && itemsContainerHeight < parentRect.height)) { this._transform.scale = Math.min(parentRect.width / itemsContainerWidth, parentRect.height / itemsContainerHeight); } const newX = (parentRect.width - itemsContainerWidth * this._transform.scale) / 2 - this._transform.position.x * this._transform.scale; const newY = (parentRect.height - itemsContainerHeight * this._transform.scale) / 2 - this._transform.position.y * this._transform.scale; this._transform.position = PointExtensions.initialize(newX + (toCenter.x / 2) * this._transform.scale, newY + (toCenter.y / 2) * this._transform.scale); } _getZeroPositionWithoutScale(points) { const xPoint = points.length ? Math.min(...points.map((point) => point.x)) : 0; const yPoint = points.length ? Math.min(...points.map((point) => point.y)) : 0; return PointExtensions.initialize(xPoint, yPoint); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: FitToFlow, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: FitToFlow }); }; FitToFlow = __decorate([ FExecutionRegister(FitToFlowRequest) ], FitToFlow); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: FitToFlow, decorators: [{ type: Injectable }] }); class InputCanvasPositionRequest { transform; position; static fToken = Symbol('InputCanvasPositionRequest'); constructor(transform, position) { this.transform = transform; this.position = position; } } /** * It updates the canvas position and redraws the canvas when the user set a new position using the input. */ let InputCanvasPosition = class InputCanvasPosition { _store = inject(FComponentsStore); handle({ transform, position }) { if (!position) { return; } if (!PointExtensions.isEqual(this._calculateTransformPosition(transform), position)) { transform.position = position; transform.scaledPosition = PointExtensions.initialize(); this._store.fCanvas?.redraw(); } } _calculateTransformPosition(transform) { return PointExtensions.sum(transform.position, transform.scaledPosition); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: InputCanvasPosition, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: InputCanvasPosition }); }; InputCanvasPosition = __decorate([ FExecutionRegister(InputCanvasPositionRequest) ], InputCanvasPosition); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: InputCanvasPosition, decorators: [{ type: Injectable }] }); class InputCanvasScaleRequest { transform; scale; static fToken = Symbol('InputCanvasScaleRequest'); constructor(transform, scale) { this.transform = transform; this.scale = scale; } } /** * Execution that handles the scaling of the input canvas. * It updates the scale of the canvas transform and redraws the canvas when the user sets a new scale using the input. */ let InputCanvasScale = class InputCanvasScale { _store = inject(FComponentsStore); handle(request) { if (!request.scale && request.scale !== 0) { return; } request.transform.scale = request.scale; this._store.fCanvas?.redraw(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: InputCanvasScale, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: InputCanvasScale }); }; InputCanvasScale = __decorate([ FExecutionRegister(InputCanvasScaleRequest) ], InputCanvasScale); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: InputCanvasScale, decorators: [{ type: Injectable }] }); var ECanvasRedrawContext; (function (ECanvasRedrawContext) { ECanvasRedrawContext["VIEWPORT_ONLY"] = "VIEWPORT_ONLY"; ECanvasRedrawContext["WITH_CONNECTION_CHANGES"] = "WITH_CONNECTION_CHANGES"; })(ECanvasRedrawContext || (ECanvasRedrawContext = {})); class RedrawCanvasWithAnimationRequest { animated; context; static fToken = Symbol('RedrawCanvasWithAnimationRequest'); constructor(animated, context = ECanvasRedrawContext.WITH_CONNECTION_CHANGES) { this.animated = animated; this.context = context; } } function transitionEnd(element, callback) { const onTransitionEnd = (event) => { if (event.propertyName === 'transform') { element.removeEventListener('transitionend', onTransitionEnd); callback(event); } }; element.addEventListener('transitionend', onTransitionEnd); } /** * Execution that redraws the canvas with or without animation based on the request. * If animated, it will redraw with animation and wait for the transition end to notify data change. * If not animated, it will redraw immediately and notify data change. */ let RedrawCanvasWithAnimation = class RedrawCanvasWithAnimation { _store = inject(FComponentsStore); get _canvas() { return this._store.fCanvas; } handle(request) { request.animated ? this._redrawWithAnimation(request.context) : this._redraw(request.context); this._canvas?.emitCanvasChangeEvent(); } _redrawWithAnimation(context) { this._store.beginViewportAnimation(); this._canvas?.redrawWithAnimation(); transitionEnd(this._canvas.hostElement, () => { this._store.endViewportAnimation(); if (context === ECanvasRedrawContext.WITH_CONNECTION_CHANGES) { this._store.emitConnectionChanges(); } }); } _redraw(context) { this._canvas?.redraw(); if (context === ECanvasRedrawContext.WITH_CONNECTION_CHANGES) { this._store.emitConnectionChanges(); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RedrawCanvasWithAnimation, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RedrawCanvasWithAnimation }); }; RedrawCanvasWithAnimation = __decorate([ FExecutionRegister(RedrawCanvasWithAnimationRequest) ], RedrawCanvasWithAnimation); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RedrawCanvasWithAnimation, decorators: [{ type: Injectable }] }); class RemoveCanvasFromStoreRequest { static fToken = Symbol('RemoveCanvasFromStoreRequest'); } /** * Execution that removes the canvas from the FComponentsStore. */ let RemoveCanvasFromStore = class RemoveCanvasFromStore { _store = inject(FComponentsStore); handle(_) { this._store.fCanvas = undefined; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RemoveCanvasFromStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RemoveCanvasFromStore }); }; RemoveCanvasFromStore = __decorate([ FExecutionRegister(RemoveCanvasFromStoreRequest) ], RemoveCanvasFromStore); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RemoveCanvasFromStore, decorators: [{ type: Injectable }] }); class ResetScaleRequest { static fToken = Symbol('ResetScaleRequest'); } /** * Execution that resets the scale of the canvas in the FComponentsStore. */ let ResetScale = class ResetScale { _store = inject(FComponentsStore); get _transform() { return this._store.transform; } handle(_) { this._transform.scale = 1; this._transform.scaledPosition = PointExtensions.initialize(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ResetScale, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ResetScale }); }; ResetScale = __decorate([ FExecutionRegister(ResetScaleRequest) ], ResetScale); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ResetScale, decorators: [{ type: Injectable }] }); class ResetScaleAndCenterRequest { animated; static fToken = Symbol('ResetScaleAndCenterRequest'); constructor(animated) { this.animated = animated; } } /** * Execution that resets the scale of the canvas and centers the nodes and groups inside the flow. */ let ResetScaleAndCenter = class ResetScaleAndCenter { _mediator = inject(FMediator); _store = inject(FComponentsStore); get _transform() { return this._store.transform; } handle({ animated }) { const nodesRect = this._mediator.execute(new CalculateNodesBoundingBoxRequest()) || RectExtensions.initialize(); if (nodesRect.width === 0 || nodesRect.height === 0) { return; } this._oneToOneCentering(nodesRect, RectExtensions.fromElement(this._store.flowHost), this._store.nodes.getAll().map((x) => x._position)); this._mediator.execute(new RedrawCanvasWithAnimationRequest(animated, ECanvasRedrawContext.VIEWPORT_ONLY)); } _oneToOneCentering(rect, parentRect, points) { this._transform.scaledPosition = PointExtensions.initialize(); this._transform.position = this._getZeroPositionWithoutScale(points); const newX = (parentRect.width - rect.width / this._transform.scale) / 2 - this._transform.position.x; const newY = (parentRect.height - rect.height / this._transform.scale) / 2 - this._transform.position.y; this._transform.scale = 1; this._transform.position = PointExtensions.initialize(newX, newY); } _getZeroPositionWithoutScale(points) { const xPoint = points.length ? Math.min(...points.map((point) => point.x)) : 0; const yPoint = points.length ? Math.min(...points.map((point) => point.y)) : 0; return PointExtensions.initialize(xPoint, yPoint); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ResetScaleAndCenter, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ResetScaleAndCenter }); }; ResetScaleAndCenter = __decorate([ FExecutionRegister(ResetScaleAndCenterRequest) ], ResetScaleAndCenter); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ResetScaleAndCenter, decorators: [{ type: Injectable }] }); class UpdateScaleRequest { scale; toPosition; static fToken = Symbol('UpdateScaleRequest'); constructor(scale, toPosition) { this.scale = scale; this.toPosition = toPosition; } } /** * Execution that updates the scale of the canvas in the FComponentsStore. * Occurs when the fZoom directive or User call the setScale method. */ let UpdateScale = class UpdateScale { _store = inject(FComponentsStore); get _transform() { return this._store.transform; } handle({ scale, toPosition }) { if (scale === this._transform.scale) { return; } const summaryPosition = PointExtensions.sum(this._transform.scaledPosition, this._transform.position); const newX = toPosition.x - ((toPosition.x - summaryPosition.x) * scale) / this._transform.scale; const newY = toPosition.y - ((toPosition.y - summaryPosition.y) * scale) / this._transform.scale; this._transform.scale = scale; this._transform.scaledPosition = PointExtensions.sub(PointExtensions.initialize(newX, newY), this._transform.position); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: UpdateScale, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: UpdateScale }); }; UpdateScale = __decorate([ FExecutionRegister(UpdateScaleRequest) ], UpdateScale); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: UpdateScale, decorators: [{ type: Injectable }] }); /** * This file exports all the canvas-related executions that can be used in the FCanvas feature. */ const F_CANVAS_FEATURES = [ AddCanvasToStore, CenterGroupOrNode, FitToFlow, InputCanvasPosition, InputCanvasScale, RedrawCanvasWithAnimation, RemoveCanvasFromStore, ResetScale, ResetScaleAndCenter, UpdateScale, ]; class AddConnectionForCreateToStoreRequest { connection; static fToken = Symbol('AddConnectionForCreateToStoreRequest'); constructor(connection) { this.connection = connection; } } /** * Execution that adds a connection for creation to the FComponentsStore. */ let AddConnectionForCreateToStore = class AddConnectionForCreateToStore { _store = inject(FComponentsStore); handle({ connection }) { this._store.connections.addForCreate(connection); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddConnectionForCreateToStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddConnectionForCreateToStore }); }; AddConnectionForCreateToStore = __decorate([ FExecutionRegister(AddConnectionForCreateToStoreRequest) ], AddConnectionForCreateToStore); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddConnectionForCreateToStore, decorators: [{ type: Injectable }] }); class AddConnectionMarkerToStoreRequest { instance; static fToken = Symbol('AddConnectionMarkerToStoreRequest'); constructor(instance) { this.instance = instance; } } /** * Execution that adds a connection marker to the FComponentsStore. */ let AddConnectionMarkerToStore = class AddConnectionMarkerToStore { _store = inject(FComponentsStore); handle({ instance }) { this._store.connectionMarkers.add(instance); this._store.emitNodeChanges(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddConnectionMarkerToStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddConnectionMarkerToStore }); }; AddConnectionMarkerToStore = __decorate([ FExecutionRegister(AddConnectionMarkerToStoreRequest) ], AddConnectionMarkerToStore); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddConnectionMarkerToStore, decorators: [{ type: Injectable }] }); class AddConnectionToStoreRequest { connection; static fToken = Symbol('AddConnectionToStoreRequest'); constructor(connection) { this.connection = connection; } } /** * Execution that adds a connection to the FComponentsStore. */ let AddConnectionToStore = class AddConnectionToStore { _store = inject(FComponentsStore); handle({ connection }) { this._store.connections.add(connection); this._store.emitConnectionChanges(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddConnectionToStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddConnectionToStore }); }; AddConnectionToStore = __decorate([ FExecutionRegister(AddConnectionToStoreRequest) ], AddConnectionToStore); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddConnectionToStore, decorators: [{ type: Injectable }] }); class AddSnapConnectionToStoreRequest { connection; static fToken = Symbol('AddSnapConnectionToStoreRequest'); constructor(connection) { this.connection = connection; } } /** * Execution that adds a snap connection to the FComponentsStore. */ let AddSnapConnectionToStore = class AddSnapConnectionToStore { _store = inject(FComponentsStore); handle({ connection }) { this._store.connections.addForSnap(connection); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddSnapConnectionToStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddSnapConnectionToStore }); }; AddSnapConnectionToStore = __decorate([ FExecutionRegister(AddSnapConnectionToStoreRequest) ], AddSnapConnectionToStore); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AddSnapConnectionToStore, decorators: [{ type: Injectable }] }); class CreateConnectionMarkersRequest { connection; static fToken = Symbol('CreateConnectionMarkersRequest'); constructor(connection) { this.connection = connection; } } const F_CONNECTION_CONTENT = new InjectionToken('F_CONNECTION_CONTENT'); class FConnectionContentBase { /** * The host DOM element to which the directive is applied. * Used internally for positioning calculations. */ hostElement = inject((ElementRef)).nativeElement; // ResizeObserver-backed size cache. Avoids `getBoundingClientRect` // inside the placement loop, where one DOM read per label per redraw // forced a synchronous layout flush and dominated drag-time at a few // hundred labelled connections (issue #304). The observer also keeps // the cache correct when the label content actually resizes. // // The initial size is populated by the observer's first asynchronous // delivery — calling `getBoundingClientRect()` from the constructor // re-introduces the thrashing during Angular CD when many labels // mount