@squeed/flow-sdk 2.0.1 → 2.0.3
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/README.md +701 -57
- package/THIRD_PARTY_NOTICES.txt +3 -2
- package/dist/index-CPKLNAh1.js +461 -0
- package/dist/index-CPKLNAh1.js.map +1 -0
- package/dist/index-sHQXTNv9.cjs +3 -0
- package/dist/index-sHQXTNv9.cjs.map +1 -0
- package/dist/index.cjs +185 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +739 -17
- package/dist/index.d.ts +739 -17
- package/dist/index.js +14297 -7010
- package/dist/index.js.map +1 -1
- package/package.json +18 -5
package/README.md
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# @squeed/flow-sdk
|
|
2
2
|
|
|
3
|
-
Interactive React flow diagrams from JSON, using DOM nodes, SVG edges, and Dagre layout.
|
|
3
|
+
Interactive React flow diagrams from JSON, using DOM nodes, SVG edges, and Dagre layout.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @squeed/flow-sdk@^2 react@^18 react-dom@^18
|
|
8
|
+
npm install @squeed/flow-sdk@^2 react@^18 react-dom@^18 markdown-to-jsx@^9.10.2
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Requires React 18 and Chakra UI 3.36 or later. Peer dependencies: `react`, `react-dom`, `@chakra-ui/react`, `@emotion/react`, `@emotion/styled`,
|
|
11
|
+
Requires React 18 and Chakra UI 3.36 or later. Peer dependencies: `react`, `react-dom`, `@chakra-ui/react`, `@emotion/react`, `@emotion/styled`, `framer-motion` (11 or 12), and `markdown-to-jsx` (^9.10.2). npm installs the remaining peers automatically. React 19 is not currently supported.
|
|
12
|
+
|
|
13
|
+
The Markdown renderer is a required peer supplied by the consuming application, not embedded in the SDK bundle. It remains part of the application's dependency footprint.
|
|
12
14
|
|
|
13
15
|
## Quick Start
|
|
14
16
|
|
|
@@ -87,56 +89,561 @@ You can provide either `json` (auto-generates nodes/edges) or `nodes` + `edges`
|
|
|
87
89
|
|
|
88
90
|
## Config
|
|
89
91
|
|
|
92
|
+
Clicking the empty graph background opens the **Graph theme** swatches after the
|
|
93
|
+
set/array control. They change the default node theme without replacing explicit
|
|
94
|
+
node colors or modifying graph JSON, and hide while a node or edge is selected
|
|
95
|
+
or the diagram is read-only. Without `config.themeColor`, the selection is stored
|
|
96
|
+
locally. For a controlled or persisted theme, pass `config.themeColor` and update
|
|
97
|
+
it from `callbacks.onThemeColorChange(color)`.
|
|
98
|
+
|
|
90
99
|
### `FlowDiagramConfig`
|
|
91
100
|
|
|
92
|
-
| Option | Type | Default | Description
|
|
93
|
-
| ----------------------------------------------------------------------- | ------------------------------------------------ | ------------ |
|
|
94
|
-
| `direction` | `"LR" \| "RL" \| "TB" \| "BT" \| "C"` | `"LR"` | Layout direction; `C` splits the JSON root's branches
|
|
95
|
-
| `nodeAlignment` | `"leading" \| "center" \| "trailing"` | `"center"` | Align unequal node sizes within each layout rank
|
|
96
|
-
| `edgeMode` | `"
|
|
97
|
-
| `layout` | `LayoutAlgorithm` | `"dagre"` | Only Dagre is implemented; other legacy values do not select a different layout
|
|
98
|
-
| `themeColor` | `string` | `"blue.500"` | Accent color (Chakra token)
|
|
99
|
-
| `colorMode` | `"light" \| "dark"` | `"light"` | Color mode
|
|
100
|
-
| `backgroundPattern` | `"dot" \| "grid" \| "steel" \| "none"` | `"dot"` | Background pattern
|
|
101
|
-
| `backgroundColor` | `string` | — | Background color override
|
|
102
|
-
| `edgeColor` | `string` | — | Edge/line color override
|
|
103
|
-
| `minZoom` | `number` | `0.01` | Minimum zoom level
|
|
104
|
-
| `maxZoom` | `number` | `1.5` | Maximum zoom level
|
|
105
|
-
| `initialViewport` | `{ pan: { x, y }, zoom }` | — | Initial viewport; omitted means fit on first layout
|
|
106
|
-
| `edgeHandles` | `boolean` | — | `false` disables edge drawing;
|
|
107
|
-
| `readOnly` | `boolean` | `false` | Disable graph gestures and edits; toolbar navigation remains available
|
|
108
|
-
| `editable` | `boolean` | `false` | Enable inline node label editing
|
|
109
|
-
| `parentNodeStyle` | `ParentNodeStyle` | — | Compound-node fill, border, padding, and label defaults
|
|
110
|
-
| `selectionToolbar` | `SelectionToolbarConfig \| false` | — | Customize palette and hidden controls, or disable selection tools
|
|
111
|
-
| `nodeOverlayActions` | `NodeOverlayAction[]` | — | Actions in node hover overlay
|
|
112
|
-
| `renderNodeOverlay` | `(props: NodeOverlayProps) => ReactNode` | — | Custom node hover overlay
|
|
113
|
-
| `renderSelectedNodeToolbar` | `(props: SelectedNodeToolbarProps) => ReactNode` | — | Custom multi-select toolbar
|
|
114
|
-
| `renderSelectedEdgeToolbar` | `(props: SelectedEdgeToolbarProps) => ReactNode` | — | Custom edge selection toolbar
|
|
115
|
-
| `renderBottomBar` | `(props: BottomBarRenderProps) => ReactNode` | — | Replace the bottom bar
|
|
116
|
-
| `customViews` | `Record<string, ComponentType>` | — | Override node views
|
|
117
|
-
| `disableKeyboardShortcuts` | `boolean` | `false` | Disable built-in keyboard shortcuts
|
|
118
|
-
| `isZoomMode`, `isMultiSelectMode`, `isDragMode`, `isEdgeHandlesEnabled` | `boolean` | — | Externally controlled interaction modes
|
|
101
|
+
| Option | Type | Default | Description |
|
|
102
|
+
| ----------------------------------------------------------------------- | ------------------------------------------------ | ------------ | ------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| `direction` | `"LR" \| "RL" \| "TB" \| "BT" \| "C"` | `"LR"` | Layout direction; `C` splits the JSON root's branches |
|
|
104
|
+
| `nodeAlignment` | `"leading" \| "center" \| "trailing"` | `"center"` | Align unequal node sizes within each layout rank |
|
|
105
|
+
| `edgeMode` | `"nodes" \| "properties" \| "workflow"` | `"nodes"` | Attach edges to nodes or named property rows; `"workflow"` is a compatibility alias for `"nodes"` |
|
|
106
|
+
| `layout` | `LayoutAlgorithm` | `"dagre"` | Only Dagre is implemented; other legacy values do not select a different layout |
|
|
107
|
+
| `themeColor` | `string` | `"blue.500"` | Accent color (Chakra token) |
|
|
108
|
+
| `colorMode` | `"light" \| "dark"` | `"light"` | Color mode |
|
|
109
|
+
| `backgroundPattern` | `"dot" \| "grid" \| "steel" \| "none"` | `"dot"` | Background pattern |
|
|
110
|
+
| `backgroundColor` | `string` | — | Background color override |
|
|
111
|
+
| `edgeColor` | `string` | — | Edge/line color override |
|
|
112
|
+
| `minZoom` | `number` | `0.01` | Minimum zoom level |
|
|
113
|
+
| `maxZoom` | `number` | `1.5` | Maximum zoom level |
|
|
114
|
+
| `initialViewport` | `{ pan: { x, y }, zoom }` | — | Initial viewport; omitted means fit on first layout |
|
|
115
|
+
| `edgeHandles` | `boolean` | — | `false` disables edge drawing; enabling handles does not activate Draw mode |
|
|
116
|
+
| `readOnly` | `boolean` | `false` | Disable graph gestures and edits; toolbar navigation remains available |
|
|
117
|
+
| `editable` | `boolean` | `false` | Enable inline node label editing |
|
|
118
|
+
| `parentNodeStyle` | `ParentNodeStyle` | — | Compound-node fill, border, padding, and label defaults |
|
|
119
|
+
| `selectionToolbar` | `SelectionToolbarConfig \| false` | — | Customize palette and hidden controls, or disable selection tools |
|
|
120
|
+
| `nodeOverlayActions` | `NodeOverlayAction[]` | — | Actions in node hover overlay |
|
|
121
|
+
| `renderNodeOverlay` | `(props: NodeOverlayProps) => ReactNode` | — | Custom node hover overlay |
|
|
122
|
+
| `renderSelectedNodeToolbar` | `(props: SelectedNodeToolbarProps) => ReactNode` | — | Custom multi-select toolbar |
|
|
123
|
+
| `renderSelectedEdgeToolbar` | `(props: SelectedEdgeToolbarProps) => ReactNode` | — | Custom edge selection toolbar |
|
|
124
|
+
| `renderBottomBar` | `(props: BottomBarRenderProps) => ReactNode` | — | Replace the bottom bar |
|
|
125
|
+
| `customViews` | `Record<string, ComponentType>` | — | Override node views |
|
|
126
|
+
| `disableKeyboardShortcuts` | `boolean` | `false` | Disable built-in keyboard shortcuts |
|
|
127
|
+
| `isZoomMode`, `isMultiSelectMode`, `isDragMode`, `isEdgeHandlesEnabled` | `boolean` | — | Externally controlled interaction modes |
|
|
119
128
|
|
|
120
129
|
Use `$collapsed: true` to collapse a JSON branch, and `false` to keep the expansion control. Automatic depth-based collapse is not part of the current API.
|
|
121
130
|
|
|
122
131
|
`nodeAlignment: "leading"` aligns left edges in `LR`/`C`, right edges in `RL`, top edges in `TB`, and bottom edges in `BT`. `"trailing"` aligns the opposite edge. Node dimensions and graph connections are unchanged; containers and edge paths follow the aligned positions.
|
|
123
132
|
|
|
133
|
+
## JSON Streaming And Playback
|
|
134
|
+
|
|
135
|
+
`createJsonStream` accepts incremental `append(chunk)` or cumulative
|
|
136
|
+
`snapshot(text)` input. Supply `parse`, `repair`, `preview`, and `commit`
|
|
137
|
+
callbacks: repaired previews are throttled, while `finish()` strictly parses the
|
|
138
|
+
original JSON before committing. `cancel()` clears pending work. The defaults
|
|
139
|
+
are a 150 ms preview interval and a 1 MiB input limit. The host supplies its
|
|
140
|
+
repair library, such as `jsonrepair`; the SDK has no model or transport dependency.
|
|
141
|
+
|
|
142
|
+
`createJsonPlayback` reuses that buffer for a saved document:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
const player = createJsonPlayback({
|
|
146
|
+
...streamOptions,
|
|
147
|
+
source: JSON.stringify(document),
|
|
148
|
+
onChange: ({ position, total, playing }) =>
|
|
149
|
+
updateProgress(position, total, playing),
|
|
150
|
+
onError: reportError,
|
|
151
|
+
});
|
|
152
|
+
player.play();
|
|
153
|
+
player.pause();
|
|
154
|
+
player.seek(0);
|
|
155
|
+
player.dispose();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Playback defaults to eight seconds, supports resuming and replaying, and reports
|
|
159
|
+
character offsets for a progress slider. Use `projectJsonPlaybackPreview(partial,
|
|
160
|
+
document)` when replaying Squeed JSON to keep structured metadata such as `$state`
|
|
161
|
+
references atomic. Keep previews separate from saved documents. SDK Test includes
|
|
162
|
+
a themed top-right player that reveals connections with their endpoints and
|
|
163
|
+
automatically fits every new layout without modifying the saved document.
|
|
164
|
+
|
|
165
|
+
## Custom Node Editing
|
|
166
|
+
|
|
167
|
+
Custom views replace the node renderer. Wrap custom visuals in the exported
|
|
168
|
+
`NodeView` to reuse SDK selection, multi-selection, overlays, and collapse
|
|
169
|
+
controls. Use `EditableLabel` for the standard inline editor, including
|
|
170
|
+
read-only handling and `onNodeLabelChange` notifications.
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
import { NodeView, EditableLabel } from "@squeed/flow-sdk";
|
|
174
|
+
import type { FlowDiagramNode } from "@squeed/flow-sdk";
|
|
175
|
+
|
|
176
|
+
function CustomNode({ data }: FlowDiagramNode) {
|
|
177
|
+
return (
|
|
178
|
+
<NodeView data={data}>
|
|
179
|
+
{({ isActive, isEditing, onEditStart, onEditEnd }) => (
|
|
180
|
+
<div
|
|
181
|
+
data-flow-node-surface=""
|
|
182
|
+
data-selected={isActive}
|
|
183
|
+
style={{ width: 240, height: 160 }}
|
|
184
|
+
>
|
|
185
|
+
<EditableLabel
|
|
186
|
+
nodeId={data.id}
|
|
187
|
+
label={data.label ?? "Custom"}
|
|
188
|
+
color={data.viewProps?.textColor ?? "gray.900"}
|
|
189
|
+
isEditing={isEditing}
|
|
190
|
+
onEditStart={onEditStart}
|
|
191
|
+
onEditEnd={onEditEnd}
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
195
|
+
</NodeView>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Register the component under its node type in `config.customViews`. Do not stop
|
|
201
|
+
click propagation on the visual surface or maintain a separate selection state.
|
|
202
|
+
The custom renderer must apply the styling fields it supports, such as
|
|
203
|
+
`data.viewProps.bgColor`, `borderColor`, `borderWidth`, and `outline`, to its
|
|
204
|
+
HTML or SVG. Raw-graph callbacks must update node data; JSON mode persists edits
|
|
205
|
+
through `onJsonChange`. The wrapper does not change custom SVG geometry.
|
|
206
|
+
|
|
207
|
+
Mark the visible shape with `data-flow-node-surface`, excluding captions and
|
|
208
|
+
form summaries. For SVG views, put it on the closed geometry element (such as
|
|
209
|
+
`circle`, `polygon`, or `path`), not the surrounding `svg`. Connectors, draw
|
|
210
|
+
handles, and animated borders use that surface; layout still reserves the full
|
|
211
|
+
node bounds. Unmarked custom views retain rectangular host bounds.
|
|
212
|
+
|
|
213
|
+
For content and custom-shape views, use `<FlowPort id="tools" role="source"
|
|
214
|
+
side="bottom" position={0.5} />` inside the node. `side` accepts `top`, `right`,
|
|
215
|
+
`bottom`, or `left`; `position` ranges from 0 to 1 and defaults to the midpoint.
|
|
216
|
+
Top/bottom positions run left to right; left/right positions run top to bottom.
|
|
217
|
+
The handle and connector attach to the marked outline, including SVG slopes.
|
|
218
|
+
These ports work in Auto and Properties modes. Omitting
|
|
219
|
+
`side` retains the existing property-row behavior.
|
|
220
|
+
|
|
221
|
+
Built-in views accept the same declarations in `data.config.ports` or JSON
|
|
222
|
+
`$ports`, for example `[{ "id": "prompt", "role": "target", "side": "top" }]`.
|
|
223
|
+
Connections identify these ports using `sourceProperty` and `targetProperty`.
|
|
224
|
+
Read-only mode and disabled edge handles continue to prevent dragging.
|
|
225
|
+
|
|
226
|
+
`config.edgeMode` controls visibility: `"auto"` (default) shows both normal
|
|
227
|
+
node edges and property/port edges; `"nodes"` shows only edges without property
|
|
228
|
+
endpoints; `"properties"` shows only edges with `sourceProperty` or
|
|
229
|
+
`targetProperty`. `"workflow"` remains an alias for `"nodes"`. Switching modes
|
|
230
|
+
does not remove saved edges or change the graph used for layout by default.
|
|
231
|
+
|
|
232
|
+
Set `config.compactPropertyLayout: true` to re-layout and fit only visible
|
|
233
|
+
nodes and edges in Properties mode and in isolated views (including Auto and
|
|
234
|
+
Nodes modes). Hidden nodes and connections no longer reserve layout space or
|
|
235
|
+
influence edge routes. This flag defaults to `false`; SDK Test enables it.
|
|
236
|
+
The expanded bottom toolbar includes the labelled **Compact layout**
|
|
237
|
+
icon toggle in Auto, Nodes, and Properties modes. In SDK Test it follows
|
|
238
|
+
**Connected nodes** and stays in sync with the Layout-panel pill.
|
|
239
|
+
The SDK manages the toggle locally when `config.compactPropertyLayout` is
|
|
240
|
+
omitted. For controlled usage, handle
|
|
241
|
+
`callbacks.onCompactPropertyLayoutChange(enabled)` and update the config flag.
|
|
242
|
+
Leaving Properties mode or restoring an isolated view restores the applicable
|
|
243
|
+
full layout. Saved graph data is unchanged. Without the flag, isolation keeps
|
|
244
|
+
the existing node positions.
|
|
245
|
+
|
|
246
|
+
## Bind Your Own Data
|
|
247
|
+
|
|
248
|
+
Start with the [minimal properties example](examples/form-connection/README.md):
|
|
249
|
+
two nodes, plain state, one binding, and a three-field interface. Its
|
|
250
|
+
[React component](examples/form-connection/MinimalExample.tsx) shows the complete
|
|
251
|
+
setup, including form edits, display references, appearance, and connection modes.
|
|
252
|
+
No workflow backend is required.
|
|
253
|
+
|
|
254
|
+
Then try **Shared Configuration** and **Reusable Property** in SDK Test's Templates
|
|
255
|
+
menu. The [small-step walkthrough](examples/configuration-basics/README.md) shows
|
|
256
|
+
two consumers sharing values, editing them, and exporting plain data, before
|
|
257
|
+
introducing field-level mapping and returning values inline.
|
|
258
|
+
|
|
259
|
+
## Maintainer Guide
|
|
260
|
+
|
|
261
|
+
The SDK Test **Form Connection** dataset loads
|
|
262
|
+
[examples/form-connection/ui.json](examples/form-connection/ui.json), its
|
|
263
|
+
[bindings](examples/form-connection/bindings.json), and the actual
|
|
264
|
+
[TypeScript interface](examples/form-connection/interface.ts). Double-click
|
|
265
|
+
**Generated Form** to edit the separate state shown by the State JSON node.
|
|
266
|
+
The test page preloads the bundled interface; manual uploads can replace it.
|
|
267
|
+
|
|
268
|
+
**Config Catalog** covers all 25 enabled Backflow step types with independently
|
|
269
|
+
bound, reusable configuration nodes. The full editor interface retains all
|
|
270
|
+
138 declared config fields, including JSON and dictionaries. This is form
|
|
271
|
+
coverage, not production execution validation. See the
|
|
272
|
+
[coverage and readiness checks](examples/workflow-form/README.md).
|
|
273
|
+
|
|
274
|
+
**Workflow State 2** loads the supplied
|
|
275
|
+
[Run Cast Session workflow](examples/workflow-state-2/workflow.json) as a
|
|
276
|
+
separate local dataset, with 16 step forms and 23 reusable tools. Its
|
|
277
|
+
[view generator](examples/workflow-state-2/example.ts) renders all tools in one
|
|
278
|
+
Object View node, with a distinct output port and connection identity per tool.
|
|
279
|
+
Its `portResources` binding maps those ports to individual state entries.
|
|
280
|
+
Forms edit `config`; the original `params`, `input`, and other
|
|
281
|
+
workflow fields are retained independently. Dependency links initialize the
|
|
282
|
+
view from `dependsOn`; diagram edits are not a production deployment adapter.
|
|
283
|
+
|
|
284
|
+
SDK Test's **Forms** popover exposes **Reusable configuration** after an
|
|
285
|
+
interface upload, or when a static mapping is configured. Choose an interface
|
|
286
|
+
and check the inline fields to lift into resource nodes. Object values use
|
|
287
|
+
Object View; compact form-bound nodes expose a floating dot to reveal their
|
|
288
|
+
properties, and the object header dot returns to the icon.
|
|
289
|
+
|
|
290
|
+
SDK Test fits the switched node or parent after layout completes. Its
|
|
291
|
+
`focusOptions` prop controls both click focus and dot-switch focus:
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
<SDKTest focusOptions={{ animate: true, padding: 90 }} />
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
These are the defaults; `animate: false` makes the fit immediate, and `padding`
|
|
298
|
+
is in screen pixels. The SDK itself does not auto-focus on dot switches.
|
|
299
|
+
`callbacks.onNodeViewToggle(nodeId)` notifies a client of a dot action; clients
|
|
300
|
+
can queue `engine.focusNode(nodeId, padding, animate)` for `onLayoutComplete`.
|
|
301
|
+
|
|
302
|
+
Object rows with an empty string value and an attached property connection
|
|
303
|
+
show a full-width label without an empty value cell. Other fields retain both
|
|
304
|
+
columns, and row order, port identities, and saved values are unchanged.
|
|
305
|
+
|
|
306
|
+
The [mapping adapter](examples/workflow-state/resourceMapping.ts) uses the same
|
|
307
|
+
JSON-serializable definition for static setup and popover edits:
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
const resourceMappings = {
|
|
311
|
+
"workflow-state-2": {
|
|
312
|
+
version: 1 as const,
|
|
313
|
+
templates: true,
|
|
314
|
+
fields: [
|
|
315
|
+
{ type: "CastLlmConfig", field: "prompt" },
|
|
316
|
+
{ type: "CastLlmConfig", field: "model", createEmpty: true },
|
|
317
|
+
{ type: "CastDatabaseConfig", field: "data" },
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
<SDKTest resourceMappings={resourceMappings} />;
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
These are also the static defaults for Workflow State 2. An empty Model resource
|
|
326
|
+
does not add a model value to exported configuration until configured. Resource
|
|
327
|
+
forms retain their source interface through `StateFormBinding.schemaType`, so
|
|
328
|
+
reuploading the original interface does not require generated type declarations.
|
|
329
|
+
The popover imports/exports mapping JSON and exports a resolved workflow;
|
|
330
|
+
unmapping restores current resource values inline. Existing annotated resource
|
|
331
|
+
ports stay separate from user-selected inline mappings.
|
|
332
|
+
|
|
333
|
+
`templates` exposes runtime variables as Object View nodes and links step-output
|
|
334
|
+
references to their consuming property rows. Expressions are parsed, never
|
|
335
|
+
evaluated or rewritten; both sides of fallback expressions are represented.
|
|
336
|
+
Collapsing a node retains its links, and deleted template links are not restored
|
|
337
|
+
merely by rerendering. Runtime variable values are not fabricated.
|
|
338
|
+
|
|
339
|
+
[examples/squeed.json](examples/squeed.json) is an expandable diagram for new
|
|
340
|
+
maintainers: setup, architecture, data ownership, UI code, change recipes,
|
|
341
|
+
testing, workflow contracts, and SDK Test integration. Load it through the
|
|
342
|
+
`json` prop; the local Backflow admin SDK Test page includes it as **squeed**
|
|
343
|
+
alongside **advanced**. Its **File Map** chapter links concrete source files
|
|
344
|
+
with labeled runtime imports, type-only dependencies, re-exports, and tooling
|
|
345
|
+
relationships. **Trace a Change Across Files** explains callback/data flow and
|
|
346
|
+
ownership separately. The map covers important internal relationships, not every
|
|
347
|
+
external dependency. Tests verify mapped paths and import/export arrows against
|
|
348
|
+
the source tree. Edit this single JSON source to keep the guide current.
|
|
349
|
+
|
|
124
350
|
## Callbacks
|
|
125
351
|
|
|
352
|
+
### All Icon-Label Views
|
|
353
|
+
|
|
354
|
+
Inside a diagram, `useNodeViewActions()` (or `useFlowDiagramContext()`) exposes
|
|
355
|
+
`allNodesIconLabel` and `setAllNodesIconLabel(boolean)`. The bulk action reuses
|
|
356
|
+
`setNodeViewMode(id, "icon-label")` for objects/arrays and `onNodeCollapse` for
|
|
357
|
+
parents. Disable it to restore default object views and reopen parents. Dots use
|
|
358
|
+
the same per-node actions and remain interactive; there is no global view lock
|
|
359
|
+
or separate override state. `allNodesIconLabel` reflects the current node modes.
|
|
360
|
+
|
|
361
|
+
`NodeViewMode` supports `"default"`, `"properties"` and `"icon-label"`; controlled
|
|
362
|
+
consumers continue using `config.nodeViewModes` and `onNodeViewModeChange`.
|
|
363
|
+
Parent changes follow the host's existing collapse persistence behavior. The
|
|
364
|
+
name `collapsed` remains reserved for child-collapse behavior, not object view
|
|
365
|
+
modes. `onAllNodesIconLabelChange` reports bulk actions. SDK Test exposes the
|
|
366
|
+
action under Visibility as **All icon labels** and fits the resulting layout.
|
|
367
|
+
|
|
368
|
+
### Reusable Nodes From Property Draws
|
|
369
|
+
|
|
370
|
+
SDK Test's **Blank Workflow** starts with zero nodes, steps and resources. The
|
|
371
|
+
canvas **Create node** button opens the existing reusable-node picker without a
|
|
372
|
+
source port. It creates one standalone step with an empty configuration and no
|
|
373
|
+
connection. Subsequent nodes can be created by dragging ports into empty canvas;
|
|
374
|
+
these use the same picker and the existing typed-port and dependency mapping.
|
|
375
|
+
Canceling the picker does not change the graph. No example workflow is imported.
|
|
376
|
+
|
|
377
|
+
SDK Test opens a reusable-node picker when a Properties-mode draw ends on empty
|
|
378
|
+
canvas. It reuses `NodeFormPopover` with a top filter, a fixed-height scrolling
|
|
379
|
+
list and the form vignette. Items use the current JSON bindings, interfaces and
|
|
380
|
+
icons; incompatible ports are disabled and multiple compatible ports can be
|
|
381
|
+
selected explicitly. Closing the modal makes no changes.
|
|
382
|
+
|
|
383
|
+
`callbacks.onNodeCreateRequest(request)` lets a host defer creation by returning
|
|
384
|
+
`true`. The request contains `sourceNodeId`, `sourceProperty`, `reverse` and the
|
|
385
|
+
model-space `position`. A workflow template creates a new step through the
|
|
386
|
+
configured collection/identity/dependency fields; an existing reusable retains
|
|
387
|
+
its shared state and connection. Node-mode drawing is unchanged.
|
|
388
|
+
|
|
389
|
+
After layout, `engine.setNodePosition(id, position)` places a measured node at
|
|
390
|
+
the draw endpoint without changing viewport zoom or pan. This renderer-local
|
|
391
|
+
placement is retained across relayouts while the node remains rendered; pass
|
|
392
|
+
`undefined` to release it back to automatic layout. It is not serialized into
|
|
393
|
+
the workflow document.
|
|
394
|
+
|
|
395
|
+
Use `engine.setNodePosition(id, position, { persist: false })` for an initial
|
|
396
|
+
drop placement that settles into the size-aware automatic layout. The example
|
|
397
|
+
creation picker uses this mode so object expansion, property edits, and the
|
|
398
|
+
spacing slider can reposition new nodes without retaining their drop positions.
|
|
399
|
+
|
|
400
|
+
### Animated Viewport Actions
|
|
401
|
+
|
|
402
|
+
Capture `FlowEngine` through `callbacks.onInit`. Set `config.animateViewport`
|
|
403
|
+
to animate viewport actions by default, or pass a boolean per call:
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
engine.fit(60, true);
|
|
407
|
+
engine.centerAndFit(60, true);
|
|
408
|
+
engine.center(true);
|
|
409
|
+
engine.focusNode(nodeId, 60, true);
|
|
410
|
+
engine.focusBranch(nodeId, 60, true);
|
|
411
|
+
engine.isolateBranch(nodeId, 60, true);
|
|
412
|
+
engine.restoreView(true);
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Animation defaults to false. Explicit false overrides the config default.
|
|
416
|
+
Transitions interpolate pan and zoom over 300ms; center preserves zoom.
|
|
417
|
+
Reduced-motion preferences disable animation. New viewport commands cancel
|
|
418
|
+
previous transitions, and unmount cancels pending frames. Methods return when
|
|
419
|
+
dispatched, not when animation finishes; `onViewportChange` reports intermediate
|
|
420
|
+
and final positions. Use `onLayoutComplete(engine)` to fit newly laid-out data:
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
<FlowDiagram
|
|
424
|
+
config={{ animateViewport: true }}
|
|
425
|
+
callbacks={{ onLayoutComplete: (engine) => engine.centerAndFit(60) }}
|
|
426
|
+
/>
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
This callback runs after each settled layout, not viewport-only changes. Hosts
|
|
430
|
+
that only want to fit after dataset selection should gate it with a pending flag.
|
|
431
|
+
|
|
432
|
+
### Structured Form Bindings
|
|
433
|
+
|
|
434
|
+
Set `forms.structured: true` with `forms.state`, `forms.bindings` and
|
|
435
|
+
`config.formSchema` to author nested objects and arrays through flow nodes.
|
|
436
|
+
Use `config.edgeMode: "properties"` for their input and output handles.
|
|
437
|
+
This is opt-in; existing form consumers keep their current controls.
|
|
438
|
+
|
|
439
|
+
Objects expose typed scalar fields and nested configuration ports. Arrays have
|
|
440
|
+
ordered item nodes with add, remove and move controls. Open dictionaries expose
|
|
441
|
+
property-name and value-type controls. JSON and mixed-type values have typed
|
|
442
|
+
value nodes instead of JSON textareas. Existing resource option selectors remain
|
|
443
|
+
available. Missing optional values are not created until the user adds them.
|
|
444
|
+
|
|
445
|
+
Dragging an empty configuration port into space creates its value node.
|
|
446
|
+
Connecting a compatible value node to another field copies the value into that
|
|
447
|
+
field; it does not create a shared reference. Subsequent edits are independent.
|
|
448
|
+
Deleting an ownership edge or generated node removes the value; array deletion
|
|
449
|
+
compacts the remaining indices. Reordering changes index-based item identities.
|
|
450
|
+
All edits use `forms.onStateChange` (or `onChange`) and preserve the original
|
|
451
|
+
inline state/export shape. Read-only diagrams disable these mutations.
|
|
452
|
+
|
|
453
|
+
Generated nodes, ports and schemas are derived, not written into your view JSON;
|
|
454
|
+
their appearance is inherited rather than saved through document style callbacks.
|
|
455
|
+
Existing explicit bindings at the same state address are reused. Projection is
|
|
456
|
+
limited to 24 levels and 512 form nodes, using the SDK's existing safe dot-path
|
|
457
|
+
property addressing. Custom `renderForm` implementations remain host-owned.
|
|
458
|
+
|
|
459
|
+
### Compose Your Own Controls
|
|
460
|
+
|
|
461
|
+
All reusable control components are exported from `@squeed/flow-sdk` through
|
|
462
|
+
[src/index.ts](src/index.ts). Hide the default toolbar with `showBottomBar={false}`
|
|
463
|
+
and mount your controls as children to share the diagram and form providers:
|
|
464
|
+
|
|
465
|
+
```tsx
|
|
466
|
+
import {
|
|
467
|
+
FlowDiagram,
|
|
468
|
+
WorkflowControls,
|
|
469
|
+
DiagramAppearanceControls,
|
|
470
|
+
SvgExportButton,
|
|
471
|
+
ToolbarDivider,
|
|
472
|
+
} from "@squeed/flow-sdk";
|
|
473
|
+
|
|
474
|
+
<FlowDiagram
|
|
475
|
+
json={view}
|
|
476
|
+
forms={forms}
|
|
477
|
+
callbacks={callbacks}
|
|
478
|
+
showBottomBar={false}
|
|
479
|
+
>
|
|
480
|
+
<div
|
|
481
|
+
style={{
|
|
482
|
+
position: "absolute",
|
|
483
|
+
bottom: 20,
|
|
484
|
+
left: 20,
|
|
485
|
+
right: 20,
|
|
486
|
+
display: "flex",
|
|
487
|
+
alignItems: "center",
|
|
488
|
+
overflowX: "auto",
|
|
489
|
+
zIndex: 100,
|
|
490
|
+
}}
|
|
491
|
+
>
|
|
492
|
+
<WorkflowControls />
|
|
493
|
+
<ToolbarDivider />
|
|
494
|
+
<DiagramAppearanceControls />
|
|
495
|
+
<SvgExportButton />
|
|
496
|
+
</div>
|
|
497
|
+
</FlowDiagram>;
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
| Category | Public exports |
|
|
501
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
502
|
+
| Toolbar primitives | `ToolbarIconButton`, `ToolbarTooltip`, `ToolbarDivider`, `ToolbarSelect`, `ToolbarScrollContainer`, `TooltipIconButton`, `ToolbarCommitInput`, `ToolbarCloseButton` |
|
|
503
|
+
| Selection controls | `NodeTools`, `EdgeTools`, `Swatches`, `NodeBorderRadiusControl`, `NodeIconPicker`, `DEFAULT_PALETTE`, `TEXT_PALETTE` |
|
|
504
|
+
| Control groups | `BottomBar`, `WorkflowControls`, `DiagramAppearanceControls`, `SvgExportButton` |
|
|
505
|
+
| Forms | `NodeForm`, `NodeFormPopover`, `NodeConfigSummary`, `FormDocumentContext` |
|
|
506
|
+
| Composition | `FlowDiagramProvider`, `FlowView`, `useFlowDiagramContext`, `useFlowData`, `FlowDiagramContextValue` |
|
|
507
|
+
| Feedback | `ErrorToast` |
|
|
508
|
+
|
|
509
|
+
Use `useFlowDiagramContext()` inside a child component for mode setters,
|
|
510
|
+
selection, bound edit callbacks and `engineRef`; `useFlowData()` supplies node
|
|
511
|
+
and edge lookups. Its maps are read-only by convention: do not mutate them.
|
|
512
|
+
Standalone controls outside these providers cannot access a diagram. For an
|
|
513
|
+
external DOM toolbar, render a React portal from a child component so context
|
|
514
|
+
is preserved. `SvgExportButton` needs a DOM ancestor with `data-flow-diagram`,
|
|
515
|
+
so use the public `exportSvg` helper when your export button is portaled outside it.
|
|
516
|
+
|
|
517
|
+
`FlowDiagramProvider` plus `FlowView` supports advanced raw-graph composition;
|
|
518
|
+
it does not compile JSON or create form state. Prefer `FlowDiagram` children
|
|
519
|
+
for document mode. Context mode setters do not override explicitly controlled
|
|
520
|
+
config props; the host must update those props. Selection setters change state
|
|
521
|
+
only; invoke selection callbacks explicitly if your custom UI requires them.
|
|
522
|
+
`NodeTools`/`EdgeTools` receive selected IDs and an `onClose` handler. Their
|
|
523
|
+
edit actions use bound callbacks, preserving document-mode updates.
|
|
524
|
+
|
|
525
|
+
The main toolbar's individual mode buttons remain part of `BottomBar`; compose
|
|
526
|
+
equivalent buttons with `ToolbarIconButton` and the context setters. Appearance
|
|
527
|
+
sliders are exposed together through `DiagramAppearanceControls`.
|
|
528
|
+
|
|
529
|
+
### Named Ports In Custom Views
|
|
530
|
+
|
|
531
|
+
Use `FlowPort` inside a custom node component with `config.edgeMode: "auto"` or `"properties"`
|
|
532
|
+
and `config.edgeHandles: true`. Port IDs must be unique within a node.
|
|
533
|
+
|
|
534
|
+
```tsx
|
|
535
|
+
<FlowPort id="true" role="source">True</FlowPort>
|
|
536
|
+
<FlowPort id="false" role="source">False</FlowPort>
|
|
537
|
+
<FlowPort id="input" role="target">Input</FlowPort>
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
The engine measures each port row and supplies connection handles at the node
|
|
541
|
+
boundary. Outputs are on the right and inputs on the left, reversed in RL mode.
|
|
542
|
+
Top/bottom anchors are not supported yet. These ports do not require object
|
|
543
|
+
property rows or form state fields. Existing `sourceProperty`/`targetProperty`
|
|
544
|
+
edge fields carry the port IDs, including in `onEdgeCreate`. The host owns edge
|
|
545
|
+
persistence and any resulting workflow-state changes. Enable Draw mode to connect.
|
|
546
|
+
|
|
547
|
+
### Schema-Bound Node Creation
|
|
548
|
+
|
|
549
|
+
For independent workflow state, use the `forms` prop and external node bindings;
|
|
550
|
+
see [Independent Workflow State](examples/workflow-state/README.md). This keeps
|
|
551
|
+
form values outside the Squeed document and supports state-backed `$content`.
|
|
552
|
+
The `$form` document binding described here remains a legacy-compatible mode.
|
|
553
|
+
|
|
554
|
+
In legacy JSON mode, a selected form-bound node has a gear that opens its
|
|
555
|
+
configuration in a screen-sized dialog. The form resolves `$form.$type` using
|
|
556
|
+
`config.formSchema` or the **Upload workflow interface** control, which also
|
|
557
|
+
supplies connection validation. Nested objects and
|
|
558
|
+
non-enum arrays are edited as validated JSON; simple fields use inferred controls.
|
|
559
|
+
|
|
560
|
+
To create a typed item when drawing from a node into empty space, declare:
|
|
561
|
+
|
|
562
|
+
```json
|
|
563
|
+
{
|
|
564
|
+
"workflow": {
|
|
565
|
+
"$label": "Trigger",
|
|
566
|
+
"$form": {
|
|
567
|
+
"$type": "Workflow",
|
|
568
|
+
"$create": {
|
|
569
|
+
"$address": "root.workflow.steps",
|
|
570
|
+
"$type": "Step",
|
|
571
|
+
"$label": "Step",
|
|
572
|
+
"$edgeLabel": "Next",
|
|
573
|
+
"$defaults": { "name": "New step", "type": "conditional" }
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
"name": "Orders",
|
|
577
|
+
"trigger": "manual",
|
|
578
|
+
"steps": []
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
The schema must define `Workflow.steps` as an object array and expose `Step`.
|
|
584
|
+
Upload discovers both binding types. Creation appends to that array, gives a
|
|
585
|
+
string `id` field a UUID, copies the form/creation binding, and adds a labeled
|
|
586
|
+
`$connections` link from the source. One `onJsonChange` reports the new node.
|
|
587
|
+
Required fields receive initial values; strings may remain empty and require
|
|
588
|
+
configuration. `$defaults` supplies explicit initial values. Further draws from
|
|
589
|
+
the new step append siblings to the same destination. Untyped drawing is unchanged.
|
|
590
|
+
The link label does not execute sequence or if/else logic; those semantics remain
|
|
591
|
+
the host application's responsibility.
|
|
592
|
+
|
|
593
|
+
### SVG Download
|
|
594
|
+
|
|
595
|
+
The expanded toolbar's **Download SVG** icon exports the current graph view,
|
|
596
|
+
including HTML nodes, SVG edges and the background, without toolbar buttons or
|
|
597
|
+
drawing handles. Pan and zoom are preserved; off-screen content is not fitted
|
|
598
|
+
automatically. The exporter is loaded on demand.
|
|
599
|
+
|
|
600
|
+
For custom UI, call `downloadSvg(element, "diagram.svg")`, or use
|
|
601
|
+
`await exportSvg(element)` to obtain an SVG data URL. Pass the diagram's
|
|
602
|
+
`[data-flow-background]` element. The SVG embeds styled HTML via `foreignObject`:
|
|
603
|
+
it works in browsers but is not a pure-path SVG for every vector editor.
|
|
604
|
+
Remote images/fonts need accessible URLs and may be limited by CORS.
|
|
605
|
+
|
|
606
|
+
### Edge Shapes
|
|
607
|
+
|
|
608
|
+
Set `config.edgeType` to `straight`, `bezier`, `orthogonal`, or
|
|
609
|
+
`rounded-orthogonal` (default). `edge.data.type` overrides it for one raw edge.
|
|
610
|
+
Register custom SVG geometry factories in `config.customEdgeTypes`; their keys
|
|
611
|
+
also appear in the selected-edge toolbar's **Edge shape** menu.
|
|
612
|
+
|
|
613
|
+
Selecting a shape calls `callbacks.onEdgeTypeChange(edgeId, type)`.
|
|
614
|
+
Raw-graph consumers must update that edge's `data.type`; choosing **Default**
|
|
615
|
+
passes `undefined` to remove the override. JSON mode persists it automatically
|
|
616
|
+
through `onJsonChange`: `$edgeType` for hierarchy edges, `$targetEdgeType` for
|
|
617
|
+
`$target` links, and `type` on the selected `$connections` entry.
|
|
618
|
+
The selector follows `selectionToolbar.hide: ["style"]` and read-only mode.
|
|
619
|
+
|
|
620
|
+
Custom factories receive `edge`, node bounds (`source`, `target`),
|
|
621
|
+
`sourcePoint`, `targetPoint`, `direction`, property row positions, `inverted`,
|
|
622
|
+
and `defaultGeometry`. Return `{ d, label: { x, y, angle }, arrow? }` in model
|
|
623
|
+
coordinates; `arrow` is an optional filled SVG path. Return `null` to use the
|
|
624
|
+
default route. The SDK retains selection, colors, dash animation, and labels.
|
|
625
|
+
|
|
126
626
|
### Node Events
|
|
127
627
|
|
|
128
|
-
| Callback
|
|
129
|
-
|
|
|
130
|
-
| `onNodeSelect`
|
|
131
|
-
| `onNodeDeselect`
|
|
132
|
-
| `onNodeDoubleClick`
|
|
133
|
-
| `onNodeCreate`
|
|
134
|
-
| `onNodeDelete`
|
|
135
|
-
| `onNodeLabelChange`
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
628
|
+
| Callback | Signature | Description |
|
|
629
|
+
| -------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
630
|
+
| `onNodeSelect` | `(nodeId, nodeData) => void` | Node clicked/selected |
|
|
631
|
+
| `onNodeDeselect` | `() => void` | Selection cleared |
|
|
632
|
+
| `onNodeDoubleClick` | `(nodeId, nodeData) => void` | Node double-clicked |
|
|
633
|
+
| `onNodeCreate` | `(params) => void` | Node created via edge drawing into empty space |
|
|
634
|
+
| `onNodeDelete` | `(nodeId, nodeData) => void` | Node deleted |
|
|
635
|
+
| `onNodeLabelChange` | `(nodeId, newLabel) => void` | Node label edited inline |
|
|
636
|
+
| `onNodeContentChange` | `(nodeId, content: string) => void` | Multiline content saved; raw-graph consumers store `data.config.content` |
|
|
637
|
+
| `onNodeAnimationChange` | `(nodeId, animated: boolean) => void` | Border animation toggled; raw-graph consumers store `data.isNodeAnimated` |
|
|
638
|
+
| `onNodeCollapse` | `(nodeId, collapsed) => void` | Collapse toggle clicked |
|
|
639
|
+
| `onNodeColorChange` | `(nodeId, colorType, color) => void` | Node color changed via toolbar |
|
|
640
|
+
| `onNodeBorderColorChange` | `(nodeId, color: string) => void` | Border color picked; raw consumers update `data.viewProps.borderColor` (empty string resets) |
|
|
641
|
+
| `onNodeBorderStyleChange` | `(nodeId, "solid" \| "dashed") => void` | Border style changed; raw consumers update `data.viewProps.borderStyle` |
|
|
642
|
+
| `onNodeBorderWidthChange` | `(nodeId, width: number \| undefined) => void` | Border thickness changed; raw consumers update `data.viewProps.borderWidth` (`undefined` resets) |
|
|
643
|
+
| `onNodeBorderRadiusChange` | `(nodeId, radius: number \| undefined) => void` | Corner radius changed; raw consumers update `data.viewProps.borderRadius` (`undefined` resets) |
|
|
644
|
+
| `onNodeIconChange` | `(nodeId, iconName) => void` | Node icon changed via toolbar |
|
|
645
|
+
| `onNodeOutlineChange` | `(nodeId, outline: boolean) => void` | Outline toggled; raw-graph consumers store `data.viewProps.outline` |
|
|
646
|
+
| `onNodeHoverAction` | `(actionId, nodeId, nodeData) => void` | Hover overlay action clicked |
|
|
140
647
|
|
|
141
648
|
### Edge Events
|
|
142
649
|
|
|
@@ -165,20 +672,27 @@ Use `$collapsed: true` to collapse a JSON branch, and `false` to keep the expans
|
|
|
165
672
|
|
|
166
673
|
Special `$`-prefixed keys control node rendering:
|
|
167
674
|
|
|
168
|
-
| Key | Description
|
|
169
|
-
| ----------------------------------- |
|
|
170
|
-
| `$label` | Display label for the node
|
|
171
|
-
| `$bgColor` | Background color (Chakra token, e.g. `"blue.500"`)
|
|
172
|
-
| `$textColor` | Text color
|
|
173
|
-
| `$
|
|
174
|
-
| `$
|
|
175
|
-
| `$
|
|
176
|
-
| `$
|
|
177
|
-
| `$
|
|
178
|
-
| `$
|
|
179
|
-
| `$
|
|
180
|
-
| `$
|
|
181
|
-
|
|
|
675
|
+
| Key | Description |
|
|
676
|
+
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
677
|
+
| `$label` | Display label for the node |
|
|
678
|
+
| `$bgColor` | Background color (Chakra token, e.g. `"blue.500"`) |
|
|
679
|
+
| `$textColor` | Text color |
|
|
680
|
+
| `$borderColor` | Border color; accepts Chakra tokens, hex, or `"transparent"` |
|
|
681
|
+
| `$borderStyle` | Border style; the toolbar toggles `"solid"` / `"dashed"` |
|
|
682
|
+
| `$borderWidth` | Nonnegative border thickness in diagram pixels; `0` hides the border, removing the key restores the default |
|
|
683
|
+
| `$borderRadius` | Nonnegative corner radius; `0` makes square corners, removing the key restores the default |
|
|
684
|
+
| `$outline` | `true` for an unfilled node with a border; `false` restores its saved fill |
|
|
685
|
+
| `$content` | Markdown source, including an empty string; editable when `config.editable` is enabled |
|
|
686
|
+
| `$isNodeAnimated` | `true` for a clockwise circulating border stroke; independent of edge animation and off by default |
|
|
687
|
+
| `$icon` | React Icons name, e.g. `PiGear` |
|
|
688
|
+
| `$collapsed` | `true` to collapse children, `false` to force expanded |
|
|
689
|
+
| `$parent` | Parent node ID (creates compound/container nodes) |
|
|
690
|
+
| `$target` | Cross-link to a JSON address, e.g. `root.database`; one per source node |
|
|
691
|
+
| `$connections` | `SqueedConnection[]`; multiple node/property mappings, independent of `$target` |
|
|
692
|
+
| `$sourceLabel` / `$targetLabel` | Hierarchy-edge / cross-link label |
|
|
693
|
+
| `$edgeColor` | Edge color |
|
|
694
|
+
| `$isEdgeDashed` / `$isEdgeAnimated` | Dashed or animated edges |
|
|
695
|
+
| `$` | Array value (creates array child list, e.g. `{ $: ["a", "b"] }`) |
|
|
182
696
|
|
|
183
697
|
```json
|
|
184
698
|
{
|
|
@@ -196,6 +710,61 @@ Special `$`-prefixed keys control node rendering:
|
|
|
196
710
|
}
|
|
197
711
|
```
|
|
198
712
|
|
|
713
|
+
### Content And Node Animation
|
|
714
|
+
|
|
715
|
+
```json
|
|
716
|
+
{
|
|
717
|
+
"runbook": {
|
|
718
|
+
"$label": "Runbook",
|
|
719
|
+
"$content": "## Recovery\n\n1. Check queue depth\n2. Restart workers",
|
|
720
|
+
"$isNodeAnimated": true
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
`$content` renders Markdown headings, lists, emphasis, tables, links, and fenced code. Supply Markdown source text, not a filename or URL to load. Raw HTML is disabled and unsafe link schemes are filtered.
|
|
726
|
+
|
|
727
|
+
With `config.editable: true`, double-click the content body to edit the original Markdown source. Enter inserts a new line; Ctrl/Cmd+Enter or clicking away saves, and Escape cancels. Source whitespace is preserved. The node grows vertically as text is added; label editors grow horizontally within their width limit. Both keep their starting size as a minimum and omit input focus outlines. Clearing the body keeps an empty, editable content view; use `setNodeProp(json, address, "$content", undefined)` to remove the content property. Content nodes use the same minimum width as their object-property rows, and the right-aligned table/article button switches views without changing the document.
|
|
728
|
+
|
|
729
|
+
The node animation toggle supports single and multiple selections. The SVG stroke follows the measured node border without changing layout or intercepting input. Text, array, object/content, and compound nodes support it, including outlined or transparent nodes. It uses the explicit border color or theme accent. Reduced-motion preferences pause the movement; graphs over 200 nodes suppress the trace in performance mode. `readOnly` prevents editing/toggling but still renders configured animation; `selectionToolbar.hide: ["animation"]` hides the toggle.
|
|
730
|
+
|
|
731
|
+
When animation is enabled, an **Animation** selector appears immediately to
|
|
732
|
+
the right of its bottom-toolbar icon:
|
|
733
|
+
|
|
734
|
+
- Nodes: **Trace** (the existing travelling stroke) or **Pulse** (the full border fades in and out).
|
|
735
|
+
- Edges: **Dash** (the existing moving dashes) or **Trace** (a travelling highlight along the routed edge).
|
|
736
|
+
|
|
737
|
+
Stopping animation hides the selector but retains the chosen variant. Node
|
|
738
|
+
Trace and edge Dash remain the defaults. Both respect reduced-motion preferences.
|
|
739
|
+
|
|
740
|
+
Node JSON stores `$nodeAnimationVariant: "traveling" | "pulse"` independently
|
|
741
|
+
of `$isNodeAnimated`. Hierarchy edges use `$edgeAnimationVariant`, target
|
|
742
|
+
links use `$targetAnimationVariant`, and `$connections` entries use
|
|
743
|
+
`animationVariant: "dashed" | "traveling"` independently of `style`.
|
|
744
|
+
Raw graphs use `data.nodeAnimationVariant` and `data.edgeAnimationVariant`.
|
|
745
|
+
Handle `onNodeAnimationVariantChange(nodeId, variant)` and
|
|
746
|
+
`onEdgeAnimationVariantChange(edgeId, variant)` for controlled raw-graph edits;
|
|
747
|
+
JSON mode persists them through `onJsonChange`.
|
|
748
|
+
|
|
749
|
+
In JSON mode content and animation edits emit `onJsonChange` with types `"nodeContent"` and `"nodeAnimation"`. In raw `nodes`/`edges` mode, handle the callbacks listed above and return updated nodes. Existing edges, colors, outlines, and mappings are unchanged.
|
|
750
|
+
|
|
751
|
+
### Transparent Colors And Outlines
|
|
752
|
+
|
|
753
|
+
Every built-in color palette includes a checkerboard `transparent` swatch, including custom palettes. This is distinct from reset, which removes the override. Node fill, text/icon, border, edge, container, and canvas colors accept `"transparent"`. Transparent fills keep default text readable in light and dark modes; explicit transparent text also hides its icon.
|
|
754
|
+
|
|
755
|
+
The outline toggle applies to one or multiple selected nodes. It clears all node fills (including object headers/rows) without overwriting stored colors. Text, array, object, and compound nodes support it. Selection highlighting remains visible; an explicit transparent border is still respected.
|
|
756
|
+
|
|
757
|
+
The separate **Edit node border** toggle is a toolbar-only mode: it does not remove the fill or change `$outline`. It reuses the node-color swatches for border color, hides only the text-color controls, and reveals a dashed toggle and thickness input. Switching it off restores background/text-color editing. Color, style, and thickness persist independently; explicit border colors remain visible during selection and hover.
|
|
758
|
+
|
|
759
|
+
Border colors emit `nodeColor` changes, while style and thickness emit `nodeStyle` with `$borderStyle` / `$borderWidth`. Raw consumers handle the border callbacks above. Thickness accepts nonnegative numbers; clearing the input resets it, and zero hides the border and its animation. `selectionToolbar.hide: ["color"]` hides the shared palette and border mode; `"borderColor"` hides the mode toggle, and `"style"` hides dashed/thickness controls. Hiding `"outline"` affects only the separate fill-removing toggle.
|
|
760
|
+
|
|
761
|
+
```tsx
|
|
762
|
+
setDoc((doc) => setNodeProp(doc, "root.service", "$outline", true));
|
|
763
|
+
setDoc((doc) => setNodeProp(doc, "root.service", "$bgColor", "transparent"));
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
For raw nodes use `data.viewProps.outline` and handle `onNodeOutlineChange(nodeId, outline)`; in JSON mode the SDK emits `onJsonChange` with `type: "nodeOutline"`. Container defaults can use `config.parentNodeStyle.outline`, with per-node values taking precedence. `selectionToolbar.hide: ["outline"]` hides the toggle. `readOnly` disables edits.
|
|
767
|
+
|
|
199
768
|
### Property Connections
|
|
200
769
|
|
|
201
770
|
Store mappings on the source node. Property references are exact row keys, while `target` is the destination node's JSON address:
|
|
@@ -230,7 +799,56 @@ Store mappings on the source node. Property references are exact row keys, while
|
|
|
230
799
|
/>
|
|
231
800
|
```
|
|
232
801
|
|
|
233
|
-
`"
|
|
802
|
+
Use **Nodes / Properties** for edge-mode controls: `"nodes"` (the default) attaches edges to node boxes; `"properties"` attaches to rows. The previous `"workflow"` value remains accepted as an alias for `"nodes"`. Switching modes does not change the document or graph connections. Edges without property references keep their ordinary node attachment. Row ports stay left/right, reversed in `RL`, even for vertical layouts.
|
|
803
|
+
|
|
804
|
+
### Workflow Interfaces
|
|
805
|
+
|
|
806
|
+
Selected edges expose **Choose edge icon** and an **Edge icon** text field. The picker supports named React icons, SVG uploads (stored as base64), and HTTP(S) image URLs, with **Remove edge icon** to clear the choice. Icons render beside the label or by themselves for unlabeled edges; arrowheads and validation markers remain independent. Named icons follow the edge color; SVG images retain their own artwork colors. `selectionToolbar.hide: ["icon"]` hides both controls.
|
|
807
|
+
|
|
808
|
+
Raw graph hosts store `data.icon` through `callbacks.onEdgeIconChange(edgeId, icon)`; an empty string removes the icon. JSON mode persists automatically through `onJsonChange`: `$edgeIcon` on the child for hierarchy edges, `$targetIcon` on the source for `$target` cross-links, and `icon` on the selected `$connections` entry. Editing an icon never changes sibling connections.
|
|
809
|
+
|
|
810
|
+
`DynamicIcon` works in HTML and SVG contexts. Inside an SVG, use `<DynamicIcon name={icon} svg size={48} x={-24} y={-24} color="#dc2626" />`. Named React icons render as SVG; uploaded base64 SVGs and HTTP(S) image URLs render as SVG `<image>` elements with preserved aspect ratio. Without `svg`, image sources keep using HTML `<img>`. Uploaded markup is never inserted into the DOM. Edge validation markers reuse this renderer with `PiWarningCircleFill`.
|
|
811
|
+
|
|
812
|
+
The bottom toolbar includes the Nodes/Properties dropdown after Graph theme, with a divider before the theme swatches. Properties mode exposes an upload icon for `.ts` interfaces or `.json` JSON Schema. Uploads stay local and separate from graph JSON. Incompatible connections remain editable and turn red; missing declarations are amber/unverified. Hover the `!` marker for the reason. Validation compares the declared source property type to the target type, not the current property values. Node-only edges are not validated.
|
|
813
|
+
|
|
814
|
+
TypeScript contracts use a `Graph` interface or type alias keyed by node ID. A single declaration may use another name. Exact IDs are checked first; `root.alpha` also matches `alpha`. Labels are not used for matching.
|
|
815
|
+
|
|
816
|
+
```ts
|
|
817
|
+
interface Graph {
|
|
818
|
+
alpha: { output: string; optional?: number };
|
|
819
|
+
beta: { input: string; count: number };
|
|
820
|
+
}
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
Equivalent JSON Schema:
|
|
824
|
+
|
|
825
|
+
```json
|
|
826
|
+
{
|
|
827
|
+
"type": "object",
|
|
828
|
+
"properties": {
|
|
829
|
+
"alpha": {
|
|
830
|
+
"type": "object",
|
|
831
|
+
"properties": {
|
|
832
|
+
"output": { "type": "string" },
|
|
833
|
+
"optional": { "type": "number" }
|
|
834
|
+
},
|
|
835
|
+
"required": ["output"]
|
|
836
|
+
},
|
|
837
|
+
"beta": {
|
|
838
|
+
"type": "object",
|
|
839
|
+
"properties": {
|
|
840
|
+
"input": { "type": "string" },
|
|
841
|
+
"count": { "type": "number" }
|
|
842
|
+
},
|
|
843
|
+
"required": ["input", "count"]
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
Supported types are string, number, boolean, null, primitive literals/unions, arrays, and nested structural objects. JSON additionally supports integer and nonrecursive local `$ref`. Properties are optional unless included in JSON Schema `required`; optional sources cannot supply required targets. Extra object fields are allowed. This is a bounded structural compatibility checker, not the complete TypeScript type system or a full JSON Schema validator. Unsupported constraints (including closed objects), recursion, inheritance, generics, imports and executable code are rejected. Files are limited to 256 KB and expansion to 512 type nodes. TypeScript is parsed, never executed; its runtime dependency loads lazily on `.ts` uploads. Vite hosts may pre-optimize `typescript` to avoid a development-only reload on its first use.
|
|
850
|
+
|
|
851
|
+
For host-owned state, supply `config.edgeMode` and `config.workflowContract`, and apply `callbacks.onEdgeModeChange(mode)` and `callbacks.onWorkflowContractChange(contract)` to that state. Omit these config fields for local state. An explicitly present `workflowContract: undefined` clears a controlled contract; a failed upload retains the previous one. Read-only mode disables mode changes and uploads. Exported `parseJsonContract(source, name)`, async `parseTypeScriptContract(source, name)`, `readWorkflowContract(file)`, and `connectionIssue(contract, edgeData)` let workflow hosts reuse the same check outside the UI. `connectionIssue` returns a reason or `undefined`; it does not block workflow execution itself.
|
|
234
852
|
|
|
235
853
|
Drag an output port to an input port or destination row to append a mapping. Property drags into empty space cancel. Existing node-level drawing still writes `$target`. The edge toolbar edits/deletes only the selected mapping. In document mode, store the full `onJsonChange` document; in raw `nodes`/`edges` mode, store the optional third `onEdgeCreate` argument in the edge's `data`.
|
|
236
854
|
|
|
@@ -238,6 +856,8 @@ Each entry accepts `target`, optional `sourceProperty`/`targetProperty`, `id`, `
|
|
|
238
856
|
|
|
239
857
|
Missing or unrendered rows fall back to the corresponding node endpoint. Missing/collapsed nodes hide their edges without deleting the saved mappings. Custom views can expose rows with `data-flow-property={key}`; their positions are measured automatically. `readOnly` and `edgeHandles: false` disable port dragging.
|
|
240
858
|
|
|
859
|
+
Draw mode starts off by default, including when `edgeHandles: true`. Enable it with the toolbar, or explicitly control it with `config.isEdgeHandlesEnabled`. Hover handles remain available for individual connections when Draw mode is off.
|
|
860
|
+
|
|
241
861
|
Mappings are metadata. The SDK does not execute workflows or transfer data.
|
|
242
862
|
|
|
243
863
|
## Exported Utilities
|
|
@@ -255,6 +875,25 @@ const visible = applyCollapsed(handBuiltNodes, handBuiltEdges);
|
|
|
255
875
|
|
|
256
876
|
`createFlowDiagram` remains exported as the lower-level compiler. Use `compileSqueed` for stable edge IDs, document styling, and `$connections`. `addConnection(json, sourceAddress, connection)` appends a mapping immutably. Hierarchy edges are removed by removing the child; `$target` links and individual `$connections` can be deleted independently.
|
|
257
877
|
|
|
878
|
+
### Address-Based Edits
|
|
879
|
+
|
|
880
|
+
| Helper | Result |
|
|
881
|
+
| ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
882
|
+
| `getNode(json, address)` | Value at an address, or `undefined` |
|
|
883
|
+
| `updateNode(json, address, updater)` | New document with the updater's replacement at that address |
|
|
884
|
+
| `setNodeProp(json, address, key, value)` | New document with a `$` rendering property; null/undefined resets it, as does empty string except for `$content` |
|
|
885
|
+
| `setCollapsed(json, address, collapsed)` | New document; preserves an explicit `false` |
|
|
886
|
+
| `addChild(json, parentAddress, child, key?)` | `{ json, address }`; appends to plain or `$`-wrapped arrays, otherwise uses a key |
|
|
887
|
+
| `removeNode(json, address)` | New document without that node; root and absent paths are left unchanged |
|
|
888
|
+
| `addTargetEdge(json, sourceAddress, targetAddress, label?)` | New document; replaces the source's single `$target` |
|
|
889
|
+
| `addConnection(json, sourceAddress, connection)` | New document; appends a mapping unless its endpoints already exist |
|
|
890
|
+
| `setEdgeProp(json, edgeRef, key, value)` / `setEdgeStyle(json, edgeRef, style)` | New document updating only the edge's JSON owner |
|
|
891
|
+
| `removeEdge(json, edgeRef)` | `{ json, removed }`; independently removes cross-links or property mappings |
|
|
892
|
+
|
|
893
|
+
Addresses use dot-separated paths such as `root.backend.runtime` or `root.tags.1`; `$` array wrappers are traversed automatically. Keys containing literal dots are not escaped by this address format. Array deletion shifts subsequent indices; these addresses are not permanent IDs across structural edits.
|
|
894
|
+
|
|
895
|
+
Helpers reuse untouched branches and do not fire callbacks themselves. The `updateNode` updater must return a replacement without mutating its argument. It can add a leaf under an existing parent but does not create missing ancestor chains. Pass the returned document back through `json` to refresh the diagram.
|
|
896
|
+
|
|
258
897
|
## Custom Overlays
|
|
259
898
|
|
|
260
899
|
### Node Hover Overlay
|
|
@@ -338,6 +977,11 @@ const hex = getChakraColorHex("blue.500"); // → "#3182ce"
|
|
|
338
977
|
|
|
339
978
|
## Development Checks
|
|
340
979
|
|
|
980
|
+
See [CONTRIBUTING.md](https://github.com/sciphergfx/squeed-flow-sdk/blob/main/CONTRIBUTING.md)
|
|
981
|
+
for local setup and pull-request guidance, and
|
|
982
|
+
[SECURITY.md](https://github.com/sciphergfx/squeed-flow-sdk/blob/main/SECURITY.md)
|
|
983
|
+
for private vulnerability reporting.
|
|
984
|
+
|
|
341
985
|
```bash
|
|
342
986
|
npm ci
|
|
343
987
|
npx playwright install chromium
|