@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
JavaScript
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