@hlang-tech/editor-ui 0.0.0-stage → 0.1.1

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/lib/index.js ADDED
@@ -0,0 +1,2402 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AssetGrid: () => AssetGrid,
24
+ AssetNodeChrome: () => AssetNodeChrome,
25
+ CanvasChrome: () => CanvasChrome,
26
+ CanvasDock: () => CanvasDock,
27
+ Cheatsheet: () => Cheatsheet,
28
+ DiagnosticBadge: () => DiagnosticBadge,
29
+ EditorShell: () => EditorShell,
30
+ EditorUIThemeProvider: () => EditorUIThemeProvider,
31
+ EmptyState: () => EmptyState,
32
+ FlowEdge: () => FlowEdge,
33
+ HlangNode: () => HlangNode,
34
+ InspectorPanel: () => InspectorPanel,
35
+ KIND_ROLE: () => KIND_ROLE,
36
+ KIND_TOKEN: () => KIND_TOKEN,
37
+ LiveSignalProvider: () => LiveSignalProvider,
38
+ LiveSignalStore: () => LiveSignalStore,
39
+ NodeBodySlot: () => NodeBodySlot,
40
+ NodeChrome: () => NodeChrome,
41
+ NodeContextMenu: () => NodeContextMenu,
42
+ NodeInteractionProvider: () => NodeInteractionProvider,
43
+ NodeSearch: () => NodeSearch,
44
+ OutlineTree: () => OutlineTree,
45
+ ParamForm: () => ParamForm,
46
+ PillNodeChrome: () => PillNodeChrome,
47
+ PortRow: () => PortRow,
48
+ PositionPanel: () => PositionPanel,
49
+ PreviewPanel: () => PreviewPanel,
50
+ RailTabs: () => RailTabs,
51
+ RunButton: () => RunButton,
52
+ SignalBar: () => SignalBar,
53
+ TOKEN_NAMES: () => TOKEN_NAMES,
54
+ ToastProvider: () => ToastProvider,
55
+ ToolCluster: () => ToolCluster,
56
+ Toolbar: () => Toolbar,
57
+ ToolbarGhostButton: () => ToolbarGhostButton,
58
+ ToolbarStat: () => ToolbarStat,
59
+ UndoRedo: () => UndoRedo,
60
+ connectableCandidates: () => connectableCandidates,
61
+ createHlangFlowTypes: () => createHlangFlowTypes,
62
+ createTheme: () => createTheme,
63
+ deriveParamSchema: () => deriveParamSchema,
64
+ descriptorCandidates: () => descriptorCandidates,
65
+ fuzzyRank: () => fuzzyRank,
66
+ fuzzyScore: () => fuzzyScore,
67
+ isEditableTarget: () => isEditableTarget,
68
+ searchNodes: () => searchNodes,
69
+ tokenVar: () => tokenVar,
70
+ useEdgeFlowing: () => useEdgeFlowing,
71
+ useEditorHotkeys: () => useEditorHotkeys,
72
+ useLiveSignalStore: () => useLiveSignalStore,
73
+ useLiveSignals: () => useLiveSignals,
74
+ useNodeContextMenu: () => useNodeContextMenu,
75
+ useNodeInteraction: () => useNodeInteraction,
76
+ useNodeLiveState: () => useNodeLiveState,
77
+ useNodeParams: () => useNodeParams,
78
+ useNodeSearch: () => useNodeSearch,
79
+ useNodeSearchTriggers: () => useNodeSearchTriggers,
80
+ useToast: () => useToast
81
+ });
82
+ module.exports = __toCommonJS(index_exports);
83
+
84
+ // src/tokens/tokens.ts
85
+ var TOKEN_NAMES = [
86
+ "hl-void",
87
+ "hl-void-2",
88
+ "hl-surface",
89
+ "hl-surface-2",
90
+ "hl-line",
91
+ "hl-line-strong",
92
+ "hl-silver",
93
+ "hl-silver-hi",
94
+ "hl-silver-lo",
95
+ "hl-spark",
96
+ "hl-r",
97
+ "hl-t",
98
+ "hl-w",
99
+ "hl-error",
100
+ "hl-ink",
101
+ "hl-ink-2",
102
+ "hl-ink-3",
103
+ "hl-font-ui",
104
+ "hl-font-mono",
105
+ "hl-r-sm",
106
+ "hl-r-md",
107
+ "hl-r-lg",
108
+ "hl-r-pill",
109
+ "hl-shadow-node",
110
+ "hl-shadow-glass",
111
+ "hl-glow-silver",
112
+ "hl-ease"
113
+ ];
114
+ function tokenVar(name) {
115
+ return `var(--${name})`;
116
+ }
117
+ var KIND_TOKEN = {
118
+ R: "var(--hl-r)",
119
+ T: "var(--hl-t)",
120
+ W: "var(--hl-w)"
121
+ };
122
+ var KIND_ROLE = {
123
+ R: "source",
124
+ T: "transform",
125
+ W: "sink"
126
+ };
127
+
128
+ // src/tokens/theme.ts
129
+ function createTheme(overrides) {
130
+ const style = {};
131
+ for (const [name, value] of Object.entries(overrides)) {
132
+ if (value !== void 0) {
133
+ style[`--${name}`] = value;
134
+ }
135
+ }
136
+ return style;
137
+ }
138
+
139
+ // src/tokens/ThemeProvider.tsx
140
+ var import_jsx_runtime = require("react/jsx-runtime");
141
+ function EditorUIThemeProvider({
142
+ theme,
143
+ className,
144
+ style,
145
+ children
146
+ }) {
147
+ const themeStyle = theme ? createTheme(theme) : void 0;
148
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: { ...themeStyle, ...style }, children });
149
+ }
150
+
151
+ // src/nodes/SignalBar.tsx
152
+ var import_jsx_runtime2 = require("react/jsx-runtime");
153
+ function SignalBar() {
154
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hl-signal", "aria-hidden": "true" });
155
+ }
156
+
157
+ // src/nodes/PortRow.tsx
158
+ var import_react = require("@xyflow/react");
159
+ var import_jsx_runtime3 = require("react/jsx-runtime");
160
+ function PortRow({ port }) {
161
+ const isInput = port.direction === "I";
162
+ const side = isInput ? "target" : "source";
163
+ const isLive = port.stream !== "value";
164
+ const rowClass = `hl-port${port.isError ? " is-error" : ""}`;
165
+ const handleClass = `hl-handle${isLive ? " is-live" : ""}`;
166
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: rowClass, children: [
167
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
168
+ import_react.Handle,
169
+ {
170
+ type: side,
171
+ position: isInput ? import_react.Position.Left : import_react.Position.Right,
172
+ id: port.id,
173
+ className: handleClass,
174
+ isConnectable: true
175
+ }
176
+ ),
177
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hl-port-label", children: port.isError ? "error" : port.name })
178
+ ] });
179
+ }
180
+
181
+ // src/nodes/NodeBodySlot.tsx
182
+ var import_jsx_runtime4 = require("react/jsx-runtime");
183
+ function NodeBodySlot({ children }) {
184
+ if (children === void 0 || children === null || children === false) {
185
+ return null;
186
+ }
187
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hl-body", children });
188
+ }
189
+
190
+ // src/live/LiveSignalContext.tsx
191
+ var import_react2 = require("react");
192
+
193
+ // src/live/live-signal-store.ts
194
+ var ACTIVE_WINDOW_MS = 700;
195
+ function portKey(nodeId, portId) {
196
+ return `${nodeId}@${portId}`;
197
+ }
198
+ var defaultDeps = {
199
+ now: () => typeof performance !== "undefined" ? performance.now() : Date.now(),
200
+ schedule: (cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : (
201
+ // `window.setTimeout` returns a numeric handle (the DOM overload),
202
+ // unlike the Node overload's `Timeout` object — this store deals in
203
+ // numeric handles because it runs in the browser.
204
+ window.setTimeout(cb, 16)
205
+ ),
206
+ cancel: (h) => typeof cancelAnimationFrame !== "undefined" ? cancelAnimationFrame(h) : window.clearTimeout(h)
207
+ };
208
+ var LiveSignalStore = class {
209
+ constructor(deps = defaultDeps) {
210
+ /** nodeId -> last-active timestamp. */
211
+ this.nodeActiveAt = /* @__PURE__ */ new Map();
212
+ /** nodeId set that errored during this run. */
213
+ this.erroredNodes = /* @__PURE__ */ new Set();
214
+ /** edgeId -> last-flowing timestamp. */
215
+ this.edgeFlowAt = /* @__PURE__ */ new Map();
216
+ /** Per-id listeners. */
217
+ this.nodeListeners = /* @__PURE__ */ new Map();
218
+ this.edgeListeners = /* @__PURE__ */ new Map();
219
+ /** Snapshot of last-emitted state per id, so we only notify on real change. */
220
+ this.lastNodeState = /* @__PURE__ */ new Map();
221
+ this.lastEdgeFlowing = /* @__PURE__ */ new Map();
222
+ /** Edges sourced by a given (nodeId@portId), for pulse -> edge mapping. */
223
+ this.edgesBySourcePort = /* @__PURE__ */ new Map();
224
+ this.rafHandle = null;
225
+ this.running = false;
226
+ this.deps = deps;
227
+ }
228
+ /** Provide the current edge index (call whenever edges change). */
229
+ setEdgeIndex(edges) {
230
+ const next = /* @__PURE__ */ new Map();
231
+ for (const edge of edges) {
232
+ const key = portKey(edge.sourceNodeId, edge.sourcePortId);
233
+ const list = next.get(key);
234
+ if (list === void 0) {
235
+ next.set(key, [edge]);
236
+ } else {
237
+ list.push(edge);
238
+ }
239
+ }
240
+ this.edgesBySourcePort = next;
241
+ }
242
+ /** Record a node emitting on an output port (drives node + edge activity). */
243
+ markActivity(nodeId, portId) {
244
+ const t = this.deps.now();
245
+ this.nodeActiveAt.set(nodeId, t);
246
+ const edges = this.edgesBySourcePort.get(portKey(nodeId, portId));
247
+ if (edges !== void 0) {
248
+ for (const edge of edges) {
249
+ this.edgeFlowAt.set(edge.id, t);
250
+ }
251
+ }
252
+ this.ensureLoop();
253
+ }
254
+ /** Record a node erroring (sticky for the run). */
255
+ markError(nodeId) {
256
+ this.erroredNodes.add(nodeId);
257
+ this.ensureLoop();
258
+ }
259
+ /** Clear all live state (on stop/teardown) and notify everyone. */
260
+ reset() {
261
+ this.nodeActiveAt.clear();
262
+ this.erroredNodes.clear();
263
+ this.edgeFlowAt.clear();
264
+ this.stopLoop();
265
+ this.flushNotifications();
266
+ }
267
+ /** Subscribe to a single node's live state. */
268
+ subscribeNode(nodeId, listener) {
269
+ return this.subscribe(this.nodeListeners, nodeId, listener);
270
+ }
271
+ /** Subscribe to a single edge's flowing state. */
272
+ subscribeEdge(edgeId, listener) {
273
+ return this.subscribe(this.edgeListeners, edgeId, listener);
274
+ }
275
+ /** Current live state of a node. */
276
+ getNodeState(nodeId) {
277
+ if (this.erroredNodes.has(nodeId)) {
278
+ return "error";
279
+ }
280
+ const at = this.nodeActiveAt.get(nodeId);
281
+ if (at !== void 0 && this.deps.now() - at <= ACTIVE_WINDOW_MS) {
282
+ return "active";
283
+ }
284
+ return "idle";
285
+ }
286
+ /** Whether an edge is currently flowing. */
287
+ isEdgeFlowing(edgeId) {
288
+ const at = this.edgeFlowAt.get(edgeId);
289
+ return at !== void 0 && this.deps.now() - at <= ACTIVE_WINDOW_MS;
290
+ }
291
+ subscribe(registry, id, listener) {
292
+ var _a;
293
+ const set = (_a = registry.get(id)) != null ? _a : /* @__PURE__ */ new Set();
294
+ set.add(listener);
295
+ registry.set(id, set);
296
+ return () => {
297
+ set.delete(listener);
298
+ if (set.size === 0) {
299
+ registry.delete(id);
300
+ }
301
+ };
302
+ }
303
+ ensureLoop() {
304
+ if (this.running) {
305
+ this.flushNotifications();
306
+ return;
307
+ }
308
+ this.running = true;
309
+ const step = () => {
310
+ const stillLive = this.flushNotifications();
311
+ if (stillLive) {
312
+ this.rafHandle = this.deps.schedule(step);
313
+ } else {
314
+ this.running = false;
315
+ this.rafHandle = null;
316
+ }
317
+ };
318
+ this.rafHandle = this.deps.schedule(step);
319
+ }
320
+ stopLoop() {
321
+ if (this.rafHandle !== null) {
322
+ this.deps.cancel(this.rafHandle);
323
+ this.rafHandle = null;
324
+ }
325
+ this.running = false;
326
+ }
327
+ /**
328
+ * Recompute every subscribed id's state, notify only those that changed, and
329
+ * report whether anything is still within its active window (so the loop can
330
+ * stop when the graph goes quiet).
331
+ */
332
+ flushNotifications() {
333
+ let anyLive = false;
334
+ for (const [nodeId, listeners] of this.nodeListeners) {
335
+ const state = this.getNodeState(nodeId);
336
+ if (state !== "idle") {
337
+ anyLive = anyLive || state === "active";
338
+ }
339
+ if (this.lastNodeState.get(nodeId) !== state) {
340
+ this.lastNodeState.set(nodeId, state);
341
+ for (const listener of listeners) {
342
+ listener();
343
+ }
344
+ }
345
+ }
346
+ for (const [edgeId, listeners] of this.edgeListeners) {
347
+ const flowing = this.isEdgeFlowing(edgeId);
348
+ anyLive = anyLive || flowing;
349
+ if (this.lastEdgeFlowing.get(edgeId) !== flowing) {
350
+ this.lastEdgeFlowing.set(edgeId, flowing);
351
+ for (const listener of listeners) {
352
+ listener();
353
+ }
354
+ }
355
+ }
356
+ return anyLive;
357
+ }
358
+ };
359
+
360
+ // src/live/LiveSignalContext.tsx
361
+ var import_jsx_runtime5 = require("react/jsx-runtime");
362
+ var LiveSignalContext = (0, import_react2.createContext)(null);
363
+ function LiveSignalProvider({ store, children }) {
364
+ const value = (0, import_react2.useMemo)(() => store != null ? store : new LiveSignalStore(), [store]);
365
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LiveSignalContext.Provider, { value, children });
366
+ }
367
+ function useLiveSignalStore() {
368
+ return (0, import_react2.useContext)(LiveSignalContext);
369
+ }
370
+ function useNodeLiveState(nodeId) {
371
+ const store = (0, import_react2.useContext)(LiveSignalContext);
372
+ return (0, import_react2.useSyncExternalStore)(
373
+ (onChange) => store === null ? () => void 0 : store.subscribeNode(nodeId, onChange),
374
+ () => store === null ? "idle" : store.getNodeState(nodeId),
375
+ () => "idle"
376
+ );
377
+ }
378
+ function useEdgeFlowing(edgeId) {
379
+ const store = (0, import_react2.useContext)(LiveSignalContext);
380
+ return (0, import_react2.useSyncExternalStore)(
381
+ (onChange) => store === null ? () => void 0 : store.subscribeEdge(edgeId, onChange),
382
+ () => store === null ? false : store.isEdgeFlowing(edgeId),
383
+ () => false
384
+ );
385
+ }
386
+
387
+ // src/nodes/AssetNode.tsx
388
+ var import_jsx_runtime6 = require("react/jsx-runtime");
389
+ function stringParam(node, key) {
390
+ const v = node.params[key];
391
+ return typeof v === "string" ? v : void 0;
392
+ }
393
+ function AssetNodeChrome({ node, selected }) {
394
+ var _a, _b;
395
+ const imageUrl = (_a = stringParam(node, "imageUrl")) != null ? _a : "";
396
+ const caption = (_b = stringParam(node, "caption")) != null ? _b : node.name;
397
+ const role = stringParam(node, "role");
398
+ const live = useNodeLiveState(node.id);
399
+ const outputs = node.ports.filter((p) => p.direction === "O" && !p.isError);
400
+ const className = [
401
+ "hl-asset",
402
+ selected ? "is-selected" : "",
403
+ live === "active" ? "is-active" : "",
404
+ live === "error" ? "is-live-error" : ""
405
+ ].filter(Boolean).join(" ");
406
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className, "data-kind": node.kind, children: [
407
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hl-asset-frame", children: [
408
+ imageUrl.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { className: "hl-asset-img", src: imageUrl, alt: caption, draggable: false }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hl-asset-placeholder", "aria-hidden": true }),
409
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hl-asset-scrim" }),
410
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hl-asset-caption", children: [
411
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "hl-asset-name", children: caption }),
412
+ role !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "hl-asset-role hl-mono", children: role })
413
+ ] })
414
+ ] }),
415
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "hl-asset-ports", children: outputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PortRow, { port }, port.id)) })
416
+ ] });
417
+ }
418
+
419
+ // src/nodes/PillNode.tsx
420
+ var import_react4 = require("react");
421
+
422
+ // src/icons/node-icons.tsx
423
+ var import_jsx_runtime7 = require("react/jsx-runtime");
424
+ function svg(size, children) {
425
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
426
+ "svg",
427
+ {
428
+ width: size,
429
+ height: size,
430
+ viewBox: "0 0 24 24",
431
+ fill: "none",
432
+ stroke: "currentColor",
433
+ strokeWidth: 1.6,
434
+ strokeLinecap: "round",
435
+ strokeLinejoin: "round",
436
+ "aria-hidden": true,
437
+ children
438
+ }
439
+ );
440
+ }
441
+ var BY_CODE = {
442
+ "image-source": (s) => svg(s, [
443
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2.5" }, "r"),
444
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "8.5", cy: "9", r: "1.6" }, "c"),
445
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 17l5-5 4 4 3-2 4 4" }, "p")
446
+ ]),
447
+ "text-source": (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M5 6h14M12 6v12M9 18h6" }, "p")]),
448
+ "interval-source": (s) => svg(s, [
449
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "12", r: "8" }, "c"),
450
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 8v4l3 2" }, "h")
451
+ ]),
452
+ "range-source": (s) => svg(s, [
453
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 12h16" }, "l"),
454
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "6", cy: "12", r: "2" }, "a"),
455
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "18", cy: "12", r: "2" }, "b")
456
+ ]),
457
+ "constant-source": (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M6 12h12M9 8l-3 4 3 4M15 8l3 4-3 4" }, "p")]),
458
+ "map-scale": (s) => svg(s, [
459
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 20V8l8-4" }, "a"),
460
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 8l8 4 8-4M12 12v8" }, "b")
461
+ ]),
462
+ uppercase: (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M5 18L9 6l4 12M6.5 14h5M15 10v8M15 10c3 0 3 4 0 4" }, "p")]),
463
+ "filter-threshold": (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 5h16l-6 7v6l-4-2v-4z" }, "p")]),
464
+ "scan-sum": (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M17 6H7l6 6-6 6h10" }, "p")]),
465
+ delay: (s) => svg(s, [
466
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "13", r: "7" }, "c"),
467
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 10v3l2 2M9 3h6" }, "h")
468
+ ]),
469
+ merge: (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M6 5v4c0 3 3 3 6 3s6 0 6-3V5M12 12v7" }, "p")]),
470
+ "console-sink": (s) => svg(s, [
471
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "4", y: "5", width: "16", height: "12", rx: "2" }, "r"),
472
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 10l2.5 2L8 14M13 14h3" }, "p")
473
+ ]),
474
+ "collect-sink": (s) => svg(s, [
475
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 7h16M4 12h16M4 17h10" }, "p")
476
+ ])
477
+ };
478
+ var BY_KIND = {
479
+ R: (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 5l10 7-10 7z" }, "p")]),
480
+ // source ▷ (play)
481
+ T: (s) => svg(s, [/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 8h6l4 8h6M14 8h6M4 16h6" }, "p")]),
482
+ // transform
483
+ W: (s) => svg(s, [
484
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M5 4h11l3 3v13H5z" }, "p"),
485
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M9 4v5h6" }, "q")
486
+ ])
487
+ // sink (save)
488
+ };
489
+ function NodeIcon({ code, kind, size = 15 }) {
490
+ var _a;
491
+ const build = (_a = BY_CODE[code]) != null ? _a : BY_KIND[kind];
492
+ return build(size);
493
+ }
494
+ function EyeIcon({ size = 14 }) {
495
+ return svg(size, [
496
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M2 12s3.5-6.5 10-6.5S22 12 22 12s-3.5 6.5-10 6.5S2 12 2 12z" }, "p"),
497
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "12", r: "2.6" }, "c")
498
+ ]);
499
+ }
500
+ function EyeOffIcon({ size = 14 }) {
501
+ return svg(size, [
502
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M2 12s3.5-6.5 10-6.5c1.7 0 3.2.45 4.5 1.1M22 12s-3.5 6.5-10 6.5c-1.7 0-3.2-.45-4.5-1.1" }, "p"),
503
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M9.9 9.9a2.6 2.6 0 003.7 3.7" }, "e"),
504
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M3 3l18 18" }, "s")
505
+ ]);
506
+ }
507
+ function UnlockIcon({ size = 14 }) {
508
+ return svg(size, [
509
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "5", y: "11", width: "14", height: "9", rx: "2" }, "r"),
510
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 11V7a4 4 0 017.9-1" }, "s")
511
+ ]);
512
+ }
513
+ function LockIcon({ size = 14 }) {
514
+ return svg(size, [
515
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "5", y: "11", width: "14", height: "9", rx: "2" }, "r"),
516
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 11V7a4 4 0 018 0v4" }, "s")
517
+ ]);
518
+ }
519
+ function WarningIcon({ size = 13 }) {
520
+ return svg(size, [
521
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 4l9 15H3z" }, "t"),
522
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 10v4" }, "b"),
523
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 17v.5" }, "d")
524
+ ]);
525
+ }
526
+ function ErrorIcon({ size = 13 }) {
527
+ return svg(size, [
528
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "12", r: "9" }, "c"),
529
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 7v6" }, "b"),
530
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 16v.5" }, "d")
531
+ ]);
532
+ }
533
+
534
+ // src/panels/DiagnosticBadge.tsx
535
+ var import_jsx_runtime8 = require("react/jsx-runtime");
536
+ function DiagnosticBadge({ diagnostic, variant, size = 12 }) {
537
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
538
+ "span",
539
+ {
540
+ className: `${variant} hl-diag-${diagnostic.severity}`,
541
+ title: diagnostic.message,
542
+ "aria-label": `${diagnostic.severity}: ${diagnostic.message}`,
543
+ children: diagnostic.severity === "error" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorIcon, { size }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(WarningIcon, { size })
544
+ }
545
+ );
546
+ }
547
+
548
+ // src/nodes/NodeInteractionContext.tsx
549
+ var import_react3 = require("react");
550
+ var import_jsx_runtime9 = require("react/jsx-runtime");
551
+ var NoopInteraction = {
552
+ diagnosticFor: () => void 0,
553
+ rename: () => void 0
554
+ };
555
+ var NodeInteractionCtx = (0, import_react3.createContext)(NoopInteraction);
556
+ function NodeInteractionProvider({
557
+ value,
558
+ children
559
+ }) {
560
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(NodeInteractionCtx.Provider, { value, children });
561
+ }
562
+ function useNodeInteraction() {
563
+ return (0, import_react3.useContext)(NodeInteractionCtx);
564
+ }
565
+
566
+ // src/nodes/PillNode.tsx
567
+ var import_jsx_runtime10 = require("react/jsx-runtime");
568
+ var DOUBLE_CLICK_MS = 320;
569
+ function PillNodeChrome({ node, selected }) {
570
+ const live = useNodeLiveState(node.id);
571
+ const interaction = useNodeInteraction();
572
+ const diagnostic = interaction.diagnosticFor(node.id);
573
+ const [editing, setEditing] = (0, import_react4.useState)(false);
574
+ const [draft, setDraft] = (0, import_react4.useState)(node.name);
575
+ const inputRef = (0, import_react4.useRef)(null);
576
+ (0, import_react4.useEffect)(() => {
577
+ if (editing) {
578
+ setDraft(node.name);
579
+ requestAnimationFrame(() => {
580
+ var _a;
581
+ return (_a = inputRef.current) == null ? void 0 : _a.select();
582
+ });
583
+ }
584
+ }, [editing, node.name]);
585
+ const commit = (0, import_react4.useCallback)(() => {
586
+ setEditing(false);
587
+ interaction.rename(node.id, draft);
588
+ }, [interaction, node.id, draft]);
589
+ const onKeyDown = (0, import_react4.useCallback)(
590
+ (e) => {
591
+ if (e.key === "Enter") {
592
+ e.preventDefault();
593
+ commit();
594
+ } else if (e.key === "Escape") {
595
+ e.preventDefault();
596
+ setEditing(false);
597
+ }
598
+ },
599
+ [commit]
600
+ );
601
+ const lastClick = (0, import_react4.useRef)(0);
602
+ const onNameClick = (0, import_react4.useCallback)(() => {
603
+ const now = performance.now();
604
+ if (now - lastClick.current < DOUBLE_CLICK_MS) {
605
+ setEditing(true);
606
+ lastClick.current = 0;
607
+ } else {
608
+ lastClick.current = now;
609
+ }
610
+ }, []);
611
+ const inputs = node.ports.filter((p) => p.direction === "I" && !p.isError);
612
+ const outputs = node.ports.filter((p) => p.direction === "O" && !p.isError);
613
+ const className = [
614
+ "hl-pill",
615
+ `hl-pill-${node.kind.toLowerCase()}`,
616
+ selected ? "is-selected" : "",
617
+ live === "active" ? "is-active" : "",
618
+ live === "error" ? "is-live-error" : "",
619
+ (diagnostic == null ? void 0 : diagnostic.severity) === "error" ? "has-diag-error" : "",
620
+ (diagnostic == null ? void 0 : diagnostic.severity) === "warning" ? "has-diag-warning" : ""
621
+ ].filter(Boolean).join(" ");
622
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className, "data-kind": node.kind, children: [
623
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "hl-pill-dot", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(NodeIcon, { code: node.code, kind: node.kind, size: 13 }) }),
624
+ editing ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
625
+ "input",
626
+ {
627
+ ref: inputRef,
628
+ className: "hl-pill-rename nodrag",
629
+ value: draft,
630
+ onChange: (e) => setDraft(e.target.value),
631
+ onKeyDown,
632
+ onBlur: commit,
633
+ spellCheck: false,
634
+ "aria-label": "Rename node"
635
+ }
636
+ ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
637
+ "span",
638
+ {
639
+ className: "hl-pill-name nodrag",
640
+ onClick: (e) => {
641
+ e.stopPropagation();
642
+ onNameClick();
643
+ },
644
+ title: "Double-click to rename",
645
+ children: node.name
646
+ }
647
+ ),
648
+ diagnostic !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DiagnosticBadge, { diagnostic, variant: "hl-pill-diag", size: 12 }),
649
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "hl-pill-status", "aria-hidden": true }),
650
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { className: "hl-pill-ports hl-in", children: inputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PortRow, { port }, port.id)) }),
651
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { className: "hl-pill-ports hl-out", children: outputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PortRow, { port }, port.id)) })
652
+ ] });
653
+ }
654
+
655
+ // src/nodes/HlangNode.tsx
656
+ var import_jsx_runtime11 = require("react/jsx-runtime");
657
+ function NodeChrome({ node, selected, body }) {
658
+ const inputs = node.ports.filter((p) => p.direction === "I");
659
+ const outputs = node.ports.filter((p) => p.direction === "O");
660
+ const live = useNodeLiveState(node.id);
661
+ const className = [
662
+ "hl-node",
663
+ `hl-${node.kind.toLowerCase()}`,
664
+ selected ? "is-selected" : "",
665
+ live === "active" ? "is-active" : "",
666
+ live === "error" ? "is-live-error" : ""
667
+ ].filter(Boolean).join(" ");
668
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className, "data-kind": node.kind, children: [
669
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SignalBar, {}),
670
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("header", { className: "hl-head", children: [
671
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "hl-kind", children: node.kind }),
672
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "hl-name", children: node.name }),
673
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "hl-role", children: KIND_ROLE[node.kind] })
674
+ ] }),
675
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(NodeBodySlot, { node, children: body }),
676
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "hl-ports", children: [
677
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { className: "hl-col hl-in", children: inputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PortRow, { port }, port.id)) }),
678
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { className: "hl-col hl-out", children: outputs.map((port) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PortRow, { port }, port.id)) })
679
+ ] })
680
+ ] });
681
+ }
682
+ function HlangNode({ data, selected }) {
683
+ const node = data.editorNode;
684
+ if (typeof node.params.imageUrl === "string" && node.params.imageUrl.length > 0) {
685
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AssetNodeChrome, { node, selected });
686
+ }
687
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PillNodeChrome, { node, selected });
688
+ }
689
+
690
+ // src/edges/FlowEdge.tsx
691
+ var import_react5 = require("@xyflow/react");
692
+ var import_jsx_runtime12 = require("react/jsx-runtime");
693
+ function useSourceRole(sourceId) {
694
+ var _a, _b;
695
+ const node = (0, import_react5.useInternalNode)(sourceId);
696
+ const editorNode = (_a = node == null ? void 0 : node.data) == null ? void 0 : _a.editorNode;
697
+ const role = (_b = editorNode == null ? void 0 : editorNode.params) == null ? void 0 : _b.role;
698
+ return typeof role === "string" && role.length > 0 ? role : void 0;
699
+ }
700
+ function FlowEdge(props) {
701
+ var _a;
702
+ const { id, source, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data } = props;
703
+ const [path] = (0, import_react5.getBezierPath)({
704
+ sourceX,
705
+ sourceY,
706
+ sourcePosition,
707
+ targetX,
708
+ targetY,
709
+ targetPosition
710
+ });
711
+ const t = 0.62;
712
+ const labelX = sourceX + (targetX - sourceX) * t;
713
+ const labelY = sourceY + (targetY - sourceY) * t;
714
+ const isError = (_a = data == null ? void 0 : data.isError) != null ? _a : false;
715
+ const isValue = (data == null ? void 0 : data.stream) === "value";
716
+ const flowing = useEdgeFlowing(id);
717
+ const role = useSourceRole(source);
718
+ const className = [
719
+ "hl-edge",
720
+ isError ? "is-error" : "",
721
+ isValue ? "is-value" : "is-live",
722
+ flowing ? "is-flowing" : ""
723
+ ].filter(Boolean).join(" ");
724
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
725
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("g", { className, children: [
726
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react5.BaseEdge, { id, path, className: "hl-edge-base" }),
727
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: path, className: "hl-edge-flow" })
728
+ ] }),
729
+ role !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react5.EdgeLabelRenderer, { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
730
+ "div",
731
+ {
732
+ className: "hl-edge-label hl-mono",
733
+ style: { transform: `translate(-50%, -50%) translate(${String(labelX)}px, ${String(labelY)}px)` },
734
+ children: role
735
+ }
736
+ ) })
737
+ ] });
738
+ }
739
+
740
+ // src/nodes/flow-types.ts
741
+ var nodeComponent = HlangNode;
742
+ var edgeComponent = FlowEdge;
743
+ function createHlangFlowTypes() {
744
+ return {
745
+ nodeTypes: { R: nodeComponent, T: nodeComponent, W: nodeComponent },
746
+ edgeTypes: { default: edgeComponent }
747
+ };
748
+ }
749
+
750
+ // src/shell/EditorShell.tsx
751
+ var import_jsx_runtime13 = require("react/jsx-runtime");
752
+ function EditorShell({
753
+ toolbar,
754
+ left,
755
+ right,
756
+ canvas,
757
+ footer,
758
+ leftCollapsed = false,
759
+ rightCollapsed = false
760
+ }) {
761
+ const className = [
762
+ "hl-shell",
763
+ leftCollapsed ? "hl-left-collapsed" : "",
764
+ rightCollapsed ? "hl-right-collapsed" : ""
765
+ ].filter(Boolean).join(" ");
766
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className, children: [
767
+ toolbar !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "hl-region-toolbar", children: toolbar }),
768
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "hl-region-body", children: [
769
+ left !== void 0 && !leftCollapsed && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("aside", { className: "hl-region-left", children: left }),
770
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("main", { className: "hl-region-canvas", children: canvas }),
771
+ right !== void 0 && !rightCollapsed && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("aside", { className: "hl-region-right", children: right })
772
+ ] }),
773
+ footer !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "hl-region-footer", children: footer })
774
+ ] });
775
+ }
776
+
777
+ // src/toolbar/Toolbar.tsx
778
+ var import_editor_react = require("@hlang-tech/editor-react");
779
+ var import_jsx_runtime14 = require("react/jsx-runtime");
780
+ function Toolbar({ title, center, actions }) {
781
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("header", { className: "hl-toolbar", children: [
782
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "hl-toolbar-brand", children: title != null ? title : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
783
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "hl-toolbar-glyph", "aria-hidden": "true" }),
784
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "hl-toolbar-wordmark", children: "hlang" })
785
+ ] }) }),
786
+ center !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "hl-toolbar-center", children: center }),
787
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "hl-toolbar-actions", children: actions })
788
+ ] });
789
+ }
790
+ function UndoRedo({ core }) {
791
+ const history = (0, import_editor_react.useHistory)(core);
792
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
793
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
794
+ "button",
795
+ {
796
+ type: "button",
797
+ className: "hl-toolbar-btn",
798
+ onClick: () => history.undo(),
799
+ disabled: !history.canUndo,
800
+ title: "Undo (\u2318Z)",
801
+ children: "Undo"
802
+ }
803
+ ),
804
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
805
+ "button",
806
+ {
807
+ type: "button",
808
+ className: "hl-toolbar-btn",
809
+ onClick: () => history.redo(),
810
+ disabled: !history.canRedo,
811
+ title: "Redo (\u2318\u21E7Z)",
812
+ children: "Redo"
813
+ }
814
+ )
815
+ ] });
816
+ }
817
+ function RunButton({
818
+ onRun,
819
+ running = false,
820
+ runLabel = "Run",
821
+ stopLabel = "Stop"
822
+ }) {
823
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
824
+ "button",
825
+ {
826
+ type: "button",
827
+ className: `hl-toolbar-btn hl-toolbar-run${running ? " is-running" : ""}`,
828
+ onClick: onRun,
829
+ "aria-pressed": running,
830
+ children: [
831
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
832
+ "span",
833
+ {
834
+ className: `hl-toolbar-run-icon${running ? " is-stop" : ""}`,
835
+ "aria-hidden": "true"
836
+ }
837
+ ),
838
+ running ? stopLabel : runLabel
839
+ ]
840
+ }
841
+ );
842
+ }
843
+ function ToolbarStat({ label, value }) {
844
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "hl-toolbar-stat hl-mono", title: label, children: value });
845
+ }
846
+ function ToolbarGhostButton({
847
+ children,
848
+ onClick,
849
+ title
850
+ }) {
851
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: "hl-toolbar-btn", onClick, title, children });
852
+ }
853
+
854
+ // src/toolbar/ToolCluster.tsx
855
+ var import_jsx_runtime15 = require("react/jsx-runtime");
856
+ function ToolCluster({ items }) {
857
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "hl-toolcluster", role: "toolbar", "aria-label": "Canvas tools", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "hl-toolcluster-slot", children: [
858
+ item.separatorBefore === true && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "hl-toolcluster-sep", "aria-hidden": true }),
859
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
860
+ "button",
861
+ {
862
+ type: "button",
863
+ className: "hl-toolcluster-btn" + (item.active === true ? " is-active" : ""),
864
+ onClick: item.onClick,
865
+ disabled: item.disabled,
866
+ title: item.title,
867
+ "aria-label": item.title,
868
+ "aria-pressed": item.active,
869
+ children: item.icon
870
+ }
871
+ )
872
+ ] }, item.id)) });
873
+ }
874
+
875
+ // src/canvas/CanvasChrome.tsx
876
+ var import_react6 = require("@xyflow/react");
877
+ var import_jsx_runtime16 = require("react/jsx-runtime");
878
+ function CanvasChrome({ background = true }) {
879
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children: background && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react6.Background, { variant: import_react6.BackgroundVariant.Dots, gap: 26, size: 1, color: "#242830" }) });
880
+ }
881
+
882
+ // src/canvas/CanvasDock.tsx
883
+ var import_jsx_runtime17 = require("react/jsx-runtime");
884
+ function CursorIcon() {
885
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M5 3l6 16 2-6 6-2z" }) });
886
+ }
887
+ function HandIcon() {
888
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M8 12V6a1.5 1.5 0 013 0v5m0-1V5a1.5 1.5 0 013 0v6m0-1V6.5a1.5 1.5 0 013 0V13c0 3.5-2 6-5.5 6H12c-2 0-3-.8-4.2-2.5L5 12.5c-.7-1 .6-2.3 1.6-1.6z" }) });
889
+ }
890
+ function PlusIcon() {
891
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { width: 17, height: 17, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M12 5v14M5 12h14" }) });
892
+ }
893
+ function FitIcon() {
894
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5" }) });
895
+ }
896
+ function UndoIcon() {
897
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M9 7L4 12l5 5M4 12h11a5 5 0 010 10h-1" }) });
898
+ }
899
+ function RedoIcon() {
900
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M15 7l5 5-5 5M20 12H9a5 5 0 000 10h1" }) });
901
+ }
902
+ function CanvasDock({
903
+ zoom,
904
+ onZoomIn,
905
+ onZoomOut,
906
+ onFitView,
907
+ onAdd,
908
+ mode,
909
+ onModeChange,
910
+ onUndo,
911
+ onRedo,
912
+ canUndo,
913
+ canRedo
914
+ }) {
915
+ const pct = `${String(Math.round(zoom * 100))}%`;
916
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "hl-dock", role: "toolbar", "aria-label": "Canvas tools", children: [
917
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
918
+ "button",
919
+ {
920
+ className: "hl-dock-btn",
921
+ onClick: onUndo,
922
+ disabled: !canUndo,
923
+ title: "Undo (\u2318Z)",
924
+ "aria-label": "Undo",
925
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(UndoIcon, {})
926
+ }
927
+ ),
928
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
929
+ "button",
930
+ {
931
+ className: "hl-dock-btn",
932
+ onClick: onRedo,
933
+ disabled: !canRedo,
934
+ title: "Redo (\u2318\u21E7Z)",
935
+ "aria-label": "Redo",
936
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(RedoIcon, {})
937
+ }
938
+ ),
939
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "hl-dock-sep", "aria-hidden": true }),
940
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
941
+ "button",
942
+ {
943
+ className: "hl-dock-btn" + (mode === "select" ? " is-active" : ""),
944
+ onClick: () => onModeChange("select"),
945
+ title: "Select (V)",
946
+ "aria-pressed": mode === "select",
947
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CursorIcon, {})
948
+ }
949
+ ),
950
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
951
+ "button",
952
+ {
953
+ className: "hl-dock-btn" + (mode === "pan" ? " is-active" : ""),
954
+ onClick: () => onModeChange("pan"),
955
+ title: "Pan (Space)",
956
+ "aria-pressed": mode === "pan",
957
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(HandIcon, {})
958
+ }
959
+ ),
960
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { className: "hl-dock-btn hl-dock-add", onClick: onAdd, title: "Add node", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PlusIcon, {}) }),
961
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "hl-dock-sep", "aria-hidden": true }),
962
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { className: "hl-dock-btn", onClick: onZoomOut, title: "Zoom out", "aria-label": "Zoom out", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "hl-dock-glyph", children: "\u2212" }) }),
963
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { className: "hl-dock-zoom hl-mono", onClick: onFitView, title: "Fit view", children: pct }),
964
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { className: "hl-dock-btn", onClick: onZoomIn, title: "Zoom in", "aria-label": "Zoom in", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "hl-dock-glyph", children: "+" }) }),
965
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { className: "hl-dock-btn", onClick: onFitView, title: "Fit view", "aria-label": "Fit view", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(FitIcon, {}) })
966
+ ] });
967
+ }
968
+
969
+ // src/canvas/EmptyState.tsx
970
+ var import_jsx_runtime18 = require("react/jsx-runtime");
971
+ function EmptyState({
972
+ title = "An empty canvas.",
973
+ hint = "add a source \xB7 wire it to a sink \xB7 run"
974
+ }) {
975
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "hl-empty", "aria-hidden": "true", children: [
976
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "hl-empty-title", children: title }),
977
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "hl-empty-hint", children: hint })
978
+ ] });
979
+ }
980
+
981
+ // src/panels/InspectorPanel.tsx
982
+ var import_editor_react2 = require("@hlang-tech/editor-react");
983
+
984
+ // src/schema/param-field.ts
985
+ function humanizeKey(key) {
986
+ const spaced = key.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim();
987
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
988
+ }
989
+ function fieldFor(key, value) {
990
+ const label = humanizeKey(key);
991
+ if (typeof value === "boolean") {
992
+ return { key, label, type: "toggle", value };
993
+ }
994
+ if (typeof value === "number") {
995
+ return { key, label, type: "number", value };
996
+ }
997
+ if (typeof value === "string") {
998
+ const type = value.includes("\n") ? "textarea" : "text";
999
+ return { key, label, type, value };
1000
+ }
1001
+ return { key, label, type: "custom", fieldType: "json", value };
1002
+ }
1003
+ function deriveParamSchema(descriptor, node) {
1004
+ const fields = [];
1005
+ for (const key of Object.keys(descriptor.defaultParams)) {
1006
+ const current = key in node.params ? node.params[key] : descriptor.defaultParams[key];
1007
+ fields.push(fieldFor(key, current != null ? current : null));
1008
+ }
1009
+ return fields;
1010
+ }
1011
+
1012
+ // src/hooks/useNodeParams.ts
1013
+ var import_react7 = require("react");
1014
+ var import_editor_core = require("@hlang-tech/editor-core");
1015
+ function useNodeParams(core, nodeId) {
1016
+ const versionRef = (0, import_react7.useRef)(0);
1017
+ const subscribe = (0, import_react7.useCallback)(
1018
+ (onStoreChange) => {
1019
+ const disposable = core.events.on("nodeChanged", (event) => {
1020
+ if (nodeId !== void 0 && event.node.id === nodeId) {
1021
+ versionRef.current += 1;
1022
+ onStoreChange();
1023
+ }
1024
+ });
1025
+ return () => {
1026
+ disposable.dispose();
1027
+ };
1028
+ },
1029
+ [core, nodeId]
1030
+ );
1031
+ const getSnapshot = (0, import_react7.useCallback)(() => versionRef.current, []);
1032
+ const version = (0, import_react7.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1033
+ const setParams = (0, import_react7.useCallback)(
1034
+ (next) => {
1035
+ if (nodeId === void 0) {
1036
+ return;
1037
+ }
1038
+ core.commands.execute(new import_editor_core.UpdateParamsCommand(nodeId, next));
1039
+ },
1040
+ [core, nodeId]
1041
+ );
1042
+ const setParam = (0, import_react7.useCallback)(
1043
+ (key, value) => {
1044
+ if (nodeId === void 0) {
1045
+ return;
1046
+ }
1047
+ const node = core.model.getNode(nodeId);
1048
+ if (node === void 0) {
1049
+ return;
1050
+ }
1051
+ setParams({ ...node.params, [key]: value });
1052
+ },
1053
+ [core, nodeId, setParams]
1054
+ );
1055
+ return (0, import_react7.useMemo)(
1056
+ () => ({
1057
+ node: nodeId === void 0 ? void 0 : core.model.getNode(nodeId),
1058
+ setParam,
1059
+ setParams
1060
+ }),
1061
+ // `version` re-reads the node after each nodeChanged for this id.
1062
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1063
+ [core, nodeId, version, setParam, setParams]
1064
+ );
1065
+ }
1066
+
1067
+ // src/panels/ParamForm.tsx
1068
+ var import_react8 = require("react");
1069
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1070
+ function ParamForm({ fields, onCommit }) {
1071
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "hl-form", children: fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(FieldRow, { field, onCommit }, field.key)) });
1072
+ }
1073
+ function FieldRow({
1074
+ field,
1075
+ onCommit
1076
+ }) {
1077
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { className: "hl-field", children: [
1078
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "hl-field-label", children: field.label }),
1079
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(FieldControl, { field, onCommit })
1080
+ ] });
1081
+ }
1082
+ function FieldControl({
1083
+ field,
1084
+ onCommit
1085
+ }) {
1086
+ switch (field.type) {
1087
+ case "toggle":
1088
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1089
+ "button",
1090
+ {
1091
+ type: "button",
1092
+ role: "switch",
1093
+ "aria-checked": field.value,
1094
+ className: `hl-toggle${field.value ? " is-on" : ""}`,
1095
+ disabled: field.disabled,
1096
+ onClick: () => onCommit(field.key, !field.value),
1097
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "hl-toggle-knob" })
1098
+ }
1099
+ );
1100
+ case "number":
1101
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(NumberControl, { field, onCommit });
1102
+ case "textarea":
1103
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TextControl, { field, onCommit, multiline: true });
1104
+ case "text":
1105
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TextControl, { field, onCommit });
1106
+ case "custom":
1107
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "hl-field-custom hl-mono", title: field.fieldType, children: describeCustom(field.value) });
1108
+ }
1109
+ }
1110
+ function TextControl({
1111
+ field,
1112
+ onCommit,
1113
+ multiline = false
1114
+ }) {
1115
+ const [draft, setDraft] = (0, import_react8.useState)(field.value);
1116
+ const commit = () => {
1117
+ if (draft !== field.value) {
1118
+ onCommit(field.key, draft);
1119
+ }
1120
+ };
1121
+ const shared = {
1122
+ className: "hl-input",
1123
+ value: draft,
1124
+ disabled: field.disabled,
1125
+ spellCheck: false,
1126
+ onBlur: commit
1127
+ };
1128
+ if (multiline) {
1129
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1130
+ "textarea",
1131
+ {
1132
+ ...shared,
1133
+ rows: 4,
1134
+ onChange: (e) => setDraft(e.target.value)
1135
+ }
1136
+ );
1137
+ }
1138
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1139
+ "input",
1140
+ {
1141
+ ...shared,
1142
+ type: "text",
1143
+ onChange: (e) => setDraft(e.target.value),
1144
+ onKeyDown: (e) => {
1145
+ if (e.key === "Enter") {
1146
+ e.currentTarget.blur();
1147
+ }
1148
+ }
1149
+ }
1150
+ );
1151
+ }
1152
+ function NumberControl({
1153
+ field,
1154
+ onCommit
1155
+ }) {
1156
+ const [draft, setDraft] = (0, import_react8.useState)(String(field.value));
1157
+ const commit = () => {
1158
+ const parsed = Number(draft);
1159
+ if (!Number.isNaN(parsed) && parsed !== field.value) {
1160
+ onCommit(field.key, parsed);
1161
+ }
1162
+ };
1163
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1164
+ "input",
1165
+ {
1166
+ className: "hl-input hl-mono",
1167
+ type: "number",
1168
+ inputMode: "decimal",
1169
+ value: draft,
1170
+ disabled: field.disabled,
1171
+ onChange: (e) => setDraft(e.target.value),
1172
+ onBlur: commit,
1173
+ onKeyDown: (e) => {
1174
+ if (e.key === "Enter") {
1175
+ e.currentTarget.blur();
1176
+ }
1177
+ }
1178
+ }
1179
+ );
1180
+ }
1181
+ function describeCustom(value) {
1182
+ if (Array.isArray(value)) {
1183
+ return `[${String(value.length)} items]`;
1184
+ }
1185
+ if (value === null) {
1186
+ return "null";
1187
+ }
1188
+ if (typeof value === "object") {
1189
+ return "{\u2026}";
1190
+ }
1191
+ return String(value);
1192
+ }
1193
+
1194
+ // src/panels/InspectorPanel.tsx
1195
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1196
+ function InspectorPanel({ core, emptyState }) {
1197
+ const selection = (0, import_editor_react2.useSelection)(core);
1198
+ const selectedNodes2 = selection.selectedNodes;
1199
+ const singleId = selectedNodes2.length === 1 ? selectedNodes2[0] : void 0;
1200
+ const params = useNodeParams(core, singleId);
1201
+ const node = params.node;
1202
+ if (node === void 0) {
1203
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "hl-inspector hl-inspector-empty", children: emptyState != null ? emptyState : /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "hl-inspector-blank", children: [
1204
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-inspector-blank-badge", "aria-hidden": true }),
1205
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "hl-inspector-blank-title", children: selectedNodes2.length > 1 ? `${String(selectedNodes2.length)} nodes selected` : "Nothing selected" }),
1206
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "hl-inspector-hint", children: selectedNodes2.length > 1 ? "Select a single node to edit its parameters." : "Pick a node on the canvas or in the outline to inspect and tune it." })
1207
+ ] }) });
1208
+ }
1209
+ const descriptor = core.registry.get(node.code);
1210
+ const fields = descriptor === void 0 ? [] : deriveParamSchema(descriptor, node);
1211
+ const imageUrl = typeof node.params.imageUrl === "string" && node.params.imageUrl.length > 0 ? node.params.imageUrl : void 0;
1212
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "hl-inspector", children: [
1213
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: `hl-inspector-preview hl-${node.kind.toLowerCase()}`, children: imageUrl !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("img", { className: "hl-inspector-preview-img", src: imageUrl, alt: node.name }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-inspector-preview-badge hl-mono", children: node.kind }) }),
1214
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("header", { className: "hl-inspector-head", children: [
1215
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: `hl-inspector-kind hl-${node.kind.toLowerCase()}`, children: node.kind }),
1216
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "hl-inspector-titles", children: [
1217
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-inspector-name", children: node.name }),
1218
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "hl-inspector-role hl-mono", children: [
1219
+ node.code,
1220
+ " \xB7 ",
1221
+ KIND_ROLE[node.kind]
1222
+ ] })
1223
+ ] })
1224
+ ] }),
1225
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("section", { className: "hl-inspector-section", children: [
1226
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "hl-inspector-section-title", children: "Parameters" }),
1227
+ fields.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "hl-inspector-hint", children: "This node has no parameters." }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1228
+ ParamForm,
1229
+ {
1230
+ fields,
1231
+ onCommit: (key, value) => params.setParam(key, value)
1232
+ }
1233
+ )
1234
+ ] }),
1235
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("section", { className: "hl-inspector-section", children: [
1236
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "hl-inspector-section-title", children: "Ports" }),
1237
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("ul", { className: "hl-inspector-ports", children: node.ports.map((port) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1238
+ "li",
1239
+ {
1240
+ className: `hl-inspector-port${port.isError ? " is-error" : ""}`,
1241
+ children: [
1242
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: `hl-port-dot hl-dir-${port.direction.toLowerCase()}` }),
1243
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-mono", children: port.name }),
1244
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hl-inspector-port-dir hl-mono", children: port.direction })
1245
+ ]
1246
+ },
1247
+ port.id
1248
+ )) })
1249
+ ] })
1250
+ ] });
1251
+ }
1252
+
1253
+ // src/panels/OutlineTree.tsx
1254
+ var import_react9 = require("react");
1255
+ var import_editor_core2 = require("@hlang-tech/editor-core");
1256
+ var import_editor_react3 = require("@hlang-tech/editor-react");
1257
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1258
+ function useTopologyComponents(core) {
1259
+ const [tick, setTick] = (0, import_react9.useState)(0);
1260
+ const bump = (0, import_react9.useCallback)(() => setTick((t) => t + 1), []);
1261
+ (0, import_react9.useEffect)(() => {
1262
+ const kinds = ["nodeAdded", "nodeRemoved", "nodeChanged", "edgeAdded", "edgeRemoved"];
1263
+ const disposers = kinds.map((k) => core.events.on(k, bump));
1264
+ bump();
1265
+ return () => {
1266
+ for (const d of disposers) {
1267
+ d.dispose();
1268
+ }
1269
+ };
1270
+ }, [core, bump]);
1271
+ return (0, import_react9.useMemo)(() => {
1272
+ void tick;
1273
+ const topo = new import_editor_core2.GraphTopology();
1274
+ topo.reset(core.model.getNodes(), core.model.getEdges());
1275
+ return topo.components();
1276
+ }, [core, tick]);
1277
+ }
1278
+ function OutlineTree({ core, onFocus }) {
1279
+ const components = useTopologyComponents(core);
1280
+ const selection = (0, import_editor_react3.useSelection)(core);
1281
+ const viewState = (0, import_editor_react3.useViewState)(core);
1282
+ const diagnostics = (0, import_editor_react3.useDiagnostics)(core);
1283
+ const [collapsed, setCollapsed] = (0, import_react9.useState)(/* @__PURE__ */ new Set());
1284
+ const nodeById = (0, import_react9.useMemo)(() => {
1285
+ const m = /* @__PURE__ */ new Map();
1286
+ for (const n of core.model.getNodes()) {
1287
+ m.set(n.id, n);
1288
+ }
1289
+ return m;
1290
+ }, [core, components]);
1291
+ const toggleGroup = (0, import_react9.useCallback)((id) => {
1292
+ setCollapsed((prev) => {
1293
+ const next = new Set(prev);
1294
+ if (next.has(id)) {
1295
+ next.delete(id);
1296
+ } else {
1297
+ next.add(id);
1298
+ }
1299
+ return next;
1300
+ });
1301
+ }, []);
1302
+ const select = (0, import_react9.useCallback)(
1303
+ (id) => {
1304
+ selection.selectNodes([id], "replace");
1305
+ onFocus == null ? void 0 : onFocus(id);
1306
+ },
1307
+ [selection, onFocus]
1308
+ );
1309
+ if (components.length === 0) {
1310
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "hl-outline-empty hl-mono", children: "no layers yet" });
1311
+ }
1312
+ const groups = [...components].sort((a, b) => b.nodeIds.size - a.nodeIds.size);
1313
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "hl-outline", role: "tree", "aria-label": "Outline", children: groups.map((group, gi) => {
1314
+ const members = [...group.nodeIds].map((id) => nodeById.get(id)).filter((n) => n !== void 0);
1315
+ const open = !collapsed.has(group.id);
1316
+ const groupName = `Flow ${String(gi + 1)}`;
1317
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "hl-outline-group", role: "treeitem", "aria-expanded": open, children: [
1318
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1319
+ "button",
1320
+ {
1321
+ className: "hl-outline-grouphead",
1322
+ onClick: () => toggleGroup(group.id),
1323
+ children: [
1324
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-caret" + (open ? " is-open" : ""), "aria-hidden": true, children: "\u25B8" }),
1325
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-groupname", children: groupName }),
1326
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-count hl-mono", children: group.nodeIds.size })
1327
+ ]
1328
+ }
1329
+ ),
1330
+ open && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { className: "hl-outline-members", children: members.map((node) => {
1331
+ const active = selection.isSelected(node.id);
1332
+ const isHidden = viewState.isHidden(node.id);
1333
+ const isLocked = viewState.isLocked(node.id);
1334
+ const hasImage = typeof node.params.imageUrl === "string" && node.params.imageUrl.length > 0;
1335
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1336
+ "div",
1337
+ {
1338
+ className: "hl-outline-row" + (active ? " is-active" : "") + (isHidden ? " is-hidden" : "") + (isLocked ? " is-locked" : ""),
1339
+ children: [
1340
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("button", { className: "hl-outline-main", onClick: () => select(node.id), children: [
1341
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1342
+ "span",
1343
+ {
1344
+ className: `hl-outline-icon hl-kind-${node.kind.toLowerCase()}`,
1345
+ "aria-hidden": true,
1346
+ children: hasImage ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1347
+ "img",
1348
+ {
1349
+ className: "hl-outline-thumb",
1350
+ src: node.params.imageUrl,
1351
+ alt: ""
1352
+ }
1353
+ ) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(NodeIcon, { code: node.code, kind: node.kind, size: 13 })
1354
+ }
1355
+ ),
1356
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-name", children: node.name }),
1357
+ (() => {
1358
+ const diag = diagnostics.worstOf(node.id);
1359
+ return diag === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DiagnosticBadge, { diagnostic: diag, variant: "hl-outline-diag", size: 11 });
1360
+ })(),
1361
+ isLocked && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-badge", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LockIcon, { size: 11 }) }),
1362
+ isHidden && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "hl-outline-badge", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EyeOffIcon, { size: 11 }) })
1363
+ ] }),
1364
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "hl-outline-controls", children: [
1365
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1366
+ "button",
1367
+ {
1368
+ className: "hl-outline-ctl" + (isHidden ? " is-active-ctl" : ""),
1369
+ onClick: () => viewState.toggleHidden(node.id),
1370
+ "aria-label": isHidden ? "Show layer" : "Hide layer",
1371
+ title: isHidden ? "Show" : "Hide",
1372
+ children: isHidden ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EyeOffIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EyeIcon, {})
1373
+ }
1374
+ ),
1375
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1376
+ "button",
1377
+ {
1378
+ className: "hl-outline-ctl" + (isLocked ? " is-active-ctl" : ""),
1379
+ onClick: () => viewState.toggleLocked(node.id),
1380
+ "aria-label": isLocked ? "Unlock layer" : "Lock layer",
1381
+ title: isLocked ? "Unlock" : "Lock",
1382
+ children: isLocked ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LockIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(UnlockIcon, {})
1383
+ }
1384
+ )
1385
+ ] })
1386
+ ]
1387
+ }
1388
+ ) }, node.id);
1389
+ }) })
1390
+ ] }, group.id);
1391
+ }) });
1392
+ }
1393
+
1394
+ // src/panels/PreviewPanel.tsx
1395
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1396
+ function PreviewPanel({ imageUrl, title = "Preview", status }) {
1397
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("section", { className: "hl-preview", "aria-label": "Preview", children: [
1398
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "hl-preview-head", children: [
1399
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "hl-preview-title", children: title }),
1400
+ status !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "hl-preview-status hl-mono", children: status })
1401
+ ] }),
1402
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "hl-preview-frame", children: imageUrl !== void 0 && imageUrl.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("img", { className: "hl-preview-img", src: imageUrl, alt: title, draggable: false }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "hl-preview-placeholder hl-mono", children: "no output yet" }) })
1403
+ ] });
1404
+ }
1405
+
1406
+ // src/panels/RailTabs.tsx
1407
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1408
+ function RailTabs({ tabs, active, onChange }) {
1409
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "hl-railtabs", role: "tablist", children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1410
+ "button",
1411
+ {
1412
+ role: "tab",
1413
+ "aria-selected": tab.id === active,
1414
+ className: "hl-railtab" + (tab.id === active ? " is-active" : ""),
1415
+ onClick: () => onChange(tab.id),
1416
+ children: tab.label
1417
+ },
1418
+ tab.id
1419
+ )) });
1420
+ }
1421
+
1422
+ // src/panels/AssetGrid.tsx
1423
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1424
+ function AssetGrid({ items, onSelect, emptyHint = "No assets yet" }) {
1425
+ if (items.length === 0) {
1426
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "hl-assetgrid-empty hl-mono", children: emptyHint });
1427
+ }
1428
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "hl-assetgrid", children: items.map((item) => {
1429
+ var _a;
1430
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1431
+ "button",
1432
+ {
1433
+ className: "hl-assetgrid-cell",
1434
+ onClick: () => onSelect == null ? void 0 : onSelect(item.id),
1435
+ title: item.label,
1436
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("img", { className: "hl-assetgrid-img", src: item.url, alt: (_a = item.label) != null ? _a : "", loading: "lazy" })
1437
+ },
1438
+ item.id
1439
+ );
1440
+ }) });
1441
+ }
1442
+
1443
+ // src/panels/PositionPanel.tsx
1444
+ var import_react10 = require("react");
1445
+ var import_editor_core3 = require("@hlang-tech/editor-core");
1446
+ var import_editor_react4 = require("@hlang-tech/editor-react");
1447
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1448
+ function selectedNodes(core) {
1449
+ const ids = core.selection.getSelectedNodes();
1450
+ const out = [];
1451
+ for (const id of ids) {
1452
+ const n = core.model.getNode(id);
1453
+ if (n !== void 0) {
1454
+ out.push(n);
1455
+ }
1456
+ }
1457
+ return out;
1458
+ }
1459
+ function PositionPanel({ core, livePosition }) {
1460
+ var _a;
1461
+ (0, import_editor_react4.useSelection)(core);
1462
+ const [, bump] = (0, import_react10.useReducer)((n) => n + 1, 0);
1463
+ (0, import_react10.useEffect)(() => {
1464
+ const kinds = ["nodeChanged", "nodeAdded", "nodeRemoved"];
1465
+ const disposers = kinds.map((k) => core.events.on(k, bump));
1466
+ return () => {
1467
+ for (const d of disposers) {
1468
+ d.dispose();
1469
+ }
1470
+ };
1471
+ }, [core]);
1472
+ const nodes = selectedNodes(core);
1473
+ const single = nodes.length === 1 ? nodes[0] : void 0;
1474
+ const shownPos = single !== void 0 ? (_a = livePosition == null ? void 0 : livePosition(single.id)) != null ? _a : single.position : void 0;
1475
+ const moveAll = (0, import_react10.useCallback)(
1476
+ (fn) => {
1477
+ const targets = selectedNodes(core);
1478
+ if (targets.length === 0) {
1479
+ return;
1480
+ }
1481
+ core.history.transact("Align", () => {
1482
+ for (const n of targets) {
1483
+ core.commands.execute(new import_editor_core3.MoveNodeCommand(n.id, fn(n)));
1484
+ }
1485
+ });
1486
+ },
1487
+ [core]
1488
+ );
1489
+ const align = (0, import_react10.useCallback)(
1490
+ (kind) => {
1491
+ const targets = selectedNodes(core);
1492
+ if (targets.length === 0) {
1493
+ return;
1494
+ }
1495
+ const xs = targets.map((n) => n.position.x);
1496
+ const ys = targets.map((n) => n.position.y);
1497
+ const minX = Math.min(...xs);
1498
+ const maxX = Math.max(...xs);
1499
+ const minY = Math.min(...ys);
1500
+ const maxY = Math.max(...ys);
1501
+ const cx = (minX + maxX) / 2;
1502
+ const cy = (minY + maxY) / 2;
1503
+ moveAll((n) => {
1504
+ switch (kind) {
1505
+ case "left":
1506
+ return { x: minX, y: n.position.y };
1507
+ case "hcenter":
1508
+ return { x: cx, y: n.position.y };
1509
+ case "right":
1510
+ return { x: maxX, y: n.position.y };
1511
+ case "top":
1512
+ return { x: n.position.x, y: minY };
1513
+ case "vcenter":
1514
+ return { x: n.position.x, y: cy };
1515
+ case "bottom":
1516
+ return { x: n.position.x, y: maxY };
1517
+ }
1518
+ });
1519
+ },
1520
+ [core, moveAll]
1521
+ );
1522
+ const setCoord = (0, import_react10.useCallback)(
1523
+ (axis, value) => {
1524
+ if (single === void 0 || Number.isNaN(value)) {
1525
+ return;
1526
+ }
1527
+ const next = axis === "x" ? { x: value, y: single.position.y } : { x: single.position.x, y: value };
1528
+ core.commands.execute(new import_editor_core3.MoveNodeCommand(single.id, next));
1529
+ },
1530
+ [core, single]
1531
+ );
1532
+ const disabled = nodes.length === 0;
1533
+ const alignButtons = [
1534
+ { kind: "left", label: "Align left", d: "M5 4v16M9 8h8M9 16h5" },
1535
+ { kind: "hcenter", label: "Align centre", d: "M12 4v16M8 9h8M9 15h6" },
1536
+ { kind: "right", label: "Align right", d: "M19 4v16M7 8h8M12 16h3" },
1537
+ { kind: "top", label: "Align top", d: "M4 5h16M8 9v8M16 9v5" },
1538
+ { kind: "vcenter", label: "Align middle", d: "M4 12h16M9 8v8M15 9v6" },
1539
+ { kind: "bottom", label: "Align bottom", d: "M4 19h16M8 7v8M16 12v3" }
1540
+ ];
1541
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("section", { className: "hl-position", children: [
1542
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("h3", { className: "hl-position-title hl-mono", children: "Position" }),
1543
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "hl-position-align", role: "group", "aria-label": "Align", children: alignButtons.map((b) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1544
+ "button",
1545
+ {
1546
+ className: "hl-position-alignbtn",
1547
+ onClick: () => align(b.kind),
1548
+ disabled,
1549
+ title: b.label,
1550
+ "aria-label": b.label,
1551
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("path", { d: b.d }) })
1552
+ },
1553
+ b.kind
1554
+ )) }),
1555
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "hl-position-coords", children: [
1556
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("label", { className: "hl-position-field", children: [
1557
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1558
+ "input",
1559
+ {
1560
+ type: "number",
1561
+ className: "hl-position-input hl-mono",
1562
+ value: shownPos !== void 0 ? Math.round(shownPos.x) : "",
1563
+ disabled: single === void 0,
1564
+ onChange: (e) => setCoord("x", Number(e.target.value)),
1565
+ "aria-label": "X position"
1566
+ }
1567
+ ),
1568
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "hl-position-axis hl-mono", children: "X" })
1569
+ ] }),
1570
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("label", { className: "hl-position-field", children: [
1571
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1572
+ "input",
1573
+ {
1574
+ type: "number",
1575
+ className: "hl-position-input hl-mono",
1576
+ value: shownPos !== void 0 ? Math.round(shownPos.y) : "",
1577
+ disabled: single === void 0,
1578
+ onChange: (e) => setCoord("y", Number(e.target.value)),
1579
+ "aria-label": "Y position"
1580
+ }
1581
+ ),
1582
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "hl-position-axis hl-mono", children: "Y" })
1583
+ ] })
1584
+ ] })
1585
+ ] });
1586
+ }
1587
+
1588
+ // src/command-palette/NodeSearch.tsx
1589
+ var import_react11 = require("react");
1590
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1591
+ var PANEL_WIDTH = 320;
1592
+ var PANEL_MAX_HEIGHT = 360;
1593
+ function NodeSearch({ controller }) {
1594
+ const { state } = controller;
1595
+ const [query, setQuery] = (0, import_react11.useState)("");
1596
+ const [active, setActive] = (0, import_react11.useState)(0);
1597
+ const inputRef = (0, import_react11.useRef)(null);
1598
+ const isOpen = state !== void 0;
1599
+ (0, import_react11.useEffect)(() => {
1600
+ var _a;
1601
+ if (isOpen) {
1602
+ setQuery("");
1603
+ setActive(0);
1604
+ (_a = inputRef.current) == null ? void 0 : _a.focus();
1605
+ }
1606
+ }, [isOpen]);
1607
+ const results = (0, import_react11.useMemo)(
1608
+ () => state !== void 0 ? controller.results(query) : [],
1609
+ [controller, state, query]
1610
+ );
1611
+ const clampedActive = results.length === 0 ? 0 : Math.min(active, results.length - 1);
1612
+ const choose = (0, import_react11.useCallback)(
1613
+ (view) => {
1614
+ if (view !== void 0) {
1615
+ controller.commit(view);
1616
+ }
1617
+ },
1618
+ [controller]
1619
+ );
1620
+ const onKeyDown = (0, import_react11.useCallback)(
1621
+ (event) => {
1622
+ switch (event.key) {
1623
+ case "ArrowDown":
1624
+ event.preventDefault();
1625
+ setActive((i) => results.length === 0 ? 0 : (i + 1) % results.length);
1626
+ break;
1627
+ case "ArrowUp":
1628
+ event.preventDefault();
1629
+ setActive((i) => results.length === 0 ? 0 : (i - 1 + results.length) % results.length);
1630
+ break;
1631
+ case "Enter":
1632
+ event.preventDefault();
1633
+ choose(results[clampedActive]);
1634
+ break;
1635
+ case "Escape":
1636
+ event.preventDefault();
1637
+ controller.close();
1638
+ break;
1639
+ default:
1640
+ break;
1641
+ }
1642
+ },
1643
+ [results, clampedActive, choose, controller]
1644
+ );
1645
+ if (state === void 0) {
1646
+ return null;
1647
+ }
1648
+ const style = clampToViewport(state.screen.x, state.screen.y);
1649
+ const connectMode = state.pending !== void 0;
1650
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
1651
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1652
+ "div",
1653
+ {
1654
+ className: "hl-node-search-scrim",
1655
+ onPointerDown: () => {
1656
+ controller.close();
1657
+ }
1658
+ }
1659
+ ),
1660
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "hl-node-search", style, role: "dialog", "aria-label": "Create node", children: [
1661
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "hl-node-search-field", children: [
1662
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-node-search-glyph", "aria-hidden": true, children: connectMode ? "\u2933" : "\uFF0B" }),
1663
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1664
+ "input",
1665
+ {
1666
+ ref: inputRef,
1667
+ className: "hl-node-search-input",
1668
+ value: query,
1669
+ placeholder: connectMode ? "connect to\u2026" : "search nodes\u2026",
1670
+ onChange: (e) => {
1671
+ setQuery(e.target.value);
1672
+ setActive(0);
1673
+ },
1674
+ onKeyDown,
1675
+ "aria-label": "Search nodes",
1676
+ autoComplete: "off",
1677
+ spellCheck: false
1678
+ }
1679
+ )
1680
+ ] }),
1681
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("ul", { className: "hl-node-search-list", role: "listbox", children: [
1682
+ results.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("li", { className: "hl-node-search-empty hl-mono", children: "no matching nodes" }),
1683
+ results.map((view, index) => {
1684
+ const descriptor = view.result.value;
1685
+ const kind = descriptor.kind;
1686
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1687
+ "li",
1688
+ {
1689
+ role: "option",
1690
+ "aria-selected": index === clampedActive,
1691
+ className: "hl-node-search-item" + (index === clampedActive ? " is-active" : ""),
1692
+ onPointerEnter: () => setActive(index),
1693
+ onPointerDown: (e) => {
1694
+ e.preventDefault();
1695
+ choose(view);
1696
+ },
1697
+ children: [
1698
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1699
+ "span",
1700
+ {
1701
+ className: `hl-node-search-kind hl-mono hl-kind-${kind.toLowerCase()}`,
1702
+ children: kind
1703
+ }
1704
+ ),
1705
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-node-search-label", children: highlight(descriptor.displayName, view.result.match) }),
1706
+ view.connectPortId !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "hl-node-search-port hl-mono", children: view.connectPortId })
1707
+ ]
1708
+ },
1709
+ descriptor.code
1710
+ );
1711
+ })
1712
+ ] })
1713
+ ] })
1714
+ ] });
1715
+ }
1716
+ function highlight(text, match) {
1717
+ const hit = new Set(match.positions);
1718
+ const chars = Array.from(text);
1719
+ return chars.map(
1720
+ (ch, i) => hit.has(i) ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("mark", { className: "hl-node-search-hit", children: ch }, i) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { children: ch }, i)
1721
+ );
1722
+ }
1723
+ function clampToViewport(x, y) {
1724
+ const maxLeft = Math.max(8, window.innerWidth - PANEL_WIDTH - 8);
1725
+ const maxTop = Math.max(8, window.innerHeight - PANEL_MAX_HEIGHT - 8);
1726
+ return {
1727
+ left: Math.min(Math.max(8, x), maxLeft),
1728
+ top: Math.min(Math.max(8, y), maxTop)
1729
+ };
1730
+ }
1731
+
1732
+ // src/command-palette/useNodeSearch.ts
1733
+ var import_react12 = require("react");
1734
+ var import_editor_core4 = require("@hlang-tech/editor-core");
1735
+
1736
+ // src/command-palette/fuzzy.ts
1737
+ function fuzzyScore(query, target) {
1738
+ if (query.length === 0) {
1739
+ return { score: 0, positions: [] };
1740
+ }
1741
+ const q = query.toLowerCase();
1742
+ const t = target.toLowerCase();
1743
+ const positions = [];
1744
+ let score = 0;
1745
+ let qi = 0;
1746
+ let prevMatch = -2;
1747
+ for (let ti = 0; ti < t.length && qi < q.length; ti += 1) {
1748
+ if (t[ti] !== q[qi]) {
1749
+ continue;
1750
+ }
1751
+ positions.push(ti);
1752
+ let point = 1;
1753
+ if (ti === prevMatch + 1) {
1754
+ point += 3;
1755
+ }
1756
+ if (ti === 0 || isBoundary(target, ti)) {
1757
+ point += 2;
1758
+ }
1759
+ point += Math.max(0, 2 - ti * 0.05);
1760
+ score += point;
1761
+ prevMatch = ti;
1762
+ qi += 1;
1763
+ }
1764
+ if (qi < q.length) {
1765
+ return null;
1766
+ }
1767
+ score -= target.length * 0.01;
1768
+ return { score, positions };
1769
+ }
1770
+ function isBoundary(target, index) {
1771
+ const prev = target[index - 1];
1772
+ const cur = target[index];
1773
+ if (prev === void 0 || cur === void 0) {
1774
+ return false;
1775
+ }
1776
+ const isSep = /[\s\-_./]/.test(prev);
1777
+ const isCamel = prev === prev.toLowerCase() && cur === cur.toUpperCase();
1778
+ return isSep || isCamel;
1779
+ }
1780
+ function fuzzyRank(query, candidates) {
1781
+ const results = [];
1782
+ for (const candidate of candidates) {
1783
+ const match = fuzzyScore(query, candidate.text);
1784
+ if (match !== null) {
1785
+ results.push({ value: candidate.value, text: candidate.text, match });
1786
+ }
1787
+ }
1788
+ if (query.length === 0) {
1789
+ return results;
1790
+ }
1791
+ return results.sort((a, b) => b.match.score - a.match.score);
1792
+ }
1793
+
1794
+ // src/command-palette/node-search-model.ts
1795
+ function descriptorCandidates(descriptors) {
1796
+ return descriptors.map((descriptor) => ({ text: descriptor.displayName, value: descriptor }));
1797
+ }
1798
+ function searchNodes(query, descriptors) {
1799
+ return fuzzyRank(query, descriptorCandidates(descriptors));
1800
+ }
1801
+ function connectableCandidates(query, descriptors, pending, validator) {
1802
+ const ranked = searchNodes(query, descriptors);
1803
+ const out = [];
1804
+ for (const result of ranked) {
1805
+ const port = firstCompletingPort(result.value.ports, pending, validator);
1806
+ if (port !== void 0) {
1807
+ out.push({ candidate: result, connectPortId: port });
1808
+ }
1809
+ }
1810
+ return out;
1811
+ }
1812
+ function firstCompletingPort(ports, pending, validator) {
1813
+ for (const port of ports) {
1814
+ const ok = pending.direction === "from-source" ? validator.canLink(pending.fromPort, port).ok : validator.canLink(port, pending.fromPort).ok;
1815
+ if (ok) {
1816
+ return port.id;
1817
+ }
1818
+ }
1819
+ return void 0;
1820
+ }
1821
+
1822
+ // src/command-palette/useNodeSearch.ts
1823
+ function useNodeSearch(core) {
1824
+ const [state, setState] = (0, import_react12.useState)(void 0);
1825
+ const openBlank = (0, import_react12.useCallback)((screen, flow) => {
1826
+ setState({ screen, flow });
1827
+ }, []);
1828
+ const openFromConnection = (0, import_react12.useCallback)(
1829
+ (screen, flow, pending) => {
1830
+ setState({ screen, flow, pending });
1831
+ },
1832
+ []
1833
+ );
1834
+ const close = (0, import_react12.useCallback)(() => {
1835
+ setState(void 0);
1836
+ }, []);
1837
+ const descriptors = core.registry.list();
1838
+ const results = (0, import_react12.useCallback)(
1839
+ (query) => {
1840
+ if ((state == null ? void 0 : state.pending) !== void 0) {
1841
+ return connectableCandidates(
1842
+ query,
1843
+ descriptors,
1844
+ state.pending,
1845
+ core.validator
1846
+ ).map((entry) => ({ result: entry.candidate, connectPortId: entry.connectPortId }));
1847
+ }
1848
+ return searchNodes(query, descriptors).map((result) => ({ result }));
1849
+ },
1850
+ [state, descriptors, core]
1851
+ );
1852
+ const commit = (0, import_react12.useCallback)(
1853
+ (view) => {
1854
+ if (state === void 0) {
1855
+ return;
1856
+ }
1857
+ const descriptor = view.result.value;
1858
+ const node = core.registry.createNode(descriptor.code, state.flow);
1859
+ if (state.pending !== void 0 && view.connectPortId !== void 0) {
1860
+ const pending = state.pending;
1861
+ const connectPortId = view.connectPortId;
1862
+ core.history.transact("Create & Connect Node", () => {
1863
+ core.commands.execute(new import_editor_core4.AddNodeCommand(node));
1864
+ const edgeId = core.idGenerator.nextEdgeId();
1865
+ const newEndpoint = { nodeId: node.id, portId: connectPortId };
1866
+ const [source, target] = pending.direction === "from-source" ? [pending.fromEndpoint, newEndpoint] : [newEndpoint, pending.fromEndpoint];
1867
+ core.commands.execute(new import_editor_core4.ConnectCommand(edgeId, source, target));
1868
+ });
1869
+ } else {
1870
+ core.commands.execute(new import_editor_core4.AddNodeCommand(node));
1871
+ }
1872
+ setState(void 0);
1873
+ },
1874
+ [state, core]
1875
+ );
1876
+ return (0, import_react12.useMemo)(
1877
+ () => ({ state, openBlank, openFromConnection, close, results, commit }),
1878
+ [state, openBlank, openFromConnection, close, results, commit]
1879
+ );
1880
+ }
1881
+
1882
+ // src/command-palette/useNodeSearchTriggers.ts
1883
+ var import_react13 = require("react");
1884
+ var import_react14 = require("@xyflow/react");
1885
+ var DEFAULT_DOUBLE_CLICK_MS = 350;
1886
+ function useNodeSearchTriggers(core, search, options = {}) {
1887
+ var _a, _b;
1888
+ const flow = (0, import_react14.useReactFlow)();
1889
+ const doubleClickMs = (_a = options.doubleClickMs) != null ? _a : DEFAULT_DOUBLE_CLICK_MS;
1890
+ const now = (_b = options.now) != null ? _b : defaultNow;
1891
+ const startRef = (0, import_react13.useRef)(null);
1892
+ const lastPaneClickRef = (0, import_react13.useRef)(Number.NEGATIVE_INFINITY);
1893
+ const onConnectStart = (0, import_react13.useCallback)((_event, params) => {
1894
+ startRef.current = params;
1895
+ }, []);
1896
+ const onConnectEnd = (0, import_react13.useCallback)(
1897
+ (event, connectionState) => {
1898
+ const start = startRef.current;
1899
+ startRef.current = null;
1900
+ if (connectionState.toNode !== null || start === null) {
1901
+ return;
1902
+ }
1903
+ if (start.nodeId === null || start.handleId === null || start.handleType === null) {
1904
+ return;
1905
+ }
1906
+ const point = pointerOf(event);
1907
+ if (point === null) {
1908
+ return;
1909
+ }
1910
+ const fromPort = resolvePort(core, start.nodeId, start.handleId);
1911
+ if (fromPort === void 0) {
1912
+ return;
1913
+ }
1914
+ const pending = {
1915
+ fromEndpoint: { nodeId: start.nodeId, portId: start.handleId },
1916
+ fromPort,
1917
+ // A drag from a 'source' (output) handle needs a target node; a drag
1918
+ // from a 'target' (input) handle needs a source node.
1919
+ direction: start.handleType === "source" ? "from-source" : "from-target"
1920
+ };
1921
+ search.openFromConnection(
1922
+ point,
1923
+ flow.screenToFlowPosition(point),
1924
+ pending
1925
+ );
1926
+ },
1927
+ [core, flow, search]
1928
+ );
1929
+ const onPaneClick = (0, import_react13.useCallback)(
1930
+ (event) => {
1931
+ const t = now();
1932
+ const isDouble = t - lastPaneClickRef.current <= doubleClickMs;
1933
+ lastPaneClickRef.current = t;
1934
+ if (!isDouble) {
1935
+ return;
1936
+ }
1937
+ lastPaneClickRef.current = Number.NEGATIVE_INFINITY;
1938
+ const screen = { x: event.clientX, y: event.clientY };
1939
+ search.openBlank(screen, flow.screenToFlowPosition(screen));
1940
+ },
1941
+ [flow, search, doubleClickMs, now]
1942
+ );
1943
+ return { onConnectStart, onConnectEnd, onPaneClick };
1944
+ }
1945
+ function defaultNow() {
1946
+ return performance.now();
1947
+ }
1948
+ function resolvePort(core, nodeId, portId) {
1949
+ const node = core.model.getNode(nodeId);
1950
+ return node == null ? void 0 : node.ports.find((port) => port.id === portId);
1951
+ }
1952
+ function pointerOf(event) {
1953
+ if ("clientX" in event) {
1954
+ return { x: event.clientX, y: event.clientY };
1955
+ }
1956
+ const touch = event.changedTouches[0];
1957
+ return touch ? { x: touch.clientX, y: touch.clientY } : null;
1958
+ }
1959
+
1960
+ // src/hooks/useEditorHotkeys.ts
1961
+ var import_react15 = require("react");
1962
+ var import_editor_react5 = require("@hlang-tech/editor-react");
1963
+ function isEditableTarget(target) {
1964
+ if (!(target instanceof HTMLElement)) {
1965
+ return false;
1966
+ }
1967
+ const tag = target.tagName;
1968
+ return tag === "INPUT" || tag === "TEXTAREA" || target.isContentEditable;
1969
+ }
1970
+ function useEditorHotkeys(core) {
1971
+ const clipboard = (0, import_editor_react5.useClipboard)(core);
1972
+ (0, import_react15.useEffect)(() => {
1973
+ const onKeyDown = (event) => {
1974
+ if (isEditableTarget(event.target)) {
1975
+ return;
1976
+ }
1977
+ const mod = event.metaKey || event.ctrlKey;
1978
+ if (mod && !event.altKey) {
1979
+ const key = event.key.toLowerCase();
1980
+ if (key === "z" && !event.shiftKey) {
1981
+ event.preventDefault();
1982
+ core.history.undo();
1983
+ return;
1984
+ }
1985
+ if (key === "z" && event.shiftKey || key === "y") {
1986
+ event.preventDefault();
1987
+ core.history.redo();
1988
+ return;
1989
+ }
1990
+ }
1991
+ if (mod && !event.shiftKey && !event.altKey) {
1992
+ const key = event.key.toLowerCase();
1993
+ if (key === "c") {
1994
+ event.preventDefault();
1995
+ clipboard.copy();
1996
+ return;
1997
+ }
1998
+ if (key === "x") {
1999
+ event.preventDefault();
2000
+ clipboard.cut();
2001
+ return;
2002
+ }
2003
+ if (key === "v") {
2004
+ event.preventDefault();
2005
+ clipboard.paste();
2006
+ return;
2007
+ }
2008
+ if (key === "d") {
2009
+ event.preventDefault();
2010
+ clipboard.duplicate();
2011
+ return;
2012
+ }
2013
+ if (key === "a") {
2014
+ event.preventDefault();
2015
+ core.selection.selectNodes(
2016
+ core.model.getNodes().map((n) => n.id),
2017
+ "replace"
2018
+ );
2019
+ return;
2020
+ }
2021
+ }
2022
+ if (event.key === "Delete" || event.key === "Backspace") {
2023
+ event.preventDefault();
2024
+ clipboard.remove();
2025
+ }
2026
+ };
2027
+ window.addEventListener("keydown", onKeyDown);
2028
+ return () => {
2029
+ window.removeEventListener("keydown", onKeyDown);
2030
+ };
2031
+ }, [core, clipboard]);
2032
+ }
2033
+
2034
+ // src/context-menu/NodeContextMenu.tsx
2035
+ var import_react16 = require("react");
2036
+ var import_jsx_runtime27 = require("react/jsx-runtime");
2037
+ var PANEL_WIDTH2 = 208;
2038
+ var ITEM_HEIGHT = 34;
2039
+ var VIEWPORT_MARGIN = 8;
2040
+ var MENU_VPAD = 12;
2041
+ function NodeContextMenu({ controller, items }) {
2042
+ const { state } = controller;
2043
+ const [active, setActive] = (0, import_react16.useState)(0);
2044
+ const menuRef = (0, import_react16.useRef)(null);
2045
+ const isOpen = state !== void 0;
2046
+ (0, import_react16.useEffect)(() => {
2047
+ var _a;
2048
+ if (isOpen) {
2049
+ setActive(0);
2050
+ (_a = menuRef.current) == null ? void 0 : _a.focus();
2051
+ }
2052
+ }, [isOpen]);
2053
+ const rows = (0, import_react16.useMemo)(
2054
+ () => state !== void 0 ? items(state.nodeId) : [],
2055
+ [items, state]
2056
+ );
2057
+ const enabledIndices = (0, import_react16.useMemo)(
2058
+ () => rows.map((r, i) => r.disabled ? -1 : i).filter((i) => i >= 0),
2059
+ [rows]
2060
+ );
2061
+ const move = (0, import_react16.useCallback)(
2062
+ (delta) => {
2063
+ if (enabledIndices.length === 0) {
2064
+ return;
2065
+ }
2066
+ setActive((current) => {
2067
+ var _a;
2068
+ const pos = enabledIndices.indexOf(current);
2069
+ const nextPos = pos === -1 ? 0 : (pos + delta + enabledIndices.length) % enabledIndices.length;
2070
+ return (_a = enabledIndices[nextPos]) != null ? _a : current;
2071
+ });
2072
+ },
2073
+ [enabledIndices]
2074
+ );
2075
+ const choose = (0, import_react16.useCallback)(
2076
+ (item) => {
2077
+ if (item === void 0 || item.disabled) {
2078
+ return;
2079
+ }
2080
+ item.onSelect();
2081
+ controller.close();
2082
+ },
2083
+ [controller]
2084
+ );
2085
+ const onKeyDown = (0, import_react16.useCallback)(
2086
+ (event) => {
2087
+ switch (event.key) {
2088
+ case "ArrowDown":
2089
+ event.preventDefault();
2090
+ move(1);
2091
+ break;
2092
+ case "ArrowUp":
2093
+ event.preventDefault();
2094
+ move(-1);
2095
+ break;
2096
+ case "Enter":
2097
+ event.preventDefault();
2098
+ choose(rows[active]);
2099
+ break;
2100
+ case "Escape":
2101
+ event.preventDefault();
2102
+ controller.close();
2103
+ break;
2104
+ default:
2105
+ break;
2106
+ }
2107
+ },
2108
+ [move, choose, rows, active, controller]
2109
+ );
2110
+ if (state === void 0) {
2111
+ return null;
2112
+ }
2113
+ const style = clampToViewport2(state.screen.x, state.screen.y, rows.length);
2114
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
2115
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2116
+ "div",
2117
+ {
2118
+ className: "hl-ctx-scrim",
2119
+ onPointerDown: () => {
2120
+ controller.close();
2121
+ },
2122
+ onContextMenu: (e) => {
2123
+ e.preventDefault();
2124
+ controller.close();
2125
+ }
2126
+ }
2127
+ ),
2128
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2129
+ "div",
2130
+ {
2131
+ ref: menuRef,
2132
+ className: "hl-ctx-menu",
2133
+ style,
2134
+ role: "menu",
2135
+ "aria-label": "Node actions",
2136
+ tabIndex: -1,
2137
+ onKeyDown,
2138
+ children: rows.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
2139
+ "button",
2140
+ {
2141
+ type: "button",
2142
+ role: "menuitem",
2143
+ disabled: item.disabled,
2144
+ className: "hl-ctx-item" + (item.danger === true ? " is-danger" : "") + (index === active ? " is-active" : ""),
2145
+ onPointerEnter: () => !item.disabled && setActive(index),
2146
+ onPointerDown: (e) => {
2147
+ e.preventDefault();
2148
+ choose(item);
2149
+ },
2150
+ children: [
2151
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-ctx-item-icon", "aria-hidden": true, children: item.icon }),
2152
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-ctx-item-label", children: item.label }),
2153
+ item.shortcut !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "hl-ctx-item-key hl-mono", children: item.shortcut })
2154
+ ]
2155
+ },
2156
+ item.id
2157
+ ))
2158
+ }
2159
+ )
2160
+ ] });
2161
+ }
2162
+ function clampToViewport2(x, y, rowCount) {
2163
+ const height = Math.max(ITEM_HEIGHT, rowCount * ITEM_HEIGHT + MENU_VPAD);
2164
+ const maxLeft = Math.max(VIEWPORT_MARGIN, window.innerWidth - PANEL_WIDTH2 - VIEWPORT_MARGIN);
2165
+ const maxTop = Math.max(VIEWPORT_MARGIN, window.innerHeight - height - VIEWPORT_MARGIN);
2166
+ return {
2167
+ left: Math.min(Math.max(VIEWPORT_MARGIN, x), maxLeft),
2168
+ top: Math.min(Math.max(VIEWPORT_MARGIN, y), maxTop)
2169
+ };
2170
+ }
2171
+
2172
+ // src/context-menu/useNodeContextMenu.ts
2173
+ var import_react17 = require("react");
2174
+ function useNodeContextMenu() {
2175
+ const [state, setState] = (0, import_react17.useState)(void 0);
2176
+ const openForNode = (0, import_react17.useCallback)((screen, nodeId) => {
2177
+ setState({ screen, nodeId });
2178
+ }, []);
2179
+ const close = (0, import_react17.useCallback)(() => {
2180
+ setState(void 0);
2181
+ }, []);
2182
+ return (0, import_react17.useMemo)(() => ({ state, openForNode, close }), [state, openForNode, close]);
2183
+ }
2184
+
2185
+ // src/overlays/Cheatsheet.tsx
2186
+ var import_react18 = require("react");
2187
+ var import_jsx_runtime28 = require("react/jsx-runtime");
2188
+ var DEFAULT_GROUPS = [
2189
+ {
2190
+ title: "Edit",
2191
+ rows: [
2192
+ { keys: "\u2318Z", label: "Undo" },
2193
+ { keys: "\u2318\u21E7Z / \u2318Y", label: "Redo" },
2194
+ { keys: "\u2318C", label: "Copy" },
2195
+ { keys: "\u2318X", label: "Cut" },
2196
+ { keys: "\u2318V", label: "Paste (at cursor)" },
2197
+ { keys: "\u2318D", label: "Duplicate" },
2198
+ { keys: "\u232B", label: "Delete" }
2199
+ ]
2200
+ },
2201
+ {
2202
+ title: "Select & navigate",
2203
+ rows: [
2204
+ { keys: "\u2318A", label: "Select all" },
2205
+ { keys: "Drag", label: "Box-select (left drag)" },
2206
+ { keys: "Middle / Right drag", label: "Pan" },
2207
+ { keys: "Space + drag", label: "Pan" },
2208
+ { keys: "Double-click name", label: "Rename node" }
2209
+ ]
2210
+ },
2211
+ {
2212
+ title: "Build",
2213
+ rows: [
2214
+ { keys: "Double-click canvas", label: "Search & create node" },
2215
+ { keys: "Drag port to empty", label: "Create & connect" },
2216
+ { keys: "Drop node on wire", label: "Insert inline (if compatible)" },
2217
+ { keys: "Drag edge end", label: "Reconnect" },
2218
+ { keys: "?", label: "This cheatsheet" }
2219
+ ]
2220
+ }
2221
+ ];
2222
+ function Cheatsheet({ open, onClose, groups = DEFAULT_GROUPS }) {
2223
+ const dialogRef = (0, import_react18.useRef)(null);
2224
+ (0, import_react18.useEffect)(() => {
2225
+ var _a;
2226
+ if (!open) {
2227
+ return;
2228
+ }
2229
+ (_a = dialogRef.current) == null ? void 0 : _a.focus();
2230
+ const onKey = (e) => {
2231
+ if (e.key === "Escape") {
2232
+ onClose();
2233
+ }
2234
+ };
2235
+ window.addEventListener("keydown", onKey);
2236
+ return () => window.removeEventListener("keydown", onKey);
2237
+ }, [open, onClose]);
2238
+ if (!open) {
2239
+ return null;
2240
+ }
2241
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
2242
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "hl-cheat-scrim", onPointerDown: onClose }),
2243
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
2244
+ "div",
2245
+ {
2246
+ ref: dialogRef,
2247
+ className: "hl-cheat",
2248
+ role: "dialog",
2249
+ "aria-modal": "true",
2250
+ "aria-label": "Keyboard shortcuts",
2251
+ tabIndex: -1,
2252
+ children: [
2253
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "hl-cheat-head", children: [
2254
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "hl-cheat-title", children: "Shortcuts" }),
2255
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("button", { className: "hl-cheat-close", onClick: onClose, "aria-label": "Close", children: "\u2715" })
2256
+ ] }),
2257
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "hl-cheat-grid", children: groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("section", { className: "hl-cheat-group", children: [
2258
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h3", { className: "hl-cheat-group-title hl-mono", children: group.title }),
2259
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("ul", { className: "hl-cheat-rows", children: group.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("li", { className: "hl-cheat-row", children: [
2260
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "hl-cheat-label", children: row.label }),
2261
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("kbd", { className: "hl-cheat-keys hl-mono", children: row.keys })
2262
+ ] }, row.label)) })
2263
+ ] }, group.title)) })
2264
+ ]
2265
+ }
2266
+ )
2267
+ ] });
2268
+ }
2269
+
2270
+ // src/overlays/Toast.tsx
2271
+ var import_react19 = require("react");
2272
+ var import_jsx_runtime29 = require("react/jsx-runtime");
2273
+ var ToastCtx = (0, import_react19.createContext)({ show: () => void 0 });
2274
+ function ToastProvider({ children, duration = 2200 }) {
2275
+ const [items, setItems] = (0, import_react19.useState)([]);
2276
+ const seq = (0, import_react19.useRef)(0);
2277
+ const show = (0, import_react19.useCallback)(
2278
+ (message) => {
2279
+ seq.current += 1;
2280
+ const id = seq.current;
2281
+ setItems((prev) => [...prev, { id, message }]);
2282
+ setTimeout(() => {
2283
+ setItems((prev) => prev.filter((t) => t.id !== id));
2284
+ }, duration);
2285
+ },
2286
+ [duration]
2287
+ );
2288
+ const api = (0, import_react19.useMemo)(() => ({ show }), [show]);
2289
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(ToastCtx.Provider, { value: api, children: [
2290
+ children,
2291
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "hl-toast-stack", "aria-live": "polite", children: items.map((t) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "hl-toast", children: t.message }, t.id)) })
2292
+ ] });
2293
+ }
2294
+ function useToast() {
2295
+ return (0, import_react19.useContext)(ToastCtx);
2296
+ }
2297
+
2298
+ // src/hooks/useLiveSignals.ts
2299
+ var import_react20 = require("react");
2300
+ function useLiveSignals(core, handle, explicitStore) {
2301
+ const contextStore = useLiveSignalStore();
2302
+ const store = explicitStore != null ? explicitStore : contextStore;
2303
+ (0, import_react20.useEffect)(() => {
2304
+ if (store === null) {
2305
+ return;
2306
+ }
2307
+ const syncEdges = () => {
2308
+ const entries = core.model.getEdges().map((edge) => ({
2309
+ id: edge.id,
2310
+ sourceNodeId: edge.source.nodeId,
2311
+ sourcePortId: edge.source.portId,
2312
+ isError: edge.source.portId === "@node/error"
2313
+ }));
2314
+ store.setEdgeIndex(entries);
2315
+ };
2316
+ syncEdges();
2317
+ const added = core.events.on("edgeAdded", syncEdges);
2318
+ const removed = core.events.on("edgeRemoved", syncEdges);
2319
+ return () => {
2320
+ added.dispose();
2321
+ removed.dispose();
2322
+ };
2323
+ }, [core, store]);
2324
+ (0, import_react20.useEffect)(() => {
2325
+ if (store === null) {
2326
+ return;
2327
+ }
2328
+ if (handle === void 0) {
2329
+ store.reset();
2330
+ return;
2331
+ }
2332
+ const activitySub = handle.onActivity((e) => store.markActivity(e.nodeId, e.portId));
2333
+ const errorSub = handle.onError((e) => store.markError(e.nodeId));
2334
+ return () => {
2335
+ activitySub.dispose();
2336
+ errorSub.dispose();
2337
+ store.reset();
2338
+ };
2339
+ }, [store, handle]);
2340
+ }
2341
+ // Annotate the CommonJS export names for ESM import in node:
2342
+ 0 && (module.exports = {
2343
+ AssetGrid,
2344
+ AssetNodeChrome,
2345
+ CanvasChrome,
2346
+ CanvasDock,
2347
+ Cheatsheet,
2348
+ DiagnosticBadge,
2349
+ EditorShell,
2350
+ EditorUIThemeProvider,
2351
+ EmptyState,
2352
+ FlowEdge,
2353
+ HlangNode,
2354
+ InspectorPanel,
2355
+ KIND_ROLE,
2356
+ KIND_TOKEN,
2357
+ LiveSignalProvider,
2358
+ LiveSignalStore,
2359
+ NodeBodySlot,
2360
+ NodeChrome,
2361
+ NodeContextMenu,
2362
+ NodeInteractionProvider,
2363
+ NodeSearch,
2364
+ OutlineTree,
2365
+ ParamForm,
2366
+ PillNodeChrome,
2367
+ PortRow,
2368
+ PositionPanel,
2369
+ PreviewPanel,
2370
+ RailTabs,
2371
+ RunButton,
2372
+ SignalBar,
2373
+ TOKEN_NAMES,
2374
+ ToastProvider,
2375
+ ToolCluster,
2376
+ Toolbar,
2377
+ ToolbarGhostButton,
2378
+ ToolbarStat,
2379
+ UndoRedo,
2380
+ connectableCandidates,
2381
+ createHlangFlowTypes,
2382
+ createTheme,
2383
+ deriveParamSchema,
2384
+ descriptorCandidates,
2385
+ fuzzyRank,
2386
+ fuzzyScore,
2387
+ isEditableTarget,
2388
+ searchNodes,
2389
+ tokenVar,
2390
+ useEdgeFlowing,
2391
+ useEditorHotkeys,
2392
+ useLiveSignalStore,
2393
+ useLiveSignals,
2394
+ useNodeContextMenu,
2395
+ useNodeInteraction,
2396
+ useNodeLiveState,
2397
+ useNodeParams,
2398
+ useNodeSearch,
2399
+ useNodeSearchTriggers,
2400
+ useToast
2401
+ });
2402
+ //# sourceMappingURL=index.js.map