UNPKG

@circonomit/solid-flow

Version:

Solid Flow - A highly customizable Solid library for building node-based editors and interactive flow charts.

1,575 lines (1,538 loc) 185 kB
// src/index.ts import { ConnectionLineType as ConnectionLineType3, ConnectionMode as ConnectionMode3, MarkerType as MarkerType2, PanOnScrollMode as PanOnScrollMode3, Position as Position10, ResizeControlVariant as ResizeControlVariant3, SelectionMode as SelectionMode3 } from "@xyflow/system"; // src/additional-components/Background/Background.tsx import cc2 from "classcat"; import { mergeProps, Show } from "solid-js"; // src/hooks/useStore.ts import { errorMessages } from "@xyflow/system"; import { batch, useContext } from "solid-js"; // src/contexts/StoreContext.ts import { createContext } from "solid-js"; 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; } }; // src/additional-components/Background/Patterns.tsx import cc from "classcat"; function LinePattern(p) { return <path stroke-width={p.lineWidth} d={`M${p.dimensions[0] / 2} 0 V${p.dimensions[1]} M0 ${p.dimensions[1] / 2} H${p.dimensions[0]}`} class={cc(["solid-flow__background-pattern", p.variant, p.className])} />; } function DotPattern(p) { return <circle cx={p.radius} cy={p.radius} r={p.radius} class={cc(["solid-flow__background-pattern", "dots", p.className])} />; } // 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 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 <svg class={cc2(["solid-flow__background", p.className])} style={style2()} ref={(el) => ref.current = el} data-testid="rf__background" > <pattern id={_patternId()} x={storeData.transform.get()[0] % scaledGap()[0]} y={storeData.transform.get()[1] % scaledGap()[1]} width={scaledGap()[0]} height={scaledGap()[1]} patternUnits="userSpaceOnUse" patternTransform={`translate(-${scaledOffset()[0]},-${scaledOffset()[1]})`} > <Show when={isDots()} fallback={<LinePattern dimensions={patternDimensions()} lineWidth={p.lineWidth} variant={p.variant} className={p.patternClassName} />} > <DotPattern radius={scaledSize() / 2} className={p.patternClassName} /> </Show> </pattern> <rect x="0" y="0" width="100%" height="100%" fill={`url(#${_patternId()})`} /> </svg>; } BackgroundComponent.displayName = "Background"; var Background = BackgroundComponent; // src/additional-components/Controls/ControlButton.tsx import cc3 from "classcat"; import { splitProps } from "solid-js"; function ControlButton(p) { const [_, rest] = splitProps(p, ["children", "class"]); return <button type="button" class={cc3(["solid-flow__controls-button", p.class])} {...rest} > {p.children} </button>; } // src/additional-components/Controls/Controls.tsx import cc5 from "classcat"; import { mergeProps as mergeProps3, Show as Show2 } from "solid-js"; // src/components/Panel/index.tsx import cc4 from "classcat"; import { mergeProps as mergeProps2 } from "solid-js"; var selector2 = (s) => () => s.userSelectionActive.get() ? "none" : "all"; function Panel(_p) { const p = mergeProps2({ 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 <div {...p} class={cc4(["solid-flow__panel", p.class, ...positionClasses()])} style={style2()} > {p.children} </div>; } // src/hooks/useSolidFlow.ts import { evaluateAbsolutePosition, getElementsToRemove, getNodesBounds, getOverlappingArea, isRectObject, nodeToRect, withResolvers } from "@xyflow/system"; import { createMemo as createMemo2 } from "solid-js"; // src/components/BatchProvider/index.tsx import { createContext as createContext2, useContext as useContext2 } from "solid-js"; // 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" }; } // src/utils/general.ts import { isEdgeBase, isNodeBase } from "@xyflow/system"; var isNode = (element) => isNodeBase(element); var isEdge = (element) => isEdgeBase(element); // src/components/BatchProvider/useQueue.ts import { createSignal as createSignal2 } from "solid-js"; // src/hooks/useIsomorphicLayoutEffect.ts import { createEffect } from "solid-js"; var useIsomorphicLayoutEffect = typeof window !== "undefined" ? createEffect : createEffect; // src/store/initialState.ts import { ReactiveMap } from "@solid-primitives/map"; import { adoptUserNodes, ConnectionMode, devWarn, getInternalNodesBounds, getViewportForBounds, infiniteExtent, initialConnection, updateConnectionLookup } from "@xyflow/system"; import { createSignal } from "solid-js"; import { createStore } from "solid-js/store"; 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] = createSignal2(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 = createContext2(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 <BatchContext.Provider value={value}>{p.children}</BatchContext.Provider>; } function useBatchContext() { const batchContext = useContext2(BatchContext); if (!batchContext) { throw new Error("useBatchContext must be used within a BatchProvider"); } return batchContext; } // src/hooks/useViewportHelper.ts import { getViewportForBounds as getViewportForBounds2, pointToRendererPoint, rendererPointToPoint } from "@xyflow/system"; import { createMemo } from "solid-js"; 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 = getViewportForBounds2( 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 = createMemo2(() => { 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 createMemo2(() => ({ ...generalHelper(), ...viewportHelper, viewportInitialized }))(); } // src/additional-components/Controls/Icons/FitView.tsx function FitViewIcon() { return <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" /> </svg>; } // src/additional-components/Controls/Icons/Lock.tsx function LockIcon() { return <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" /> </svg>; } // src/additional-components/Controls/Icons/Minus.tsx function MinusIcon() { return <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 5"> <path d="M0 0h32v4.2H0z" /> </svg>; } // src/additional-components/Controls/Icons/Plus.tsx function PlusIcon() { return <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" /> </svg>; } // src/additional-components/Controls/Icons/Unlock.tsx function UnlockIcon() { return <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" /> </svg>; } // 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 = mergeProps3( { 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 <Panel class={cc5(["solid-flow__controls", orientationClass, p.className])} position={p.position} style={p.style} data-testid="rf__controls" aria-label={p["aria-label"]} > <Show2 when={p.showZoom}> <ControlButton onClick={onZoomInHandler} class="solid-flow__controls-zoomin" title="zoom in" aria-label="zoom in" disabled={storeData.maxZoomReached()} > <PlusIcon /> </ControlButton> <ControlButton onClick={onZoomOutHandler} class="solid-flow__controls-zoomout" title="zoom out" aria-label="zoom out" disabled={storeData.minZoomReached()} > <MinusIcon /> </ControlButton> </Show2> <Show2 when={p.showFitView}> <ControlButton class="solid-flow__controls-fitview" onClick={onFitViewHandler} title="fit view" aria-label="fit view" > <FitViewIcon /> </ControlButton> </Show2> <Show2 when={p.showInteractive}> <ControlButton class="solid-flow__controls-interactive" onClick={onToggleInteractivity} title="toggle interactivity" aria-label="toggle interactivity" > {storeData.isInteractive() ? <UnlockIcon /> : <LockIcon />} </ControlButton> </Show2> {p.children} </Panel>; } ControlsComponent.displayName = "Controls"; var Controls = ControlsComponent; // src/additional-components/MiniMap/MiniMap.tsx import { getBoundsOfRects, getInternalNodesBounds as getInternalNodesBounds2, XYMinimap } from "@xyflow/system"; import cc7 from "classcat"; import { createEffect as createEffect2, mergeProps as mergeProps5, onCleanup, Show as Show4 } from "solid-js"; // src/additional-components/MiniMap/MiniMapNodes.tsx import { getNodeDimensions, nodeHasDimensions } from "@xyflow/system"; import { For, mergeProps as mergeProps4, Show as Show3 } from "solid-js"; import { Dynamic } from "solid-js/web"; // src/additional-components/MiniMap/MiniMapNode.tsx import cc6 from "classcat"; 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 <rect class={cc6(["solid-flow__minimap-node", { selected: selected2 }, className])} x={x} y={y} rx={borderRadius} ry={borderRadius} width={width} height={height} style={{ fill, stroke: strokeColor, "stroke-width": strokeWidth }} shape-rendering={shapeRendering} onClick={onClick ? (event) => onClick(event, id) : void 0} />; } var MiniMapNode = MiniMapNodeComponent; // 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 = mergeProps4( { 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 <For each={nodeIds()}> {(nodeId) => <NodeComponentWrapper id={nodeId} nodeColorFunc={nodeColorFunc} nodeStrokeColorFunc={nodeStrokeColorFunc} nodeClassNameFunc={nodeClassNameFunc} nodeBorderRadius={p.nodeBorderRadius} nodeStrokeWidth={p.nodeStrokeWidth} NodeComponent={p.nodeComponent} onClick={p.onClick} shapeRendering={shapeRendering} />} </For>; } 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 <Show3 when={storeData.node() && !storeData.node().hidden && nodeHasDimensions(storeData.node()) ? storeData.node() : false} > {(validNode) => <Dynamic component={p.NodeComponent} x={storeData.x()} y={storeData.y()} width={storeData.width()} height={storeData.height()} style={validNode().style} selected={!!validNode().selected} className={p.nodeClassNameFunc(validNode())} color={p.nodeColorFunc(validNode())} borderRadius={p.nodeBorderRadius} strokeColor={p.nodeStrokeColorFunc(validNode())} strokeWidth={p.nodeStrokeWidth} shapeRendering={p.shapeRendering} onClick={p.onClick} id={validNode().id} />} </Show3>; } var NodeComponentWrapper = NodeComponentWrapperInner; var MiniMapNodes_default = MiniMapNodes; // src/additional-components/MiniMap/MiniMap.tsx 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( getInternalNodesBounds2(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 = mergeProps5( { 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; createEffect2(() => { viewScaleRef = viewScale(); }); createEffect2(() => { const pZoom = storeData.panZoom(); if (svg && pZoom) { minimapInstance = XYMinimap({ domNode: svg, panZoom: pZoom, getTransform: () => store.transform.get(), getViewScale: () => viewScaleRef }); onCleanup(() => { minimapInstance?.destroy(); }); } }); createEffect2(() => { 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 <Panel position={p.position} style={{ ...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 }} class={cc7(["solid-flow__minimap", p.class])} data-testid="rf__minimap" > <svg width={elementWidth()} height={elementHeight()} viewBox={`${x()} ${y()} ${width()} ${height()}`} class="solid-flow__minimap-svg" role="img" aria-labelledby={labelledBy()} ref={svg} onClick={onSvgClick} > <Show4 when={p.ariaLabel}> <title id={labelledBy()}>{p.ariaLabel}</title> </Show4> <MiniMapNodes_default onClick={onSvgNodeClick} nodeColor={p.nodeColor} nodeStrokeColor={p.nodeStrokeColor} nodeBorderRadius={p.nodeBorderRadius} nodeClassName={p.nodeClassName} nodeStrokeWidth={p.nodeStrokeWidth} nodeComponent={p.nodeComponent} /> <path class="solid-flow__minimap-mask" d={`M${x() - offset()},${y() - offset()}h${width() + offset() * 2}v${height() + offset() * 2}h${-width() - offset() * 2}z M${storeData.viewBB().x},${storeData.viewBB().y}h${storeData.viewBB().width}v${storeData.viewBB().height}h${-storeData.viewBB().width}z`} fill-rule="evenodd" pointer-events="none" /> </svg> </Panel>; } MiniMapComponent.displayName = "MiniMap"; var MiniMap = MiniMapComponent; // src/additional-components/NodeResizer/NodeResizeControl.tsx import { evaluateAbsolutePosition as evaluateAbsolutePosition2, handleExpandParent, ResizeControlVariant, XYResizer } from "@xyflow/system"; import cc8 from "classcat"; import { createEffect as createEffect3, mergeProps as mergeProps6, onCleanup as onCleanup2 } from "solid-js"; // src/contexts/NodeIdContext.ts import { createContext as createContext3, useContext as useContext3 } from "solid-js"; var NodeIdContext = createContext3(null); var Provider2 = NodeIdContext.Provider; var useNodeId = () => { const nodeId = useContext3(NodeIdContext); if (!nodeId) { throw new Error("NodeIdContext is not provided"); } return nodeId; }; // src/additional-components/NodeResizer/NodeResizeControl.tsx function ResizeControl(_p) { const p = mergeProps6( { variant: ResizeControlVariant.Handle, style: {}, minWidth: 10, minHeight: 10, maxWidth: Number.MAX_VALUE, maxHeight: Number.MAX_VALUE, keepAspectRatio: false }, _p ); const contextNodeId = useNodeId(); const id = () => typeof p.nodeId === "string" ? p.nodeId : contextNodeId(); const store = useStoreApi(); const resizeControlRef = useRef(null); const defaultPosition = () => p.variant === ResizeControlVariant.Line ? "right" : "bottom-right"; const controlPosition = () => p.position ?? defaultPosition(); const resizer = useRef(null); createEffect3(() => { const nodeId = id(); if (!resizeControlRef.current || !nodeId) { return; } if (!resizer.current) { resizer.current = XYResizer({ domNode: resizeControlRef.current, nodeId, getStoreItems: () => { return { nodeLookup: store.nodeLookup, transform: store.transform.get(), snapGrid: store.snapGrid.get(), snapToGrid: store.snapToGrid.get(), nodeOrigin: store.nodeOrigin.get(), paneDomNode: store.domNode.get() }; }, onChange: (change, childChanges) => { const changes = []; const nextPosition = { x: change.x, y: change.y }; const node = store.nodeLookup.get(nodeId); if (node && node.expandParent && node.parentId) { const origin = node.origin ?? store.nodeOrigin.get(); const width = change.width ?? node.measured.width ?? 0; const height = change.height ?? node.measured.height ?? 0; const child = { id: node.id, parentId: node.parentId, rect: { width, height, ...evaluateAbsolutePosition2( { x: change.x ?? node.position.x, y: change.y ?? node.position.y }, { width, height }, node.parentId, store.nodeLookup, origin ) } }; const parentExpandChanges = handleExpandParent( [child], store.nodeLookup, store.parentLookup, store.nodeOrigin.get() ); changes.push(...parentExpandChanges); nextPosition.x = change.x ? Math.max(origin[0] * width, change.x) : void 0; nextPosition.y = change.y ? Math.max(origin[1] * height, change.y) : void 0; } if (nextPosition.x !== void 0 && nextPosition.y !== void 0) { const positionChange = { id: nodeId, type: "position", position: { ...nextPosition } }; changes.push(positionChange); } if (change.width !== void 0 && change.height !== void 0) { const setAttributes = !p.resizeDirection ? true : p.resizeDirection === "horizontal" ? "width" : "height"; const dimensionChange = { id: nodeId, type: "dimensions", resizing: true, setAttributes, dimensions: { width: change.width, height: change.height } }; changes.push(dimensionChange); } for (const childChange of childChanges) { const positionChange = { ...childChange, type: "position" }; changes.push(positionChange); } stor