@signalsafe/tree-spec-editor-react 0.1.0 → 0.1.2
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 +21 -0
- package/README.md +28 -7
- package/dist/GraphEditorCanvasContext.d.ts +26 -0
- package/dist/GraphEditorCanvasContext.d.ts.map +1 -0
- package/dist/GraphEditorCanvasContext.js +20 -0
- package/dist/TreeSpecGraphEditor.d.ts +22 -1
- package/dist/TreeSpecGraphEditor.d.ts.map +1 -1
- package/dist/TreeSpecGraphEditor.js +133 -257
- package/dist/canvas/constants.d.ts +41 -0
- package/dist/canvas/constants.d.ts.map +1 -0
- package/dist/canvas/constants.js +40 -0
- package/dist/canvas/edgeBuilders.d.ts +6 -0
- package/dist/canvas/edgeBuilders.d.ts.map +1 -0
- package/dist/canvas/edgeBuilders.js +57 -0
- package/dist/canvas/edgeStyle.d.ts +16 -0
- package/dist/canvas/edgeStyle.d.ts.map +1 -0
- package/dist/canvas/edgeStyle.js +44 -0
- package/dist/canvas/focusChoice.d.ts +3 -0
- package/dist/canvas/focusChoice.d.ts.map +1 -0
- package/dist/canvas/focusChoice.js +12 -0
- package/dist/canvas/typeGuards.d.ts +3 -0
- package/dist/canvas/typeGuards.d.ts.map +1 -0
- package/dist/canvas/typeGuards.js +16 -0
- package/dist/contextMenu/GraphCanvasContextMenu.d.ts +10 -0
- package/dist/contextMenu/GraphCanvasContextMenu.d.ts.map +1 -0
- package/dist/contextMenu/GraphCanvasContextMenu.js +39 -0
- package/dist/contextMenu/types.d.ts +11 -0
- package/dist/contextMenu/types.d.ts.map +1 -0
- package/dist/contextMenu/types.js +1 -0
- package/dist/hooks/keyboardShortcutDispatch.d.ts +30 -0
- package/dist/hooks/keyboardShortcutDispatch.d.ts.map +1 -0
- package/dist/hooks/keyboardShortcutDispatch.js +88 -0
- package/dist/hooks/types.d.ts +34 -4
- package/dist/hooks/types.d.ts.map +1 -1
- package/dist/hooks/useCanvasContextMenu.d.ts +15 -0
- package/dist/hooks/useCanvasContextMenu.d.ts.map +1 -0
- package/dist/hooks/useCanvasContextMenu.js +50 -0
- package/dist/hooks/useCanvasGraphState.d.ts +29 -0
- package/dist/hooks/useCanvasGraphState.d.ts.map +1 -0
- package/dist/hooks/useCanvasGraphState.js +150 -0
- package/dist/hooks/useCanvasIssueIndex.d.ts +12 -0
- package/dist/hooks/useCanvasIssueIndex.d.ts.map +1 -0
- package/dist/hooks/useCanvasIssueIndex.js +32 -0
- package/dist/hooks/useCanvasNodeResize.d.ts +17 -0
- package/dist/hooks/useCanvasNodeResize.d.ts.map +1 -0
- package/dist/hooks/useCanvasNodeResize.js +53 -0
- package/dist/hooks/useCanvasViewport.d.ts +12 -0
- package/dist/hooks/useCanvasViewport.d.ts.map +1 -0
- package/dist/hooks/useCanvasViewport.js +84 -0
- package/dist/hooks/useChoiceDragDrop.d.ts +25 -0
- package/dist/hooks/useChoiceDragDrop.d.ts.map +1 -0
- package/dist/hooks/useChoiceDragDrop.js +40 -0
- package/dist/hooks/useEditorAdapter.d.ts +46 -0
- package/dist/hooks/useEditorAdapter.d.ts.map +1 -0
- package/dist/hooks/useEditorAdapter.js +281 -0
- package/dist/hooks/useEditorAutosave.d.ts +18 -0
- package/dist/hooks/useEditorAutosave.d.ts.map +1 -0
- package/dist/hooks/useEditorAutosave.js +37 -0
- package/dist/hooks/useEditorHistory.d.ts +16 -0
- package/dist/hooks/useEditorHistory.d.ts.map +1 -0
- package/dist/hooks/useEditorHistory.js +103 -0
- package/dist/hooks/useEditorSelection.d.ts +22 -0
- package/dist/hooks/useEditorSelection.d.ts.map +1 -0
- package/dist/hooks/useEditorSelection.js +76 -0
- package/dist/hooks/useGraphConnect.d.ts +22 -0
- package/dist/hooks/useGraphConnect.d.ts.map +1 -0
- package/dist/hooks/useGraphConnect.js +75 -0
- package/dist/hooks/useTreeSpecEditor.d.ts +0 -8
- package/dist/hooks/useTreeSpecEditor.d.ts.map +1 -1
- package/dist/hooks/useTreeSpecEditor.js +231 -462
- package/dist/nodes/ChoiceCanvasRow.d.ts +10 -0
- package/dist/nodes/ChoiceCanvasRow.d.ts.map +1 -0
- package/dist/nodes/ChoiceCanvasRow.js +55 -0
- package/dist/nodes/EndNode.d.ts +3 -0
- package/dist/nodes/EndNode.d.ts.map +1 -0
- package/dist/nodes/EndNode.js +7 -0
- package/dist/nodes/PromptNode.d.ts +6 -0
- package/dist/nodes/PromptNode.d.ts.map +1 -0
- package/dist/nodes/PromptNode.js +51 -0
- package/dist/nodes/PromptNodeChoicesList.d.ts +14 -0
- package/dist/nodes/PromptNodeChoicesList.d.ts.map +1 -0
- package/dist/nodes/PromptNodeChoicesList.js +24 -0
- package/dist/nodes/PromptNodeHeader.d.ts +10 -0
- package/dist/nodes/PromptNodeHeader.d.ts.map +1 -0
- package/dist/nodes/PromptNodeHeader.js +6 -0
- package/dist/nodes/PromptNodeIssueBadges.d.ts +7 -0
- package/dist/nodes/PromptNodeIssueBadges.d.ts.map +1 -0
- package/dist/nodes/PromptNodeIssueBadges.js +6 -0
- package/dist/nodes/PromptNodeToolbar.d.ts +6 -0
- package/dist/nodes/PromptNodeToolbar.d.ts.map +1 -0
- package/dist/nodes/PromptNodeToolbar.js +5 -0
- package/dist/nodes/types.d.ts +13 -0
- package/dist/nodes/types.d.ts.map +1 -0
- package/dist/nodes/types.js +1 -0
- package/dist/utils/joinClasses.d.ts +2 -0
- package/dist/utils/joinClasses.d.ts.map +1 -0
- package/dist/utils/joinClasses.js +3 -0
- package/package.json +30 -12
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SignalSafe Software
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -8,20 +8,35 @@ This is the React-specific sibling to **[`@signalsafe/tree-spec-editor-core`](..
|
|
|
8
8
|
|
|
9
9
|
## What this package owns
|
|
10
10
|
|
|
11
|
-
- **`TreeSpecGraphEditor`** — the React Flow canvas (Background, Controls, MiniMap, custom node renderer, transition edges, selection wiring, focus/fit-view).
|
|
11
|
+
- **`TreeSpecGraphEditor`** — the React Flow canvas (Background, Controls, MiniMap, custom node renderer, transition edges, selection wiring, choice focus highlighting, focus/fit-view).
|
|
12
|
+
- **`useTreeSpecEditor`** — stateful orchestration hook (load, autosave, validate, publish, selection, undo/redo, choice/edge helpers). Exposes `inspectorNode` (selected node or edge source node), `focusChoiceId`, and `selectedEdge` for sidebar panels.
|
|
12
13
|
- **`TreeSpecGraphEditorProps`** — props type.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
|
|
15
|
+
## Canvas selection behavior
|
|
16
|
+
|
|
17
|
+
| Selection | Inspector context | Contextual zoom (`contextualZoom`, default `true`) |
|
|
18
|
+
|-----------|-------------------|-----------------------------------------------------|
|
|
19
|
+
| **Node** | `selectedNode` | Fits viewport to the node |
|
|
20
|
+
| **Edge** | `inspectorNode` = source node; `focusChoiceId` = source choice | **No pan/zoom** (viewport stays put) |
|
|
21
|
+
| **Choice** (canvas or inspector focus) | `focusChoiceId` set; nodes list highlights via `focusNodeId` | Fits when the parent node is selected |
|
|
22
|
+
|
|
23
|
+
Pass `onChoiceSelect` + `focusChoiceId` to keep canvas choice rows, inspector choice cards, and the nodes list in sync. Pass `contextualZoom={false}` to disable automatic viewport fitting on node selection.
|
|
16
24
|
|
|
17
25
|
## What lives elsewhere
|
|
18
26
|
|
|
19
27
|
| Concern | Package |
|
|
20
28
|
|--------|---------|
|
|
21
|
-
| Editor model, tree operations, layout, autosave/keyboard helpers,
|
|
29
|
+
| Editor model, tree operations, layout, autosave/keyboard helpers, choice edge hints | `@signalsafe/tree-spec-editor-core` |
|
|
22
30
|
| Sidebar panels, inspector, modals, toolbar (Bootstrap-styled) | `@signalsafe/tree-spec-editor` |
|
|
23
|
-
| Material-styled
|
|
24
|
-
| Angular
|
|
31
|
+
| Material-styled React shell (planned) | `@signalsafe/tree-spec-editor-react-mui` |
|
|
32
|
+
| Angular shell + canvas (planned) | `@signalsafe/tree-spec-editor-angular` |
|
|
33
|
+
| Vue shell + canvas (planned) | `@signalsafe/tree-spec-editor-vue` |
|
|
34
|
+
|
|
35
|
+
## Maintainer notes
|
|
36
|
+
|
|
37
|
+
- **This package stays React + reactflow only.** Angular/Vue hosts will use `-core` with their own canvas packages, not `-react`.
|
|
38
|
+
- **Material (React)** hosts should add `-react-mui` (planned), reusing this canvas unchanged.
|
|
39
|
+
- Layer boundaries and refactor plan: [docs/ai/packages-editor-architecture.md](../../docs/ai/packages-editor-architecture.md).
|
|
25
40
|
|
|
26
41
|
## Install
|
|
27
42
|
|
|
@@ -41,3 +56,9 @@ to ship a Material-styled editor only need to publish their own UI shell
|
|
|
41
56
|
(panels, modals, toolbar) — they reuse the canvas and the editor model
|
|
42
57
|
unchanged. This also keeps `@signalsafe/tree-spec-editor` (the
|
|
43
58
|
Bootstrap variant) from being the sole React entry point.
|
|
59
|
+
|
|
60
|
+
## Repository
|
|
61
|
+
|
|
62
|
+
Standalone source and releases: [SignalSafeSoftware/tree-spec-editor-react](https://github.com/SignalSafeSoftware/tree-spec-editor-react).
|
|
63
|
+
|
|
64
|
+
Published as [`@signalsafe/tree-spec-editor-react`](https://www.npmjs.com/package/@signalsafe/tree-spec-editor-react) on npm.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type ChoiceDropTarget = {
|
|
2
|
+
nodeId: string;
|
|
3
|
+
index: number;
|
|
4
|
+
};
|
|
5
|
+
export type ChoiceDragState = {
|
|
6
|
+
sourceNodeId: string;
|
|
7
|
+
choiceId: string;
|
|
8
|
+
};
|
|
9
|
+
export type GraphEditorCanvasContextValue = {
|
|
10
|
+
readOnly: boolean;
|
|
11
|
+
onDuplicateNode: (nodeId: string) => void;
|
|
12
|
+
onDeleteNode: (nodeId: string) => void;
|
|
13
|
+
onResizeNode: (nodeId: string, width: number, height: number) => void;
|
|
14
|
+
/** Lock the node box height when a resize drag begins (prevents wrap reflow jumps). */
|
|
15
|
+
onResizeNodeStart: (nodeId: string, width: number, height: number) => void;
|
|
16
|
+
onSelectChoice: (nodeId: string, choiceId: string) => void;
|
|
17
|
+
choiceDrag: ChoiceDragState | null;
|
|
18
|
+
choiceDropTarget: ChoiceDropTarget | null;
|
|
19
|
+
onChoiceDragStart: (nodeId: string, choiceId: string) => void;
|
|
20
|
+
onChoiceDragEnd: () => void;
|
|
21
|
+
onChoiceDragOver: (nodeId: string, index: number) => void;
|
|
22
|
+
onChoiceDrop: (nodeId: string, index: number) => void;
|
|
23
|
+
};
|
|
24
|
+
export declare const GraphEditorCanvasContext: import("react").Context<GraphEditorCanvasContextValue>;
|
|
25
|
+
export declare function useGraphEditorCanvas(): GraphEditorCanvasContextValue;
|
|
26
|
+
//# sourceMappingURL=GraphEditorCanvasContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GraphEditorCanvasContext.d.ts","sourceRoot":"","sources":["../src/GraphEditorCanvasContext.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IACxC,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtE,uFAAuF;IACvF,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3E,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9D,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzD,CAAC;AAmBF,eAAO,MAAM,wBAAwB,wDAA8B,CAAC;AAEpE,wBAAgB,oBAAoB,IAAI,6BAA6B,CAEpE"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
const noop = () => undefined;
|
|
3
|
+
const defaultValue = {
|
|
4
|
+
readOnly: true,
|
|
5
|
+
onDuplicateNode: noop,
|
|
6
|
+
onDeleteNode: noop,
|
|
7
|
+
onResizeNode: noop,
|
|
8
|
+
onResizeNodeStart: noop,
|
|
9
|
+
onSelectChoice: noop,
|
|
10
|
+
choiceDrag: null,
|
|
11
|
+
choiceDropTarget: null,
|
|
12
|
+
onChoiceDragStart: noop,
|
|
13
|
+
onChoiceDragEnd: noop,
|
|
14
|
+
onChoiceDragOver: noop,
|
|
15
|
+
onChoiceDrop: noop,
|
|
16
|
+
};
|
|
17
|
+
export const GraphEditorCanvasContext = createContext(defaultValue);
|
|
18
|
+
export function useGraphEditorCanvas() {
|
|
19
|
+
return useContext(GraphEditorCanvasContext);
|
|
20
|
+
}
|
|
@@ -7,10 +7,31 @@ export type TreeSpecGraphEditorProps = {
|
|
|
7
7
|
showMiniMap?: boolean;
|
|
8
8
|
selected?: GraphSelection;
|
|
9
9
|
onSelect?: (sel: GraphSelection) => void;
|
|
10
|
+
/** Called when a choice row is clicked on the canvas (after `onSelect` for the parent node). */
|
|
11
|
+
onChoiceSelect?: (nodeId: string, choiceId: string) => void;
|
|
12
|
+
/** Called when a choice is dropped after drag (reorder or move to another node). */
|
|
13
|
+
onRepositionChoice?: (fromNodeId: string, choiceId: string, toNodeId: string, toIndex: number) => void;
|
|
10
14
|
focusNodeId?: string | null;
|
|
15
|
+
/** Focused choice on the currently selected node (canvas + inspector sync). */
|
|
16
|
+
focusChoiceId?: string | null;
|
|
11
17
|
fitViewNonce?: number;
|
|
12
18
|
/** Optional class for the outer container (default includes h-70vh border rounded). */
|
|
13
19
|
className?: string;
|
|
20
|
+
/** When true, disables canvas editing affordances (toolbar, resize, context menu). */
|
|
21
|
+
readOnly?: boolean;
|
|
22
|
+
/** Duplicate a prompt node from the canvas toolbar or context menu. */
|
|
23
|
+
onDuplicateNode?: (nodeId: string) => void;
|
|
24
|
+
/** Delete a prompt node from the canvas toolbar or context menu. */
|
|
25
|
+
onDeleteNode?: (nodeId: string) => void;
|
|
26
|
+
/** Run auto-layout from the pane context menu. */
|
|
27
|
+
onAutoLayout?: () => void;
|
|
28
|
+
/**
|
|
29
|
+
* When true (default), zooms the viewport to fit the current node/edge selection.
|
|
30
|
+
* Skipped when `focusNodeId` already targets the same node.
|
|
31
|
+
*/
|
|
32
|
+
contextualZoom?: boolean;
|
|
33
|
+
/** Canvas chrome mode — host should pass Bootstrap `colorScheme` (`light` / `dark`). */
|
|
34
|
+
colorMode?: 'light' | 'dark';
|
|
14
35
|
};
|
|
15
|
-
export default function TreeSpecGraphEditor(props: Readonly<TreeSpecGraphEditorProps>): import("react
|
|
36
|
+
export default function TreeSpecGraphEditor(props: Readonly<TreeSpecGraphEditorProps>): import("react").JSX.Element;
|
|
16
37
|
//# sourceMappingURL=TreeSpecGraphEditor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TreeSpecGraphEditor.d.ts","sourceRoot":"","sources":["../src/TreeSpecGraphEditor.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TreeSpecGraphEditor.d.ts","sourceRoot":"","sources":["../src/TreeSpecGraphEditor.tsx"],"names":[],"mappings":"AAYA,OAAO,0BAA0B,CAAC;AAElC,OAAO,EAKH,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACxB,MAAM,mCAAmC,CAAC;AAsB3C,MAAM,MAAM,wBAAwB,GAAG;IACnC,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,gGAAgG;IAChG,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,oFAAoF;IACpF,kBAAkB,CAAC,EAAE,CACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,KACd,IAAI,CAAC;IACV,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,+EAA+E;IAC/E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uEAAuE;IACvE,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,oEAAoE;IACpE,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wFAAwF;IACxF,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAChC,CAAC;AA4PF,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,KAAK,EAAE,QAAQ,CAAC,wBAAwB,CAAC,+BAMpF"}
|
|
@@ -1,273 +1,149 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback,
|
|
3
|
-
import ReactFlow, { Background,
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import ReactFlow, { Background, ConnectionMode, Controls, MiniMap, ReactFlowProvider, useReactFlow, } from 'reactflow';
|
|
4
4
|
import 'reactflow/dist/style.css';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
function PromptNode({ data, selected }) {
|
|
25
|
-
const n = data.node;
|
|
26
|
-
const choices = n.choices ?? [];
|
|
27
|
-
const hasErrors = data.issuesErrors > 0;
|
|
28
|
-
const borderClass = getPromptNodeBorderClass(hasErrors, data.issuesWarnings);
|
|
29
|
-
return (_jsxs("div", { className: `card card-min-width-280 ${borderClass}${selected ? ` ${CANVAS_NODE_SELECTED_CLASS}` : ''}`, children: [_jsx(Handle, { type: "target", position: Position.Left, id: "in", className: "handle-bg-default" }), _jsxs("div", { className: "card-body p-2", children: [_jsxs("div", { className: "d-flex justify-content-between align-items-center", children: [_jsxs("div", { children: [_jsxs("div", { className: "fw-bold font-size-13", children: [data.isStart ? '▶ ' : '', n.type, data.issuesTotal > 0 ? (_jsxs("span", { className: "ms-2", title: `${data.issuesErrors} errors, ${data.issuesWarnings} warnings, ${data.issuesInfo} info`, children: [data.issuesErrors > 0 ? _jsx("span", { className: "badge bg-danger me-1", children: data.issuesErrors }) : null, data.issuesWarnings > 0 ? (_jsx("span", { className: "badge bg-warning text-dark me-1", children: data.issuesWarnings })) : null, data.issuesInfo > 0 ? _jsx("span", { className: "badge bg-info text-dark", children: data.issuesInfo }) : null] })) : null] }), _jsx("div", { className: "font-size-12 opacity-90 text-truncate", children: n.prompt || _jsx("em", { className: "text-muted", children: "(empty prompt)" }) })] }), _jsx("div", { className: "text-muted font-size-11", children: n.id.slice(0, 8) })] }), _jsx("hr", { className: "my-2" }), _jsx("div", { className: "font-size-12", children: choices.length === 0 ? (_jsx("div", { className: "text-muted", children: _jsx("em", { children: "No choices" }) })) : (_jsx("ul", { className: "list-unstyled mb-0", children: choices.map((c) => (_jsxs("li", { className: "d-flex justify-content-between align-items-center position-relative pr-18", children: [_jsx("span", { className: "text-truncate max-w-210", children: c.label }), _jsx("span", { className: "badge bg-light text-dark", children: c.id }), _jsx(Handle, { type: "source", position: Position.Right, id: `choice:${c.id}`, className: "graph-editor-handle", style: {
|
|
30
|
-
position: 'absolute',
|
|
31
|
-
right: -6,
|
|
32
|
-
top: '50%',
|
|
33
|
-
transform: 'translateY(-50%)',
|
|
34
|
-
} })] }, c.id))) })) })] })] }));
|
|
35
|
-
}
|
|
36
|
-
function EndNode({ selected }) {
|
|
37
|
-
return (_jsxs("div", { className: `card border-danger w-180${selected ? ` ${CANVAS_NODE_SELECTED_CLASS}` : ''}`, children: [_jsx(Handle, { type: "target", position: Position.Left, id: "in", className: "handle-bg-danger" }), _jsxs("div", { className: "card-body p-2 text-center", children: [_jsx("div", { className: "fw-bold text-danger", children: "END" }), _jsx("div", { className: "text-muted font-size-12", children: "Outcome required" })] })] }));
|
|
38
|
-
}
|
|
39
|
-
function choiceIdFromHandle(h) {
|
|
40
|
-
if (!h)
|
|
41
|
-
return '';
|
|
42
|
-
if (h.startsWith('choice:'))
|
|
43
|
-
return h.slice('choice:'.length);
|
|
44
|
-
return h;
|
|
45
|
-
}
|
|
46
|
-
function edgeLabelForTransition(tree, t) {
|
|
47
|
-
const node = tree.nodes[t.fromNodeId];
|
|
48
|
-
const choice = node?.choices?.find((c) => c.id === t.fromChoiceId);
|
|
49
|
-
const base = choice?.label || t.fromChoiceId;
|
|
50
|
-
if (t.toNodeId === END_NODE_ID) {
|
|
51
|
-
const oc = t.outcome ?? TERMINAL_OUTCOME.AT_RISK;
|
|
52
|
-
return `${base} → END (${oc})`;
|
|
53
|
-
}
|
|
54
|
-
return base;
|
|
55
|
-
}
|
|
56
|
-
function buildEdgesFromTransitions(tree) {
|
|
57
|
-
return tree.transitions.map((t) => ({
|
|
58
|
-
id: t.id,
|
|
59
|
-
source: t.fromNodeId,
|
|
60
|
-
target: t.toNodeId,
|
|
61
|
-
sourceHandle: `choice:${t.fromChoiceId}`,
|
|
62
|
-
label: edgeLabelForTransition(tree, t),
|
|
63
|
-
}));
|
|
64
|
-
}
|
|
65
|
-
function buildTransitionsFromEdges(edges, existing) {
|
|
66
|
-
const existingById = new Map(existing.map((t) => [t.id, t]));
|
|
67
|
-
return edges
|
|
68
|
-
.filter((e) => e.source && e.target && e.source !== END_NODE_ID)
|
|
69
|
-
.map((e) => {
|
|
70
|
-
const fromChoiceId = choiceIdFromHandle(e.sourceHandle);
|
|
71
|
-
const prior = existingById.get(String(e.id));
|
|
72
|
-
return {
|
|
73
|
-
id: String(e.id),
|
|
74
|
-
fromNodeId: String(e.source),
|
|
75
|
-
fromChoiceId: fromChoiceId,
|
|
76
|
-
toNodeId: String(e.target),
|
|
77
|
-
outcome: String(e.target) === END_NODE_ID ? (prior?.outcome ?? TERMINAL_OUTCOME.AT_RISK) : undefined,
|
|
78
|
-
};
|
|
79
|
-
})
|
|
80
|
-
.filter((t) => t.fromChoiceId.length > 0);
|
|
81
|
-
}
|
|
82
|
-
function TreeSpecGraphInner({ tree, onChange, issues = [], selected, onSelect, focusNodeId, showMiniMap = true, fitViewNonce, className = 'h-70vh border rounded', }) {
|
|
5
|
+
import { GRAPH_SELECTION_KIND, choiceIdFromHandle, resolveGraphViewport, LAYOUT_SNAP_GRID, } from '@signalsafe/tree-spec-editor-core';
|
|
6
|
+
import { GraphEditorCanvasContext, } from './GraphEditorCanvasContext';
|
|
7
|
+
import { buildEdgeMarker, getIssueEdgeStyle, resolveSelectedEdgeStroke } from './canvas/edgeStyle';
|
|
8
|
+
import { CANVAS_CLASS } from './canvas/constants';
|
|
9
|
+
import { resolveCanvasFocusChoiceId } from './canvas/focusChoice';
|
|
10
|
+
import { isChoiceRowClickTarget } from './canvas/typeGuards';
|
|
11
|
+
import { GraphCanvasContextMenu } from './contextMenu/GraphCanvasContextMenu';
|
|
12
|
+
import { EndNode } from './nodes/EndNode';
|
|
13
|
+
import { PromptNode } from './nodes/PromptNode';
|
|
14
|
+
import { joinClasses } from './utils/joinClasses';
|
|
15
|
+
import { useCanvasContextMenu } from './hooks/useCanvasContextMenu';
|
|
16
|
+
import { useCanvasGraphState } from './hooks/useCanvasGraphState';
|
|
17
|
+
import { useCanvasIssueIndex } from './hooks/useCanvasIssueIndex';
|
|
18
|
+
import { useCanvasNodeResize } from './hooks/useCanvasNodeResize';
|
|
19
|
+
import { useCanvasViewport } from './hooks/useCanvasViewport';
|
|
20
|
+
import { useChoiceDragDrop } from './hooks/useChoiceDragDrop';
|
|
21
|
+
import { useGraphConnect } from './hooks/useGraphConnect';
|
|
22
|
+
function TreeSpecGraphInner({ tree, onChange, issues = [], selected, onSelect, onChoiceSelect, onRepositionChoice, focusNodeId, focusChoiceId = null, showMiniMap = true, fitViewNonce, className = 'h-70vh border rounded', readOnly = false, onDuplicateNode, onDeleteNode, onAutoLayout, contextualZoom = true, colorMode = 'light', }) {
|
|
83
23
|
const rf = useReactFlow();
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
data: {},
|
|
136
|
-
selectable: true,
|
|
137
|
-
draggable: true,
|
|
138
|
-
});
|
|
139
|
-
return arr;
|
|
140
|
-
}, [tree.nodes, tree.start_node, issuesByNode]);
|
|
141
|
-
const initialEdges = useMemo(() => buildEdgesFromTransitions(tree), [tree]);
|
|
142
|
-
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
|
143
|
-
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
nodesRef.current = nodes;
|
|
146
|
-
}, [nodes]);
|
|
147
|
-
useEffect(() => {
|
|
148
|
-
edgesRef.current = edges;
|
|
149
|
-
}, [edges]);
|
|
150
|
-
const lastTreeRef = useRef('');
|
|
151
|
-
useEffect(() => {
|
|
152
|
-
const nextKey = JSON.stringify({ tree, issues: issues.length });
|
|
153
|
-
if (nextKey === lastTreeRef.current)
|
|
154
|
-
return;
|
|
155
|
-
lastTreeRef.current = nextKey;
|
|
156
|
-
setNodes(initialNodes);
|
|
157
|
-
setEdges(initialEdges);
|
|
158
|
-
}, [tree, issues.length, initialNodes, initialEdges, setNodes, setEdges]);
|
|
159
|
-
useEffect(() => {
|
|
160
|
-
if (!focusNodeId)
|
|
161
|
-
return;
|
|
162
|
-
try {
|
|
163
|
-
const n = rf.getNode(focusNodeId);
|
|
164
|
-
if (!n)
|
|
165
|
-
return;
|
|
166
|
-
rf.setCenter(n.position.x + 150, n.position.y + 60, { zoom: 1, duration: 300 });
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
// ignore
|
|
170
|
-
}
|
|
171
|
-
}, [focusNodeId, rf]);
|
|
172
|
-
useEffect(() => {
|
|
173
|
-
if (!fitViewNonce)
|
|
174
|
-
return;
|
|
175
|
-
const id = setTimeout(() => {
|
|
176
|
-
try {
|
|
177
|
-
rf.fitView({ padding: 0.2, duration: 300 });
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
// ignore
|
|
181
|
-
}
|
|
182
|
-
}, 100);
|
|
183
|
-
return () => clearTimeout(id);
|
|
184
|
-
}, [fitViewNonce, rf]);
|
|
24
|
+
const treeRef = useRef(tree);
|
|
25
|
+
treeRef.current = tree;
|
|
26
|
+
const suppressViewportSaveRef = useRef(true);
|
|
27
|
+
const isResizingRef = useRef(false);
|
|
28
|
+
const { issuesByNode, issueKeySet } = useCanvasIssueIndex(issues);
|
|
29
|
+
const { choiceDrag, choiceDropTarget, handleChoiceDragStart, handleChoiceDragEnd, handleChoiceDragOver, handleChoiceDrop, } = useChoiceDragDrop({
|
|
30
|
+
readOnly,
|
|
31
|
+
onChoiceSelect,
|
|
32
|
+
onSelect,
|
|
33
|
+
onRepositionChoice,
|
|
34
|
+
});
|
|
35
|
+
const { contextMenu, closeContextMenu, onNodeContextMenu, onPaneContextMenu } = useCanvasContextMenu({
|
|
36
|
+
readOnly,
|
|
37
|
+
onAutoLayout,
|
|
38
|
+
});
|
|
39
|
+
const [resizeHeightByNodeId, setResizeHeightByNodeId] = useState({});
|
|
40
|
+
const graphState = useCanvasGraphState({
|
|
41
|
+
tree,
|
|
42
|
+
onChange,
|
|
43
|
+
issues,
|
|
44
|
+
issuesByNode,
|
|
45
|
+
readOnly,
|
|
46
|
+
resizeHeightByNodeId,
|
|
47
|
+
isResizingRef,
|
|
48
|
+
suppressViewportSaveRef,
|
|
49
|
+
});
|
|
50
|
+
const { nodes, edges, onNodesChangeWrapped, onEdgesChangeWrapped, onNodeDragStart, onNodeDragStop, onMoveEnd, setNodes, } = graphState;
|
|
51
|
+
const resizeHandlers = useCanvasNodeResize({
|
|
52
|
+
readOnly,
|
|
53
|
+
treeRef,
|
|
54
|
+
onChange,
|
|
55
|
+
setNodes,
|
|
56
|
+
isResizingRef,
|
|
57
|
+
resizeHeightByNodeId,
|
|
58
|
+
setResizeHeightByNodeId,
|
|
59
|
+
});
|
|
60
|
+
useCanvasViewport({
|
|
61
|
+
rf,
|
|
62
|
+
focusNodeId,
|
|
63
|
+
fitViewNonce,
|
|
64
|
+
contextualZoom,
|
|
65
|
+
selected,
|
|
66
|
+
suppressViewportSaveRef,
|
|
67
|
+
});
|
|
68
|
+
const { onConnect, onConnectStart, onConnectEnd, onReconnect, onEdgesDelete, isValidConnection, } = useGraphConnect({
|
|
69
|
+
treeRef,
|
|
70
|
+
onChange,
|
|
71
|
+
onSelect,
|
|
72
|
+
readOnly,
|
|
73
|
+
});
|
|
74
|
+
const savedViewport = useMemo(() => resolveGraphViewport(tree), [tree._meta]);
|
|
185
75
|
const nodeTypes = useMemo(() => ({
|
|
186
76
|
promptNode: PromptNode,
|
|
187
77
|
endNode: EndNode,
|
|
188
78
|
}), []);
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
79
|
+
const onNodeClick = useCallback((evt, node) => {
|
|
80
|
+
if (isChoiceRowClickTarget(evt.target))
|
|
191
81
|
return;
|
|
192
|
-
const updatedNodes = { ...tree.nodes };
|
|
193
|
-
for (const n of nextNodes) {
|
|
194
|
-
if (n.id === END_NODE_ID)
|
|
195
|
-
continue;
|
|
196
|
-
const existing = updatedNodes[n.id];
|
|
197
|
-
if (!existing)
|
|
198
|
-
continue;
|
|
199
|
-
updatedNodes[n.id] = {
|
|
200
|
-
...existing,
|
|
201
|
-
position: { x: n.position.x, y: n.position.y },
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
const transitions = buildTransitionsFromEdges(nextEdges, tree.transitions);
|
|
205
|
-
onChange({
|
|
206
|
-
...tree,
|
|
207
|
-
nodes: updatedNodes,
|
|
208
|
-
transitions,
|
|
209
|
-
});
|
|
210
|
-
}, [tree, onChange]);
|
|
211
|
-
const onConnect = useCallback((conn) => {
|
|
212
|
-
const choiceId = choiceIdFromHandle(conn.sourceHandle);
|
|
213
|
-
if (!conn.source || !conn.target || !choiceId)
|
|
214
|
-
return;
|
|
215
|
-
const newEdge = {
|
|
216
|
-
id: safeUUID(),
|
|
217
|
-
source: conn.source,
|
|
218
|
-
target: conn.target,
|
|
219
|
-
sourceHandle: conn.sourceHandle,
|
|
220
|
-
label: choiceId,
|
|
221
|
-
};
|
|
222
|
-
setEdges((eds) => {
|
|
223
|
-
const next = addEdge(newEdge, eds);
|
|
224
|
-
commit(nodesRef.current, next);
|
|
225
|
-
return next;
|
|
226
|
-
});
|
|
227
|
-
}, [setEdges, commit]);
|
|
228
|
-
const onNodesChangeWrapped = useCallback((changes) => {
|
|
229
|
-
onNodesChange(changes);
|
|
230
|
-
const shouldCommit = changes.some((c) => {
|
|
231
|
-
if (c?.type === 'select' || c?.type === 'dimensions')
|
|
232
|
-
return false;
|
|
233
|
-
if (c?.type === 'position' && c?.dragging)
|
|
234
|
-
return false;
|
|
235
|
-
return true;
|
|
236
|
-
});
|
|
237
|
-
if (shouldCommit)
|
|
238
|
-
queueMicrotask(() => commit(nodesRef.current, edgesRef.current));
|
|
239
|
-
}, [onNodesChange, commit]);
|
|
240
|
-
const onEdgesChangeWrapped = useCallback((changes) => {
|
|
241
|
-
onEdgesChange(changes);
|
|
242
|
-
const shouldCommit = changes.some((c) => c?.type !== 'select');
|
|
243
|
-
if (shouldCommit)
|
|
244
|
-
queueMicrotask(() => commit(nodesRef.current, edgesRef.current));
|
|
245
|
-
}, [onEdgesChange, commit]);
|
|
246
|
-
const onNodeDragStart = useCallback(() => {
|
|
247
|
-
isDraggingRef.current = true;
|
|
248
|
-
}, []);
|
|
249
|
-
const onNodeDragStop = useCallback(() => {
|
|
250
|
-
isDraggingRef.current = false;
|
|
251
|
-
commit(nodesRef.current, edgesRef.current);
|
|
252
|
-
}, [commit]);
|
|
253
|
-
const onNodeClick = useCallback((_evt, node) => {
|
|
254
82
|
onSelect?.({ kind: GRAPH_SELECTION_KIND.NODE, id: node.id });
|
|
255
83
|
}, [onSelect]);
|
|
256
84
|
const onEdgeClick = useCallback((_evt, edge) => {
|
|
257
85
|
onSelect?.({ kind: GRAPH_SELECTION_KIND.EDGE, id: edge.id });
|
|
258
86
|
}, [onSelect]);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
87
|
+
const onPaneClick = useCallback(() => {
|
|
88
|
+
onSelect?.({ kind: null, id: null });
|
|
89
|
+
}, [onSelect]);
|
|
90
|
+
const canvasContextValue = useMemo(() => ({
|
|
91
|
+
readOnly,
|
|
92
|
+
onDuplicateNode: (nodeId) => onDuplicateNode?.(nodeId),
|
|
93
|
+
onDeleteNode: (nodeId) => onDeleteNode?.(nodeId),
|
|
94
|
+
onResizeNode: resizeHandlers.handleResizeNode,
|
|
95
|
+
onResizeNodeStart: resizeHandlers.handleResizeNodeStart,
|
|
96
|
+
onSelectChoice: (nodeId, choiceId) => {
|
|
97
|
+
if (onChoiceSelect) {
|
|
98
|
+
onChoiceSelect(nodeId, choiceId);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
onSelect?.({ kind: GRAPH_SELECTION_KIND.NODE, id: nodeId });
|
|
102
|
+
},
|
|
103
|
+
choiceDrag,
|
|
104
|
+
choiceDropTarget,
|
|
105
|
+
onChoiceDragStart: handleChoiceDragStart,
|
|
106
|
+
onChoiceDragEnd: handleChoiceDragEnd,
|
|
107
|
+
onChoiceDragOver: handleChoiceDragOver,
|
|
108
|
+
onChoiceDrop: handleChoiceDrop,
|
|
109
|
+
}), [
|
|
110
|
+
readOnly,
|
|
111
|
+
onDuplicateNode,
|
|
112
|
+
onDeleteNode,
|
|
113
|
+
resizeHandlers.handleResizeNode,
|
|
114
|
+
resizeHandlers.handleResizeNodeStart,
|
|
115
|
+
onSelect,
|
|
116
|
+
onChoiceSelect,
|
|
117
|
+
choiceDrag,
|
|
118
|
+
choiceDropTarget,
|
|
119
|
+
handleChoiceDragStart,
|
|
120
|
+
handleChoiceDragEnd,
|
|
121
|
+
handleChoiceDragOver,
|
|
122
|
+
handleChoiceDrop,
|
|
123
|
+
]);
|
|
124
|
+
return (_jsx(GraphEditorCanvasContext.Provider, { value: canvasContextValue, children: _jsxs("div", { className: joinClasses(className, CANVAS_CLASS), "data-color-mode": colorMode, children: [_jsx(GraphCanvasContextMenu, { menu: contextMenu, readOnly: readOnly, onClose: closeContextMenu, onDuplicateNode: onDuplicateNode, onDeleteNode: onDeleteNode, onAutoLayout: onAutoLayout }), _jsxs(ReactFlow, { defaultViewport: savedViewport, elementsSelectable: false, selectNodesOnDrag: false, nodes: nodes.map((n) => ({
|
|
125
|
+
...n,
|
|
126
|
+
selected: selected?.kind === GRAPH_SELECTION_KIND.NODE && selected.id === n.id,
|
|
127
|
+
data: n.type === 'promptNode'
|
|
128
|
+
? {
|
|
129
|
+
...n.data,
|
|
130
|
+
focusChoiceId: resolveCanvasFocusChoiceId(n.id, selected, focusNodeId, focusChoiceId),
|
|
131
|
+
}
|
|
132
|
+
: n.data,
|
|
133
|
+
})), edges: edges.map((e) => {
|
|
134
|
+
const fromChoiceId = choiceIdFromHandle(e.sourceHandle);
|
|
135
|
+
const hasIssue = fromChoiceId ? issueKeySet.has(`${e.source}::${fromChoiceId}`) : false;
|
|
136
|
+
const isEdgeSelected = selected?.kind === GRAPH_SELECTION_KIND.EDGE && selected.id === e.id;
|
|
137
|
+
let style = getIssueEdgeStyle(e.style, hasIssue);
|
|
138
|
+
const { style: resolvedStyle, stroke } = resolveSelectedEdgeStroke(style, isEdgeSelected);
|
|
139
|
+
style = resolvedStyle;
|
|
140
|
+
return {
|
|
141
|
+
...e,
|
|
142
|
+
selected: isEdgeSelected,
|
|
143
|
+
style,
|
|
144
|
+
markerEnd: buildEdgeMarker(stroke),
|
|
145
|
+
};
|
|
146
|
+
}), onNodesChange: onNodesChangeWrapped, onEdgesChange: onEdgesChangeWrapped, onConnect: onConnect, onConnectStart: onConnectStart, onConnectEnd: onConnectEnd, onReconnect: onReconnect, onEdgesDelete: onEdgesDelete, isValidConnection: isValidConnection, connectionMode: ConnectionMode.Strict, snapToGrid: true, snapGrid: [LAYOUT_SNAP_GRID, LAYOUT_SNAP_GRID], onNodeDragStart: onNodeDragStart, onNodeDragStop: onNodeDragStop, onNodeClick: onNodeClick, onEdgeClick: onEdgeClick, onPaneClick: onPaneClick, onNodeContextMenu: onNodeContextMenu, onPaneContextMenu: onPaneContextMenu, onMoveEnd: onMoveEnd, nodeTypes: nodeTypes, children: [showMiniMap ? _jsx(MiniMap, { pannable: true, ariaLabel: "Canvas overview \u2014 drag to pan" }) : null, _jsx(Controls, {}), _jsx(Background, {})] })] }) }));
|
|
271
147
|
}
|
|
272
148
|
export default function TreeSpecGraphEditor(props) {
|
|
273
149
|
return (_jsx(ReactFlowProvider, { children: _jsx(TreeSpecGraphInner, { ...props }) }));
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MarkerType } from 'reactflow';
|
|
2
|
+
/** Background highlight when the node matches editor selection (sidebar / issues / canvas). */
|
|
3
|
+
export declare const CANVAS_NODE_SELECTED_CLASS = "bg-primary-subtle";
|
|
4
|
+
/** Dark readable text on selected canvas node cards. */
|
|
5
|
+
export declare const CANVAS_NODE_SELECTED_TEXT_CLASS = "graph-editor-canvas-selected";
|
|
6
|
+
export declare const CANVAS_CLASS = "graph-editor-canvas";
|
|
7
|
+
export declare const CANVAS_NODE_CLASS = "graph-editor-canvas-node";
|
|
8
|
+
export declare const CANVAS_NODE_BODY_CLASS = "graph-editor-canvas-node-body";
|
|
9
|
+
export declare const CANVAS_CHOICE_SELECTED_CLASS = "graph-editor-canvas-choice-selected";
|
|
10
|
+
export declare const CHOICE_ROW_CLASS = "graph-editor-choice-row";
|
|
11
|
+
export declare const CHOICE_ROW_SELECTOR = ".graph-editor-choice-row";
|
|
12
|
+
export declare const CHOICE_DRAG_HANDLE_CLASS = "graph-editor-choice-drag-handle";
|
|
13
|
+
export declare const CHOICE_DRAG_HANDLE_SELECTOR = ".graph-editor-choice-drag-handle";
|
|
14
|
+
export declare const CHOICE_DROP_TARGET_CLASS = "graph-editor-choice-drop-target";
|
|
15
|
+
export declare const CHOICE_DROP_APPEND_CLASS = "graph-editor-choice-drop-append";
|
|
16
|
+
export declare const CHOICE_ROW_SELECT_CLASS = "graph-editor-choice-row-select";
|
|
17
|
+
export declare const CHOICE_ROW_SELECTABLE_CLASS = "graph-editor-choice-row-selectable";
|
|
18
|
+
export declare const CHOICE_HANDLE_CLASS = "graph-editor-handle graph-editor-choice-handle";
|
|
19
|
+
export declare const NODE_DRAG_HANDLE_CLASS = "graph-editor-drag-handle";
|
|
20
|
+
export declare const NODE_DRAG_HANDLE_SELECTOR = ".graph-editor-drag-handle";
|
|
21
|
+
export declare const TARGET_HANDLE_CLASS_DEFAULT = "handle-bg-default graph-editor-target-handle";
|
|
22
|
+
export declare const TARGET_HANDLE_CLASS_DANGER = "handle-bg-danger graph-editor-target-handle";
|
|
23
|
+
export declare const CONTEXT_MENU_CLASS = "graph-editor-context-menu dropdown-menu show position-fixed shadow-sm";
|
|
24
|
+
export declare const REACT_FLOW_PANE_CLASS = "react-flow__pane";
|
|
25
|
+
export declare const BORDER_DANGER_CLASS = "border-danger";
|
|
26
|
+
export declare const BORDER_WARNING_CLASS = "border-warning";
|
|
27
|
+
export declare const END_NODE_WIDTH_CLASS = "w-180";
|
|
28
|
+
export declare const MIN_NODE_WIDTH = 180;
|
|
29
|
+
export declare const MAX_NODE_WIDTH = 560;
|
|
30
|
+
export declare const MIN_NODE_HEIGHT = 80;
|
|
31
|
+
export declare const MAX_NODE_HEIGHT = 480;
|
|
32
|
+
/** Matches React Flow selected edge stroke (`.react-flow__edge.selected .react-flow__edge-path`). */
|
|
33
|
+
export declare const SELECTED_EDGE_STROKE = "#555";
|
|
34
|
+
export declare const EDGE_ARROW_MARKER: {
|
|
35
|
+
type: MarkerType;
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
};
|
|
39
|
+
export declare const TARGET_HANDLE_ID = "in";
|
|
40
|
+
export declare const CHOICE_HANDLE_PREFIX = "choice:";
|
|
41
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/canvas/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,+FAA+F;AAC/F,eAAO,MAAM,0BAA0B,sBAAsB,CAAC;AAE9D,wDAAwD;AACxD,eAAO,MAAM,+BAA+B,iCAAiC,CAAC;AAE9E,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAClD,eAAO,MAAM,iBAAiB,6BAA6B,CAAC;AAC5D,eAAO,MAAM,sBAAsB,kCAAkC,CAAC;AACtE,eAAO,MAAM,4BAA4B,wCAAwC,CAAC;AAElF,eAAO,MAAM,gBAAgB,4BAA4B,CAAC;AAC1D,eAAO,MAAM,mBAAmB,6BAAyB,CAAC;AAC1D,eAAO,MAAM,wBAAwB,oCAAoC,CAAC;AAC1E,eAAO,MAAM,2BAA2B,qCAAiC,CAAC;AAC1E,eAAO,MAAM,wBAAwB,oCAAoC,CAAC;AAC1E,eAAO,MAAM,wBAAwB,oCAAoC,CAAC;AAC1E,eAAO,MAAM,uBAAuB,mCAAmC,CAAC;AACxE,eAAO,MAAM,2BAA2B,uCAAuC,CAAC;AAChF,eAAO,MAAM,mBAAmB,mDAAmD,CAAC;AAEpF,eAAO,MAAM,sBAAsB,6BAA6B,CAAC;AACjE,eAAO,MAAM,yBAAyB,8BAA+B,CAAC;AAEtE,eAAO,MAAM,2BAA2B,iDAAiD,CAAC;AAC1F,eAAO,MAAM,0BAA0B,gDAAgD,CAAC;AAExF,eAAO,MAAM,kBAAkB,0EAA0E,CAAC;AAE1G,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AAExD,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AACnD,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AAErD,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C,eAAO,MAAM,cAAc,MAAM,CAAC;AAClC,eAAO,MAAM,cAAc,MAAM,CAAC;AAClC,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC,qGAAqG;AACrG,eAAO,MAAM,oBAAoB,SAAS,CAAC;AAE3C,eAAO,MAAM,iBAAiB;;;;CAI7B,CAAC;AAEF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AACrC,eAAO,MAAM,oBAAoB,YAAY,CAAC"}
|