@circonomit/solid-flow
Version:
Solid Flow - A highly customizable Solid library for building node-based editors and interactive flow charts.
1,481 lines (1,468 loc) • 213 kB
JavaScript
import { errorMessages, isEdgeBase, isNodeBase, withResolvers, getNodesBounds, isRectObject, getOverlappingArea, nodeToRect, getElementsToRemove, ResizeControlVariant, XYResizer, evaluateAbsolutePosition, handleExpandParent, XY_RESIZER_LINE_POSITIONS, XY_RESIZER_HANDLE_POSITIONS, Position, getNodeToolbarTransform, isNumeric, getBezierPath, getBezierEdgeCenter, getSmoothStepPath, getStraightPath, PanOnScrollMode, infiniteExtent, SelectionMode, ConnectionLineType, isMacOs, handleConnectionChange, isMouseEvent, XYHandle, getHostForElement, XYMinimap, isInputDOMNode, rendererPointToPoint, pointToRendererPoint, getViewportForBounds, getBoundsOfRects, getInternalNodesBounds, ConnectionMode, nodeHasDimensions, addEdge, getNodeDimensions, updateConnectionLookup, adoptUserNodes, initialConnection, fitViewport, panBy, updateNodeInternals, updateAbsolutePositions, getConnectionStatus, isEdgeVisible, getEdgePosition, getElevatedEdgeZIndex, getMarkerId, getEventPosition, getNodesInside, areSetsEqual, createMarkerIds, elementSelectionKeys, XYDrag, getDimensions, snapPosition, calculateNodePosition, MarkerType, devWarn, XYPanZoom } from '@xyflow/system';
export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, ResizeControlVariant, SelectionMode, addEdge, getBezierEdgeCenter, getBezierPath, getConnectedEdges, getEdgeCenter, getIncomers, getNodesBounds, getOutgoers, getSmoothStepPath, getStraightPath, getViewportForBounds, reconnectEdge } from '@xyflow/system';
import { delegateEvents, template, use, insert, createComponent, effect, setAttribute, style as style$1, spread, mergeProps as mergeProps$1, memo, addEventListener, className, Portal, Dynamic } from 'solid-js/web';
import cc from 'classcat';
import { createContext, useContext, batch, mergeProps, Show, splitProps, createMemo, createEffect, onCleanup, For, createSignal, onMount, untrack, Switch, Match } from 'solid-js';
import { ReactiveMap } from '@solid-primitives/map';
import { createStore } from 'solid-js/store';
// src/index.ts
var StoreContext = createContext(null);
var Provider = StoreContext.Provider;
var StoreContext_default = StoreContext;
// src/hooks/useStore.ts
var zustandErrorMessage = errorMessages["error001"]();
function useStore(selector26) {
const store = useContext(StoreContext_default);
if (store === null) {
throw new Error(zustandErrorMessage);
}
return selector26(store);
}
function useStoreApi() {
const store = useContext(StoreContext_default);
if (store === null) {
throw new Error(zustandErrorMessage);
}
return {
...store,
batch: (fn) => {
batch(() => {
fn(store);
});
}
};
}
// src/styles/utils.ts
var containerStyle = {
position: "absolute",
width: "100%",
height: "100%",
top: 0,
left: 0
};
// src/utils/hooks.ts
var useRef = (initialValue) => {
return new RefObject(initialValue);
};
var RefObject = class {
constructor(_current) {
this._current = _current;
}
get current() {
return this._current;
}
set current(value) {
this._current = value;
}
};
var _tmpl$ = /* @__PURE__ */ template(`<svg><path></svg>`, false, true, false);
var _tmpl$2 = /* @__PURE__ */ template(`<svg><circle></svg>`, false, true, false);
function LinePattern(p) {
return (() => {
var _el$ = _tmpl$();
effect((_p$) => {
var _v$ = p.lineWidth, _v$2 = `M${p.dimensions[0] / 2} 0 V${p.dimensions[1]} M0 ${p.dimensions[1] / 2} H${p.dimensions[0]}`, _v$3 = cc(["solid-flow__background-pattern", p.variant, p.className]);
_v$ !== _p$.e && setAttribute(_el$, "stroke-width", _p$.e = _v$);
_v$2 !== _p$.t && setAttribute(_el$, "d", _p$.t = _v$2);
_v$3 !== _p$.a && setAttribute(_el$, "class", _p$.a = _v$3);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$;
})();
}
function DotPattern(p) {
return (() => {
var _el$2 = _tmpl$2();
effect((_p$) => {
var _v$4 = p.radius, _v$5 = p.radius, _v$6 = p.radius, _v$7 = cc(["solid-flow__background-pattern", "dots", p.className]);
_v$4 !== _p$.e && setAttribute(_el$2, "cx", _p$.e = _v$4);
_v$5 !== _p$.t && setAttribute(_el$2, "cy", _p$.t = _v$5);
_v$6 !== _p$.a && setAttribute(_el$2, "r", _p$.a = _v$6);
_v$7 !== _p$.o && setAttribute(_el$2, "class", _p$.o = _v$7);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0
});
return _el$2;
})();
}
// src/additional-components/Background/types.ts
var BackgroundVariant = /* @__PURE__ */ ((BackgroundVariant2) => {
BackgroundVariant2["Lines"] = "lines";
BackgroundVariant2["Dots"] = "dots";
BackgroundVariant2["Cross"] = "cross";
return BackgroundVariant2;
})(BackgroundVariant || {});
// src/additional-components/Background/Background.tsx
var _tmpl$3 = /* @__PURE__ */ template(`<svg data-testid=rf__background><pattern patternUnits=userSpaceOnUse></pattern><rect x=0 y=0 width=100% height=100%>`);
var defaultSize = {
["dots" /* Dots */]: 1,
["lines" /* Lines */]: 1,
["cross" /* Cross */]: 6
};
var selector = (s) => ({
transform: s.transform,
patternId: `pattern-${s.rfId.get()}`
});
function BackgroundComponent(_p) {
const p = mergeProps({
variant: "dots" /* Dots */,
gap: 20,
lineWidth: 1,
offset: 0
}, _p);
const ref = useRef(null);
const storeData = useStore(selector);
const patternSize = () => p.size || defaultSize[p.variant];
const isDots = () => p.variant === "dots" /* Dots */;
const isCross = () => p.variant === "cross" /* Cross */;
const gapXY = () => {
if (Array.isArray(p.gap)) {
return p.gap;
} else {
return [p.gap, p.gap];
}
};
const scaledGap = () => [gapXY()[0] * storeData.transform.get()[2] || 1, gapXY()[1] * storeData.transform.get()[2] || 1];
const scaledSize = () => patternSize() * storeData.transform.get()[2];
const offsetXY = () => {
if (Array.isArray(p.offset)) {
return p.offset;
} else {
return [p.offset, p.offset];
}
};
const patternDimensions = () => isCross() ? [scaledSize(), scaledSize()] : scaledGap();
const scaledOffset = () => [offsetXY()[0] * storeData.transform.get()[2] || 1 + patternDimensions()[0] / 2, offsetXY()[1] * storeData.transform.get()[2] || 1 + patternDimensions()[1] / 2];
const _patternId = () => `${storeData.patternId}${p.id ? p.id : ""}`;
const style2 = () => {
return {
...p.style,
...containerStyle,
"--xy-background-color-props": p.bgColor,
"--xy-background-pattern-color-props": p.color
};
};
return (() => {
var _el$ = _tmpl$3(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling;
use((el) => ref.current = el, _el$);
insert(_el$2, createComponent(Show, {
get when() {
return isDots();
},
get fallback() {
return createComponent(LinePattern, {
get dimensions() {
return patternDimensions();
},
get lineWidth() {
return p.lineWidth;
},
get variant() {
return p.variant;
},
get className() {
return p.patternClassName;
}
});
},
get children() {
return createComponent(DotPattern, {
get radius() {
return scaledSize() / 2;
},
get className() {
return p.patternClassName;
}
});
}
}));
effect((_p$) => {
var _v$ = cc(["solid-flow__background", p.className]), _v$2 = style2(), _v$3 = _patternId(), _v$4 = storeData.transform.get()[0] % scaledGap()[0], _v$5 = storeData.transform.get()[1] % scaledGap()[1], _v$6 = scaledGap()[0], _v$7 = scaledGap()[1], _v$8 = `translate(-${scaledOffset()[0]},-${scaledOffset()[1]})`, _v$9 = `url(#${_patternId()})`;
_v$ !== _p$.e && setAttribute(_el$, "class", _p$.e = _v$);
_p$.t = style$1(_el$, _v$2, _p$.t);
_v$3 !== _p$.a && setAttribute(_el$2, "id", _p$.a = _v$3);
_v$4 !== _p$.o && setAttribute(_el$2, "x", _p$.o = _v$4);
_v$5 !== _p$.i && setAttribute(_el$2, "y", _p$.i = _v$5);
_v$6 !== _p$.n && setAttribute(_el$2, "width", _p$.n = _v$6);
_v$7 !== _p$.s && setAttribute(_el$2, "height", _p$.s = _v$7);
_v$8 !== _p$.h && setAttribute(_el$2, "patternTransform", _p$.h = _v$8);
_v$9 !== _p$.r && setAttribute(_el$3, "fill", _p$.r = _v$9);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0,
h: void 0,
r: void 0
});
return _el$;
})();
}
BackgroundComponent.displayName = "Background";
var Background = BackgroundComponent;
var _tmpl$4 = /* @__PURE__ */ template(`<button type=button>`);
function ControlButton(p) {
const [_, rest] = splitProps(p, ["children", "class"]);
return (() => {
var _el$ = _tmpl$4();
spread(_el$, mergeProps$1({
get ["class"]() {
return cc(["solid-flow__controls-button", p.class]);
}
}, rest), false, true);
insert(_el$, () => p.children);
return _el$;
})();
}
var _tmpl$5 = /* @__PURE__ */ template(`<div>`);
var selector2 = (s) => () => s.userSelectionActive.get() ? "none" : "all";
function Panel(_p) {
const p = mergeProps({
position: "top-left"
}, _p);
const pointerEvents = useStore(selector2);
const positionClasses = () => `${p.position}`.split("-");
const style2 = () => {
if (typeof p.style === "object") {
return {
...p.style,
"pointer-events": pointerEvents()
};
} else {
return {
"pointer-events": pointerEvents()
};
}
};
return (() => {
var _el$ = _tmpl$5();
spread(_el$, mergeProps$1(p, {
get ["class"]() {
return cc(["solid-flow__panel", p.class, ...positionClasses()]);
},
get style() {
return style2();
}
}), false, true);
insert(_el$, () => p.children);
return _el$;
})();
}
// src/utils/changes.ts
function applyChanges(changes, elements) {
const updatedElements = [];
const changesMap = /* @__PURE__ */ new Map();
const addItemChanges = [];
for (const change of changes) {
if (change.type === "add") {
addItemChanges.push(change);
} else if (change.type === "remove" || change.type === "replace") {
changesMap.set(change.id, [change]);
} else {
const elementChanges = changesMap.get(change.id);
if (elementChanges) {
elementChanges.push(change);
} else {
changesMap.set(change.id, [change]);
}
}
}
for (const element of elements) {
const changes2 = changesMap.get(element.id);
if (!changes2) {
updatedElements.push(element);
continue;
}
if (changes2[0].type === "remove") {
continue;
}
if (changes2[0].type === "replace") {
updatedElements.push({ ...changes2[0].item });
continue;
}
const updatedElement = { ...element };
for (const change of changes2) {
applyChange(change, updatedElement);
}
updatedElements.push(updatedElement);
}
if (addItemChanges.length) {
addItemChanges.forEach((change) => {
if (change.index !== void 0) {
updatedElements.splice(change.index, 0, { ...change.item });
} else {
updatedElements.push({ ...change.item });
}
});
}
return updatedElements;
}
function applyChange(change, element) {
switch (change.type) {
case "select": {
element.selected = change.selected;
break;
}
case "position": {
if (typeof change.position !== "undefined") {
element.position = change.position;
}
if (typeof change.dragging !== "undefined") {
element.dragging = change.dragging;
}
break;
}
case "dimensions": {
if (typeof change.dimensions !== "undefined") {
element.measured ??= {};
element.measured.width = change.dimensions.width;
element.measured.height = change.dimensions.height;
if (change.setAttributes) {
if (change.setAttributes === true || change.setAttributes === "width") {
element.width = change.dimensions.width;
}
if (change.setAttributes === true || change.setAttributes === "height") {
element.height = change.dimensions.height;
}
}
}
if (typeof change.resizing === "boolean") {
element.resizing = change.resizing;
}
break;
}
}
}
function applyNodeChanges(changes, nodes) {
return applyChanges(changes, nodes);
}
function applyEdgeChanges(changes, edges) {
return applyChanges(changes, edges);
}
function createSelectionChange(id, selected2) {
return {
id,
type: "select",
selected: selected2
};
}
function getSelectionChanges(items, selectedIds = /* @__PURE__ */ new Set(), mutateItem = false) {
const changes = [];
for (const [id, item] of items) {
const willBeSelected = selectedIds.has(id);
if (!(item.selected === void 0 && !willBeSelected) && item.selected !== willBeSelected) {
if (mutateItem) {
item.selected = willBeSelected;
}
changes.push(createSelectionChange(item.id, willBeSelected));
}
}
return changes;
}
function getElementsDiffChanges({
items = [],
lookup
}) {
const changes = [];
const itemsLookup = new Map(
items.map((item) => [item.id, item])
);
for (const [index, item] of items.entries()) {
const lookupItem = lookup.get(item.id);
const storeItem = lookupItem?.internals?.userNode ?? lookupItem;
if (storeItem !== void 0 && storeItem !== item) {
changes.push({ id: item.id, item, type: "replace" });
}
if (storeItem === void 0) {
changes.push({ item, type: "add", index });
}
}
for (const [id] of lookup) {
const nextNode = itemsLookup.get(id);
if (nextNode === void 0) {
changes.push({ id, type: "remove" });
}
}
return changes;
}
function elementToRemoveChange(item) {
return {
id: item.id,
type: "remove"
};
}
var isNode = (element) => isNodeBase(element);
var isEdge = (element) => isEdgeBase(element);
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? createEffect : createEffect;
var getInitialState = ({
nodes,
edges,
defaultNodes,
defaultEdges,
width,
height,
fitView,
fitViewOptions,
minZoom = 0.5,
maxZoom = 2,
nodeOrigin,
nodeExtent
} = {}) => {
const nodeLookup = /* @__PURE__ */ new Map();
const parentLookup = /* @__PURE__ */ new Map();
const connectionLookup = /* @__PURE__ */ new Map();
const edgeLookup = /* @__PURE__ */ new Map();
const storeEdges = defaultEdges ?? edges ?? [];
const storeNodes = defaultNodes ?? nodes ?? [];
const storeNodeOrigin = nodeOrigin ?? [0, 0];
const storeNodeExtent = nodeExtent ?? infiniteExtent;
updateConnectionLookup(connectionLookup, edgeLookup, storeEdges);
const nodesInitialized = adoptUserNodes(
storeNodes,
nodeLookup,
parentLookup,
{
nodeOrigin: storeNodeOrigin,
nodeExtent: storeNodeExtent,
elevateNodesOnSelect: false
}
);
let transform = [0, 0, 1];
if (fitView && width && height) {
const bounds = getInternalNodesBounds(nodeLookup, {
filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight))
});
const { x, y, zoom } = getViewportForBounds(
bounds,
width,
height,
minZoom,
maxZoom,
fitViewOptions?.padding ?? 0.1
);
transform = [x, y, zoom];
}
return {
rfId: new Writable("1"),
width: new Writable(width ?? 0),
height: new Writable(height ?? 0),
transform: new Writable(transform),
nodes: new Writable(storeNodes),
nodesInitialized: new Writable(nodesInitialized),
nodeLookup: new ReactiveMap(),
parentLookup: new ReactiveMap(),
edges: new Writable(storeEdges),
edgeLookup: new ReactiveMap(),
connectionLookup: new ReactiveMap(),
onNodesChange: wNullable(),
onEdgesChange: wNullable(),
hasDefaultNodes: new Writable(defaultNodes !== void 0),
hasDefaultEdges: new Writable(defaultEdges !== void 0),
panZoom: new Writable(null),
minZoom: new Writable(minZoom),
maxZoom: new Writable(maxZoom),
translateExtent: new Writable(infiniteExtent),
nodeExtent: new Writable(storeNodeExtent),
nodesSelectionActive: new Writable(false),
userSelectionActive: new Writable(false),
userSelectionRect: new Writable(null),
connection: new ReactiveStore(
{ ...initialConnection },
"connection"
),
connectionMode: new Writable(ConnectionMode.Strict),
domNode: new Writable(null),
paneDragging: new Writable(false, "paneDragging"),
noPanClassName: new Writable("nopan"),
nodeOrigin: new Writable(storeNodeOrigin),
nodeDragThreshold: new Writable(1),
snapGrid: new Writable([15, 15]),
snapToGrid: new Writable(false),
nodesDraggable: new Writable(true),
nodesConnectable: new Writable(true),
nodesFocusable: new Writable(true),
edgesFocusable: new Writable(true),
edgesReconnectable: new Writable(true),
elementsSelectable: new Writable(true),
elevateNodesOnSelect: new Writable(true),
elevateEdgesOnSelect: new Writable(false),
selectNodesOnDrag: new Writable(true),
multiSelectionActive: new Writable(false),
connectionClickStartHandle: wNullable(),
connectOnClick: w(true),
ariaLiveMessage: w(""),
autoPanOnConnect: w(true),
autoPanOnNodeDrag: w(true),
autoPanSpeed: w(15),
connectionRadius: w(20),
onError: w(devWarn),
isValidConnection: void 0,
onSelectionChangeHandlers: w([]),
onConnect: wEmpty(),
onDelete: wEmpty(),
onEdgesDelete: wEmpty(),
onNodesDelete: wEmpty(),
onBeforeDelete: wEmpty(),
onViewportChange: wEmpty(),
onViewportChangeEnd: wEmpty(),
onViewportChangeStart: wEmpty(),
onMoveStart: wEmpty(),
onMove: wEmpty(),
onMoveEnd: wEmpty(),
lib: new Writable("solid"),
debug: new Writable(false),
// New properties for React compatibility
fitViewQueued: new Writable(fitView ?? false),
fitViewOptions: new Writable(fitViewOptions),
fitViewResolver: wNullable()
};
};
var initialState_default = getInitialState;
var w = (x) => new Writable(x);
var wEmpty = () => new Writable(void 0);
var wNullable = () => new Writable(null);
var Writable = class {
constructor(initial, debugName) {
this.debugName = debugName;
this.s = createSignal(initial, {
name: debugName
});
}
s;
get = () => {
const value = this.s[0]();
return value;
};
set = (v) => {
this.s[1](() => {
return v;
});
};
setFromPrev = (fn) => {
this.s[1]((prev) => {
const newValue = fn(prev);
return newValue;
});
};
};
var ReactiveStore = class {
constructor(initial, debugName) {
this.debugName = debugName;
this.store = createStore(initial);
}
store;
get = () => {
const value = this.store[0];
return value;
};
setter = () => {
return this.store[1];
};
};
// src/components/BatchProvider/useQueue.ts
function useQueue(runQueue) {
const [shouldFlush, setShouldFlush] = createSignal(false);
const queue = createQueue(() => setShouldFlush(true));
useIsomorphicLayoutEffect(() => {
if (!shouldFlush()) {
queue.reset();
return;
}
const queueItems = queue.get();
if (queueItems.length) {
runQueue(queueItems);
queue.reset();
}
setShouldFlush(false);
});
return queue;
}
function createQueue(cb) {
const queue = new Writable([]);
return {
get: () => queue.get(),
reset: () => {
queue.set([]);
},
push: (item) => {
queue.setFromPrev((prev) => [...prev, item]);
cb();
}
};
}
// src/components/BatchProvider/index.tsx
var BatchContext = createContext(null);
function BatchProvider(p) {
const store = useStoreApi();
const nodeQueueHandler = (queueItems) => {
const {
nodes,
setNodes,
hasDefaultNodes,
onNodesChange: getOnNodesChange,
nodeLookup
// edgeLookup,
} = store;
let next = nodes.get();
for (const payload of queueItems) {
next = typeof payload === "function" ? payload(next) : payload;
}
const onNodesChange = getOnNodesChange.get();
if (hasDefaultNodes.get()) {
setNodes(next);
} else if (onNodesChange) {
onNodesChange(getElementsDiffChanges({
items: next,
lookup: nodeLookup
}));
}
};
const nodeQueue = useQueue(nodeQueueHandler);
const edgeQueueHandler = (queueItems) => {
const {
edges,
setEdges,
hasDefaultEdges,
onEdgesChange: getOnEdgesChange,
edgeLookup
} = store;
let next = edges.get();
for (const payload of queueItems) {
next = typeof payload === "function" ? payload(next) : payload;
}
const onEdgesChange = getOnEdgesChange.get();
if (hasDefaultEdges.get()) {
setEdges(next);
} else if (onEdgesChange) {
onEdgesChange(getElementsDiffChanges({
items: next,
lookup: edgeLookup
}));
}
};
const edgeQueue = useQueue(edgeQueueHandler);
const value = {
nodeQueue,
edgeQueue
};
return createComponent(BatchContext.Provider, {
value,
get children() {
return p.children;
}
});
}
function useBatchContext() {
const batchContext = useContext(BatchContext);
if (!batchContext) {
throw new Error("useBatchContext must be used within a BatchProvider");
}
return batchContext;
}
var useViewportHelper = () => {
const store = useStoreApi();
return createMemo(() => {
const getState = () => ({
panZoom: store.panZoom.get(),
transform: store.transform.get(),
width: store.width.get(),
height: store.height.get(),
minZoom: store.minZoom.get(),
maxZoom: store.maxZoom.get(),
nodeOrigin: store.nodeOrigin.get(),
nodeLookup: store.nodeLookup,
snapGrid: store.snapGrid.get(),
snapToGrid: store.snapToGrid.get(),
domNode: store.domNode.get()
});
return {
zoomIn: (options) => {
const { panZoom } = getState();
return panZoom ? panZoom.scaleBy(1.2, { duration: options?.duration }) : Promise.resolve(false);
},
zoomOut: (options) => {
const { panZoom } = getState();
return panZoom ? panZoom.scaleBy(1 / 1.2, { duration: options?.duration }) : Promise.resolve(false);
},
zoomTo: (zoomLevel, options) => {
const { panZoom } = getState();
return panZoom ? panZoom.scaleTo(zoomLevel, { duration: options?.duration }) : Promise.resolve(false);
},
getZoom: () => getState().transform[2],
setViewport: async (viewport, options) => {
const {
transform: [tX, tY, tZoom],
panZoom
} = getState();
if (!panZoom) {
return Promise.resolve(false);
}
await panZoom.setViewport(
{
x: viewport.x ?? tX,
y: viewport.y ?? tY,
zoom: viewport.zoom ?? tZoom
},
options
);
return Promise.resolve(true);
},
getViewport: () => {
const [x, y, zoom] = getState().transform;
return { x, y, zoom };
},
setCenter: async (x, y, options) => {
const { width, height, maxZoom, panZoom } = getState();
const nextZoom = typeof options?.zoom !== "undefined" ? options.zoom : maxZoom;
const centerX = width / 2 - x * nextZoom;
const centerY = height / 2 - y * nextZoom;
if (!panZoom) {
return Promise.resolve(false);
}
await panZoom.setViewport(
{
x: centerX,
y: centerY,
zoom: nextZoom
},
{
duration: options?.duration,
ease: options?.ease,
interpolate: options?.interpolate
}
);
return Promise.resolve(true);
},
fitBounds: async (bounds, options) => {
const { width, height, minZoom, maxZoom, panZoom } = getState();
const viewport = getViewportForBounds(
bounds,
width,
height,
minZoom,
maxZoom,
options?.padding ?? 0.1
);
if (!panZoom) {
return Promise.resolve(false);
}
await panZoom.setViewport(viewport, {
duration: options?.duration,
ease: options?.ease,
interpolate: options?.interpolate
});
return Promise.resolve(true);
},
screenToFlowPosition: (clientPosition, options = {}) => {
const { transform, snapGrid, snapToGrid, domNode } = getState();
if (!domNode) {
return clientPosition;
}
const { x: domX, y: domY } = domNode.getBoundingClientRect();
const correctedPosition = {
x: clientPosition.x - domX,
y: clientPosition.y - domY
};
const _snapGrid = options.snapGrid ?? snapGrid;
const _snapToGrid = options.snapToGrid ?? snapToGrid;
return pointToRendererPoint(
correctedPosition,
transform,
_snapToGrid,
_snapGrid
);
},
flowToScreenPosition: (flowPosition) => {
const { transform, domNode } = getState();
if (!domNode) {
return flowPosition;
}
const { x: domX, y: domY } = domNode.getBoundingClientRect();
const rendererPosition = rendererPointToPoint(flowPosition, transform);
return {
x: rendererPosition.x + domX,
y: rendererPosition.y + domY
};
}
};
})();
};
var useViewportHelper_default = useViewportHelper;
// src/hooks/useSolidFlow.ts
var selector3 = (s) => () => !!s.panZoom.get();
function useSolidFlow() {
const viewportHelper = useViewportHelper_default();
const store = useStoreApi();
const batchContext = useBatchContext();
const viewportInitialized = useStore(selector3);
const generalHelper = createMemo(() => {
const getInternalNode = (id) => store.nodeLookup.get(id);
const setNodes = (payload) => {
batchContext.nodeQueue.push(payload);
};
const setEdges = (payload) => {
batchContext.edgeQueue.push(payload);
};
const getNodeRect = (node) => {
const { nodeLookup, nodeOrigin } = store;
const nodeToUse = isNode(node) ? node : nodeLookup.get(node.id);
const position = nodeToUse.parentId ? evaluateAbsolutePosition(
nodeToUse.position,
nodeToUse.measured,
nodeToUse.parentId,
nodeLookup,
nodeOrigin.get()
) : nodeToUse.position;
const nodeWithPosition = {
...nodeToUse,
position,
width: nodeToUse.measured?.width ?? nodeToUse.width,
height: nodeToUse.measured?.height ?? nodeToUse.height
};
return nodeToRect(nodeWithPosition);
};
const updateNode = (id, nodeUpdate, options = { replace: false }) => {
setNodes(
(prevNodes) => prevNodes.map((node) => {
if (node.id === id) {
const nextNode = typeof nodeUpdate === "function" ? nodeUpdate(node) : nodeUpdate;
return options.replace && isNode(nextNode) ? nextNode : { ...node, ...nextNode };
}
return node;
})
);
};
const updateEdge = (id, edgeUpdate, options = { replace: false }) => {
setEdges(
(prevEdges) => prevEdges.map((edge) => {
if (edge.id === id) {
const nextEdge = typeof edgeUpdate === "function" ? edgeUpdate(edge) : edgeUpdate;
return options.replace && isEdge(nextEdge) ? nextEdge : { ...edge, ...nextEdge };
}
return edge;
})
);
};
return {
getNodes: () => store.nodes.get().map((n) => ({ ...n })),
getNode: (id) => getInternalNode(id)?.internals.userNode,
getInternalNode,
getEdges: () => {
const { edges } = store;
return edges.get().map((e) => ({ ...e }));
},
getEdge: (id) => store.edgeLookup.get(id),
setNodes,
setEdges,
addNodes: (payload) => {
const newNodes = Array.isArray(payload) ? payload : [payload];
batchContext.nodeQueue.push((nodes) => [...nodes, ...newNodes]);
},
addEdges: (payload) => {
const newEdges = Array.isArray(payload) ? payload : [payload];
batchContext.edgeQueue.push((edges) => [...edges, ...newEdges]);
},
toObject: () => {
const { nodes, edges, transform } = store;
const [x, y, zoom] = transform.get();
return {
nodes: nodes.get().map((n) => ({ ...n })),
edges: edges.get().map((e) => ({ ...e })),
viewport: {
x,
y,
zoom
}
};
},
deleteElements: async ({
nodes: nodesToRemove = [],
edges: edgesToRemove = []
}) => {
const {
nodes,
edges,
onNodesDelete,
onEdgesDelete,
triggerNodeChanges,
triggerEdgeChanges,
onDelete,
onBeforeDelete
} = store;
const { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove({
nodesToRemove,
edgesToRemove,
nodes: nodes.get(),
edges: edges.get(),
onBeforeDelete: onBeforeDelete.get()
});
const hasMatchingEdges = matchingEdges.length > 0;
const hasMatchingNodes = matchingNodes.length > 0;
if (hasMatchingEdges) {
const edgeChanges = matchingEdges.map(
elementToRemoveChange
);
onEdgesDelete.get()?.(matchingEdges);
triggerEdgeChanges(edgeChanges);
}
if (hasMatchingNodes) {
const nodeChanges = matchingNodes.map(
elementToRemoveChange
);
onNodesDelete.get()?.(matchingNodes);
triggerNodeChanges(nodeChanges);
}
if (hasMatchingNodes || hasMatchingEdges) {
onDelete.get()?.({ nodes: matchingNodes, edges: matchingEdges });
}
return { deletedNodes: matchingNodes, deletedEdges: matchingEdges };
},
getIntersectingNodes: (nodeOrRect, partially = true, nodes) => {
const isRect = isRectObject(nodeOrRect);
const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
const hasNodesOption = nodes !== void 0;
if (!nodeRect) {
return [];
}
return (nodes || store.nodes.get()).filter((n) => {
const internalNode = store.nodeLookup.get(n.id);
if (internalNode && !isRect && (n.id === nodeOrRect.id || !internalNode.internals.positionAbsolute)) {
return false;
}
const currNodeRect = nodeToRect(hasNodesOption ? n : internalNode);
const overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
const partiallyVisible = partially && overlappingArea > 0;
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
});
},
isNodeIntersecting: (nodeOrRect, area, partially = true) => {
const isRect = isRectObject(nodeOrRect);
const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
if (!nodeRect) {
return false;
}
const overlappingArea = getOverlappingArea(nodeRect, area);
const partiallyVisible = partially && overlappingArea > 0;
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
},
updateNode,
updateNodeData: (id, dataUpdate, options = { replace: false }) => {
updateNode(
id,
(node) => {
const nextData = typeof dataUpdate === "function" ? dataUpdate(node) : dataUpdate;
return options.replace ? { ...node, data: nextData } : { ...node, data: { ...node.data, ...nextData } };
},
options
);
},
updateEdge,
updateEdgeData: (id, dataUpdate, options = { replace: false }) => {
updateEdge(
id,
(edge) => {
const nextData = typeof dataUpdate === "function" ? dataUpdate(edge) : dataUpdate;
return options.replace ? { ...edge, data: nextData } : { ...edge, data: { ...edge.data, ...nextData } };
},
options
);
},
getNodesBounds: (nodes) => {
const { nodeLookup, nodeOrigin } = store;
return getNodesBounds(nodes, {
nodeLookup,
nodeOrigin: nodeOrigin.get()
});
},
getHandleConnections: ({ type, id, nodeId }) => Array.from(
store.connectionLookup.get(`${nodeId}-${type}${id ? `-${id}` : ""}`)?.values() ?? []
),
getNodeConnections: ({ type, handleId, nodeId }) => Array.from(
store.connectionLookup.get(
`${nodeId}${type ? handleId ? `-${type}-${handleId}` : `-${type}` : ""}`
)?.values() ?? []
),
fitView: async (options) => {
const fitViewResolver = store.fitViewResolver.get() ?? withResolvers();
store.fitViewQueued.set(true);
store.fitViewOptions.set(options);
store.fitViewResolver.set(fitViewResolver);
batchContext.nodeQueue.push((nodes) => [...nodes]);
return fitViewResolver.promise;
}
};
});
return createMemo(() => ({
...generalHelper(),
...viewportHelper,
viewportInitialized
}))();
}
var _tmpl$6 = /* @__PURE__ */ template(`<svg xmlns=http://www.w3.org/2000/svg viewBox="0 0 32 30"><path d="M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z">`);
function FitViewIcon() {
return _tmpl$6();
}
var _tmpl$7 = /* @__PURE__ */ template(`<svg xmlns=http://www.w3.org/2000/svg viewBox="0 0 25 32"><path d="M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z">`);
function LockIcon() {
return _tmpl$7();
}
var _tmpl$8 = /* @__PURE__ */ template(`<svg xmlns=http://www.w3.org/2000/svg viewBox="0 0 32 5"><path d="M0 0h32v4.2H0z">`);
function MinusIcon() {
return _tmpl$8();
}
var _tmpl$9 = /* @__PURE__ */ template(`<svg xmlns=http://www.w3.org/2000/svg viewBox="0 0 32 32"><path d="M32 18.133H18.133V32h-4.266V18.133H0v-4.266h13.867V0h4.266v13.867H32z">`);
function PlusIcon() {
return _tmpl$9();
}
var _tmpl$10 = /* @__PURE__ */ template(`<svg xmlns=http://www.w3.org/2000/svg viewBox="0 0 25 32"><path d="M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z">`);
function UnlockIcon() {
return _tmpl$10();
}
// src/additional-components/Controls/Controls.tsx
var selector4 = (s) => ({
isInteractive: () => s.nodesDraggable.get() || s.nodesConnectable.get() || s.elementsSelectable.get(),
minZoomReached: () => s.transform.get()[2] <= s.minZoom.get(),
maxZoomReached: () => s.transform.get()[2] >= s.maxZoom.get()
});
function ControlsComponent(_p) {
const p = mergeProps({
showZoom: true,
showFitView: true,
showInteractive: true,
position: "bottom-left",
orientation: "vertical",
"aria-label": "React Flow controls"
}, _p);
const store = useStoreApi();
const storeData = useStore(selector4);
const {
zoomIn,
zoomOut,
fitView
} = useSolidFlow();
const onZoomInHandler = () => {
zoomIn();
p.onZoomIn?.();
};
const onZoomOutHandler = () => {
zoomOut();
p.onZoomOut?.();
};
const onFitViewHandler = () => {
fitView(p.fitViewOptions);
p.onFitView?.();
};
const onToggleInteractivity = () => {
store.batch((store2) => {
store2.nodesDraggable.set(!storeData.isInteractive());
store2.nodesConnectable.set(!storeData.isInteractive());
store2.elementsSelectable.set(!storeData.isInteractive());
});
p.onInteractiveChange?.(!storeData.isInteractive());
};
const orientationClass = () => p.orientation === "horizontal" ? "horizontal" : "vertical";
return createComponent(Panel, {
get ["class"]() {
return cc(["solid-flow__controls", orientationClass, p.className]);
},
get position() {
return p.position;
},
get style() {
return p.style;
},
"data-testid": "rf__controls",
get ["aria-label"]() {
return p["aria-label"];
},
get children() {
return [createComponent(Show, {
get when() {
return p.showZoom;
},
get children() {
return [createComponent(ControlButton, {
onClick: onZoomInHandler,
"class": "solid-flow__controls-zoomin",
title: "zoom in",
"aria-label": "zoom in",
get disabled() {
return storeData.maxZoomReached();
},
get children() {
return createComponent(PlusIcon, {});
}
}), createComponent(ControlButton, {
onClick: onZoomOutHandler,
"class": "solid-flow__controls-zoomout",
title: "zoom out",
"aria-label": "zoom out",
get disabled() {
return storeData.minZoomReached();
},
get children() {
return createComponent(MinusIcon, {});
}
})];
}
}), createComponent(Show, {
get when() {
return p.showFitView;
},
get children() {
return createComponent(ControlButton, {
"class": "solid-flow__controls-fitview",
onClick: onFitViewHandler,
title: "fit view",
"aria-label": "fit view",
get children() {
return createComponent(FitViewIcon, {});
}
});
}
}), createComponent(Show, {
get when() {
return p.showInteractive;
},
get children() {
return createComponent(ControlButton, {
"class": "solid-flow__controls-interactive",
onClick: onToggleInteractivity,
title: "toggle interactivity",
"aria-label": "toggle interactivity",
get children() {
return memo(() => !!storeData.isInteractive())() ? createComponent(UnlockIcon, {}) : createComponent(LockIcon, {});
}
});
}
}), memo(() => p.children)];
}
});
}
ControlsComponent.displayName = "Controls";
var Controls = ControlsComponent;
var _tmpl$11 = /* @__PURE__ */ template(`<svg><rect></svg>`, false, true, false);
function MiniMapNodeComponent({
id,
x,
y,
width,
height,
style: style2,
color,
strokeColor,
strokeWidth,
className,
borderRadius,
shapeRendering,
selected: selected2,
onClick
}) {
const {
background,
"background-color": backgroundColor
} = style2 || {};
const fill = color || background || backgroundColor;
return (() => {
var _el$ = _tmpl$11();
addEventListener(_el$, "click", onClick ? (event) => onClick(event, id) : void 0, true);
setAttribute(_el$, "x", x);
setAttribute(_el$, "y", y);
setAttribute(_el$, "rx", borderRadius);
setAttribute(_el$, "ry", borderRadius);
setAttribute(_el$, "width", width);
setAttribute(_el$, "height", height);
fill != null ? _el$.style.setProperty("fill", fill) : _el$.style.removeProperty("fill");
strokeColor != null ? _el$.style.setProperty("stroke", strokeColor) : _el$.style.removeProperty("stroke");
strokeWidth != null ? _el$.style.setProperty("stroke-width", strokeWidth) : _el$.style.removeProperty("stroke-width");
setAttribute(_el$, "shape-rendering", shapeRendering);
effect(() => setAttribute(_el$, "class", cc(["solid-flow__minimap-node", {
selected: selected2
}, className])));
return _el$;
})();
}
var MiniMapNode = MiniMapNodeComponent;
delegateEvents(["click"]);
// src/additional-components/MiniMap/MiniMapNodes.tsx
var selectorNodeIds = (s) => () => s.nodes.get().map((node) => node.id);
var getAttrFunction = (func) => func instanceof Function ? func : () => func;
function MiniMapNodes(_p) {
const p = mergeProps({
nodeClassName: "",
nodeBorderRadius: 5,
nodeComponent: MiniMapNode
}, _p);
const nodeIds = useStore(selectorNodeIds);
const nodeColorFunc = getAttrFunction(p.nodeColor);
const nodeStrokeColorFunc = getAttrFunction(p.nodeStrokeColor);
const nodeClassNameFunc = getAttrFunction(p.nodeClassName);
const shapeRendering = typeof window === "undefined" || !!window.chrome ? "crispEdges" : "geometricPrecision";
return createComponent(For, {
get each() {
return nodeIds();
},
children: (nodeId) => createComponent(NodeComponentWrapper, {
id: nodeId,
nodeColorFunc,
nodeStrokeColorFunc,
nodeClassNameFunc,
get nodeBorderRadius() {
return p.nodeBorderRadius;
},
get nodeStrokeWidth() {
return p.nodeStrokeWidth;
},
get NodeComponent() {
return p.nodeComponent;
},
get onClick() {
return p.onClick;
},
shapeRendering
})
});
}
function NodeComponentWrapperInner(p) {
const storeData = useStore((s) => {
const internalNode = s.nodeLookup.get(p.id);
if (!internalNode) {
return {
node: () => null,
x: () => 0,
y: () => 0,
width: () => 0,
height: () => 0
};
}
const userNode = internalNode.internals.userNode;
const positionAbsolute = internalNode.internals.positionAbsolute;
const dimensions = getNodeDimensions(userNode);
return {
node: () => userNode,
x: () => positionAbsolute.x,
y: () => positionAbsolute.y,
width: () => dimensions.width,
height: () => dimensions.height
};
});
return createComponent(Show, {
get when() {
return memo(() => !!(storeData.node() && !storeData.node().hidden && nodeHasDimensions(storeData.node())))() ? storeData.node() : false;
},
children: (validNode) => createComponent(Dynamic, {
get component() {
return p.NodeComponent;
},
get x() {
return storeData.x();
},
get y() {
return storeData.y();
},
get width() {
return storeData.width();
},
get height() {
return storeData.height();
},
get style() {
return validNode().style;
},
get selected() {
return !!validNode().selected;
},
get className() {
return p.nodeClassNameFunc(validNode());
},
get color() {
return p.nodeColorFunc(validNode());
},
get borderRadius() {
return p.nodeBorderRadius;
},
get strokeColor() {
return p.nodeStrokeColorFunc(validNode());
},
get strokeWidth() {
return p.nodeStrokeWidth;
},
get shapeRendering() {
return p.shapeRendering;
},
get onClick() {
return p.onClick;
},
get id() {
return validNode().id;
}
})
});
}
var NodeComponentWrapper = NodeComponentWrapperInner;
var MiniMapNodes_default = MiniMapNodes;
// src/additional-components/MiniMap/MiniMap.tsx
var _tmpl$12 = /* @__PURE__ */ template(`<title>`);
var _tmpl$22 = /* @__PURE__ */ template(`<svg class=solid-flow__minimap-svg role=img><path class=solid-flow__minimap-mask fill-rule=evenodd pointer-events=none>`);
var defaultWidth = 200;
var defaultHeight = 150;
var filterHidden = (node) => !node.hidden;
var selector5 = (s) => {
const viewBB = () => ({
x: -s.transform.get()[0] / s.transform.get()[2],
y: -s.transform.get()[1] / s.transform.get()[2],
width: s.width.get() / s.transform.get()[2],
height: s.height.get() / s.transform.get()[2]
});
return {
viewBB,
boundingRect: () => s.nodeLookup.size > 0 ? getBoundsOfRects(getInternalNodesBounds(s.nodeLookup, {
filter: filterHidden
}), viewBB()) : viewBB(),
rfId: () => s.rfId.get(),
panZoom: () => s.panZoom.get(),
translateExtent: () => s.translateExtent.get(),
flowWidth: () => s.width.get(),
flowHeight: () => s.height.get()
};
};
var ARIA_LABEL_KEY = "solid-flow__minimap-desc";
function MiniMapComponent(_p) {
const p = mergeProps({
nodeClassName: "",
nodeBorderRadius: 5,
position: "bottom-right",
pannable: false,
zoomable: false,
ariaLabel: "React Flow mini map",
zoomStep: 10,
offsetScale: 5
}, _p);
const store = useStoreApi();
let svg;
const storeData = useStore(selector5);
const elementWidth = () => p.style?.width ?? defaultWidth;
const elementHeight = () => p.style?.height ?? defaultHeight;
const scaledWidth = () => storeData.boundingRect().width / elementWidth();
const scaledHeight = () => storeData.boundingRect().height / elementHeight();
const viewScale = () => Math.max(scaledWidth(), scaledHeight());
const viewWidth = () => viewScale() * elementWidth();
const viewHeight = () => viewScale() * elementHeight();
const offset = () => p.offsetScale * viewScale();
const x = () => storeData.boundingRect().x - (viewWidth() - storeData.boundingRect().width) / 2 - offset();
const y = () => storeData.boundingRect().y - (viewHeight() - storeData.boundingRect().height) / 2 - offset();
const width = () => viewWidth() + offset() * 2;
const height = () => viewHeight() + offset() * 2;
const labelledBy = () => `${ARIA_LABEL_KEY}-${storeData.rfId()}`;
let viewScaleRef = 0;
let minimapInstance;
createEffect(() => {
viewScaleRef = viewScale();
});
createEffect(() => {
const pZoom = storeData.panZoom();
if (svg && pZoom) {
minimapInstance = XYMinimap({
domNode: svg,
panZoom: pZoom,
getTransform: () => store.transform.get(),
getViewScale: () => viewScaleRef
});
onCleanup(() => {
minimapInstance?.destroy();
});
}
});
createEffect(() => {
minimapInstance?.update({
translateExtent: storeData.translateExtent(),
width: storeData.flowWidth(),
height: storeData.flowHeight(),
inversePan: p.inversePan,
pannable: p.pannable,
zoomStep: p.zoomStep,
zoomable: p.zoomable
});
});
const onSvgClick = p.onClick ? (event) => {
const [x2, y2] = minimapInstance?.pointer(event) || [0, 0];
p.onClick(event, {
x: x2,
y: y2
});
} : void 0;
const onSvgNodeClick = p.onNodeClick ? (event, nodeId) => {
const node = store.nodeLookup.get(nodeId).internals.userNode;
p.onNodeClick(event, node);
} : void 0;
return createComponent(Panel, {
get position() {
return p.position;
},
get style() {
return {
...p.style,
"--xy-minimap-background-color-props": typeof p.bgColor === "string" ? p.bgColor : void 0,
"--xy-minimap-mask-background-color-props": typeof p.maskColor === "string" ? p.maskColor : void 0,
"--xy-minimap-mask-stroke-color-props": typeof p.maskStrokeColor === "string" ? p.maskStrokeColor : void 0,
"--xy-minimap-mask-stroke-width-props": typeof p.maskStrokeWidth === "number" ? p.maskStrokeWidth * viewScale() : void 0,
"--xy-minimap-node-background-color-props": typeof p.nodeColor === "string" ? p.nodeColor : void 0,
"--xy-minimap-node-stroke-color-props": typeof p.nodeStrokeColor === "string" ? p.nodeStrokeColor : void 0,
"--xy-minimap-node-stroke-width-props": typeof p.nodeStrokeWidth === "number" ? p.nodeStrokeWidth : void 0
};
},
get ["class"]() {
return cc(["solid-flow__minimap", p.class]);
},
"data-testid": "rf__minimap",
get children() {
var _el$ = _tmpl$22(), _el$3 = _el$.firstChild;
addEventListener(_el$, "click", onSvgClick, true);
var _ref$ = svg;
typeof _ref$ === "function" ? use(_ref$, _el$) : svg = _el$;
insert(_el$, createComponent(Show, {
get when() {
return p.ariaLabel;
},
get children() {
var _el$2 = _tmpl$12();
insert(_el$2, () => p.ariaLabel);
effect(() => setAttribute(_el$2, "id", labelledBy()));
return _el$2;
}
}), _el$3);
insert(_el$, createComponent(MiniMapNodes_default, {
onClick: onSvgNodeClick,
get nodeColor() {
return p.nodeColor;
},
get nodeStrokeColor() {
return p.nodeStrokeColor;
},
get nodeBorderRadius() {
return p.nodeBorderRadius;
},
get nodeClassName() {
return p.nodeClassName;
},
get nodeStrokeWidth() {
return p.nodeStrokeWidth;
},
get nodeComponent() {
return p.nodeComponent;
}
}), _el$3);
effect((_p$) => {
var _v$ = elementWidth(), _v$2 = elementHeight(), _v$3 = `${x()} ${y()} ${width()} ${height()}`, _v$4 = labelled