@hlang-tech/editor-ui 0.0.0-stage → 0.1.0
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 +78 -2
- package/lib/canvas/CanvasChrome.css +45 -0
- package/lib/canvas/CanvasDock.css +82 -0
- package/lib/canvas/EmptyState.css +23 -0
- package/lib/command-palette/NodeSearch.css +153 -0
- package/lib/context-menu/NodeContextMenu.css +102 -0
- package/lib/edges/FlowEdge.css +80 -0
- package/lib/index.d.mts +971 -0
- package/lib/index.d.ts +971 -0
- package/lib/index.js +2402 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +2318 -0
- package/lib/index.mjs.map +1 -0
- package/lib/nodes/AssetNode.css +146 -0
- package/lib/nodes/HlangNode.css +192 -0
- package/lib/nodes/PillNode.css +185 -0
- package/lib/overlays/Cheatsheet.css +107 -0
- package/lib/overlays/Toast.css +34 -0
- package/lib/panels/AssetGrid.css +38 -0
- package/lib/panels/InspectorPanel.css +161 -0
- package/lib/panels/OutlineTree.css +208 -0
- package/lib/panels/ParamForm.css +88 -0
- package/lib/panels/PositionPanel.css +87 -0
- package/lib/panels/PreviewPanel.css +51 -0
- package/lib/panels/RailTabs.css +34 -0
- package/lib/shell/EditorShell.css +75 -0
- package/lib/styles.css +33 -0
- package/lib/tokens/tokens.css +100 -0
- package/lib/toolbar/ToolCluster.css +48 -0
- package/lib/toolbar/Toolbar.css +126 -0
- package/package.json +97 -4
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,971 @@
|
|
|
1
|
+
import { NodeKind, EditorNode, PortDescriptor, EditorCore, NodeId, Diagnostic, ParamValue, NodeDescriptor, ParamBag, Endpoint, IConnectionValidator, Point, IExecutionHandle } from '@hlang-tech/editor-core';
|
|
2
|
+
import { CSSProperties, ReactNode, ReactElement, MouseEvent } from 'react';
|
|
3
|
+
import { NodeProps, EdgeProps, NodeTypes, EdgeTypes, OnConnectStart, OnConnectEnd } from '@xyflow/react';
|
|
4
|
+
import { HlangRFNode, HlangRFEdge } from '@hlang-tech/editor-react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Typed accessors for the design tokens. This module only NAMES the CSS custom
|
|
8
|
+
* properties declared in `tokens.css` — it never duplicates their values, so
|
|
9
|
+
* there is a single source of truth. Components reference tokens through here
|
|
10
|
+
* (e.g. `KIND_TOKEN[node.kind]`) instead of hardcoding hex values.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** The full set of token custom-property names (without the leading `--`). */
|
|
14
|
+
declare const TOKEN_NAMES: readonly ["hl-void", "hl-void-2", "hl-surface", "hl-surface-2", "hl-line", "hl-line-strong", "hl-silver", "hl-silver-hi", "hl-silver-lo", "hl-spark", "hl-r", "hl-t", "hl-w", "hl-error", "hl-ink", "hl-ink-2", "hl-ink-3", "hl-font-ui", "hl-font-mono", "hl-r-sm", "hl-r-md", "hl-r-lg", "hl-r-pill", "hl-shadow-node", "hl-shadow-glass", "hl-glow-silver", "hl-ease"];
|
|
15
|
+
/** A token name (without `--`), e.g. `'hl-silver'`. */
|
|
16
|
+
type TokenName = (typeof TOKEN_NAMES)[number];
|
|
17
|
+
/** `var(--<name>)` reference string for a token. */
|
|
18
|
+
declare function tokenVar(name: TokenName): string;
|
|
19
|
+
/** Maps a node kind to its color-temperature token reference. */
|
|
20
|
+
declare const KIND_TOKEN: Record<NodeKind, string>;
|
|
21
|
+
/** Human-readable role label per node kind (used in node headers). */
|
|
22
|
+
declare const KIND_ROLE: Record<NodeKind, string>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Theming: a host overrides any subset of tokens by producing a CSS-variable
|
|
26
|
+
* style object with `createTheme(...)` and applying it (via
|
|
27
|
+
* `EditorUIThemeProvider` or directly on a wrapper element). Zero-runtime and
|
|
28
|
+
* SSR-safe — it is just inline custom properties.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A partial override map from token name to CSS value. Values may be
|
|
33
|
+
* `undefined` (treated as "not overridden") so callers can pass conditional
|
|
34
|
+
* overrides without pre-filtering.
|
|
35
|
+
*/
|
|
36
|
+
type ThemeOverride = Partial<Record<TokenName, string | undefined>>;
|
|
37
|
+
/**
|
|
38
|
+
* Build a style object that overrides the given tokens as inline custom
|
|
39
|
+
* properties. Apply it to a wrapping element to re-theme everything beneath.
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* <div style={createTheme({ 'hl-silver': '#d8dce4' })}> … </div>
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
declare function createTheme(overrides: ThemeOverride): CSSProperties;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `EditorUIThemeProvider` — applies a token override to its subtree by wrapping
|
|
49
|
+
* children in an element carrying the inline custom properties from
|
|
50
|
+
* `createTheme`. Import `@hlang-tech/editor-ui/tokens.css` once at the app root
|
|
51
|
+
* for the base tokens; use this provider only to override.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
interface EditorUIThemeProviderProps {
|
|
55
|
+
readonly theme?: ThemeOverride;
|
|
56
|
+
readonly className?: string;
|
|
57
|
+
readonly style?: CSSProperties;
|
|
58
|
+
readonly children: ReactNode;
|
|
59
|
+
}
|
|
60
|
+
declare function EditorUIThemeProvider({ theme, className, style, children, }: EditorUIThemeProviderProps): ReactElement;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The R/T/W node component. A near-monochrome glass card whose personality
|
|
64
|
+
* comes from a thin kind-tinted signal bar and glowing ports; body content is
|
|
65
|
+
* an extension point (empty for generic nodes). Ports are laid out in two
|
|
66
|
+
* columns — inputs (I) left, outputs (O) right — each Handle anchored to its
|
|
67
|
+
* own row so the `@node/error` port never drifts. W nodes render no error port
|
|
68
|
+
* because their descriptor has none.
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/** Presentational node chrome, decoupled from React Flow for easy testing. */
|
|
72
|
+
interface NodeChromeProps {
|
|
73
|
+
readonly node: EditorNode;
|
|
74
|
+
readonly selected?: boolean;
|
|
75
|
+
readonly body?: ReactNode;
|
|
76
|
+
}
|
|
77
|
+
declare function NodeChrome({ node, selected, body }: NodeChromeProps): ReactElement;
|
|
78
|
+
/**
|
|
79
|
+
* The React Flow node component. Reads everything from `data.editorNode`, then
|
|
80
|
+
* DISPATCHES to the right dressing — all in editor-ui, with no change to the
|
|
81
|
+
* runtime (every case is just a node with ports):
|
|
82
|
+
* - an image asset (params.imageUrl) → {@link AssetNodeChrome} (framed photo)
|
|
83
|
+
* - anything else → {@link PillNodeChrome} (nover capsule)
|
|
84
|
+
* The verbose port-board {@link NodeChrome} stays available as a future
|
|
85
|
+
* "expanded" view.
|
|
86
|
+
*/
|
|
87
|
+
declare function HlangNode({ data, selected }: NodeProps<HlangRFNode>): ReactElement;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* `AssetNode` — a source node dressed as a real image asset.
|
|
91
|
+
*
|
|
92
|
+
* Architecturally this is just an R node: to the runtime it has output ports and
|
|
93
|
+
* emits its image (its `params.imageUrl`). editor-ui is free to DRESS it as a
|
|
94
|
+
* framed photograph — a frosted-glass card whose hero is the image itself — with
|
|
95
|
+
* no change to the input/output contract. This is the layering: the bottom is an
|
|
96
|
+
* input→output DAG; this layer decides what the node looks like.
|
|
97
|
+
*
|
|
98
|
+
* Ports are reused verbatim (PortRow → React Flow Handle), so wiring behaves
|
|
99
|
+
* exactly like any other node; the output handle rides the image's right edge.
|
|
100
|
+
*/
|
|
101
|
+
|
|
102
|
+
interface AssetNodeChromeProps {
|
|
103
|
+
readonly node: EditorNode;
|
|
104
|
+
readonly selected?: boolean;
|
|
105
|
+
}
|
|
106
|
+
declare function AssetNodeChrome({ node, selected }: AssetNodeChromeProps): ReactElement;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* `PillNode` — the default dressing for a non-asset node: a nover-style capsule.
|
|
110
|
+
*
|
|
111
|
+
* To the runtime this is still an ordinary R/T/W node with ports; editor-ui just
|
|
112
|
+
* dresses it as a rounded pill — a kind-tinted domain dot, the name, and a
|
|
113
|
+
* status dot — with the ports HIDDEN at rest and revealed on hover/selection.
|
|
114
|
+
* The picture, not the plumbing. The verbose port-board (`NodeChrome`) remains
|
|
115
|
+
* available as an "expanded" view for later.
|
|
116
|
+
*
|
|
117
|
+
* Ports reuse `PortRow` (so wiring is identical); the pill styles the handles
|
|
118
|
+
* to ride its left/right edges as small silver dots.
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
interface PillNodeChromeProps {
|
|
122
|
+
readonly node: EditorNode;
|
|
123
|
+
readonly selected?: boolean;
|
|
124
|
+
}
|
|
125
|
+
declare function PillNodeChrome({ node, selected }: PillNodeChromeProps): ReactElement;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The thin, kind-tinted bar across the top of a node — the node's kind
|
|
129
|
+
* expressed with restraint (no large color fills). Reads `--hl-kind` set by the
|
|
130
|
+
* node's kind class.
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
declare function SignalBar(): ReactElement;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A single port row: a React Flow `Handle` anchored to this row (so it centers
|
|
137
|
+
* on its own label, never drifting) plus the port's mono label. Renders the
|
|
138
|
+
* live/value distinction (filled vs hollow handle) and the error hue.
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
interface PortRowProps {
|
|
142
|
+
readonly port: PortDescriptor;
|
|
143
|
+
}
|
|
144
|
+
declare function PortRow({ port }: PortRowProps): ReactElement;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The node-body extension point. In M-UI-1 this renders nothing (generic nodes
|
|
148
|
+
* stay clean R/T/W cards). M-UI-5 wires it to the NodeBodyRegistry so a host
|
|
149
|
+
* application can render custom content — image/video previews, inline
|
|
150
|
+
* widgets — per node `code`/`kind` without editor-ui knowing anything about it.
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
interface NodeBodySlotProps {
|
|
154
|
+
readonly node: EditorNode;
|
|
155
|
+
/** Custom body content, resolved by the host/registry. Empty renders nothing. */
|
|
156
|
+
readonly children?: ReactNode;
|
|
157
|
+
}
|
|
158
|
+
declare function NodeBodySlot({ children }: NodeBodySlotProps): ReactElement | null;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The signature edge: a connection rendered as a living stream. A base bezier
|
|
162
|
+
* conduit plus a silver overlay that marches from output (O) to input (I) —
|
|
163
|
+
* exactly what an hlang RxJS port does; a cold spark lights it while flowing.
|
|
164
|
+
*
|
|
165
|
+
* When the SOURCE node carries a semantic `role` param (e.g. Character,
|
|
166
|
+
* Background), a small glass label pill rides the edge midpoint — naming what
|
|
167
|
+
* the connection carries, the way nover labels its wires. The role is read from
|
|
168
|
+
* the source node's params (no change to editor-react's edge data).
|
|
169
|
+
*/
|
|
170
|
+
|
|
171
|
+
declare function FlowEdge(props: EdgeProps<HlangRFEdge>): ReactElement;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* One-line React Flow wiring: `createHlangFlowTypes()` returns the `nodeTypes`
|
|
175
|
+
* and `edgeTypes` maps to spread onto `<ReactFlow>`, binding hlang's R/T/W node
|
|
176
|
+
* kinds to `HlangNode` and the default edge to the flowing `FlowEdge`.
|
|
177
|
+
*
|
|
178
|
+
* ```tsx
|
|
179
|
+
* const { nodeTypes, edgeTypes } = createHlangFlowTypes();
|
|
180
|
+
* <ReactFlow nodeTypes={nodeTypes} edgeTypes={edgeTypes} {...useHlangFlow(core)} />
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
interface HlangFlowTypes {
|
|
185
|
+
readonly nodeTypes: NodeTypes;
|
|
186
|
+
readonly edgeTypes: EdgeTypes;
|
|
187
|
+
}
|
|
188
|
+
/** Build the React Flow node/edge type maps for the hlang editor. */
|
|
189
|
+
declare function createHlangFlowTypes(): HlangFlowTypes;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* `EditorShell` — composes the editor's regions (top toolbar, left panel,
|
|
193
|
+
* center canvas, right inspector) as a CSS grid. It holds NO graph logic; a
|
|
194
|
+
* host slots children into named regions. Left/right panels are collapsible via
|
|
195
|
+
* UI-only props (collapse state is presentation, never part of the graph).
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
interface EditorShellProps {
|
|
199
|
+
/** Top toolbar region. */
|
|
200
|
+
readonly toolbar?: ReactNode;
|
|
201
|
+
/** Left region (node palette / outline). */
|
|
202
|
+
readonly left?: ReactNode;
|
|
203
|
+
/** Right region (inspector). */
|
|
204
|
+
readonly right?: ReactNode;
|
|
205
|
+
/** Center region — required; the host renders <ReactFlow> + <CanvasChrome>. */
|
|
206
|
+
readonly canvas: ReactNode;
|
|
207
|
+
/** Optional footer region (e.g. an output/console strip). */
|
|
208
|
+
readonly footer?: ReactNode;
|
|
209
|
+
readonly leftCollapsed?: boolean;
|
|
210
|
+
readonly rightCollapsed?: boolean;
|
|
211
|
+
}
|
|
212
|
+
declare function EditorShell({ toolbar, left, right, canvas, footer, leftCollapsed, rightCollapsed, }: EditorShellProps): ReactElement;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The top toolbar: a thin glass bar with a brand wordmark on the left and an
|
|
216
|
+
* action cluster on the right. `Toolbar` is the presentational container;
|
|
217
|
+
* `UndoRedo` and `RunButton` are the standard actions wired to editor-core via
|
|
218
|
+
* editor-react hooks. Hosts may pass extra actions through `actions`.
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
interface ToolbarProps {
|
|
222
|
+
/** Brand/title shown on the left. Defaults to a generic wordmark. */
|
|
223
|
+
readonly title?: ReactNode;
|
|
224
|
+
/** Centre slot — a tool cluster (add/frame/view), absolutely centred. */
|
|
225
|
+
readonly center?: ReactNode;
|
|
226
|
+
/** Action nodes rendered on the right (e.g. <UndoRedo/>, <RunButton/>). */
|
|
227
|
+
readonly actions?: ReactNode;
|
|
228
|
+
}
|
|
229
|
+
declare function Toolbar({ title, center, actions }: ToolbarProps): ReactElement;
|
|
230
|
+
interface UndoRedoProps {
|
|
231
|
+
readonly core: EditorCore;
|
|
232
|
+
}
|
|
233
|
+
declare function UndoRedo({ core }: UndoRedoProps): ReactElement;
|
|
234
|
+
interface RunButtonProps {
|
|
235
|
+
/**
|
|
236
|
+
* Invoked when clicked. This is a toggle: when `running` is true the button
|
|
237
|
+
* shows a Stop affordance, and the host is expected to stop the execution.
|
|
238
|
+
*/
|
|
239
|
+
readonly onRun: () => void;
|
|
240
|
+
/** Whether an execution is currently in progress. */
|
|
241
|
+
readonly running?: boolean;
|
|
242
|
+
readonly runLabel?: string;
|
|
243
|
+
readonly stopLabel?: string;
|
|
244
|
+
}
|
|
245
|
+
declare function RunButton({ onRun, running, runLabel, stopLabel, }: RunButtonProps): ReactElement;
|
|
246
|
+
/** A compact read-only stat pill (e.g. a node/vertex count) for the top bar. */
|
|
247
|
+
declare function ToolbarStat({ label, value }: {
|
|
248
|
+
readonly label?: string;
|
|
249
|
+
readonly value: ReactNode;
|
|
250
|
+
}): ReactElement;
|
|
251
|
+
/** A secondary ghost button for the top bar (e.g. Invite). Presentational. */
|
|
252
|
+
declare function ToolbarGhostButton({ children, onClick, title, }: {
|
|
253
|
+
readonly children: ReactNode;
|
|
254
|
+
readonly onClick?: () => void;
|
|
255
|
+
readonly title?: string;
|
|
256
|
+
}): ReactElement;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* `ToolCluster` — the centred group of canvas tools in the top bar (nover-style):
|
|
260
|
+
* a row of icon buttons with optional separators, in the black/silver glass
|
|
261
|
+
* language. Purely presentational: the host supplies the items and their
|
|
262
|
+
* actions. Kept generic (no business/media concepts) so it stays an editor-ui
|
|
263
|
+
* primitive — a "frame", "add", or "view" tool is just an item with an icon.
|
|
264
|
+
*/
|
|
265
|
+
|
|
266
|
+
interface ToolItem {
|
|
267
|
+
readonly id: string;
|
|
268
|
+
readonly icon: ReactNode;
|
|
269
|
+
readonly title: string;
|
|
270
|
+
readonly onClick?: () => void;
|
|
271
|
+
readonly active?: boolean;
|
|
272
|
+
readonly disabled?: boolean;
|
|
273
|
+
/** Draw a thin divider before this item. */
|
|
274
|
+
readonly separatorBefore?: boolean;
|
|
275
|
+
}
|
|
276
|
+
interface ToolClusterProps {
|
|
277
|
+
readonly items: readonly ToolItem[];
|
|
278
|
+
}
|
|
279
|
+
declare function ToolCluster({ items }: ToolClusterProps): ReactElement;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* `CanvasChrome` — themed React Flow canvas furniture. Now just the dotted
|
|
283
|
+
* background: React Flow's default MiniMap and Controls are intentionally NOT
|
|
284
|
+
* rendered — a creation studio replaces them with our own centred `CanvasDock`
|
|
285
|
+
* (pan/zoom/add) so the canvas isn't cluttered with a stock topology-editor
|
|
286
|
+
* toolbar. Rendered as children of <ReactFlow>.
|
|
287
|
+
*/
|
|
288
|
+
|
|
289
|
+
interface CanvasChromeProps {
|
|
290
|
+
readonly background?: boolean;
|
|
291
|
+
}
|
|
292
|
+
declare function CanvasChrome({ background }: CanvasChromeProps): ReactElement;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* `CanvasDock` — the centred, bottom-of-canvas floating toolbar of a creation
|
|
296
|
+
* studio: pointer/pan mode, add-node, and zoom controls, in the black/silver
|
|
297
|
+
* glass language. It replaces React Flow's default corner Controls + MiniMap
|
|
298
|
+
* with one calm, centred dock.
|
|
299
|
+
*
|
|
300
|
+
* Presentation only: it renders buttons and the current zoom %, and calls back
|
|
301
|
+
* to the host for every action. It holds no React Flow or graph state — the host
|
|
302
|
+
* wires the callbacks to `useReactFlow()` (zoom/fit) and the node search (add).
|
|
303
|
+
*/
|
|
304
|
+
|
|
305
|
+
/** Interaction mode the canvas is in — left-drag selects, or left-drag pans. */
|
|
306
|
+
type CanvasMode = 'select' | 'pan';
|
|
307
|
+
interface CanvasDockProps {
|
|
308
|
+
/** Current zoom as a fraction (1 = 100%). */
|
|
309
|
+
readonly zoom: number;
|
|
310
|
+
readonly onZoomIn: () => void;
|
|
311
|
+
readonly onZoomOut: () => void;
|
|
312
|
+
readonly onFitView: () => void;
|
|
313
|
+
/** Open the node search to add a node. */
|
|
314
|
+
readonly onAdd: () => void;
|
|
315
|
+
/** Current interaction mode + setter (left-drag selects vs pans). */
|
|
316
|
+
readonly mode: CanvasMode;
|
|
317
|
+
readonly onModeChange: (mode: CanvasMode) => void;
|
|
318
|
+
/** Undo/redo — colocated with the canvas tools rather than the top bar. */
|
|
319
|
+
readonly onUndo: () => void;
|
|
320
|
+
readonly onRedo: () => void;
|
|
321
|
+
readonly canUndo: boolean;
|
|
322
|
+
readonly canRedo: boolean;
|
|
323
|
+
}
|
|
324
|
+
declare function CanvasDock({ zoom, onZoomIn, onZoomOut, onFitView, onAdd, mode, onModeChange, onUndo, onRedo, canUndo, canRedo, }: CanvasDockProps): ReactElement;
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* `EmptyState` — a quiet, centered hint shown over an empty canvas. Purely
|
|
328
|
+
* presentational; the host decides when to render it (e.g. when the graph has
|
|
329
|
+
* no nodes). Non-interactive so it never blocks canvas gestures.
|
|
330
|
+
*/
|
|
331
|
+
|
|
332
|
+
interface EmptyStateProps {
|
|
333
|
+
readonly title?: ReactNode;
|
|
334
|
+
readonly hint?: ReactNode;
|
|
335
|
+
}
|
|
336
|
+
declare function EmptyState({ title, hint, }: EmptyStateProps): ReactElement;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* `InspectorPanel` — the right-hand property inspector. It follows selection:
|
|
340
|
+
* with exactly one node selected it shows that node's identity plus a
|
|
341
|
+
* schema-driven parameter form (edits commit undoable UpdateParamsCommands);
|
|
342
|
+
* otherwise it shows an empty/summary state. This is the unified control center
|
|
343
|
+
* (nover.studio's model) over hlang's headless core.
|
|
344
|
+
*/
|
|
345
|
+
|
|
346
|
+
interface InspectorPanelProps {
|
|
347
|
+
readonly core: EditorCore;
|
|
348
|
+
/** Shown when no single node is selected. */
|
|
349
|
+
readonly emptyState?: ReactNode;
|
|
350
|
+
}
|
|
351
|
+
declare function InspectorPanel({ core, emptyState }: InspectorPanelProps): ReactElement;
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* `OutlineTree` — the left-rail outline: a two-level GROUPING, not a tree.
|
|
355
|
+
*
|
|
356
|
+
* hlang's graph is a DAG (R/T/W nodes, O->I edges, T<->T feedback allowed), so
|
|
357
|
+
* there is no parent/child hierarchy to render. Instead we group by weakly-
|
|
358
|
+
* connected component (`GraphTopology.components()`): each component is one
|
|
359
|
+
* expandable "Flow N" container, and its member nodes are listed FLAT — they are
|
|
360
|
+
* peers. The real relationships between nodes (edges, fan-in/out) are shown on
|
|
361
|
+
* the canvas, never faked here as nesting. So: the outline expresses BELONGING
|
|
362
|
+
* (which Flow a node is in); the canvas expresses DATA FLOW.
|
|
363
|
+
*
|
|
364
|
+
* Rows carry per-type icons, a diagnostic marker, hover-revealed visible/lock
|
|
365
|
+
* controls, and a solid silver highlight when selected. Clicking a node selects
|
|
366
|
+
* and focuses it. Visibility/lock are per-node view state via `useViewState`.
|
|
367
|
+
*/
|
|
368
|
+
|
|
369
|
+
interface OutlineTreeProps {
|
|
370
|
+
readonly core: EditorCore;
|
|
371
|
+
readonly onFocus?: (id: NodeId) => void;
|
|
372
|
+
}
|
|
373
|
+
declare function OutlineTree({ core, onFocus }: OutlineTreeProps): ReactElement;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* `PreviewPanel` — a framed preview of a result image. Generic: the host passes
|
|
377
|
+
* an image URL + title (and optional status), and it renders a glass-framed
|
|
378
|
+
* hero card. In the creator flow this is the "output" of the graph — the render
|
|
379
|
+
* — completing the素材→intent→result narrative. editor-ui only draws it; what
|
|
380
|
+
* the image IS (a render, a still, a mock) is the host's business.
|
|
381
|
+
*/
|
|
382
|
+
|
|
383
|
+
interface PreviewPanelProps {
|
|
384
|
+
readonly imageUrl?: string;
|
|
385
|
+
readonly title?: string;
|
|
386
|
+
readonly status?: string;
|
|
387
|
+
}
|
|
388
|
+
declare function PreviewPanel({ imageUrl, title, status }: PreviewPanelProps): ReactElement;
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* `RailTabs` — the segmented tab strip at the top of a side rail (Outline /
|
|
392
|
+
* Insert / Assets, nover-style), in the black/silver glass language. Purely
|
|
393
|
+
* presentational and generic: the host supplies the tab ids/labels and owns the
|
|
394
|
+
* active state, so this is an editor-ui primitive with no coupling to what the
|
|
395
|
+
* tabs contain.
|
|
396
|
+
*/
|
|
397
|
+
|
|
398
|
+
interface RailTab {
|
|
399
|
+
readonly id: string;
|
|
400
|
+
readonly label: string;
|
|
401
|
+
}
|
|
402
|
+
interface RailTabsProps {
|
|
403
|
+
readonly tabs: readonly RailTab[];
|
|
404
|
+
readonly active: string;
|
|
405
|
+
readonly onChange: (id: string) => void;
|
|
406
|
+
}
|
|
407
|
+
declare function RailTabs({ tabs, active, onChange }: RailTabsProps): ReactElement;
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* `AssetGrid` — a thumbnail grid of render results / source images (the nover
|
|
411
|
+
* "Renders" / "Assets" panel). Purely presentational: the host supplies image
|
|
412
|
+
* URLs; clicking one calls back. editor-ui stays generic — an "asset" here is
|
|
413
|
+
* just an image with an id, no media/model business concept.
|
|
414
|
+
*/
|
|
415
|
+
|
|
416
|
+
interface AssetItem {
|
|
417
|
+
readonly id: string;
|
|
418
|
+
readonly url: string;
|
|
419
|
+
readonly label?: string;
|
|
420
|
+
}
|
|
421
|
+
interface AssetGridProps {
|
|
422
|
+
readonly items: readonly AssetItem[];
|
|
423
|
+
readonly onSelect?: (id: string) => void;
|
|
424
|
+
/** Shown when there are no assets yet. */
|
|
425
|
+
readonly emptyHint?: string;
|
|
426
|
+
}
|
|
427
|
+
declare function AssetGrid({ items, onSelect, emptyHint }: AssetGridProps): ReactElement;
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* `PositionPanel` — the "Position" section of the inspector (nover-style): a
|
|
431
|
+
* 3×2 align cluster, the selected node's X/Y, and a Pin toggle. Generic geometry
|
|
432
|
+
* only — alignment/coordinates carry no media/model concept, so this belongs in
|
|
433
|
+
* editor-ui. All moves go through the command service (undoable) via `useRename`-
|
|
434
|
+
* style command execution; nothing mutates the model directly.
|
|
435
|
+
*
|
|
436
|
+
* With a single selected node it shows and edits that node's position. With
|
|
437
|
+
* multiple, the align actions distribute them against their common bounding box.
|
|
438
|
+
*/
|
|
439
|
+
|
|
440
|
+
interface PositionPanelProps {
|
|
441
|
+
readonly core: EditorCore;
|
|
442
|
+
/**
|
|
443
|
+
* Optional LIVE position reader (canvas coords) for a node. A drag commits to
|
|
444
|
+
* the model only on drag-stop, so to show X/Y updating DURING a drag the host
|
|
445
|
+
* supplies the canvas's in-flight position (e.g. React Flow's node.position).
|
|
446
|
+
* When omitted, the committed model position is shown. Keeping this a callback
|
|
447
|
+
* means editor-ui never imports React Flow — the binding stays in the host.
|
|
448
|
+
*/
|
|
449
|
+
readonly livePosition?: (id: string) => {
|
|
450
|
+
x: number;
|
|
451
|
+
y: number;
|
|
452
|
+
} | undefined;
|
|
453
|
+
}
|
|
454
|
+
declare function PositionPanel({ core, livePosition }: PositionPanelProps): ReactElement;
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* `DiagnosticBadge` — a single edit-time diagnostic marker (error/warning icon +
|
|
458
|
+
* tooltip), shared by the node chrome and the outline so the severity→icon and
|
|
459
|
+
* severity→class mapping lives in ONE place. `variant` only sets the wrapper
|
|
460
|
+
* class prefix so each host can size/position it via its own CSS.
|
|
461
|
+
*/
|
|
462
|
+
|
|
463
|
+
interface DiagnosticBadgeProps {
|
|
464
|
+
readonly diagnostic: Diagnostic;
|
|
465
|
+
/** Class-prefix so hosts style it in context (e.g. 'hl-pill-diag'). */
|
|
466
|
+
readonly variant: string;
|
|
467
|
+
readonly size?: number;
|
|
468
|
+
}
|
|
469
|
+
declare function DiagnosticBadge({ diagnostic, variant, size }: DiagnosticBadgeProps): ReactElement;
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Schema-driven property editing. A node's editable parameters are derived into
|
|
473
|
+
* a strongly-typed `ParamField[]` (zero `any`), which the inspector renders as
|
|
474
|
+
* form controls. Field types are inferred from each default value's runtime
|
|
475
|
+
* type — the only metadata a `NodeDescriptor` carries today (`defaultParams`).
|
|
476
|
+
*/
|
|
477
|
+
|
|
478
|
+
/** Common fields on every derived form field. */
|
|
479
|
+
interface ParamFieldBase {
|
|
480
|
+
readonly key: string;
|
|
481
|
+
readonly label: string;
|
|
482
|
+
readonly disabled?: boolean;
|
|
483
|
+
}
|
|
484
|
+
/** A discriminated union of the built-in form field types (zero `any`). */
|
|
485
|
+
type ParamField = (ParamFieldBase & {
|
|
486
|
+
readonly type: 'text';
|
|
487
|
+
readonly value: string;
|
|
488
|
+
}) | (ParamFieldBase & {
|
|
489
|
+
readonly type: 'textarea';
|
|
490
|
+
readonly value: string;
|
|
491
|
+
}) | (ParamFieldBase & {
|
|
492
|
+
readonly type: 'number';
|
|
493
|
+
readonly value: number;
|
|
494
|
+
}) | (ParamFieldBase & {
|
|
495
|
+
readonly type: 'toggle';
|
|
496
|
+
readonly value: boolean;
|
|
497
|
+
}) | (ParamFieldBase & {
|
|
498
|
+
readonly type: 'custom';
|
|
499
|
+
readonly fieldType: string;
|
|
500
|
+
readonly value: ParamValue;
|
|
501
|
+
});
|
|
502
|
+
/**
|
|
503
|
+
* Derive the ordered form schema for a node. Keys come from the descriptor's
|
|
504
|
+
* `defaultParams` (the canonical param set); current values come from the live
|
|
505
|
+
* node, falling back to the default.
|
|
506
|
+
*/
|
|
507
|
+
declare function deriveParamSchema(descriptor: NodeDescriptor, node: EditorNode): readonly ParamField[];
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* `ParamForm` — renders a derived `ParamField[]` as compact, pro-tool form
|
|
511
|
+
* controls. Text/number inputs commit on blur or Enter (not per keystroke, so
|
|
512
|
+
* one edit is one undo entry); toggles commit immediately. `custom` fields are
|
|
513
|
+
* a placeholder until the ParamFieldRegistry lands (M-UI-5).
|
|
514
|
+
*/
|
|
515
|
+
|
|
516
|
+
interface ParamFormProps {
|
|
517
|
+
readonly fields: readonly ParamField[];
|
|
518
|
+
/** Commit a single field's new value (undoable, handled by the caller). */
|
|
519
|
+
readonly onCommit: (key: string, value: ParamValue) => void;
|
|
520
|
+
}
|
|
521
|
+
declare function ParamForm({ fields, onCommit }: ParamFormProps): ReactElement;
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* `useNodeParams` — reactively read a node's params and commit edits through the
|
|
525
|
+
* command service (so every change is undoable). Re-reads on the `nodeChanged`
|
|
526
|
+
* event via a version counter (the same pattern editor-react's selectors use).
|
|
527
|
+
*/
|
|
528
|
+
|
|
529
|
+
interface NodeParamsHandle {
|
|
530
|
+
/** The node, or undefined if it no longer exists. */
|
|
531
|
+
readonly node: EditorNode | undefined;
|
|
532
|
+
/** Set a single param, committing an undoable UpdateParamsCommand. */
|
|
533
|
+
setParam(key: string, value: ParamValue): void;
|
|
534
|
+
/** Replace the whole param bag, committing an undoable UpdateParamsCommand. */
|
|
535
|
+
setParams(next: ParamBag): void;
|
|
536
|
+
}
|
|
537
|
+
declare function useNodeParams(core: EditorCore, nodeId: NodeId | undefined): NodeParamsHandle;
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Small, dependency-free fuzzy matcher for the node search palette. Pure
|
|
541
|
+
* functions — the only logic in editor-ui that carries its own unit tests
|
|
542
|
+
* (behavior, not pixels). Ranks candidates by subsequence match quality:
|
|
543
|
+
* contiguous and word-boundary matches score higher, earlier matches score
|
|
544
|
+
* higher, shorter targets break ties.
|
|
545
|
+
*/
|
|
546
|
+
/** A scored match with the matched character positions (for highlighting). */
|
|
547
|
+
interface FuzzyMatch {
|
|
548
|
+
readonly score: number;
|
|
549
|
+
readonly positions: readonly number[];
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Score `query` against `target` as a case-insensitive subsequence match.
|
|
553
|
+
* Returns `null` when `target` does not contain `query` as a subsequence.
|
|
554
|
+
* An empty query matches everything with score 0 (stable, no reordering).
|
|
555
|
+
*/
|
|
556
|
+
declare function fuzzyScore(query: string, target: string): FuzzyMatch | null;
|
|
557
|
+
/** A candidate to rank, carrying an opaque payload of type `T`. */
|
|
558
|
+
interface FuzzyCandidate<T> {
|
|
559
|
+
readonly text: string;
|
|
560
|
+
readonly value: T;
|
|
561
|
+
}
|
|
562
|
+
/** A ranked result: the candidate plus its match. */
|
|
563
|
+
interface FuzzyResult<T> {
|
|
564
|
+
readonly value: T;
|
|
565
|
+
readonly text: string;
|
|
566
|
+
readonly match: FuzzyMatch;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Rank candidates against a query, best first. Non-matching candidates are
|
|
570
|
+
* dropped. With an empty query, candidates are returned in their original order.
|
|
571
|
+
*/
|
|
572
|
+
declare function fuzzyRank<T>(query: string, candidates: readonly FuzzyCandidate<T>[]): readonly FuzzyResult<T>[];
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Pure model behind the node-search palette. No React, no DOM.
|
|
576
|
+
*
|
|
577
|
+
* Two responsibilities:
|
|
578
|
+
* 1. Turn the design-time registry into fuzzy-searchable candidates and rank
|
|
579
|
+
* them against a query (`descriptorCandidates` + `searchNodes`).
|
|
580
|
+
* 2. When the palette is opened by releasing a dangling connection into empty
|
|
581
|
+
* space, offer ONLY nodes that can legally complete that connection, and
|
|
582
|
+
* remember which port on each closes it (`connectableCandidates`). The
|
|
583
|
+
* legality decision is delegated to the core `IConnectionValidator` — this
|
|
584
|
+
* module never re-encodes hlang's connection rules.
|
|
585
|
+
*/
|
|
586
|
+
|
|
587
|
+
/** A registry descriptor made searchable, keyed by its display name. */
|
|
588
|
+
type NodeCandidate = FuzzyCandidate<NodeDescriptor>;
|
|
589
|
+
/** A ranked search result carrying the matched descriptor. */
|
|
590
|
+
type NodeSearchResult = FuzzyResult<NodeDescriptor>;
|
|
591
|
+
/** Build fuzzy candidates from registry descriptors (display-name keyed). */
|
|
592
|
+
declare function descriptorCandidates(descriptors: readonly NodeDescriptor[]): readonly NodeCandidate[];
|
|
593
|
+
/** Rank descriptors against `query`, best first (empty query keeps order). */
|
|
594
|
+
declare function searchNodes(query: string, descriptors: readonly NodeDescriptor[]): readonly NodeSearchResult[];
|
|
595
|
+
/**
|
|
596
|
+
* A connection left dangling in empty space. `fromEndpoint` is the port the
|
|
597
|
+
* user dragged from and `fromPort` its resolved descriptor; `direction` says
|
|
598
|
+
* whether that endpoint is the connection's source (an output, needing a
|
|
599
|
+
* target) or its target (an input, needing a source). The palette will create a
|
|
600
|
+
* node and wire the missing side.
|
|
601
|
+
*/
|
|
602
|
+
interface PendingConnection {
|
|
603
|
+
readonly fromEndpoint: Endpoint;
|
|
604
|
+
/** Descriptor of the port the drag started from (resolved from the model). */
|
|
605
|
+
readonly fromPort: PortDescriptor;
|
|
606
|
+
readonly direction: 'from-source' | 'from-target';
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* A search result augmented with the specific port on the candidate node that
|
|
610
|
+
* would complete a pending connection.
|
|
611
|
+
*/
|
|
612
|
+
interface ConnectableResult {
|
|
613
|
+
readonly candidate: NodeSearchResult;
|
|
614
|
+
/** The port id on the (future) new node that closes the connection. */
|
|
615
|
+
readonly connectPortId: string;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Filter and rank descriptors to those that can legally complete `pending`,
|
|
619
|
+
* annotating each with the completing port. Legality is decided by the core
|
|
620
|
+
* validator's model-free `canLink` (direction + DTO): for a dangling source we
|
|
621
|
+
* ask canLink(danglingSource, candidatePort); for a dangling target we ask
|
|
622
|
+
* canLink(candidatePort, danglingTarget). No graph model is consulted, because
|
|
623
|
+
* the candidate node does not exist yet — legality here is purely a property of
|
|
624
|
+
* the two ports.
|
|
625
|
+
*/
|
|
626
|
+
declare function connectableCandidates(query: string, descriptors: readonly NodeDescriptor[], pending: PendingConnection, validator: IConnectionValidator): readonly ConnectableResult[];
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* `useNodeSearch` — palette state + commit logic for creating nodes by search.
|
|
630
|
+
*
|
|
631
|
+
* The palette can open in two modes:
|
|
632
|
+
* - blank: create a free-standing node at a point (canvas double-click, `/`).
|
|
633
|
+
* - from a released connection: create a node AND wire the dangling link to a
|
|
634
|
+
* compatible port on it, as ONE undoable step.
|
|
635
|
+
*
|
|
636
|
+
* All mutation goes through the core command service; connection legality and
|
|
637
|
+
* edge ids come from the core (validator + id generator). This hook holds no
|
|
638
|
+
* graph rules of its own.
|
|
639
|
+
*/
|
|
640
|
+
|
|
641
|
+
/** Where and how the palette is currently open. */
|
|
642
|
+
interface NodeSearchState {
|
|
643
|
+
/** Screen-space anchor for rendering the floating panel. */
|
|
644
|
+
readonly screen: Point;
|
|
645
|
+
/** Flow-space position where the created node will be placed. */
|
|
646
|
+
readonly flow: Point;
|
|
647
|
+
/** Set when opened by releasing a dangling connection. */
|
|
648
|
+
readonly pending?: PendingConnection;
|
|
649
|
+
}
|
|
650
|
+
/** The palette controller returned by the hook. */
|
|
651
|
+
interface NodeSearchController {
|
|
652
|
+
/** Current open state, or `undefined` when closed. */
|
|
653
|
+
readonly state: NodeSearchState | undefined;
|
|
654
|
+
/** Open the palette to create a free-standing node. */
|
|
655
|
+
openBlank(screen: Point, flow: Point): void;
|
|
656
|
+
/** Open the palette to complete a released connection. */
|
|
657
|
+
openFromConnection(screen: Point, flow: Point, pending: PendingConnection): void;
|
|
658
|
+
/** Close the palette without creating anything. */
|
|
659
|
+
close(): void;
|
|
660
|
+
/**
|
|
661
|
+
* Rank registry descriptors for the current query and open mode. In blank
|
|
662
|
+
* mode every node is offered; in connection mode only nodes that can legally
|
|
663
|
+
* complete the pending link are offered.
|
|
664
|
+
*/
|
|
665
|
+
results(query: string): readonly NodeSearchResultView[];
|
|
666
|
+
/** Create the chosen node (and wire the pending link, if any), then close. */
|
|
667
|
+
commit(result: NodeSearchResultView): void;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* A ranked result as the palette renders it: the fuzzy match plus, in
|
|
671
|
+
* connection mode, the port on the new node that completes the link.
|
|
672
|
+
*/
|
|
673
|
+
interface NodeSearchResultView {
|
|
674
|
+
readonly result: NodeSearchResult;
|
|
675
|
+
/** Present only in connection mode. */
|
|
676
|
+
readonly connectPortId?: string;
|
|
677
|
+
}
|
|
678
|
+
declare function useNodeSearch(core: EditorCore): NodeSearchController;
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* `NodeSearch` — the floating command palette for creating nodes by fuzzy
|
|
682
|
+
* search. Rendered when the controller is open; anchored at the trigger point.
|
|
683
|
+
*
|
|
684
|
+
* Presentation only: it owns the query string, keyboard navigation, and match
|
|
685
|
+
* highlighting, and delegates ranking + creation to the `NodeSearchController`.
|
|
686
|
+
* Deep-space glass panel, electric-blue accent, kind-tinted result chips.
|
|
687
|
+
*/
|
|
688
|
+
|
|
689
|
+
interface NodeSearchProps {
|
|
690
|
+
readonly controller: NodeSearchController;
|
|
691
|
+
}
|
|
692
|
+
declare function NodeSearch({ controller }: NodeSearchProps): ReactElement | null;
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* `useNodeSearchTriggers` — bridge React Flow gestures to the node-search
|
|
696
|
+
* palette. Returns handlers to spread onto `<ReactFlow>`:
|
|
697
|
+
*
|
|
698
|
+
* - `onConnectStart` / `onConnectEnd`: when a link is dragged from a port and
|
|
699
|
+
* released into empty space (not onto a handle), open the palette pre-filtered
|
|
700
|
+
* to nodes that can complete that connection.
|
|
701
|
+
* - `onPaneClick`: two clicks on the empty pane within `doubleClickMs` open a
|
|
702
|
+
* blank palette. We use React Flow's first-class `onPaneClick` (which fires
|
|
703
|
+
* only for the pane, never for nodes/edges/handles) rather than a raw DOM
|
|
704
|
+
* double-click, so this never reaches into React Flow's internal markup.
|
|
705
|
+
*
|
|
706
|
+
* Screen→flow coordinate conversion uses the React Flow instance, so created
|
|
707
|
+
* nodes land under the pointer regardless of pan/zoom. This hook must be used
|
|
708
|
+
* within a `ReactFlowProvider` (React Flow's own requirement for `useReactFlow`).
|
|
709
|
+
*/
|
|
710
|
+
|
|
711
|
+
/** Options for the trigger hook (injectable clock keeps it deterministic). */
|
|
712
|
+
interface NodeSearchTriggerOptions {
|
|
713
|
+
/** Double-click detection window in ms. Defaults to 350. */
|
|
714
|
+
readonly doubleClickMs?: number;
|
|
715
|
+
/** Monotonic clock in ms. Defaults to `performance.now`. */
|
|
716
|
+
readonly now?: () => number;
|
|
717
|
+
}
|
|
718
|
+
/** Handlers to spread onto the `<ReactFlow>` element. */
|
|
719
|
+
interface NodeSearchTriggers {
|
|
720
|
+
readonly onConnectStart: OnConnectStart;
|
|
721
|
+
readonly onConnectEnd: OnConnectEnd;
|
|
722
|
+
/** Attach to `<ReactFlow onPaneClick>`; a rapid second click opens the palette. */
|
|
723
|
+
onPaneClick(event: MouseEvent): void;
|
|
724
|
+
}
|
|
725
|
+
declare function useNodeSearchTriggers(core: EditorCore, search: NodeSearchController, options?: NodeSearchTriggerOptions): NodeSearchTriggers;
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* `useEditorHotkeys` — global keyboard shortcuts for the editor canvas:
|
|
729
|
+
*
|
|
730
|
+
* ⌘/Ctrl+Z undo ⌘/Ctrl+Shift+Z, ⌘/Ctrl+Y redo
|
|
731
|
+
* ⌘/Ctrl+C copy ⌘/Ctrl+V paste ⌘/Ctrl+X cut ⌘/Ctrl+D duplicate
|
|
732
|
+
* Delete / Backspace delete
|
|
733
|
+
*
|
|
734
|
+
* Clipboard actions run through `useClipboard`; undo/redo drive the history
|
|
735
|
+
* service. Every action is a proper command, so it is itself undoable. The
|
|
736
|
+
* listener is on `window` but SUPPRESSED while a text input / textarea /
|
|
737
|
+
* contenteditable is focused, so typing never triggers a canvas action.
|
|
738
|
+
*/
|
|
739
|
+
|
|
740
|
+
/** True when focus is in an editable field, where shortcuts must NOT fire. */
|
|
741
|
+
declare function isEditableTarget(target: EventTarget | null): boolean;
|
|
742
|
+
declare function useEditorHotkeys(core: EditorCore): void;
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* `useNodeContextMenu` — controller for the right-click node menu. Holds only
|
|
746
|
+
* where the menu is open and which node it targets; the menu ITEMS (labels,
|
|
747
|
+
* shortcuts, actions) are assembled by the consumer from clipboard/view-state
|
|
748
|
+
* actions, so this hook carries no graph rules.
|
|
749
|
+
*/
|
|
750
|
+
|
|
751
|
+
/** Where the menu is open and which node it targets. */
|
|
752
|
+
interface NodeContextMenuState {
|
|
753
|
+
/** Screen-space anchor (the click point). */
|
|
754
|
+
readonly screen: Point;
|
|
755
|
+
/** The node the menu acts on. */
|
|
756
|
+
readonly nodeId: NodeId;
|
|
757
|
+
}
|
|
758
|
+
interface NodeContextMenuController {
|
|
759
|
+
readonly state: NodeContextMenuState | undefined;
|
|
760
|
+
/** Open the menu for a node at a screen point. */
|
|
761
|
+
openForNode(screen: Point, nodeId: NodeId): void;
|
|
762
|
+
close(): void;
|
|
763
|
+
}
|
|
764
|
+
declare function useNodeContextMenu(): NodeContextMenuController;
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* `NodeContextMenu` — the right-click menu for a node. A black/silver glass
|
|
768
|
+
* panel matching the command palette's material and dismissal, listing actions
|
|
769
|
+
* the consumer supplies (duplicate/copy/cut/paste/delete/hide/lock). Presentation
|
|
770
|
+
* only: each item carries its own `onSelect`; this component owns positioning,
|
|
771
|
+
* keyboard navigation, and dismissal.
|
|
772
|
+
*/
|
|
773
|
+
|
|
774
|
+
/** One menu row. A separator is a bare `{ separator: true }`. */
|
|
775
|
+
interface ContextMenuItem {
|
|
776
|
+
readonly id: string;
|
|
777
|
+
readonly label: string;
|
|
778
|
+
/** Optional keyboard-shortcut hint shown right-aligned (e.g. "⌘D"). */
|
|
779
|
+
readonly shortcut?: string;
|
|
780
|
+
/** Optional leading glyph. */
|
|
781
|
+
readonly icon?: ReactNode;
|
|
782
|
+
/** Destructive styling (e.g. Delete). */
|
|
783
|
+
readonly danger?: boolean;
|
|
784
|
+
/** Disabled rows are shown dim and are not selectable. */
|
|
785
|
+
readonly disabled?: boolean;
|
|
786
|
+
readonly onSelect: () => void;
|
|
787
|
+
}
|
|
788
|
+
interface NodeContextMenuProps {
|
|
789
|
+
readonly controller: NodeContextMenuController;
|
|
790
|
+
/** Build the items for the targeted node id (recomputed on open). */
|
|
791
|
+
readonly items: (nodeId: string) => readonly ContextMenuItem[];
|
|
792
|
+
}
|
|
793
|
+
declare function NodeContextMenu({ controller, items }: NodeContextMenuProps): ReactElement | null;
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* `NodeInteractionContext` — optional per-node interaction wiring supplied by the
|
|
797
|
+
* host (diagnostics to badge, rename to commit). Node chrome reads it to render a
|
|
798
|
+
* diagnostic badge and to support inline rename, but degrades gracefully to a
|
|
799
|
+
* plain, read-only node when no provider is present — so editor-ui stays usable
|
|
800
|
+
* without the full editor wiring.
|
|
801
|
+
*/
|
|
802
|
+
|
|
803
|
+
interface NodeInteraction {
|
|
804
|
+
/** The most severe diagnostic on a node, or undefined. */
|
|
805
|
+
diagnosticFor(id: NodeId): Diagnostic | undefined;
|
|
806
|
+
/** Commit a rename (host routes it through the command service). */
|
|
807
|
+
rename(id: NodeId, name: string): void;
|
|
808
|
+
}
|
|
809
|
+
interface NodeInteractionProviderProps {
|
|
810
|
+
readonly value: NodeInteraction;
|
|
811
|
+
readonly children: ReactNode;
|
|
812
|
+
}
|
|
813
|
+
declare function NodeInteractionProvider({ value, children, }: NodeInteractionProviderProps): ReactElement;
|
|
814
|
+
declare function useNodeInteraction(): NodeInteraction;
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* `Cheatsheet` — a keyboard-shortcut reference in the black/silver glass
|
|
818
|
+
* language. Toggled by the host (typically the `?` key). Presentation only: it
|
|
819
|
+
* takes an `open` flag and an `onClose`, and renders a grouped list of the
|
|
820
|
+
* editor's shortcuts so they are discoverable, not memorized.
|
|
821
|
+
*/
|
|
822
|
+
|
|
823
|
+
interface ShortcutRow {
|
|
824
|
+
readonly keys: string;
|
|
825
|
+
readonly label: string;
|
|
826
|
+
}
|
|
827
|
+
interface ShortcutGroup {
|
|
828
|
+
readonly title: string;
|
|
829
|
+
readonly rows: readonly ShortcutRow[];
|
|
830
|
+
}
|
|
831
|
+
interface CheatsheetProps {
|
|
832
|
+
readonly open: boolean;
|
|
833
|
+
readonly onClose: () => void;
|
|
834
|
+
/** Groups to display; a sensible default set is used when omitted. */
|
|
835
|
+
readonly groups?: readonly ShortcutGroup[];
|
|
836
|
+
}
|
|
837
|
+
declare function Cheatsheet({ open, onClose, groups }: CheatsheetProps): ReactElement | null;
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* `ToastProvider` / `useToast` — lightweight transient notifications in the
|
|
841
|
+
* black/silver glass language. A host wraps the app once; any component calls
|
|
842
|
+
* `useToast().show('Copied 3 nodes')` for a brief confirmation. Presentation
|
|
843
|
+
* only: it holds a small queue that auto-dismisses.
|
|
844
|
+
*/
|
|
845
|
+
|
|
846
|
+
interface ToastApi {
|
|
847
|
+
show(message: string): void;
|
|
848
|
+
}
|
|
849
|
+
interface ToastProviderProps {
|
|
850
|
+
readonly children: ReactNode;
|
|
851
|
+
/** Auto-dismiss delay in ms (default 2200). */
|
|
852
|
+
readonly duration?: number;
|
|
853
|
+
}
|
|
854
|
+
declare function ToastProvider({ children, duration }: ToastProviderProps): ReactElement;
|
|
855
|
+
declare function useToast(): ToastApi;
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* `LiveSignalStore` — the source of truth for a running graph's live visual
|
|
859
|
+
* state. It ingests activity/error signals, decays activity over a short
|
|
860
|
+
* window, and lets node/edge components subscribe to THEIR OWN id only.
|
|
861
|
+
*
|
|
862
|
+
* Design rationale (industrial-grade, no tricks):
|
|
863
|
+
* - No global DOM scanning and no dependency on React Flow's private CSS
|
|
864
|
+
* classes. Components read their state by id through `useSyncExternalStore`,
|
|
865
|
+
* so only the components whose state actually flips re-render.
|
|
866
|
+
* - Edge "flowing" state is computed from an injected edge index, not by
|
|
867
|
+
* reaching into another layer's model at paint time.
|
|
868
|
+
* - A single rAF loop advances decay and notifies only changed ids.
|
|
869
|
+
*/
|
|
870
|
+
/** Live state of a node during execution. */
|
|
871
|
+
type NodeLiveState = 'idle' | 'active' | 'error';
|
|
872
|
+
type Listener = () => void;
|
|
873
|
+
/** Minimal edge shape the store needs to map a source-port pulse to edges. */
|
|
874
|
+
interface EdgeIndexEntry {
|
|
875
|
+
readonly id: string;
|
|
876
|
+
readonly sourceNodeId: string;
|
|
877
|
+
readonly sourcePortId: string;
|
|
878
|
+
readonly isError: boolean;
|
|
879
|
+
}
|
|
880
|
+
/** Injected clock + scheduler so the store is deterministic and testable. */
|
|
881
|
+
interface LiveSignalStoreDeps {
|
|
882
|
+
now(): number;
|
|
883
|
+
schedule(cb: () => void): number;
|
|
884
|
+
cancel(handle: number): void;
|
|
885
|
+
}
|
|
886
|
+
declare class LiveSignalStore {
|
|
887
|
+
private readonly deps;
|
|
888
|
+
/** nodeId -> last-active timestamp. */
|
|
889
|
+
private readonly nodeActiveAt;
|
|
890
|
+
/** nodeId set that errored during this run. */
|
|
891
|
+
private readonly erroredNodes;
|
|
892
|
+
/** edgeId -> last-flowing timestamp. */
|
|
893
|
+
private readonly edgeFlowAt;
|
|
894
|
+
/** Per-id listeners. */
|
|
895
|
+
private readonly nodeListeners;
|
|
896
|
+
private readonly edgeListeners;
|
|
897
|
+
/** Snapshot of last-emitted state per id, so we only notify on real change. */
|
|
898
|
+
private readonly lastNodeState;
|
|
899
|
+
private readonly lastEdgeFlowing;
|
|
900
|
+
/** Edges sourced by a given (nodeId@portId), for pulse -> edge mapping. */
|
|
901
|
+
private edgesBySourcePort;
|
|
902
|
+
private rafHandle;
|
|
903
|
+
private running;
|
|
904
|
+
constructor(deps?: LiveSignalStoreDeps);
|
|
905
|
+
/** Provide the current edge index (call whenever edges change). */
|
|
906
|
+
setEdgeIndex(edges: readonly EdgeIndexEntry[]): void;
|
|
907
|
+
/** Record a node emitting on an output port (drives node + edge activity). */
|
|
908
|
+
markActivity(nodeId: string, portId: string): void;
|
|
909
|
+
/** Record a node erroring (sticky for the run). */
|
|
910
|
+
markError(nodeId: string): void;
|
|
911
|
+
/** Clear all live state (on stop/teardown) and notify everyone. */
|
|
912
|
+
reset(): void;
|
|
913
|
+
/** Subscribe to a single node's live state. */
|
|
914
|
+
subscribeNode(nodeId: string, listener: Listener): () => void;
|
|
915
|
+
/** Subscribe to a single edge's flowing state. */
|
|
916
|
+
subscribeEdge(edgeId: string, listener: Listener): () => void;
|
|
917
|
+
/** Current live state of a node. */
|
|
918
|
+
getNodeState(nodeId: string): NodeLiveState;
|
|
919
|
+
/** Whether an edge is currently flowing. */
|
|
920
|
+
isEdgeFlowing(edgeId: string): boolean;
|
|
921
|
+
private subscribe;
|
|
922
|
+
private ensureLoop;
|
|
923
|
+
private stopLoop;
|
|
924
|
+
/**
|
|
925
|
+
* Recompute every subscribed id's state, notify only those that changed, and
|
|
926
|
+
* report whether anything is still within its active window (so the loop can
|
|
927
|
+
* stop when the graph goes quiet).
|
|
928
|
+
*/
|
|
929
|
+
private flushNotifications;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* `useLiveSignals` — feed a running flow's activity/error signals into the
|
|
934
|
+
* LiveSignalStore, which node/edge components subscribe to by id. No global DOM
|
|
935
|
+
* access and no dependency on React Flow's private structure: the store is the
|
|
936
|
+
* single source of truth and components read their own state (see
|
|
937
|
+
* `LiveSignalContext`). Must be used within a `<LiveSignalProvider>`.
|
|
938
|
+
*/
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Drive live visuals from an execution handle. Pass the handle returned by
|
|
942
|
+
* `core.execution.run(...)`; pass `undefined` to clear. Keeps the store's edge
|
|
943
|
+
* index in sync with the model so a source-port pulse lights the right edges.
|
|
944
|
+
*
|
|
945
|
+
* The store is taken from `<LiveSignalProvider>` context; pass `explicitStore`
|
|
946
|
+
* when the feeding component sits at or above the provider (so it and the
|
|
947
|
+
* subscribing components share one store).
|
|
948
|
+
*/
|
|
949
|
+
declare function useLiveSignals(core: EditorCore, handle: IExecutionHandle | undefined, explicitStore?: LiveSignalStore): void;
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* React glue for the LiveSignalStore. A single store instance is provided via
|
|
953
|
+
* context; node/edge components subscribe to their own id through
|
|
954
|
+
* `useNodeLiveState` / `useEdgeFlowing` (each returns a primitive, so only the
|
|
955
|
+
* components whose state actually flips re-render — no global DOM scanning).
|
|
956
|
+
*/
|
|
957
|
+
|
|
958
|
+
interface LiveSignalProviderProps {
|
|
959
|
+
/** Optional external store; a fresh one is created if omitted. */
|
|
960
|
+
readonly store?: LiveSignalStore;
|
|
961
|
+
readonly children: ReactNode;
|
|
962
|
+
}
|
|
963
|
+
declare function LiveSignalProvider({ store, children }: LiveSignalProviderProps): ReactElement;
|
|
964
|
+
/** Access the live store, or `null` when there is no provider (visuals off). */
|
|
965
|
+
declare function useLiveSignalStore(): LiveSignalStore | null;
|
|
966
|
+
/** Subscribe to a single node's live state. Idle when no store is present. */
|
|
967
|
+
declare function useNodeLiveState(nodeId: string): NodeLiveState;
|
|
968
|
+
/** Subscribe to a single edge's flowing state. False when no store is present. */
|
|
969
|
+
declare function useEdgeFlowing(edgeId: string): boolean;
|
|
970
|
+
|
|
971
|
+
export { AssetGrid, type AssetGridProps, type AssetItem, AssetNodeChrome, type AssetNodeChromeProps, CanvasChrome, type CanvasChromeProps, CanvasDock, type CanvasDockProps, type CanvasMode, Cheatsheet, type CheatsheetProps, type ConnectableResult, type ContextMenuItem, DiagnosticBadge, type DiagnosticBadgeProps, type EdgeIndexEntry, EditorShell, type EditorShellProps, EditorUIThemeProvider, type EditorUIThemeProviderProps, EmptyState, type EmptyStateProps, FlowEdge, type FuzzyCandidate, type FuzzyMatch, type FuzzyResult, type HlangFlowTypes, HlangNode, InspectorPanel, type InspectorPanelProps, KIND_ROLE, KIND_TOKEN, LiveSignalProvider, type LiveSignalProviderProps, LiveSignalStore, type LiveSignalStoreDeps, NodeBodySlot, type NodeBodySlotProps, type NodeCandidate, NodeChrome, type NodeChromeProps, NodeContextMenu, type NodeContextMenuController, type NodeContextMenuProps, type NodeContextMenuState, type NodeInteraction, NodeInteractionProvider, type NodeInteractionProviderProps, type NodeLiveState, type NodeParamsHandle, NodeSearch, type NodeSearchController, type NodeSearchProps, type NodeSearchResult, type NodeSearchResultView, type NodeSearchState, type NodeSearchTriggerOptions, type NodeSearchTriggers, OutlineTree, type OutlineTreeProps, type ParamField, type ParamFieldBase, ParamForm, type ParamFormProps, type PendingConnection, PillNodeChrome, type PillNodeChromeProps, PortRow, type PortRowProps, PositionPanel, type PositionPanelProps, PreviewPanel, type PreviewPanelProps, type RailTab, RailTabs, type RailTabsProps, RunButton, type RunButtonProps, type ShortcutGroup, type ShortcutRow, SignalBar, TOKEN_NAMES, type ThemeOverride, type ToastApi, ToastProvider, type ToastProviderProps, type TokenName, ToolCluster, type ToolClusterProps, type ToolItem, Toolbar, ToolbarGhostButton, type ToolbarProps, ToolbarStat, UndoRedo, type UndoRedoProps, connectableCandidates, createHlangFlowTypes, createTheme, deriveParamSchema, descriptorCandidates, fuzzyRank, fuzzyScore, isEditableTarget, searchNodes, tokenVar, useEdgeFlowing, useEditorHotkeys, useLiveSignalStore, useLiveSignals, useNodeContextMenu, useNodeInteraction, useNodeLiveState, useNodeParams, useNodeSearch, useNodeSearchTriggers, useToast };
|