@terra-symposium/docs 0.0.0-stage → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +72 -2
- package/lib-dist/Docs.d.ts +22 -0
- package/lib-dist/Docs.d.ts.map +1 -0
- package/lib-dist/Docs.js +60 -0
- package/lib-dist/Docs.js.map +1 -0
- package/lib-dist/LocalDocsWorkspace.d.ts +30 -0
- package/lib-dist/LocalDocsWorkspace.d.ts.map +1 -0
- package/lib-dist/LocalDocsWorkspace.js +148 -0
- package/lib-dist/LocalDocsWorkspace.js.map +1 -0
- package/lib-dist/SiteLayout.d.ts +30 -0
- package/lib-dist/SiteLayout.d.ts.map +1 -0
- package/lib-dist/SiteLayout.js +34 -0
- package/lib-dist/SiteLayout.js.map +1 -0
- package/lib-dist/StaticWorkspace.d.ts +21 -0
- package/lib-dist/StaticWorkspace.d.ts.map +1 -0
- package/lib-dist/StaticWorkspace.js +179 -0
- package/lib-dist/StaticWorkspace.js.map +1 -0
- package/lib-dist/_Toolbar.d.ts +7 -0
- package/lib-dist/_Toolbar.d.ts.map +1 -0
- package/lib-dist/_Toolbar.js +85 -0
- package/lib-dist/_Toolbar.js.map +1 -0
- package/lib-dist/blocks.d.ts +23 -0
- package/lib-dist/blocks.d.ts.map +1 -0
- package/lib-dist/blocks.js +321 -0
- package/lib-dist/blocks.js.map +1 -0
- package/lib-dist/docs-catalog.d.ts +5 -0
- package/lib-dist/docs-catalog.d.ts.map +1 -0
- package/lib-dist/docs-catalog.js +38 -0
- package/lib-dist/docs-catalog.js.map +1 -0
- package/lib-dist/docs-style.css +31 -0
- package/lib-dist/fonts/Avara-Bold.woff2 +0 -0
- package/lib-dist/fonts/Avara-BoldItalic.woff2 +0 -0
- package/lib-dist/fonts/OFL-1.1.txt +93 -0
- package/lib-dist/fonts/README.md +5 -0
- package/lib-dist/host-runtime.d.ts +5 -0
- package/lib-dist/host-runtime.d.ts.map +1 -0
- package/lib-dist/host-runtime.js +21 -0
- package/lib-dist/host-runtime.js.map +1 -0
- package/lib-dist/host.d.ts +170 -0
- package/lib-dist/host.d.ts.map +1 -0
- package/lib-dist/host.js +2 -0
- package/lib-dist/host.js.map +1 -0
- package/lib-dist/index.d.ts +35 -0
- package/lib-dist/index.d.ts.map +1 -0
- package/lib-dist/index.js +28 -0
- package/lib-dist/index.js.map +1 -0
- package/lib-dist/markdown-model.d.ts +7 -0
- package/lib-dist/markdown-model.d.ts.map +1 -0
- package/lib-dist/markdown-model.js +181 -0
- package/lib-dist/markdown-model.js.map +1 -0
- package/lib-dist/markdown.d.ts +3 -0
- package/lib-dist/markdown.d.ts.map +1 -0
- package/lib-dist/markdown.js +83 -0
- package/lib-dist/markdown.js.map +1 -0
- package/lib-dist/navigation.d.ts +17 -0
- package/lib-dist/navigation.d.ts.map +1 -0
- package/lib-dist/navigation.js +9 -0
- package/lib-dist/navigation.js.map +1 -0
- package/lib-dist/schema.d.ts +135 -0
- package/lib-dist/schema.d.ts.map +1 -0
- package/lib-dist/schema.js +55 -0
- package/lib-dist/schema.js.map +1 -0
- package/lib-dist/style.css +1 -0
- package/lib-dist/terra/Workspace.d.ts +5 -0
- package/lib-dist/terra/Workspace.d.ts.map +1 -0
- package/lib-dist/terra/Workspace.js +9 -0
- package/lib-dist/terra/Workspace.js.map +1 -0
- package/lib-dist/terra/editor/TerraContentEditor.d.ts +10 -0
- package/lib-dist/terra/editor/TerraContentEditor.d.ts.map +1 -0
- package/lib-dist/terra/editor/TerraContentEditor.js +47 -0
- package/lib-dist/terra/editor/TerraContentEditor.js.map +1 -0
- package/lib-dist/terra/editor/_blocks.d.ts +71 -0
- package/lib-dist/terra/editor/_blocks.d.ts.map +1 -0
- package/lib-dist/terra/editor/_blocks.js +110 -0
- package/lib-dist/terra/editor/_blocks.js.map +1 -0
- package/lib-dist/terra/editor/docs/_Callout.d.ts +14 -0
- package/lib-dist/terra/editor/docs/_Callout.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/_Callout.js +41 -0
- package/lib-dist/terra/editor/docs/_Callout.js.map +1 -0
- package/lib-dist/terra/editor/docs/_CodeBlock.d.ts +39 -0
- package/lib-dist/terra/editor/docs/_CodeBlock.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/_CodeBlock.js +494 -0
- package/lib-dist/terra/editor/docs/_CodeBlock.js.map +1 -0
- package/lib-dist/terra/editor/docs/_DocsCode.d.ts +14 -0
- package/lib-dist/terra/editor/docs/_DocsCode.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/_DocsCode.js +95 -0
- package/lib-dist/terra/editor/docs/_DocsCode.js.map +1 -0
- package/lib-dist/terra/editor/docs/_DocsImage.d.ts +18 -0
- package/lib-dist/terra/editor/docs/_DocsImage.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/_DocsImage.js +62 -0
- package/lib-dist/terra/editor/docs/_DocsImage.js.map +1 -0
- package/lib-dist/terra/editor/docs/_Embed.d.ts +15 -0
- package/lib-dist/terra/editor/docs/_Embed.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/_Embed.js +27 -0
- package/lib-dist/terra/editor/docs/_Embed.js.map +1 -0
- package/lib-dist/terra/editor/docs/_Math.d.ts +31 -0
- package/lib-dist/terra/editor/docs/_Math.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/_Math.js +126 -0
- package/lib-dist/terra/editor/docs/_Math.js.map +1 -0
- package/lib-dist/terra/editor/docs/_Nodes.d.ts +15 -0
- package/lib-dist/terra/editor/docs/_Nodes.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/_Nodes.js +52 -0
- package/lib-dist/terra/editor/docs/_Nodes.js.map +1 -0
- package/lib-dist/terra/editor/docs/_Raw.d.ts +15 -0
- package/lib-dist/terra/editor/docs/_Raw.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/_Raw.js +26 -0
- package/lib-dist/terra/editor/docs/_Raw.js.map +1 -0
- package/lib-dist/terra/editor/docs/_Table.d.ts +12 -0
- package/lib-dist/terra/editor/docs/_Table.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/_Table.js +71 -0
- package/lib-dist/terra/editor/docs/_Table.js.map +1 -0
- package/lib-dist/terra/editor/docs/_Tabs.d.ts +14 -0
- package/lib-dist/terra/editor/docs/_Tabs.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/_Tabs.js +104 -0
- package/lib-dist/terra/editor/docs/_Tabs.js.map +1 -0
- package/lib-dist/terra/editor/docs/_TsDocs.d.ts +15 -0
- package/lib-dist/terra/editor/docs/_TsDocs.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/_TsDocs.js +81 -0
- package/lib-dist/terra/editor/docs/_TsDocs.js.map +1 -0
- package/lib-dist/terra/editor/docs/_math_inline.d.ts +14 -0
- package/lib-dist/terra/editor/docs/_math_inline.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/_math_inline.js +30 -0
- package/lib-dist/terra/editor/docs/_math_inline.js.map +1 -0
- package/lib-dist/terra/editor/docs/heading.d.ts +23 -0
- package/lib-dist/terra/editor/docs/heading.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/heading.js +60 -0
- package/lib-dist/terra/editor/docs/heading.js.map +1 -0
- package/lib-dist/terra/editor/docs/index.d.ts +13 -0
- package/lib-dist/terra/editor/docs/index.d.ts.map +1 -0
- package/lib-dist/terra/editor/docs/index.js +14 -0
- package/lib-dist/terra/editor/docs/index.js.map +1 -0
- package/lib-dist/terra/editor/types.d.ts +40 -0
- package/lib-dist/terra/editor/types.d.ts.map +1 -0
- package/lib-dist/terra/editor/types.js +11 -0
- package/lib-dist/terra/editor/types.js.map +1 -0
- package/lib-dist/terra/flow/Nodes.d.ts +78 -0
- package/lib-dist/terra/flow/Nodes.d.ts.map +1 -0
- package/lib-dist/terra/flow/Nodes.js +912 -0
- package/lib-dist/terra/flow/Nodes.js.map +1 -0
- package/lib-dist/terra/flow/_chrome.d.ts +267 -0
- package/lib-dist/terra/flow/_chrome.d.ts.map +1 -0
- package/lib-dist/terra/flow/_chrome.js +798 -0
- package/lib-dist/terra/flow/_chrome.js.map +1 -0
- package/lib-dist/terra/flow/_layout.d.ts +30 -0
- package/lib-dist/terra/flow/_layout.d.ts.map +1 -0
- package/lib-dist/terra/flow/_layout.js +53 -0
- package/lib-dist/terra/flow/_layout.js.map +1 -0
- package/lib-dist/terra/flow/menuContext.d.ts +13 -0
- package/lib-dist/terra/flow/menuContext.d.ts.map +1 -0
- package/lib-dist/terra/flow/menuContext.js +10 -0
- package/lib-dist/terra/flow/menuContext.js.map +1 -0
- package/lib-dist/terra/flow/powerSource.d.ts +16 -0
- package/lib-dist/terra/flow/powerSource.d.ts.map +1 -0
- package/lib-dist/terra/flow/powerSource.js +59 -0
- package/lib-dist/terra/flow/powerSource.js.map +1 -0
- package/lib-dist/terra/pages/docs/_attachments.d.ts +20 -0
- package/lib-dist/terra/pages/docs/_attachments.d.ts.map +1 -0
- package/lib-dist/terra/pages/docs/_attachments.js +904 -0
- package/lib-dist/terra/pages/docs/_attachments.js.map +1 -0
- package/lib-dist/terra/pages/docs/_blocks.d.ts +48 -0
- package/lib-dist/terra/pages/docs/_blocks.d.ts.map +1 -0
- package/lib-dist/terra/pages/docs/_blocks.js +192 -0
- package/lib-dist/terra/pages/docs/_blocks.js.map +1 -0
- package/lib-dist/terra/pages/docs/_custom.d.ts +5 -0
- package/lib-dist/terra/pages/docs/_custom.d.ts.map +1 -0
- package/lib-dist/terra/pages/docs/_custom.js +6 -0
- package/lib-dist/terra/pages/docs/_custom.js.map +1 -0
- package/lib-dist/terra/pages/docs/_editable.d.ts +61 -0
- package/lib-dist/terra/pages/docs/_editable.d.ts.map +1 -0
- package/lib-dist/terra/pages/docs/_editable.js +404 -0
- package/lib-dist/terra/pages/docs/_editable.js.map +1 -0
- package/lib-dist/terra/pages/docs/_export-dialog.d.ts +19 -0
- package/lib-dist/terra/pages/docs/_export-dialog.d.ts.map +1 -0
- package/lib-dist/terra/pages/docs/_export-dialog.js +78 -0
- package/lib-dist/terra/pages/docs/_export-dialog.js.map +1 -0
- package/lib-dist/terra/pages/docs/_helpers.d.ts +395 -0
- package/lib-dist/terra/pages/docs/_helpers.d.ts.map +1 -0
- package/lib-dist/terra/pages/docs/_helpers.js +911 -0
- package/lib-dist/terra/pages/docs/_helpers.js.map +1 -0
- package/lib-dist/terra/pages/docs/_icons.d.ts +20 -0
- package/lib-dist/terra/pages/docs/_icons.d.ts.map +1 -0
- package/lib-dist/terra/pages/docs/_icons.js +29 -0
- package/lib-dist/terra/pages/docs/_icons.js.map +1 -0
- package/lib-dist/terra/pages/docs/_main.d.ts +51 -0
- package/lib-dist/terra/pages/docs/_main.d.ts.map +1 -0
- package/lib-dist/terra/pages/docs/_main.js +1959 -0
- package/lib-dist/terra/pages/docs/_main.js.map +1 -0
- package/lib-dist/terra/pages/docs/_sidebar.d.ts +42 -0
- package/lib-dist/terra/pages/docs/_sidebar.d.ts.map +1 -0
- package/lib-dist/terra/pages/docs/_sidebar.js +2450 -0
- package/lib-dist/terra/pages/docs/_sidebar.js.map +1 -0
- package/lib-dist/terra/pages/docs/_toolbar.d.ts +23 -0
- package/lib-dist/terra/pages/docs/_toolbar.d.ts.map +1 -0
- package/lib-dist/terra/pages/docs/_toolbar.js +1539 -0
- package/lib-dist/terra/pages/docs/_toolbar.js.map +1 -0
- package/lib-dist/terra/pages/docs/_tree.d.ts +178 -0
- package/lib-dist/terra/pages/docs/_tree.d.ts.map +1 -0
- package/lib-dist/terra/pages/docs/_tree.js +1168 -0
- package/lib-dist/terra/pages/docs/_tree.js.map +1 -0
- package/lib-dist/terra/regex.mjs +398 -0
- package/lib-dist/terra/runtime/action-proxy.d.ts +5 -0
- package/lib-dist/terra/runtime/action-proxy.d.ts.map +1 -0
- package/lib-dist/terra/runtime/action-proxy.js +19 -0
- package/lib-dist/terra/runtime/action-proxy.js.map +1 -0
- package/lib-dist/terra/runtime/content.d.ts +150 -0
- package/lib-dist/terra/runtime/content.d.ts.map +1 -0
- package/lib-dist/terra/runtime/content.js +1171 -0
- package/lib-dist/terra/runtime/content.js.map +1 -0
- package/lib-dist/terra/runtime/leaf.d.ts +5 -0
- package/lib-dist/terra/runtime/leaf.d.ts.map +1 -0
- package/lib-dist/terra/runtime/leaf.js +13 -0
- package/lib-dist/terra/runtime/leaf.js.map +1 -0
- package/lib-dist/terra/runtime/nodes.d.ts +112 -0
- package/lib-dist/terra/runtime/nodes.d.ts.map +1 -0
- package/lib-dist/terra/runtime/nodes.js +534 -0
- package/lib-dist/terra/runtime/nodes.js.map +1 -0
- package/lib-dist/terra/runtime/runtime.d.ts +25 -0
- package/lib-dist/terra/runtime/runtime.d.ts.map +1 -0
- package/lib-dist/terra/runtime/runtime.js +50 -0
- package/lib-dist/terra/runtime/runtime.js.map +1 -0
- package/lib-dist/terra/runtime/shared.d.ts +35 -0
- package/lib-dist/terra/runtime/shared.d.ts.map +1 -0
- package/lib-dist/terra/runtime/shared.js +79 -0
- package/lib-dist/terra/runtime/shared.js.map +1 -0
- package/lib-dist/terra/runtime/types.d.ts +266 -0
- package/lib-dist/terra/runtime/types.d.ts.map +1 -0
- package/lib-dist/terra/runtime/types.js +18 -0
- package/lib-dist/terra/runtime/types.js.map +1 -0
- package/lib-dist/terra/runtime/ui.d.ts +31 -0
- package/lib-dist/terra/runtime/ui.d.ts.map +1 -0
- package/lib-dist/terra/runtime/ui.js +16 -0
- package/lib-dist/terra/runtime/ui.js.map +1 -0
- package/lib-dist/workspace.d.ts +78 -0
- package/lib-dist/workspace.d.ts.map +1 -0
- package/lib-dist/workspace.js +151 -0
- package/lib-dist/workspace.js.map +1 -0
- package/package.json +90 -3
- package/src/SiteLayout.astro +38 -0
- package/src/navigation.tsx +47 -0
- package/src/style.css +733 -0
|
@@ -0,0 +1,912 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// @ts-nocheck -- source-preserving extraction; public host contracts are typed in src/host.ts.
|
|
3
|
+
/**
|
|
4
|
+
* astro/src/components/flow/Nodes.tsx
|
|
5
|
+
*/
|
|
6
|
+
/*//////////////////////////////////////////////////
|
|
7
|
+
IMPORTS
|
|
8
|
+
//////////////////////////////////////////////////*/
|
|
9
|
+
// API
|
|
10
|
+
import katex from 'katex';
|
|
11
|
+
import { applyEdgeChanges, applyNodeChanges, useNodesInitialized, useReactFlow } from '@xyflow/react';
|
|
12
|
+
import { cn, createTrace, errorLog, isRec, validateType, warn as logWarn } from '../runtime/runtime.js';
|
|
13
|
+
import { getCustomPos, getGroupId, getNodeSize, layoutGraph, useEditor } from './_chrome.js';
|
|
14
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
15
|
+
// COMPONENTS
|
|
16
|
+
import React from 'react';
|
|
17
|
+
import { AlignHorizontalCenter as AlignHorizontalCenterIcon, AlignHorizontalLeft as AlignHorizontalLeftIcon, AlignHorizontalRight as AlignHorizontalRightIcon, AlignVerticalBottom as AlignVerticalBottomIcon, AlignVerticalCenter as AlignVerticalCenterIcon, AlignVerticalTop as AlignVerticalTopIcon, Choices as DecisionIcon, DataCategorical as TypeIcon, DataStructured as DataIcon, DistributeHorizontalCenter as DistributeHorizontalCenterIcon, DistributeHorizontalLeft as DistributeHorizontalLeftIcon, DistributeHorizontalRight as DistributeHorizontalRightIcon, DistributeVerticalBottom as DistributeVerticalBottomIcon, DistributeVerticalCenter as DistributeVerticalCenterIcon, DistributeVerticalTop as DistributeVerticalTopIcon, Document as NoteIcon, Edit as EditIcon, MagicWand as WandIcon, Maximize as MaximizeIcon, ObjectStorage as GroupIcon, PlayOutline as OriginIcon, RadioButton as TerminatorIcon, Redo as RedoIcon, StopOutline as TerminalIcon, Table as ErdIcon, Task as ProcessIcon, TrashCan as TrashIcon, Undo as UndoIcon, } from '@carbon/icons-react';
|
|
18
|
+
import { Background, ControlButton, Controls, MarkerType, NodeResizer, ReactFlow, ReactFlowProvider } from '@xyflow/react';
|
|
19
|
+
import { Button } from '@terra-symposium/styling/ui/terra-original';
|
|
20
|
+
import { ButtonGroup } from '@terra-symposium/styling/ui/terra-original';
|
|
21
|
+
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger } from '@terra-symposium/styling/ui/terra-original';
|
|
22
|
+
import { ErrorBoundary } from '../runtime/ui.js';
|
|
23
|
+
import { Handles, SkeletonCard, ZoomControls } from './_chrome.js';
|
|
24
|
+
/*//////////////////////////////////////////////////
|
|
25
|
+
CONFIG
|
|
26
|
+
//////////////////////////////////////////////////*/
|
|
27
|
+
const HANDLES = ['top', 'right', 'bottom', 'left'];
|
|
28
|
+
const NODE_STYLES = {
|
|
29
|
+
process: 'rounded border-2 border-primary-6 bg-primary-3 p-2 text-primary-12',
|
|
30
|
+
terminator: 'rounded-full border-2 border-secondary-7 bg-secondary-3 px-4 py-2 text-secondary-11',
|
|
31
|
+
decision: 'rounded border-2 border-dashed border-yellow-700 bg-yellow-300 p-2 text-yellow-900 dark:border-yellow-700 dark:bg-yellow-950 dark:text-yellow-100',
|
|
32
|
+
data: '-skew-x-12 border-2 border-gray-7 bg-gray-3 p-2 text-gray-11',
|
|
33
|
+
origin: 'rounded-full border-2 border-green-300 bg-green-100 px-4 py-2 font-bold text-green-900 dark:border-green-700 dark:bg-green-950 dark:text-green-100',
|
|
34
|
+
terminal: 'rounded-full border-2 border-red-300 bg-red-100 px-4 py-2 font-bold text-red-900 dark:border-red-700 dark:bg-red-950 dark:text-red-100',
|
|
35
|
+
};
|
|
36
|
+
const NODE_CLASS = {
|
|
37
|
+
attribute: 'break-words text-right opacity-70',
|
|
38
|
+
selected: 'ring-2 ring-red-500 ring-offset-2 ring-offset-transparent',
|
|
39
|
+
};
|
|
40
|
+
// prettier-ignore
|
|
41
|
+
const NODE_CFG = {
|
|
42
|
+
data: { label: 'Data', Icon: DataIcon, colorCls: 'text-gray-11' },
|
|
43
|
+
decision: { label: 'Decision', Icon: DecisionIcon, colorCls: 'text-yellow-600' },
|
|
44
|
+
erd: { label: 'ERD', Icon: ErdIcon, colorCls: 'text-primary-11' },
|
|
45
|
+
group: { label: 'Container', Icon: GroupIcon, colorCls: 'text-primary-11' },
|
|
46
|
+
note: { label: 'Sticky note', Icon: NoteIcon, colorCls: 'text-gray-11' },
|
|
47
|
+
origin: { label: 'Origin', Icon: OriginIcon, colorCls: 'text-green-600' },
|
|
48
|
+
process: { label: 'Process', Icon: ProcessIcon, colorCls: 'text-primary-11' },
|
|
49
|
+
terminal: { label: 'Terminal', Icon: TerminalIcon, colorCls: 'text-red-600' },
|
|
50
|
+
terminator: { label: 'Terminator', Icon: TerminatorIcon, colorCls: 'text-secondary-11' },
|
|
51
|
+
};
|
|
52
|
+
const trace = createTrace('AUTHORING:Nodes', true);
|
|
53
|
+
// prettier-ignore
|
|
54
|
+
const NODE_MENUS = {
|
|
55
|
+
align: [
|
|
56
|
+
{ label: 'Align horizontal left', Icon: AlignHorizontalLeftIcon, val: 'left' },
|
|
57
|
+
{ label: 'Align horizontal center', Icon: AlignHorizontalCenterIcon, val: 'center' },
|
|
58
|
+
{ label: 'Align horizontal right', Icon: AlignHorizontalRightIcon, val: 'right' },
|
|
59
|
+
{ label: 'sep', val: 'sep1' },
|
|
60
|
+
{ label: 'Align vertical top', Icon: AlignVerticalTopIcon, val: 'top' },
|
|
61
|
+
{ label: 'Align vertical center', Icon: AlignVerticalCenterIcon, val: 'middle' },
|
|
62
|
+
{ label: 'Align vertical bottom', Icon: AlignVerticalBottomIcon, val: 'bottom' },
|
|
63
|
+
],
|
|
64
|
+
// prettier-ignore
|
|
65
|
+
distribute: [
|
|
66
|
+
{ label: 'Distribute horizontal left', Icon: DistributeHorizontalLeftIcon, val: 'horizontal-left' },
|
|
67
|
+
{ label: 'Distribute horizontal center', Icon: DistributeHorizontalCenterIcon, val: 'horizontal-center' },
|
|
68
|
+
{ label: 'Distribute horizontal right', Icon: DistributeHorizontalRightIcon, val: 'horizontal-right' },
|
|
69
|
+
{ label: 'sep', val: 'sep2' },
|
|
70
|
+
{ label: 'Distribute vertical top', Icon: DistributeVerticalTopIcon, val: 'vertical-top' },
|
|
71
|
+
{ label: 'Distribute vertical center', Icon: DistributeVerticalCenterIcon, val: 'vertical-center' },
|
|
72
|
+
{ label: 'Distribute vertical bottom', Icon: DistributeVerticalBottomIcon, val: 'vertical-bottom' },
|
|
73
|
+
],
|
|
74
|
+
// prettier-ignore
|
|
75
|
+
edges: [
|
|
76
|
+
{ label: 'Solid Line', val: 'solid' },
|
|
77
|
+
{ label: 'Dashed Line', val: 'dashed', dash: '4 2' },
|
|
78
|
+
{ label: 'Dotted Line', val: 'dotted', dash: '1 3', cap: 'round' },
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
const MENU_CLASS = {
|
|
82
|
+
content: 'min-w-40 !border-primary-6 p-1',
|
|
83
|
+
icon: 'mr-2',
|
|
84
|
+
subContent: 'min-w-64 !border-primary-6 p-1',
|
|
85
|
+
};
|
|
86
|
+
/*//////////////////////////////////////////////////
|
|
87
|
+
NODE TYPES
|
|
88
|
+
//////////////////////////////////////////////////*/
|
|
89
|
+
/**
|
|
90
|
+
* Renders the container node used to group related flowchart nodes.
|
|
91
|
+
*
|
|
92
|
+
* @param props - Flow node props.
|
|
93
|
+
* @param data - Group node data and resize hooks.
|
|
94
|
+
* @param selected - Boolean flag controlling group node's selected behaviour.
|
|
95
|
+
*
|
|
96
|
+
* @return Resizable group frame, title bar, and group handles.
|
|
97
|
+
*/
|
|
98
|
+
export function GroupNode({ data, selected }) {
|
|
99
|
+
return (_jsxs(ErrorBoundary, { name: 'GroupNode', children: [_jsx(NodeResizer, { color: '#ef4444', isVisible: selected && data.editable === true, minWidth: 160, minHeight: 100, onResizeEnd: (_e, p) => {
|
|
100
|
+
if (data.id)
|
|
101
|
+
data.onResizeEnd?.(data.id, p);
|
|
102
|
+
} }), _jsxs("div", { className: cn('size-full text-primary-11 transition-all', selected && `rounded-lg ${NODE_CLASS.selected}`, data.isDragTarget && 'ring-dashed rounded-lg bg-primary-3/20 ring-2 ring-primary-6'), children: [_jsx("div", { className: 'border-b border-primary-6 pb-2 text-lg font-bold text-primary-12', children: data.title }), _jsx(Handles, { editable: data.editable === true, ...data.requiredHandles })] })] }));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Creates a higher-order flowchart node renderer with specified styling.
|
|
106
|
+
*
|
|
107
|
+
* @param className - CSS classes applied to the shell container.
|
|
108
|
+
* @param inner - CSS classes applied to the text wrapper.
|
|
109
|
+
*
|
|
110
|
+
* @return Flowchart node component.
|
|
111
|
+
*/
|
|
112
|
+
function createNode(className, inner) {
|
|
113
|
+
return function NodeCard({ data, selected }) {
|
|
114
|
+
const Element = data.onClick ? 'button' : data.href ? 'a' : 'div';
|
|
115
|
+
return (_jsxs(Element, { ...(Element === 'button' ? { type: 'button' } : {}), ...(Element === 'a' ? { href: data.href } : {}), onClick: data.onClick, className: cn('relative block min-w-40 text-pretty p-2 text-center text-sm font-normal !no-underline shadow-md transition-shadow', selected && 'ring-2 ring-red-500 ring-offset-2 ring-offset-transparent', className), children: [_jsx(Handles, { editable: data.editable === true, sources: data.requiredHandles?.sources ?? [], tgts: data.requiredHandles?.tgts ?? [] }), _jsx("span", { className: inner, children: data.title })] }));
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Renders a sticky-note style node in the flowchart.
|
|
120
|
+
*
|
|
121
|
+
* @param props - Flow node props.
|
|
122
|
+
* @param id - Unique clipping mask identifier.
|
|
123
|
+
* @param data - Sticky note text and resize hooks.
|
|
124
|
+
* @param selected - Boolean flag controlling note node's selected behaviour.
|
|
125
|
+
*
|
|
126
|
+
* @return Sticky note styled container.
|
|
127
|
+
*/
|
|
128
|
+
export function NoteNode({ id, data, selected }) {
|
|
129
|
+
return (_jsxs(ErrorBoundary, { name: 'NoteNode', children: [_jsx(NodeResizer, { color: '#ef4444', isVisible: selected && data.editable === true, minWidth: 128, minHeight: 128, onResizeEnd: (_e, p) => {
|
|
130
|
+
if (data.id)
|
|
131
|
+
data.onResizeEnd?.(data.id, p);
|
|
132
|
+
} }), _jsxs("div", { className: cn("relative size-full min-h-32 min-w-32 before:absolute before:left-[5px] before:top-[30%] before:h-3/4 before:w-[90%] before:bg-black/25 before:shadow-[rgba(0,0,0,0.5)_-2px_2px_15px] before:content-['']", selected && NODE_CLASS.selected), children: [_jsx("svg", { width: '0', height: '0', className: 'absolute', children: _jsx("defs", { children: _jsx("clipPath", { id: `sticky-${id}`, clipPathUnits: 'objectBoundingBox', children: _jsx("path", { d: 'M 1 0 Q 1 0.69, 0.97 0.96 0.97 0.96, 0 0.96 Q 0.04 0.69, 0.04 0 0.04 0, 1 0' }) }) }) }), _jsx("div", { className: 'relative flex size-full min-h-32 min-w-32 items-center justify-center bg-yellow-300 p-5 pt-3 text-center text-sm font-normal text-yellow-950 shadow-md', style: { clipPath: `url(#sticky-${id})` }, children: data.title })] })] }));
|
|
133
|
+
}
|
|
134
|
+
export const NODE_TYPES = {
|
|
135
|
+
erd: function ErdNode({ data, selected }) {
|
|
136
|
+
const compact = data.compact === true;
|
|
137
|
+
const Element = data.onClick ? 'button' : data.href ? 'a' : 'div';
|
|
138
|
+
return (_jsxs(Element, { type: Element === 'button' ? 'button' : undefined, href: Element === 'a' ? data.href : undefined, onClick: data.onClick, className: cn('block max-h-96 w-full overflow-y-scroll rounded-md border-2 border-primary-6 bg-primary-2 text-primary-12 !no-underline shadow-lg transition-shadow', compact ? 'text-xs' : 'text-sm', selected && NODE_CLASS.selected), children: [_jsx("div", { className: cn('border-b-2 border-primary-6 bg-primary-3 text-center font-bold text-primary-12', compact ? 'px-2 py-0.5 text-sm' : 'px-3 py-2 text-lg'), children: data.title }), data.attributes && data.attributes.length > 0 && (_jsx("ul", { className: cn('my-0', compact ? 'gap-y-0.5 p-0.5 px-1.5' : 'p-2'), children: data.attributes.map(a => (_jsxs("li", { className: cn('grid grid-cols-[auto,1fr] font-mono', compact ? 'my-0 gap-x-2 leading-tight' : 'gap-x-3 gap-y-0'), children: [_jsx("span", { className: 'shrink-0', children: a.name }), (() => {
|
|
139
|
+
const inlineMath = a.type.match(/^\$(?!\$)([\s\S]+)\$$/);
|
|
140
|
+
const displayMath = a.type.match(/^\$\$([\s\S]+)\$\$$/);
|
|
141
|
+
if (!inlineMath && !displayMath)
|
|
142
|
+
return _jsx("span", { className: NODE_CLASS.attribute, children: a.type });
|
|
143
|
+
return (_jsx("span", { className: `${NODE_CLASS.attribute} [&_.katex-display]:m-0 [&_.katex]:text-inherit`, dangerouslySetInnerHTML: {
|
|
144
|
+
__html: katex.renderToString((displayMath?.[1] ?? inlineMath?.[1] ?? '').trim(), {
|
|
145
|
+
displayMode: Boolean(displayMath),
|
|
146
|
+
throwOnError: false,
|
|
147
|
+
strict: 'ignore',
|
|
148
|
+
}),
|
|
149
|
+
} }));
|
|
150
|
+
})()] }, a.name))) })), _jsx(Handles, { editable: data.editable === true, sources: data.requiredHandles?.sources ?? [], tgts: data.requiredHandles?.tgts ?? [] })] }));
|
|
151
|
+
},
|
|
152
|
+
process: createNode(NODE_STYLES.process),
|
|
153
|
+
terminator: createNode(NODE_STYLES.terminator),
|
|
154
|
+
origin: createNode(NODE_STYLES.origin),
|
|
155
|
+
terminal: createNode(NODE_STYLES.terminal),
|
|
156
|
+
decision: createNode(NODE_STYLES.decision),
|
|
157
|
+
data: createNode(NODE_STYLES.data, 'block skew-x-12'),
|
|
158
|
+
note: NoteNode,
|
|
159
|
+
group: GroupNode,
|
|
160
|
+
};
|
|
161
|
+
/*//////////////////////////////////////////////////
|
|
162
|
+
CANVAS
|
|
163
|
+
//////////////////////////////////////////////////*/
|
|
164
|
+
/**
|
|
165
|
+
* Synchronises _node and edge state with layout and handle metadata.
|
|
166
|
+
*
|
|
167
|
+
* @param v - Flow nodes to sync.
|
|
168
|
+
* @param r - Flow edges to sync.
|
|
169
|
+
* @param dir - Whether nodes rank top-to-bottom (`TB`) or left-to-right (`LR`).
|
|
170
|
+
* @param proximityConnect - Connection rule that lets nearby graph nodes link automatically in sync graph.
|
|
171
|
+
* @param editable - Editability flag controlling whether sync graph accepts changes.
|
|
172
|
+
* @param arcControls - Arc controls input supplied to sync graph's operation.
|
|
173
|
+
* @param onResizeEnd - Callback fired after a group node resize is committed.
|
|
174
|
+
*
|
|
175
|
+
* @return Node and edge arrays after layout, handle assignment, and grouping metadata are applied.
|
|
176
|
+
*
|
|
177
|
+
* @remarks
|
|
178
|
+
* This centralises the derived handle logic so the component tree stays thin.
|
|
179
|
+
*/
|
|
180
|
+
function syncGraph(v, r, dir, proximityConnect, editable, arcControls, onResizeEnd) {
|
|
181
|
+
const sNodes = Array.isArray(v) ? v : [];
|
|
182
|
+
const nodesById = new Map(sNodes.map(_node => [_node.id, _node]));
|
|
183
|
+
const handlesByNodeId = new Map();
|
|
184
|
+
const syncedGraph = {
|
|
185
|
+
edges: (Array.isArray(r) ? r : [])
|
|
186
|
+
.filter(_e => nodesById.has(_e.source) && nodesById.has(_e.target))
|
|
187
|
+
.map(e => {
|
|
188
|
+
const edgeData = e.data ?? {};
|
|
189
|
+
const l = isRec(e) ? e : {};
|
|
190
|
+
const arrowHead = edgeData.arrowHead ?? l['arrowHead'] ?? false;
|
|
191
|
+
const srcOverride = edgeData.srcHandleOverride ?? l['srcHandleOverride'];
|
|
192
|
+
const tgtOverride = edgeData.tgtHandleOverride ?? l['tgtHandleOverride'];
|
|
193
|
+
const strokeStyle = edgeData.strokeStyle ?? 'solid';
|
|
194
|
+
const markerEnd = arrowHead ? { type: MarkerType.ArrowClosed, width: 20, height: 20 } : undefined;
|
|
195
|
+
let srcHandle;
|
|
196
|
+
let tgtHandle;
|
|
197
|
+
if (arcControls) {
|
|
198
|
+
// Arc nodes use named ports, not the positional ports used by docs flowcharts.
|
|
199
|
+
srcHandle = e.sourceHandle && !HANDLES.includes(e.sourceHandle) ? e.sourceHandle : 'default';
|
|
200
|
+
tgtHandle = 'inbound';
|
|
201
|
+
}
|
|
202
|
+
else if (proximityConnect) {
|
|
203
|
+
const dx = (nodesById.get(e.target).position?.x ?? 0) - (nodesById.get(e.source).position?.x ?? 0);
|
|
204
|
+
const dy = (nodesById.get(e.target).position?.y ?? 0) - (nodesById.get(e.source).position?.y ?? 0);
|
|
205
|
+
const horizontal = Math.abs(dx) > Math.abs(dy);
|
|
206
|
+
srcHandle = horizontal ? (dx > 0 ? 'right' : 'left') : dy > 0 ? 'bottom' : 'top';
|
|
207
|
+
tgtHandle = horizontal ? (dx > 0 ? 'left' : 'right') : dy > 0 ? 'top' : 'bottom';
|
|
208
|
+
}
|
|
209
|
+
else if (!proximityConnect && dir === 'TB') {
|
|
210
|
+
srcHandle = srcOverride ?? 'bottom';
|
|
211
|
+
tgtHandle = tgtOverride ?? 'top';
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
srcHandle = srcOverride ?? (dir === 'LR' ? 'right' : 'bottom');
|
|
215
|
+
tgtHandle = tgtOverride ?? (dir === 'LR' ? 'left' : 'top');
|
|
216
|
+
}
|
|
217
|
+
if (!arcControls) {
|
|
218
|
+
if (!handlesByNodeId.has(e.source))
|
|
219
|
+
handlesByNodeId.set(e.source, { sources: new Set(), tgts: new Set() });
|
|
220
|
+
if (!handlesByNodeId.has(e.target))
|
|
221
|
+
handlesByNodeId.set(e.target, { sources: new Set(), tgts: new Set() });
|
|
222
|
+
handlesByNodeId.get(e.source).sources.add(srcHandle);
|
|
223
|
+
handlesByNodeId.get(e.target).tgts.add(tgtHandle);
|
|
224
|
+
}
|
|
225
|
+
const updatedEdge = {
|
|
226
|
+
...e,
|
|
227
|
+
data: { ...edgeData, arrowHead, srcHandleOverride: srcOverride, tgtHandleOverride: tgtOverride, strokeStyle },
|
|
228
|
+
sourceHandle: srcHandle,
|
|
229
|
+
targetHandle: tgtHandle,
|
|
230
|
+
style: { ...e.style, strokeDasharray: strokeStyle === 'dashed' ? '5 5' : strokeStyle === 'dotted' ? '2 2' : undefined },
|
|
231
|
+
};
|
|
232
|
+
if (markerEnd)
|
|
233
|
+
updatedEdge.markerEnd = markerEnd;
|
|
234
|
+
return updatedEdge;
|
|
235
|
+
}),
|
|
236
|
+
nodes: sNodes.map(n => {
|
|
237
|
+
const handles = handlesByNodeId.get(n.id);
|
|
238
|
+
const resGroupId = getGroupId(n);
|
|
239
|
+
return {
|
|
240
|
+
...n,
|
|
241
|
+
parentId: undefined, // enforce flat absolute structure globally
|
|
242
|
+
className: cn(validateType(['string'], n.className) || Array.isArray(n.className) ? n.className : undefined, n.type === 'group' && 'rounded-lg border !border-primary-6 !bg-primary-2 !text-primary-12'),
|
|
243
|
+
zIndex: n.type === 'group' ? -10 : 10,
|
|
244
|
+
nodeOrigin: n.type === 'group' ? [0, 0] : [0.5, 0.5],
|
|
245
|
+
data: {
|
|
246
|
+
...(n.data && validateType(['object'], n.data) ? n.data : { title: '', attributes: [] }),
|
|
247
|
+
id: n.id,
|
|
248
|
+
editable,
|
|
249
|
+
...(resGroupId ? { groupId: resGroupId } : {}),
|
|
250
|
+
...(onResizeEnd ? { onResizeEnd } : {}),
|
|
251
|
+
requiredHandles: { sources: editable ? HANDLES : Array.from(handles?.sources ?? []), targets: editable ? HANDLES : Array.from(handles?.tgts ?? []) },
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}),
|
|
255
|
+
};
|
|
256
|
+
return { nodes: [...syncedGraph.nodes.filter(n => n.type === 'group'), ...syncedGraph.nodes.filter(n => n.type !== 'group')], edges: syncedGraph.edges };
|
|
257
|
+
}
|
|
258
|
+
const FLOW_COMPONENTS = {
|
|
259
|
+
nodeTypes: NODE_TYPES,
|
|
260
|
+
/**
|
|
261
|
+
* Renders the interactive flow canvas and context menus for n editing, grouping, and layout.
|
|
262
|
+
*
|
|
263
|
+
* @param props - Flow nodes and edges, node/edge renderers, graph callbacks, context-menu actions, and canvas display options.
|
|
264
|
+
* @param _onEdgeDoubleClick - Edge double-click handler.
|
|
265
|
+
* @param nodeTypes - Node renderer map that draws graph nodes in canvas.
|
|
266
|
+
* @param edgeTypes - Edge renderer map that draws graph connections in canvas.
|
|
267
|
+
* @param nodeLabels - Node label map shown alongside canvas's graph nodes.
|
|
268
|
+
* @param onNodeClick - Callback invoked when a user selects a node in canvas.
|
|
269
|
+
* @param onEdgeDoubleClick - Callback invoked for edge double click.
|
|
270
|
+
* @param onAddContent - Callback invoked for add content.
|
|
271
|
+
* @param lockedTypes - Node types that canvas must keep read-only.
|
|
272
|
+
* @param allowedTypes - Node types that canvas permits users to create.
|
|
273
|
+
* @param onReady - Callback invoked when canvas finishes initialising its graph surface.
|
|
274
|
+
* @param height - Viewport height reserved for canvas's graph surface.
|
|
275
|
+
* @param arcControls - Arc controls input supplied to canvas's operation.
|
|
276
|
+
* @param readyWhenEmpty - Readiness rule that lets canvas finish with no graph nodes.
|
|
277
|
+
* @param flow - Flow graph data rendered into canvas's canvas.
|
|
278
|
+
* @param interactive - Interaction flag that controls whether canvas accepts graph input.
|
|
279
|
+
* @param editable - Editability flag controlling whether canvas accepts changes.
|
|
280
|
+
* @param isFocused - Boolean flag controlling canvas's is focused behaviour.
|
|
281
|
+
* @param onNodesChange - Callback invoked with the updated node changes from canvas.
|
|
282
|
+
* @param onEdgesChange - Callback invoked with the updated edge changes from canvas.
|
|
283
|
+
* @param onConnect - Callback invoked with a new connection created in canvas.
|
|
284
|
+
* @param onAddNode - Callback invoked for add node.
|
|
285
|
+
* @param onGroupNodes - Callback invoked for group nodes.
|
|
286
|
+
* @param onUngroupNodes - Callback invoked for ungroup nodes.
|
|
287
|
+
* @param onDeleteSelected - Callback invoked for delete selected.
|
|
288
|
+
* @param onAlignNodes - Callback invoked for align nodes.
|
|
289
|
+
* @param onDistributeNodes - Callback invoked for distribute nodes.
|
|
290
|
+
* @param onRenameNode - Callback invoked for rename node.
|
|
291
|
+
* @param onChangeNodeType - Callback invoked for change node type.
|
|
292
|
+
* @param onChangeEdgeStyle - Callback invoked for change edge style.
|
|
293
|
+
* @param onDeleteEdge - Callback invoked for delete edge.
|
|
294
|
+
* @param onAutoLayout - Callback invoked for auto layout.
|
|
295
|
+
* @param showBg - Visibility flag for canvas's graph background.
|
|
296
|
+
* @param showCtrls - Visibility flag for canvas's graph controls.
|
|
297
|
+
*
|
|
298
|
+
* @return Interactive React Flow canvas and its context-menu actions.
|
|
299
|
+
*
|
|
300
|
+
* @remarks
|
|
301
|
+
* This component stays intentionally thin and delegates all graph mutation logic to the helper
|
|
302
|
+
* functions above.
|
|
303
|
+
*/
|
|
304
|
+
Canvas({ flow, nodeTypes, edgeTypes, nodeLabels, onNodeClick, onNodesChange, onEdgesChange, onConnect, onEdgeDoubleClick, onAddNode, onAddContent, lockedTypes, allowedTypes, onGroupNodes, onUngroupNodes, onDeleteSelected, onAlignNodes, onDistributeNodes, onRenameNode, onChangeNodeType, onChangeEdgeStyle, onDeleteEdge, onAutoLayout, onReady, interactive, editable, isFocused, height, showBg, showCtrls, arcControls, readyWhenEmpty, }) {
|
|
305
|
+
// STATES
|
|
306
|
+
const { fitView, screenToFlowPosition: screenToFlowPos, getIntersectingNodes, updateNodeData, getNodes } = useReactFlow();
|
|
307
|
+
const nodesInit = useNodesInitialized();
|
|
308
|
+
const hasInitialLayout = useRef(false);
|
|
309
|
+
const lastFit = useRef('');
|
|
310
|
+
const lastPos = useRef(null);
|
|
311
|
+
const dragTgtGroup = useRef(null);
|
|
312
|
+
const reactFlowWrapper = useRef(null);
|
|
313
|
+
const [ready, setReady] = useState(readyWhenEmpty && flow.nodes.length === 0);
|
|
314
|
+
const notified = useRef(false);
|
|
315
|
+
const [menuCtx, setMenuCtx] = useState({ type: 'pane', id: null });
|
|
316
|
+
// INIT
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
if (!flow.nodes.length) {
|
|
319
|
+
if (readyWhenEmpty)
|
|
320
|
+
setReady(true);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (!nodesInit)
|
|
324
|
+
return;
|
|
325
|
+
const key = `${height}:${flow.nodes.map(_node => _node.id).join(',')}`;
|
|
326
|
+
if (lastFit.current === key)
|
|
327
|
+
return;
|
|
328
|
+
const frame = { id: 0 };
|
|
329
|
+
frame.id = requestAnimationFrame(() => {
|
|
330
|
+
lastFit.current = key;
|
|
331
|
+
void fitView({ padding: 0.1 });
|
|
332
|
+
if (!hasInitialLayout.current) {
|
|
333
|
+
hasInitialLayout.current = true;
|
|
334
|
+
setReady(true);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
return () => {
|
|
338
|
+
cancelAnimationFrame(frame.id);
|
|
339
|
+
};
|
|
340
|
+
}, [height, fitView, flow.nodes, nodesInit]);
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
reactFlowWrapper.current?.querySelector('.react-flow.light')?.classList.remove('light');
|
|
343
|
+
}, []);
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
if (!ready || notified.current)
|
|
346
|
+
return;
|
|
347
|
+
notified.current = true;
|
|
348
|
+
onReady?.();
|
|
349
|
+
}, [onReady, ready]);
|
|
350
|
+
// HELPERS
|
|
351
|
+
const selNodes = flow.nodes.filter(n => n.selected);
|
|
352
|
+
const nodesMap = React.useMemo(() => new Map(flow.nodes.map(n => [n.id, n])), [flow.nodes]);
|
|
353
|
+
const tgtNode = React.useMemo(() => (menuCtx.type === '_node' && menuCtx.id ? nodesMap.get(menuCtx.id) : undefined), [nodesMap, menuCtx.type, menuCtx.id]);
|
|
354
|
+
/**
|
|
355
|
+
* Checks whether the n is locked.
|
|
356
|
+
*
|
|
357
|
+
* @param n - Value passed to the helper.
|
|
358
|
+
*
|
|
359
|
+
* @returns True when the n is missing or its type is blocked from editing.
|
|
360
|
+
*/
|
|
361
|
+
const locked = (n) => {
|
|
362
|
+
if (!n) {
|
|
363
|
+
logWarn('AuthNodes', 'menu.n-missing', { menuCtx, nodeCount: flow.nodes.length });
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
return lockedTypes.includes(n.type);
|
|
367
|
+
};
|
|
368
|
+
const isTgtLocked = React.useMemo(() => {
|
|
369
|
+
if (menuCtx.type !== '_node' || menuCtx.id === null)
|
|
370
|
+
return false;
|
|
371
|
+
if (!tgtNode) {
|
|
372
|
+
logWarn('AuthNodes', 'menu.n-missing', { menuCtx, nodeCount: flow.nodes.length });
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
return locked(tgtNode);
|
|
376
|
+
}, [flow.nodes.length, lockedTypes, menuCtx, tgtNode]);
|
|
377
|
+
// RENDER
|
|
378
|
+
return (_jsxs(ContextMenu, { onOpenChange: o => {
|
|
379
|
+
trace('menu.state', { open: o, context: menuCtx });
|
|
380
|
+
}, children: [_jsx(ContextMenuTrigger, { asChild: true, onContextMenu: _e => {
|
|
381
|
+
lastPos.current = { x: _e.clientX, y: _e.clientY };
|
|
382
|
+
trace('menu.open.request', { position: lastPos.current });
|
|
383
|
+
}, children: _jsxs("div", { className: 'relative z-20 size-full', ref: reactFlowWrapper, children: [!ready && (_jsx("div", { className: 'pointer-events-none absolute inset-0 z-10 rounded-md border border-primary-6 bg-primary-1/95', "aria-label": 'Loading nodes', children: _jsx("div", { className: 'absolute inset-0 flex items-center justify-center', children: _jsxs("div", { className: 'flex flex-col items-center gap-8', children: [_jsx(SkeletonCard, {}), _jsx(SkeletonCard, { className: 'translate-x-16 animate-pulse opacity-75' }), _jsx(SkeletonCard, { className: '-translate-x-12 animate-pulse opacity-50' })] }) }) })), _jsxs(ReactFlow, { nodes: flow.nodes, edges: flow.edges, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, onConnect: onConnect, onEdgeDoubleClick: onEdgeDoubleClick, onNodeContextMenu: (_e, _node) => {
|
|
384
|
+
trace('menu.n', { id: _node.id, type: _node.type });
|
|
385
|
+
setMenuCtx({ type: '_node', id: _node.id });
|
|
386
|
+
}, ...(onNodeClick
|
|
387
|
+
? {
|
|
388
|
+
onNodeClick: (_e, n) => {
|
|
389
|
+
onNodeClick(n);
|
|
390
|
+
},
|
|
391
|
+
}
|
|
392
|
+
: {}), onEdgeContextMenu: (_e, v) => {
|
|
393
|
+
trace('menu.e', { id: v.id });
|
|
394
|
+
setMenuCtx({ type: 'e', id: v.id });
|
|
395
|
+
}, onPaneContextMenu: () => {
|
|
396
|
+
trace('menu.pane', { nodeCount: flow.nodes.length, edgeCount: flow.edges.length });
|
|
397
|
+
setMenuCtx({ type: 'pane', id: null });
|
|
398
|
+
}, onNodeDrag: (_e, _node, draggedNodes) => {
|
|
399
|
+
if (!editable)
|
|
400
|
+
return;
|
|
401
|
+
const primaryNode = draggedNodes[0] || _node;
|
|
402
|
+
if (primaryNode.type === 'group')
|
|
403
|
+
return;
|
|
404
|
+
const groupId = getIntersectingNodes(primaryNode).find(n => n.type === 'group')?.id ?? null;
|
|
405
|
+
if (groupId !== dragTgtGroup.current) {
|
|
406
|
+
if (dragTgtGroup.current)
|
|
407
|
+
updateNodeData(dragTgtGroup.current, { isDragTarget: false });
|
|
408
|
+
if (groupId)
|
|
409
|
+
updateNodeData(groupId, { isDragTarget: true });
|
|
410
|
+
dragTgtGroup.current = groupId;
|
|
411
|
+
}
|
|
412
|
+
}, onNodeDragStop: (_e, _node, draggedNodes) => {
|
|
413
|
+
if (!editable)
|
|
414
|
+
return;
|
|
415
|
+
if (dragTgtGroup.current) {
|
|
416
|
+
const tgtGroupId = dragTgtGroup.current;
|
|
417
|
+
updateNodeData(tgtGroupId, { isDragTarget: false });
|
|
418
|
+
dragTgtGroup.current = null;
|
|
419
|
+
const groupNode = getNodes().find(n => n.id === tgtGroupId);
|
|
420
|
+
const nodesToGroup = draggedNodes.filter(n => n.type !== 'group' && n.id !== tgtGroupId);
|
|
421
|
+
if (groupNode && nodesToGroup.length > 0)
|
|
422
|
+
onGroupNodes(groupNode, nodesToGroup, groupNode.position.x, groupNode.position.y);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
const nodesToUngroup = draggedNodes.filter(n => getGroupId(n) && n.type !== 'group');
|
|
426
|
+
if (nodesToUngroup.length > 0)
|
|
427
|
+
onUngroupNodes(nodesToUngroup);
|
|
428
|
+
}
|
|
429
|
+
}, nodeTypes: nodeTypes, ...(edgeTypes ? { edgeTypes: edgeTypes } : {}), fitView: true, minZoom: 0.1, zoomOnScroll: false, panOnScroll: isFocused, zoomOnPinch: true, preventScrolling: false, nodesDraggable: interactive || editable, nodesConnectable: editable, elementsSelectable: true, panOnDrag: false, selectionOnDrag: true, elevateNodesOnSelect: false, children: [showBg && _jsx(Background, {}), showCtrls && arcControls ? (_jsxs(_Fragment, { children: [_jsx(Controls, { position: 'top-left', showFitView: false, showInteractive: false, showZoom: false, children: _jsxs(ButtonGroup, { children: [_jsx(Button, { variant: 'ghost', size: 'icon-sm', disabled: true, "aria-label": 'Undo', title: 'Undo', children: _jsx(UndoIcon, { "aria-hidden": 'true' }) }), _jsx(Button, { variant: 'ghost', size: 'icon-sm', disabled: true, "aria-label": 'Redo', title: 'Redo', children: _jsx(RedoIcon, { "aria-hidden": 'true' }) })] }) }), _jsx(Controls, { position: 'top-right', showFitView: false, showInteractive: false, showZoom: false, children: _jsx(ButtonGroup, { children: _jsx(Button, { variant: 'ghost', size: 'icon-sm', "aria-label": 'Arrange nodes', title: 'Arrange nodes', onClick: onAutoLayout, children: _jsx(WandIcon, { "aria-hidden": 'true' }) }) }) }), _jsx(ZoomControls, { position: 'bottom-right' })] })) : (showCtrls && (_jsxs(Controls, { position: 'bottom-left', showInteractive: false, className: '!m-3 [&_*]:!border-primary-6 [&_*]:!bg-primary-3', children: [editable && (_jsx(ControlButton, { "aria-label": 'Auto-layout nodes', title: 'Auto-layout nodes', onClick: onAutoLayout, children: _jsx(WandIcon, { "aria-hidden": 'true' }) })), _jsx(ControlButton, { "aria-label": 'Full screen', title: 'Full screen', onClick: () => (document.fullscreenElement ? void document.exitFullscreen() : void reactFlowWrapper.current?.requestFullscreen()), children: _jsx(MaximizeIcon, { "aria-hidden": 'true' }) })] })))] })] }) }), editable && (_jsx(ContextMenuContent, { className: MENU_CLASS.content, children: selNodes.length > 1 ? (_jsxs(_Fragment, { children: [_jsxs(ContextMenuSub, { children: [_jsx(ContextMenuSubTrigger, { children: "Align" }), _jsx(ContextMenuSubContent, { className: MENU_CLASS.subContent, children: NODE_MENUS.align.map(m => m.label === 'sep' ? (_jsx(ContextMenuSeparator, {}, m.val)) : (_jsxs(ContextMenuItem, { onClick: () => {
|
|
430
|
+
onAlignNodes(selNodes, m.val);
|
|
431
|
+
}, children: [m.Icon && _jsx(m.Icon, { className: MENU_CLASS.icon }), " ", m.label] }, m.val))) })] }), selNodes.length > 2 && (_jsxs(ContextMenuSub, { children: [_jsx(ContextMenuSubTrigger, { children: "Distribute" }), _jsx(ContextMenuSubContent, { className: MENU_CLASS.subContent, children: NODE_MENUS.distribute.map(m => m.label === 'sep' ? (_jsx(ContextMenuSeparator, {}, m.val)) : (_jsxs(ContextMenuItem, { onClick: () => {
|
|
432
|
+
onDistributeNodes(selNodes, m.val);
|
|
433
|
+
}, children: [m.Icon && _jsx(m.Icon, { className: MENU_CLASS.icon }), " ", m.label] }, m.val))) })] })), _jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { className: 'cursor-pointer focus:!bg-primary-3 focus:!text-primary-12', onClick: () => {
|
|
434
|
+
const minX = Math.min(...selNodes.map(n => n.position.x - getNodeSize(n).w / 2));
|
|
435
|
+
const minY = Math.min(...selNodes.map(n => n.position.y - getNodeSize(n).h / 2));
|
|
436
|
+
const padding = 40;
|
|
437
|
+
const gWidth = Math.max(...selNodes.map(n => n.position.x + getNodeSize(n).w / 2)) - minX + padding * 2;
|
|
438
|
+
const gHeight = Math.max(...selNodes.map(n => n.position.y + getNodeSize(n).h / 2)) - minY + padding * 2;
|
|
439
|
+
const gX = minX - padding + gWidth / 2;
|
|
440
|
+
const gY = minY - padding + gHeight / 2;
|
|
441
|
+
onGroupNodes({
|
|
442
|
+
id: `group-${Date.now().toString(36)}`,
|
|
443
|
+
type: 'group',
|
|
444
|
+
position: { x: gX, y: gY },
|
|
445
|
+
style: { width: gWidth, height: gHeight },
|
|
446
|
+
data: { attributes: [], customPosition: { x: gX, y: gY }, title: 'Group' },
|
|
447
|
+
}, selNodes, gX, gY);
|
|
448
|
+
}, children: [_jsx(GroupIcon, { className: MENU_CLASS.icon }), " Create Group"] }), _jsxs(ContextMenuItem, { intent: 'destructive', onClick: () => {
|
|
449
|
+
onDeleteSelected(selNodes.filter(n => !locked(n)));
|
|
450
|
+
}, disabled: selNodes.every(locked), children: [_jsx(TrashIcon, { className: MENU_CLASS.icon }), " Delete Selected"] })] })) : menuCtx.type === '_node' && menuCtx.id ? (_jsxs(_Fragment, { children: [_jsxs(ContextMenuItem, { disabled: isTgtLocked, onClick: () => {
|
|
451
|
+
onRenameNode(menuCtx.id);
|
|
452
|
+
}, children: [_jsx(EditIcon, { className: MENU_CLASS.icon }), " Rename Node"] }), _jsxs(ContextMenuSub, { children: [_jsxs(ContextMenuSubTrigger, { disabled: isTgtLocked, children: [_jsx(TypeIcon, { className: MENU_CLASS.icon }), " Change Type"] }), _jsx(ContextMenuSubContent, { className: MENU_CLASS.content, children: allowedTypes.map(t => {
|
|
453
|
+
const { Icon, label, colorCls } = NODE_CFG[t];
|
|
454
|
+
return (_jsxs(ContextMenuItem, { disabled: isTgtLocked, onClick: () => {
|
|
455
|
+
onChangeNodeType(menuCtx.id, t);
|
|
456
|
+
}, children: [_jsx(Icon, { className: cn('size-4 shrink-0', colorCls) }), _jsx("span", { children: nodeLabels[t] || label })] }, t));
|
|
457
|
+
}) })] }), _jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { disabled: isTgtLocked, intent: 'destructive', onClick: () => tgtNode && onDeleteSelected([tgtNode]), children: [_jsx(TrashIcon, { className: MENU_CLASS.icon }), " Delete Node"] })] })) : menuCtx.type === 'e' && menuCtx.id ? (_jsxs(_Fragment, { children: [_jsxs(ContextMenuSub, { children: [_jsxs(ContextMenuSubTrigger, { children: [_jsx(TypeIcon, { className: MENU_CLASS.icon }), " Change Style"] }), _jsx(ContextMenuSubContent, { className: MENU_CLASS.content, children: NODE_MENUS.edges.map(m => (_jsxs(ContextMenuItem, { onClick: () => {
|
|
458
|
+
onChangeEdgeStyle(menuCtx.id, m.val);
|
|
459
|
+
}, children: [_jsx("svg", { className: 'size-4 shrink-0 text-primary-11', viewBox: '0 0 16 16', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeDasharray: m.dash, strokeLinecap: m.cap, children: _jsx("line", { x1: '0', y1: '8', x2: '16', y2: '8' }) }), _jsx("span", { children: m.label })] }, m.val))) })] }), _jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { intent: 'destructive', onClick: () => {
|
|
460
|
+
onDeleteEdge(menuCtx.id);
|
|
461
|
+
}, children: [_jsx(TrashIcon, { className: MENU_CLASS.icon }), " Delete Edge"] })] })) : menuCtx.type === 'pane' ? (_jsxs(ContextMenuSub, { children: [_jsxs(ContextMenuSubTrigger, { children: [_jsx(TypeIcon, { className: MENU_CLASS.icon }), " Add Node"] }), _jsx(ContextMenuSubContent, { className: MENU_CLASS.content, children: allowedTypes.map(t => {
|
|
462
|
+
const { Icon, label, colorCls } = NODE_CFG[t];
|
|
463
|
+
return (_jsxs(ContextMenuItem, { onClick: () => {
|
|
464
|
+
try {
|
|
465
|
+
if (!lastPos.current) {
|
|
466
|
+
logWarn('AuthNodes', 'menu.add.missing-position', { type: t });
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const pos = screenToFlowPos(lastPos.current);
|
|
470
|
+
trace('menu.add', { type: t, position: pos });
|
|
471
|
+
if (t === 'process' && onAddContent)
|
|
472
|
+
onAddContent(pos);
|
|
473
|
+
else
|
|
474
|
+
onAddNode(t, pos);
|
|
475
|
+
}
|
|
476
|
+
catch (err) {
|
|
477
|
+
errorLog('AuthNodes', `menu.add.failed: ${t}`, { error: err instanceof Error ? err.message : String(err) });
|
|
478
|
+
}
|
|
479
|
+
}, children: [_jsx(Icon, { className: cn('size-4 shrink-0', colorCls) }), _jsx("span", { children: nodeLabels[t] || label })] }, t));
|
|
480
|
+
}) })] })) : null }))] }));
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
/*//////////////////////////////////////////////////
|
|
484
|
+
COMPONENT
|
|
485
|
+
//////////////////////////////////////////////////*/
|
|
486
|
+
/**
|
|
487
|
+
* Renders the flow editor wrapper used by docs diagrams.
|
|
488
|
+
*
|
|
489
|
+
* @param props - Initial nodes and edges, layout configuration, interactions, change callbacks, and display settings for the problem graph.
|
|
490
|
+
* @param className - Extra classes applied to the outer flow wrapper.
|
|
491
|
+
* @param onCommit - Callback that receives the fully normalised flow after each change.
|
|
492
|
+
* @param nodes - Graph nodes rendered and updated by nodes.
|
|
493
|
+
* @param edges - Graph connections rendered and updated by nodes.
|
|
494
|
+
* @param nodeSep - Horizontal gap between graph nodes in nodes.
|
|
495
|
+
* @param rankSep - Vertical gap between graph ranks in nodes.
|
|
496
|
+
* @param height - Viewport height reserved for nodes's graph surface.
|
|
497
|
+
* @param onAddContent - Callback invoked for add content.
|
|
498
|
+
* @param onNodeClick - Callback invoked when a user selects a node in nodes.
|
|
499
|
+
* @param nodeTypes - Node renderer map that draws graph nodes in nodes.
|
|
500
|
+
* @param nodeLabels - Node label map shown alongside nodes's graph nodes.
|
|
501
|
+
* @param lockedTypes - Node types that nodes must keep read-only.
|
|
502
|
+
* @param allowedTypes - Node types that nodes permits users to create.
|
|
503
|
+
* @param disabled - Disabled state controlling whether nodes accepts interaction.
|
|
504
|
+
* @param clipboardKey - Clipboard key that enables graph copy and paste in nodes.
|
|
505
|
+
* @param arcControls - Arc controls input supplied to nodes's operation.
|
|
506
|
+
* @param edgeTypes - Edge renderer map that draws graph connections in nodes.
|
|
507
|
+
* @param readyWhenEmpty - Readiness rule that lets nodes finish with no graph nodes.
|
|
508
|
+
* @param onReady - Callback invoked when nodes finishes initialising its graph surface.
|
|
509
|
+
* @param style - Inline style values merged into nodes.
|
|
510
|
+
* @param rest - Rest input supplied to nodes's operation.
|
|
511
|
+
* @param direction - Layout direction that arranges nodes's graph.
|
|
512
|
+
* @param interactive - Interaction flag that controls whether nodes accepts graph input.
|
|
513
|
+
* @param editable - Editability flag controlling whether nodes accepts changes.
|
|
514
|
+
* @param proximityConnect - Connection rule that lets nearby graph nodes link automatically in nodes.
|
|
515
|
+
* @param showBg - Visibility flag for nodes's graph background.
|
|
516
|
+
* @param showCtrls - Visibility flag for nodes's graph controls.
|
|
517
|
+
*
|
|
518
|
+
* @return Docs flow editor wrapper with derived height, toolbar controls, and drag state.
|
|
519
|
+
*
|
|
520
|
+
* @remarks
|
|
521
|
+
* This is the public entrypoint; the rest of the file exists to keep the canvas state manageable.
|
|
522
|
+
*/
|
|
523
|
+
export default function Nodes({ nodes: initialNodes = [], edges: initialEdges = [], className, direction: dir = 'TB', nodeSep = 50, rankSep = 5, proximityConnect = false, interactive = false, height, onCommit, onAddContent, onNodeClick, nodeTypes: customNodeTypes = {}, nodeLabels = {}, lockedTypes = [], allowedTypes = Object.keys(NODE_TYPES), editable = false, disabled = !editable, clipboardKey = editable ? 'terra.flow.docs' : undefined, showBg = true, showCtrls = true, arcControls = false, edgeTypes, readyWhenEmpty = true, onReady, style, ...rest }) {
|
|
524
|
+
// STATES
|
|
525
|
+
const [graph, setGraph] = useState({ nodes: initialNodes, edges: initialEdges });
|
|
526
|
+
const [flow, setFlow] = useState({ nodes: [], edges: [] });
|
|
527
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
528
|
+
const [internalHeight, setInternalHeight] = useState(384);
|
|
529
|
+
const graphRef = useRef(graph);
|
|
530
|
+
const flowRef = useRef(flow);
|
|
531
|
+
const permitted = editable && !disabled;
|
|
532
|
+
const fns = useRef({ onCommit, onAddContent });
|
|
533
|
+
fns.current = { onCommit, onAddContent };
|
|
534
|
+
// INIT
|
|
535
|
+
useEffect(() => {
|
|
536
|
+
graphRef.current = graph;
|
|
537
|
+
}, [graph]);
|
|
538
|
+
useEffect(() => {
|
|
539
|
+
flowRef.current = flow;
|
|
540
|
+
}, [flow]);
|
|
541
|
+
// HELPERS
|
|
542
|
+
const writeGraph = useCallback((v, c = true) => {
|
|
543
|
+
const n = typeof v === 'function' ? v(graphRef.current) : v;
|
|
544
|
+
graphRef.current = n;
|
|
545
|
+
setGraph(n);
|
|
546
|
+
if (c)
|
|
547
|
+
fns.current.onCommit?.(n);
|
|
548
|
+
}, []);
|
|
549
|
+
useEditor({
|
|
550
|
+
adapter: {
|
|
551
|
+
...(clipboardKey ? { clipboardKey } : {}),
|
|
552
|
+
cloneNode: (n, id) => ({ ...n, id, data: { ...n.data, ...(typeof n.data.graphKey === 'string' ? { graphKey: id } : {}) } }),
|
|
553
|
+
editableNode: n => !lockedTypes.includes(n.type),
|
|
554
|
+
read: flow,
|
|
555
|
+
write: useCallback((n) => {
|
|
556
|
+
flowRef.current = n;
|
|
557
|
+
setFlow(n);
|
|
558
|
+
writeGraph(c => {
|
|
559
|
+
const currEntities = { nodes: new Map(c.nodes.map(i => [i.id, i])), edges: new Map(c.edges.map(i => [i.id, i])) };
|
|
560
|
+
return {
|
|
561
|
+
nodes: n.nodes.map(n => {
|
|
562
|
+
const { editable: _, groupId: __, id: ___, isDragTarget: ____, onResizeEnd: _____, requiredHandles: ______, ...data } = n.data;
|
|
563
|
+
return { ...currEntities.nodes.get(n.id), id: n.id, type: n.type, position: n.position, style: n.style, data };
|
|
564
|
+
}),
|
|
565
|
+
edges: n.edges.map(e => {
|
|
566
|
+
return {
|
|
567
|
+
...currEntities.edges.get(e.id),
|
|
568
|
+
id: e.id,
|
|
569
|
+
source: e.source,
|
|
570
|
+
target: e.target,
|
|
571
|
+
sourceHandle: e.sourceHandle,
|
|
572
|
+
targetHandle: e.targetHandle,
|
|
573
|
+
label: e.label,
|
|
574
|
+
data: e.data,
|
|
575
|
+
};
|
|
576
|
+
}),
|
|
577
|
+
};
|
|
578
|
+
});
|
|
579
|
+
}, [writeGraph]),
|
|
580
|
+
},
|
|
581
|
+
disabled,
|
|
582
|
+
});
|
|
583
|
+
const handleResizeEnd = useCallback((i, p) => {
|
|
584
|
+
writeGraph(c => ({
|
|
585
|
+
...c,
|
|
586
|
+
nodes: c.nodes.map(n => n.id === i
|
|
587
|
+
? {
|
|
588
|
+
...n,
|
|
589
|
+
position: { x: p.x, y: p.y },
|
|
590
|
+
style: { ...n.style, width: p.width, height: p.height },
|
|
591
|
+
data: { ...n.data, customPosition: { x: p.x, y: p.y } },
|
|
592
|
+
}
|
|
593
|
+
: n),
|
|
594
|
+
}));
|
|
595
|
+
}, [writeGraph]);
|
|
596
|
+
// prettier-ignore
|
|
597
|
+
useEffect(() => {
|
|
598
|
+
const { positionedNodes, graphHeight } = layoutGraph(graph.nodes, graph.edges, dir, nodeSep, rankSep, {
|
|
599
|
+
getSize: node => getNodeSize(node, { width: 175, height: 60, groupWidth: 160, groupHeight: 100, erdWidth: 224, erdHeight: 60 }, n => {
|
|
600
|
+
if (n.type !== 'erd')
|
|
601
|
+
return;
|
|
602
|
+
const attrs = n.data && validateType(['object'], n.data) && 'attributes' in n.data ? n.data.attributes : undefined;
|
|
603
|
+
return 60 + (Array.isArray(attrs) ? attrs.length : 0) * 20;
|
|
604
|
+
}),
|
|
605
|
+
getGroupId,
|
|
606
|
+
getCustomPosition: getCustomPos,
|
|
607
|
+
registerNode: (graph, node, size) => {
|
|
608
|
+
if (node.type === 'group')
|
|
609
|
+
graph.setNode(node.id, { paddingLeft: 40, paddingRight: 40, paddingTop: 60, paddingBottom: 40 });
|
|
610
|
+
else
|
|
611
|
+
graph.setNode(node.id, { width: size.w, height: size.h });
|
|
612
|
+
},
|
|
613
|
+
placeNode: (node, dagreNode) => {
|
|
614
|
+
const persisted = getCustomPos(node);
|
|
615
|
+
if (node.type === 'group') {
|
|
616
|
+
const calcW = dagreNode?.width ?? 200;
|
|
617
|
+
const calcH = dagreNode?.height ?? 150;
|
|
618
|
+
const w = persisted ? (node.style?.width ?? calcW) : calcW;
|
|
619
|
+
const h = persisted ? (node.style?.height ?? calcH) : calcH;
|
|
620
|
+
return { ...node, position: persisted ?? { x: ( /* dagre computes absolute center, NodeResizer forces a top-left origin. Translating natively! */(dagreNode?.x ?? 0) - (Number(w) / 2)), y: ((dagreNode?.y ?? 0) - (Number(h) / 2)) }, style: { ...node.style, width: w, height: h } };
|
|
621
|
+
}
|
|
622
|
+
return { ...node, position: persisted ?? { x: dagreNode?.x ?? 0, y: dagreNode?.y ?? 0 } };
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
if (graphHeight && height === undefined)
|
|
626
|
+
setInternalHeight(graphHeight + 40);
|
|
627
|
+
const { nodes, edges } = syncGraph(positionedNodes, graph.edges, dir, proximityConnect, permitted, arcControls, handleResizeEnd);
|
|
628
|
+
const nFlow = { edges, nodes, selectedById: new Map(flowRef.current.nodes.filter(n => n.selected).map(n => [n.id, true])) };
|
|
629
|
+
setFlow({ nodes: nFlow.nodes.map(n => ({ ...n, selected: nFlow.selectedById.get(n.id) ?? false })), edges: nFlow.edges });
|
|
630
|
+
}, [graph, dir, nodeSep, rankSep, proximityConnect, permitted, arcControls, height, handleResizeEnd]);
|
|
631
|
+
useEffect(() => {
|
|
632
|
+
writeGraph({ nodes: initialNodes, edges: initialEdges }, false);
|
|
633
|
+
}, [initialNodes, initialEdges, writeGraph]);
|
|
634
|
+
// HELPERS
|
|
635
|
+
// RENDER
|
|
636
|
+
return (_jsx(ErrorBoundary, { name: 'Nodes', children: _jsx("div", { ...rest, style: { height: height ?? internalHeight, ...style }, className: cn('docs-flow size-full overflow-hidden rounded-md border border-primary-6', interactive && 'scrim-primary-1 scrim-20', className), tabIndex: rest.tabIndex ?? 0, onFocus: e => {
|
|
637
|
+
setIsFocused(true);
|
|
638
|
+
rest.onFocus?.(e);
|
|
639
|
+
}, onBlur: e => {
|
|
640
|
+
setIsFocused(false);
|
|
641
|
+
rest.onBlur?.(e);
|
|
642
|
+
}, children: _jsx(ReactFlowProvider, { children: _jsx(FLOW_COMPONENTS.Canvas, { flow: flow, nodeTypes: { ...FLOW_COMPONENTS.nodeTypes, ...customNodeTypes }, ...(edgeTypes ? { edgeTypes } : {}), nodeLabels: nodeLabels, onNodeClick: onNodeClick, onNodesChange:
|
|
643
|
+
/* prettier-ignore */ useCallback((r) => {
|
|
644
|
+
const typedChanges = r;
|
|
645
|
+
const nodeChanges = { byId: new Map(flowRef.current.nodes.map(n => [n.id, n])), positionIds: new Set(typedChanges.filter(c => c.type === 'position').map(c => c.id)) };
|
|
646
|
+
const childrenByGroupMap = new Map();
|
|
647
|
+
flowRef.current.nodes.forEach(n => {
|
|
648
|
+
const gId = getGroupId(n);
|
|
649
|
+
if (gId) {
|
|
650
|
+
const children = childrenByGroupMap.get(gId) ?? [];
|
|
651
|
+
children.push(n);
|
|
652
|
+
childrenByGroupMap.set(gId, children);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
const extraChanges = [];
|
|
656
|
+
typedChanges.forEach(v => {
|
|
657
|
+
if (v.type === 'position') {
|
|
658
|
+
const n = nodeChanges.byId.get(v.id);
|
|
659
|
+
if (n && n.type === 'group') {
|
|
660
|
+
(childrenByGroupMap.get(n.id) ?? []).forEach(c => {
|
|
661
|
+
if (!nodeChanges.positionIds.has(c.id)) {
|
|
662
|
+
const childChange = { type: 'position', id: c.id };
|
|
663
|
+
if (v.position) {
|
|
664
|
+
childChange.position = { x: c.position.x + (v.position.x - n.position.x), y: c.position.y + (v.position.y - n.position.y) };
|
|
665
|
+
}
|
|
666
|
+
if (v.dragging !== undefined)
|
|
667
|
+
childChange.dragging = v.dragging;
|
|
668
|
+
if (childChange.position || childChange.dragging !== undefined)
|
|
669
|
+
extraChanges.push(childChange);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
const allChanges = [...typedChanges, ...extraChanges];
|
|
676
|
+
const isDragFinished = editable && allChanges.some(c => c.type === 'position' && c.dragging === false);
|
|
677
|
+
const hasRemoval = editable && allChanges.some(c => c.type === 'remove');
|
|
678
|
+
const nNodes = applyNodeChanges(allChanges, flowRef.current.nodes);
|
|
679
|
+
let fNodes = nNodes;
|
|
680
|
+
let fEdges = flowRef.current.edges;
|
|
681
|
+
if (proximityConnect || isDragFinished) {
|
|
682
|
+
const synced = syncGraph(nNodes, flowRef.current.edges, dir, proximityConnect, editable, arcControls, handleResizeEnd);
|
|
683
|
+
fNodes = synced.nodes;
|
|
684
|
+
fEdges = synced.edges;
|
|
685
|
+
}
|
|
686
|
+
setFlow({ nodes: fNodes, edges: fEdges });
|
|
687
|
+
flowRef.current = { nodes: fNodes, edges: fEdges };
|
|
688
|
+
if (isDragFinished || hasRemoval) {
|
|
689
|
+
writeGraph(v => {
|
|
690
|
+
let nNodes = v.nodes;
|
|
691
|
+
const nodeUpdates = {
|
|
692
|
+
removedIds: hasRemoval ? new Set(allChanges.filter(c => c.type === 'remove').map(c => c.id)) : undefined,
|
|
693
|
+
draggedById: isDragFinished ? new Map(fNodes.map(n => [n.id, n])) : undefined,
|
|
694
|
+
};
|
|
695
|
+
if (hasRemoval) {
|
|
696
|
+
nNodes = nNodes.filter(n => !nodeUpdates.removedIds?.has(n.id));
|
|
697
|
+
}
|
|
698
|
+
if (isDragFinished) {
|
|
699
|
+
nNodes = nNodes.map(n => {
|
|
700
|
+
const dragged = nodeUpdates.draggedById?.get(n.id);
|
|
701
|
+
if (!dragged)
|
|
702
|
+
return n;
|
|
703
|
+
// use gracefully checked positions to avoid TypeErrors when nodes don't have static initial positions
|
|
704
|
+
const prevPos = n.position;
|
|
705
|
+
if (!prevPos || dragged.position.x !== prevPos.x || dragged.position.y !== prevPos.y)
|
|
706
|
+
return { ...n, position: dragged.position, data: { ...(n.data ?? { title: '', attributes: [] }), customPosition: dragged.position }, };
|
|
707
|
+
return n;
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
return { ...v, nodes: nNodes };
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}, [dir, proximityConnect, editable, arcControls, handleResizeEnd, writeGraph]), onEdgesChange: useCallback((v) => {
|
|
714
|
+
const nEdges = applyEdgeChanges(v, flowRef.current.edges);
|
|
715
|
+
setFlow({ ...flowRef.current, edges: nEdges });
|
|
716
|
+
flowRef.current = { ...flowRef.current, edges: nEdges };
|
|
717
|
+
if (editable && v.some(c => c.type === 'remove')) {
|
|
718
|
+
const remEdgeIds = new Set(v.filter(c => c.type === 'remove').map(c => c.id));
|
|
719
|
+
writeGraph(c => ({ ...c, edges: remEdgeIds.size ? c.edges.filter(_e => !remEdgeIds.has(_e.id)) : c.edges }));
|
|
720
|
+
}
|
|
721
|
+
}, [editable, writeGraph]), onConnect: useCallback((p) => {
|
|
722
|
+
if (!editable)
|
|
723
|
+
return;
|
|
724
|
+
writeGraph(c => ({
|
|
725
|
+
...c,
|
|
726
|
+
edges: [
|
|
727
|
+
...c.edges,
|
|
728
|
+
{
|
|
729
|
+
id: `_e-${p.source}-${p.target}-${Date.now().toString(36)}`,
|
|
730
|
+
source: p.source,
|
|
731
|
+
target: p.target,
|
|
732
|
+
sourceHandle: p.sourceHandle,
|
|
733
|
+
targetHandle: p.targetHandle,
|
|
734
|
+
labelBgStyle: { fill: 'white', fillOpacity: 0.8 },
|
|
735
|
+
data: { srcHandleOverride: p.sourceHandle, tgtHandleOverride: p.targetHandle, arrowHead: true },
|
|
736
|
+
},
|
|
737
|
+
],
|
|
738
|
+
}));
|
|
739
|
+
}, [editable, writeGraph]), onEdgeDoubleClick: useCallback((_e, r) => {
|
|
740
|
+
if (!editable)
|
|
741
|
+
return;
|
|
742
|
+
const nLabel = window.prompt('Enter edge label:', r.label || '');
|
|
743
|
+
if (nLabel === null)
|
|
744
|
+
return;
|
|
745
|
+
writeGraph(g => ({ ...g, edges: g.edges.map(e => (e.id === r.id ? { ...e, label: nLabel, labelBgStyle: { fill: 'white', fillOpacity: 0.8 } } : e)) }));
|
|
746
|
+
}, [editable, writeGraph]), onAddNode: useCallback((t, p) => {
|
|
747
|
+
writeGraph(c => ({
|
|
748
|
+
...c,
|
|
749
|
+
nodes: [
|
|
750
|
+
...c.nodes,
|
|
751
|
+
{
|
|
752
|
+
id: `${t}-${Date.now().toString(36)}`,
|
|
753
|
+
type: t,
|
|
754
|
+
position: p,
|
|
755
|
+
data: { attributes: [], customPosition: p, title: `${NODE_CFG[t].label} ${c.nodes.length + 1}` },
|
|
756
|
+
},
|
|
757
|
+
],
|
|
758
|
+
}));
|
|
759
|
+
}, [writeGraph]), lockedTypes: lockedTypes, allowedTypes: allowedTypes, onAddContent: fns.current.onAddContent, onGroupNodes: useCallback((g, v) => {
|
|
760
|
+
const group = { node: g, targetIds: new Set(v.map(s => s.id)) };
|
|
761
|
+
writeGraph(c => {
|
|
762
|
+
const nNodes = c.nodes.map(n => {
|
|
763
|
+
if (group.targetIds.has(n.id)) {
|
|
764
|
+
const { parentId: _parentId, extent: _extent, ...rest } = n;
|
|
765
|
+
return { ...rest, data: { ...n.data, groupId: g.id } };
|
|
766
|
+
}
|
|
767
|
+
return n;
|
|
768
|
+
});
|
|
769
|
+
return { ...c, nodes: nNodes.some(n => n.id === group.node.id) ? nNodes : [group.node, ...nNodes] };
|
|
770
|
+
});
|
|
771
|
+
}, [writeGraph]), onUngroupNodes: useCallback((v) => {
|
|
772
|
+
const ungroupMap = new Map(v.map(u => [u.id, u]));
|
|
773
|
+
writeGraph(c => ({
|
|
774
|
+
...c,
|
|
775
|
+
nodes: c.nodes.map(n => {
|
|
776
|
+
if (ungroupMap.has(n.id)) {
|
|
777
|
+
if (!getGroupId(ungroupMap.get(n.id)))
|
|
778
|
+
return n;
|
|
779
|
+
const { parentId: _, extent: __, ...rest } = n;
|
|
780
|
+
const { groupId: ___, ...restData } = rest.data ?? {};
|
|
781
|
+
return { ...rest, data: restData };
|
|
782
|
+
}
|
|
783
|
+
return n;
|
|
784
|
+
}),
|
|
785
|
+
}));
|
|
786
|
+
}, [writeGraph]), onDeleteSelected: useCallback((s) => {
|
|
787
|
+
writeGraph(c => {
|
|
788
|
+
const selIds = new Set(s.map(n => n.id));
|
|
789
|
+
return { ...c, nodes: c.nodes.filter(n => !selIds.has(n.id)), edges: c.edges.filter(_e => !selIds.has(_e.source) && !selIds.has(_e.target)) };
|
|
790
|
+
});
|
|
791
|
+
}, [writeGraph]), onAlignNodes: useCallback((v, t) => {
|
|
792
|
+
if (v.length < 2)
|
|
793
|
+
return;
|
|
794
|
+
const selNodesMap = new Map(v.map(s => [s.id, s]));
|
|
795
|
+
writeGraph(c => {
|
|
796
|
+
const absPos = v.map(n => ({ n, abs: n.position }));
|
|
797
|
+
const minX = Math.min(...absPos.map(p => p.abs.x - getNodeSize(p.n).w / 2));
|
|
798
|
+
const maxX = Math.max(...absPos.map(p => p.abs.x + getNodeSize(p.n).w / 2));
|
|
799
|
+
const minY = Math.min(...absPos.map(p => p.abs.y - getNodeSize(p.n).h / 2));
|
|
800
|
+
const maxY = Math.max(...absPos.map(p => p.abs.y + getNodeSize(p.n).h / 2));
|
|
801
|
+
return {
|
|
802
|
+
...c,
|
|
803
|
+
nodes: c.nodes.map(n => {
|
|
804
|
+
if (selNodesMap.has(n.id)) {
|
|
805
|
+
const flowNode = selNodesMap.get(n.id);
|
|
806
|
+
const size = getNodeSize(flowNode);
|
|
807
|
+
let nAbsX = flowNode.position.x;
|
|
808
|
+
let nAbsY = flowNode.position.y;
|
|
809
|
+
switch (t) {
|
|
810
|
+
case 'left':
|
|
811
|
+
nAbsX = minX + size.w / 2;
|
|
812
|
+
break;
|
|
813
|
+
case 'center':
|
|
814
|
+
nAbsX = (minX + maxX) / 2;
|
|
815
|
+
break;
|
|
816
|
+
case 'right':
|
|
817
|
+
nAbsX = maxX - size.w / 2;
|
|
818
|
+
break;
|
|
819
|
+
case 'top':
|
|
820
|
+
nAbsY = minY + size.h / 2;
|
|
821
|
+
break;
|
|
822
|
+
case 'middle':
|
|
823
|
+
nAbsY = (minY + maxY) / 2;
|
|
824
|
+
break;
|
|
825
|
+
case 'bottom':
|
|
826
|
+
nAbsY = maxY - size.h / 2;
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
const nPos = { x: nAbsX, y: nAbsY };
|
|
830
|
+
return { ...n, position: nPos, data: { ...n.data, customPosition: nPos } };
|
|
831
|
+
}
|
|
832
|
+
return n;
|
|
833
|
+
}),
|
|
834
|
+
};
|
|
835
|
+
});
|
|
836
|
+
}, [writeGraph]), onDistributeNodes: useCallback((v, t) => {
|
|
837
|
+
if (v.length < 3)
|
|
838
|
+
return;
|
|
839
|
+
writeGraph(c => {
|
|
840
|
+
const isHoriz = t.startsWith('horizontal');
|
|
841
|
+
const sizeKey = isHoriz ? 'w' : 'h';
|
|
842
|
+
/**
|
|
843
|
+
* Handles get edge.
|
|
844
|
+
*
|
|
845
|
+
* @param p - Graph node whose position should be rendered.
|
|
846
|
+
*/
|
|
847
|
+
const getEdge = (p) => {
|
|
848
|
+
const basePos = p.abs[isHoriz ? 'x' : 'y'];
|
|
849
|
+
const size = getNodeSize(p.n)[sizeKey];
|
|
850
|
+
if (t.endsWith('left') || t.endsWith('top'))
|
|
851
|
+
return basePos - size / 2;
|
|
852
|
+
if (t.endsWith('center'))
|
|
853
|
+
return basePos;
|
|
854
|
+
return basePos + size / 2;
|
|
855
|
+
};
|
|
856
|
+
const sorted = [...v.map(n => ({ n, abs: n.position }))].sort((a, b) => getEdge(a) - getEdge(b));
|
|
857
|
+
const min = getEdge(sorted[0]);
|
|
858
|
+
const layoutLookup = { nodes: new Map(v.map(s => [s.id, s])), indices: new Map(sorted.map((s, i) => [s.n.id, i])) };
|
|
859
|
+
return {
|
|
860
|
+
...c,
|
|
861
|
+
nodes: c.nodes.map(n => {
|
|
862
|
+
if (layoutLookup.nodes.has(n.id)) {
|
|
863
|
+
const tEdge = min + ((getEdge(sorted[sorted.length - 1]) - min) / (sorted.length - 1)) * layoutLookup.indices.get(n.id);
|
|
864
|
+
const flowNode = layoutLookup.nodes.get(n.id);
|
|
865
|
+
const size = getNodeSize(flowNode)[sizeKey];
|
|
866
|
+
let nAbsVal = tEdge;
|
|
867
|
+
if (t.endsWith('left') || t.endsWith('top'))
|
|
868
|
+
nAbsVal = tEdge + size / 2;
|
|
869
|
+
else if (t.endsWith('right') || t.endsWith('bottom'))
|
|
870
|
+
nAbsVal = tEdge - size / 2;
|
|
871
|
+
const newPos = { x: isHoriz ? nAbsVal : flowNode.position.x, y: isHoriz ? flowNode.position.y : nAbsVal };
|
|
872
|
+
return { ...n, position: newPos, data: { ...n.data, customPosition: newPos } };
|
|
873
|
+
}
|
|
874
|
+
return n;
|
|
875
|
+
}),
|
|
876
|
+
};
|
|
877
|
+
});
|
|
878
|
+
}, [writeGraph]), onRenameNode: useCallback((i) => {
|
|
879
|
+
let _node;
|
|
880
|
+
for (const n of flowRef.current.nodes) {
|
|
881
|
+
if (n.id === i) {
|
|
882
|
+
_node = n;
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (!_node)
|
|
887
|
+
return;
|
|
888
|
+
const nTitle = window.prompt('Enter new name:', _node.data.title);
|
|
889
|
+
if (!nTitle)
|
|
890
|
+
return;
|
|
891
|
+
writeGraph(c => ({ ...c, nodes: c.nodes.map(n => (n.id === i ? { ...n, data: { ...n.data, title: nTitle } } : n)) }));
|
|
892
|
+
}, [writeGraph]), onChangeNodeType: useCallback((i, t) => {
|
|
893
|
+
writeGraph(c => ({ ...c, nodes: c.nodes.map(n => (n.id === i ? { ...n, type: t } : n)) }));
|
|
894
|
+
}, [writeGraph]), onChangeEdgeStyle: useCallback((i, s) => {
|
|
895
|
+
writeGraph(c => ({ ...c, edges: c.edges.map(e => (e.id === i ? { ...e, data: { ...e.data, strokeStyle: s } } : e)) }));
|
|
896
|
+
}, [writeGraph]), onDeleteEdge: useCallback((i) => {
|
|
897
|
+
writeGraph(c => ({ ...c, edges: c.edges.filter(_e => _e.id !== i) }));
|
|
898
|
+
}, [writeGraph]), onAutoLayout: useCallback(() => {
|
|
899
|
+
writeGraph(c => {
|
|
900
|
+
const validNodeIds = new Set(c.nodes.map(n => n.id));
|
|
901
|
+
return {
|
|
902
|
+
...c,
|
|
903
|
+
nodes: c.nodes.map(n => {
|
|
904
|
+
const { parentId: _parentId, extent: _extent, ...rest } = n;
|
|
905
|
+
return { ...rest, data: Object.fromEntries(Object.entries(rest.data ?? {}).filter(([k]) => k !== 'customPosition')) };
|
|
906
|
+
}),
|
|
907
|
+
edges: c.edges.filter(e => validNodeIds.has(e.source) && validNodeIds.has(e.target)),
|
|
908
|
+
};
|
|
909
|
+
});
|
|
910
|
+
}, [writeGraph]), interactive: interactive, editable: permitted, isFocused: isFocused, height: typeof height === 'number' && Number.isFinite(height) ? height : internalHeight, showBg: showBg, showCtrls: showCtrls, arcControls: arcControls, readyWhenEmpty: readyWhenEmpty, onReady: onReady }) }) }) }));
|
|
911
|
+
}
|
|
912
|
+
//# sourceMappingURL=Nodes.js.map
|