@theclearsky/react-blender-nodes 0.0.11 → 0.0.13
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/CHANGELOG.md +619 -0
- package/README.md +312 -77
- package/dist/contract-DxqU738Q.js +514 -0
- package/dist/contract.d.ts +1119 -0
- package/dist/index.d.ts +3636 -523
- package/dist/react-blender-nodes-contract.es.js +9 -0
- package/dist/react-blender-nodes.css +1 -1
- package/dist/react-blender-nodes.es.js +34971 -22422
- package/package.json +34 -13
- package/dist/react-blender-nodes.umd.js +0 -305
package/README.md
CHANGED
|
@@ -7,12 +7,67 @@
|
|
|
7
7
|
a flexible and customizable node-based graph editor for web applications.
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
+
> **Note**: This project is not affiliated with Blender Foundation. If you find
|
|
11
|
+
> Blender useful, consider
|
|
12
|
+
> [donating to support their work](https://fund.blender.org/).
|
|
13
|
+
|
|
10
14
|
<p align="center">
|
|
11
15
|
<a href="https://bundlejs.com/?q=%40theclearsky%2Freact-blender-nodes"><img src="https://deno.bundlejs.com/?q=%40theclearsky%2Freact-blender-nodes&badge=detailed&badge-style=for-the-badge" alt="spring-easing's badge" /></a>
|
|
12
16
|
</p>
|
|
13
17
|
|
|
14
18
|

|
|
15
19
|
|
|
20
|
+
## How these projects fit together
|
|
21
|
+
|
|
22
|
+
This library is the engine at the centre of a small family: two plugins extend
|
|
23
|
+
it, and one application proves it in anger.
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
react-blender-nodes · MIT · published
|
|
27
|
+
T H E E N G I N E
|
|
28
|
+
┌────────────────────────────────────────────────────────────┐
|
|
29
|
+
│ ◀── this package │
|
|
30
|
+
│ A Blender-style node-graph editor for React. │
|
|
31
|
+
│ Typed handles · validate → plan → apply state · node │
|
|
32
|
+
│ groups · loops & switches · graph compiler + runner · │
|
|
33
|
+
│ import / export │
|
|
34
|
+
└──────┬──────────────────────┬────────────────────────┬─────┘
|
|
35
|
+
│ │ │
|
|
36
|
+
│ peerDependency │ peerDependency │ file:
|
|
37
|
+
│ >=0.0.13 <1 │ >=0.0.13 <1 │ dependency
|
|
38
|
+
│ │ (via /contract — │
|
|
39
|
+
▼ ▼ React-free) │
|
|
40
|
+
┌──────────────────────┐ ┌──────────────────────┐ │
|
|
41
|
+
│ …-timeline │ │ …-codegen │ │
|
|
42
|
+
│ AGPL-3.0 · published │ │ AGPL-3.0 · published │ │
|
|
43
|
+
├──────────────────────┤ ├──────────────────────┤ │
|
|
44
|
+
│ Keyframed CURVES and │ │ Compiles a graph into│ │
|
|
45
|
+
│ a transport. A curve │ │ a standalone, │ │
|
|
46
|
+
│ becomes a live signal│ │ dependency-free │ │
|
|
47
|
+
│ the running graph │ │ runGraph module. │ │
|
|
48
|
+
│ can read. │ │ No React at runtime. │ │
|
|
49
|
+
└──────────┬───────────┘ └──────────────────────┘ │
|
|
50
|
+
│ │
|
|
51
|
+
│ file: dependency │
|
|
52
|
+
└───────────────────┬──────────────────────────┘
|
|
53
|
+
▼
|
|
54
|
+
┌────────────────────────────────────────────────────────────┐
|
|
55
|
+
│ react-blender-nodes-sound · AGPL-3.0 · private app │
|
|
56
|
+
│ T H E A P P L I C A T I O N │
|
|
57
|
+
├────────────────────────────────────────────────────────────┤
|
|
58
|
+
│ Here the nodes ARE the audio graph (Tone.js / Web Audio): │
|
|
59
|
+
│ draw a waveform and hear it · gate-driven envelopes · │
|
|
60
|
+
│ 16-key polyphony · timeline curves automating any │
|
|
61
|
+
│ parameter while it plays · a spectrally-modelled │
|
|
62
|
+
│ instrument library │
|
|
63
|
+
└────────────────────────────────────────────────────────────┘
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
An arrow points from a package **to the package that depends on it**. This
|
|
67
|
+
library stays MIT so anyone can build on it; the plugins and the app are
|
|
68
|
+
AGPL-3.0-only. The two plugins never import each other — the app is the only
|
|
69
|
+
place they meet.
|
|
70
|
+
|
|
16
71
|
## Quick Links
|
|
17
72
|
|
|
18
73
|
- [](https://theclearsky.github.io/react-blender-nodes/?path=/story/organisms-fullgraph--with-runner) -
|
|
@@ -34,6 +89,15 @@ system with automatic inference, complex data validation, and comprehensive
|
|
|
34
89
|
connection validation to ensure your node graphs are always type-safe and
|
|
35
90
|
error-free.
|
|
36
91
|
|
|
92
|
+
Beyond editing, the library can **execute** your graphs with a built-in runner
|
|
93
|
+
and timeline debugger — or hand them to pluggable **run targets**, including a
|
|
94
|
+
separate AGPL-3.0-only codegen plugin that compiles them to standalone
|
|
95
|
+
JavaScript/TypeScript — compose reusable **node groups**, build control flow
|
|
96
|
+
with first-class **loops** and **switches** (rendered as visual zones), edit
|
|
97
|
+
those structures and node types through in-canvas **drawers**, and step backward
|
|
98
|
+
and forward through every change with full **undo/redo** history. Graph state
|
|
99
|
+
and execution recordings can be exported to and imported from JSON.
|
|
100
|
+
|
|
37
101
|
## Quick Start
|
|
38
102
|
|
|
39
103
|
### Installation
|
|
@@ -51,8 +115,8 @@ import {
|
|
|
51
115
|
makeStateWithAutoInfer,
|
|
52
116
|
makeTypeOfNodeWithAutoInfer,
|
|
53
117
|
makeDataTypeWithAutoInfer,
|
|
54
|
-
} from 'react-blender-nodes';
|
|
55
|
-
import 'react-blender-nodes/style.css';
|
|
118
|
+
} from '@theclearsky/react-blender-nodes';
|
|
119
|
+
import '@theclearsky/react-blender-nodes/style.css';
|
|
56
120
|
|
|
57
121
|
function MyNodeEditor() {
|
|
58
122
|
// Define data types with auto-infer for type safety
|
|
@@ -229,82 +293,220 @@ your graph into an execution plan and runs it — with full debugging support.
|
|
|
229
293
|
|
|
230
294
|

|
|
231
295
|
|
|
232
|
-
###
|
|
296
|
+
### ⚡ Codegen & Pluggable Run Targets — Export Your Graph as Code
|
|
297
|
+
|
|
298
|
+

|
|
299
|
+
|
|
300
|
+
The in-process runner is just the default **run target**. The Run button is a
|
|
301
|
+
split control: register your own named targets, use the built-in
|
|
302
|
+
`jsonIrRunTarget` to export the execution plan as a JSON intermediate
|
|
303
|
+
representation, or install the **separate codegen plugin** to compile a graph
|
|
304
|
+
into a **standalone, dependency-free function** — `runGraph(...)` — in
|
|
305
|
+
JavaScript or typed TypeScript.
|
|
306
|
+
|
|
307
|
+
- **Export as code (plugin)**: `codegenJsRunTarget` / `codegenTsRunTarget` from
|
|
308
|
+
[`@theclearsky/react-blender-nodes-codegen`](https://github.com/TheClearSky/react-blender-nodes-codegen)
|
|
309
|
+
(AGPL-3.0-only, published separately) emit a self-contained `runGraph()` (TS
|
|
310
|
+
adds typed parameters, a return type, and value casts). Built into this
|
|
311
|
+
package: `jsonIrRunTarget` exports the execution plan as JSON, and the default
|
|
312
|
+
`inProcessRunTarget` runs the graph live and feeds the timeline.
|
|
313
|
+
- **Clean signature from Graph I/O**: declare root **Graph Input** / **Graph
|
|
314
|
+
Output** nodes and their handle names become the function's parameters and
|
|
315
|
+
returned-object keys — an all-inlined graph compiles to a tidy
|
|
316
|
+
`function runGraph(a, b) { … return { out, flag }; }` with no plumbing
|
|
317
|
+
arguments.
|
|
318
|
+
- **Opt-in optimization passes**: by default the codegen targets emit a
|
|
319
|
+
faithful, threaded `runGraph`. Enable the codegen-v2 passes per target to get
|
|
320
|
+
the clean output — `assumePureImplementations` runs dead-code elimination
|
|
321
|
+
(drops branches no output depends on); unconnected inputs always inline their
|
|
322
|
+
current value; and `analyzeImplementations` makes value-API nodes whose
|
|
323
|
+
implementation reads inputs through the exported `readInput` intrinsic
|
|
324
|
+
**auto-emit inline as expressions** instead of threading through an
|
|
325
|
+
implementations argument.
|
|
326
|
+
- **Pluggable contract**: a `RunTarget` is either an `execute` target (produces
|
|
327
|
+
an `ExecutionRecord`, drives the timeline, and may support stepping) or an
|
|
328
|
+
`artifact` target (produces a string/download). Register them via the
|
|
329
|
+
`runTargets` prop and pick the default with `defaultRunTargetId`.
|
|
233
330
|
|
|
234
331
|
```tsx
|
|
235
|
-
import { FullGraph,
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
// Process inputs and return outputs
|
|
242
|
-
return { outputHandle: inputs.inputHandle * 2 };
|
|
243
|
-
},
|
|
244
|
-
});
|
|
332
|
+
import { FullGraph, jsonIrRunTarget } from '@theclearsky/react-blender-nodes';
|
|
333
|
+
// Code generation is a separate, AGPL-3.0-only plugin:
|
|
334
|
+
import {
|
|
335
|
+
codegenJsRunTarget,
|
|
336
|
+
codegenTsRunTarget,
|
|
337
|
+
} from '@theclearsky/react-blender-nodes-codegen';
|
|
245
338
|
|
|
246
|
-
//
|
|
339
|
+
// The Run button becomes a split control listing every registered target.
|
|
247
340
|
<FullGraph
|
|
248
341
|
state={state}
|
|
249
342
|
dispatch={dispatch}
|
|
250
343
|
functionImplementations={functionImplementations}
|
|
344
|
+
runTargets={[codegenJsRunTarget, codegenTsRunTarget, jsonIrRunTarget]}
|
|
251
345
|
/>;
|
|
252
346
|
```
|
|
253
347
|
|
|
254
|
-
###
|
|
348
|
+
### 🔁 Loops, 🔀 Switches & Zones
|
|
349
|
+
|
|
350
|
+
Build control flow directly on the canvas. Loops and switches are first-class
|
|
351
|
+
structures backed by dedicated standard nodes, and each renders as a labelled
|
|
352
|
+
**zone** — a frame polygon drawn around the nodes it contains.
|
|
353
|
+
|
|
354
|
+
- **Loops**: Drop a loop-start / loop-stop / loop-end node triplet to define an
|
|
355
|
+
iterative body. The runner compiles the body into a `LoopExecutionBlock` and
|
|
356
|
+
records every iteration, with a configurable max-iteration safety limit.
|
|
357
|
+
- **Switches**: A switch-start / switch-end pair routes execution down a `true`
|
|
358
|
+
or `false` branch based on a condition handle. The compiler resolves the taken
|
|
359
|
+
branch and skips the other, surfacing skipped nodes on the canvas.
|
|
360
|
+
- **Zones**: System zones are created and re-discovered automatically as you add
|
|
361
|
+
structures or change connections. Each zone tracks the body nodes inside it
|
|
362
|
+
and can enforce connection boundaries (blocking edges that cross in or out).
|
|
363
|
+
Zones are scope-local, so structures inside a node group get their own zones.
|
|
364
|
+
- **Nesting**: Loops, switches, and groups can be nested inside one another;
|
|
365
|
+
zone discovery and the compiler resolve nested structures recursively.
|
|
366
|
+
|
|
367
|
+
### 🪟 In-Canvas Editors
|
|
368
|
+
|
|
369
|
+
Structures and node types are edited through slide-out drawers, dispatched via
|
|
370
|
+
the graph state and tracked on `state.activeDrawer`:
|
|
371
|
+
|
|
372
|
+
- **Node Type editor** (`editNodeType`): rename a node type, change its header
|
|
373
|
+
color, and add, remove, or reorder its inputs and outputs.
|
|
374
|
+
- **Loop editor** (`editLoop`): configure the handles carried through the loop
|
|
375
|
+
triplet, organized into levels.
|
|
376
|
+
- **Switch editor** (`editSwitch`): configure the handles carried through the
|
|
377
|
+
switch pair across its true/false branches.
|
|
378
|
+
- **Graph I/O editor** (`editGraphInput` / `editGraphOutput`): rename, reorder,
|
|
379
|
+
add, or delete the handles of a root **Graph Input / Output** node — the
|
|
380
|
+
graph's I/O boundary, whose handle names define the `runGraph(...)` signature
|
|
381
|
+
used by codegen.
|
|
382
|
+
|
|
383
|
+

|
|
384
|
+
|
|
385
|
+
The Node Type / Node Group editor (`editNodeType`) edits a type's name, header
|
|
386
|
+
color, and its full input/output list — including grouping handles into
|
|
387
|
+
collapsible **panels** and drag-reordering them:
|
|
388
|
+
|
|
389
|
+

|
|
390
|
+
|
|
391
|
+
Deleting a handle or channel that carries connections opens a **deletion
|
|
392
|
+
review** — it previews exactly which connections would break (with an expandable
|
|
393
|
+
mini-map highlighting them) and lets you include or exclude each deletion before
|
|
394
|
+
committing:
|
|
395
|
+
|
|
396
|
+

|
|
397
|
+
|
|
398
|
+
### ↩️ Undo / Redo History
|
|
399
|
+
|
|
400
|
+
Every structural edit is recorded in an Immer-patch-based undo/redo history, so
|
|
401
|
+
users can freely step backward and forward.
|
|
402
|
+
|
|
403
|
+
- **Patch-based**: history stores forward and inverse Immer patches per entry —
|
|
404
|
+
compact and exact, with a configurable `maxSize`.
|
|
405
|
+
- **Smart undoability**: viewport changes, navigation, drawer open/close, and
|
|
406
|
+
selection-only ReactFlow updates are intentionally _not_ recorded.
|
|
407
|
+
- **Batching**: `BEGIN_BATCH` / `END_BATCH` collapse a sequence of related edits
|
|
408
|
+
(e.g. a multi-node drag) into a single undo step.
|
|
409
|
+
- **Keyboard shortcuts**: `<FullGraph>` listens for `Ctrl+Z` / `Ctrl+Shift+Z` /
|
|
410
|
+
`Ctrl+Y` by default (toggle with `enableUndoRedoShortcuts`).
|
|
411
|
+
- **Serializable**: history can be exported and re-imported alongside graph
|
|
412
|
+
state (non-serializable patch values such as Zod schemas are stripped).
|
|
413
|
+
|
|
414
|
+
### 📡 Graph Event Stream
|
|
415
|
+
|
|
416
|
+
For tests, dev tooling, and telemetry, subscribe to a single unified
|
|
417
|
+
observability stream via `onGraphEvent`. Reducer-layer events (`action:applied`
|
|
418
|
+
/ `action:rejected` / `state:committed`) carry typed payloads (e.g. an
|
|
419
|
+
`action:rejected` event carries the original `ValidationError` so you can switch
|
|
420
|
+
on `.code`), and UI-layer events (`ui:drag:ended` / `ui:delete:attempted` /
|
|
421
|
+
`ui:state:imported` / `ui:recording:imported`) cover moments that bypass the
|
|
422
|
+
reducer. Pass the _same_ handler to both
|
|
423
|
+
`useFullGraph(initialState, { onGraphEvent })` and
|
|
424
|
+
`<FullGraph onGraphEvent={...} />` to receive every event.
|
|
255
425
|
|
|
256
|
-
|
|
257
|
-
instead of relying on the built-in runner UI:
|
|
426
|
+
### Usage
|
|
258
427
|
|
|
259
428
|
```tsx
|
|
260
|
-
import {
|
|
429
|
+
import {
|
|
430
|
+
FullGraph,
|
|
431
|
+
useFullGraph,
|
|
432
|
+
makeFunctionImplementationsWithAutoInfer,
|
|
433
|
+
} from '@theclearsky/react-blender-nodes';
|
|
434
|
+
|
|
435
|
+
// Define what each node type does when executed.
|
|
436
|
+
// An implementation receives positional args: (inputs, outputs, context).
|
|
437
|
+
// `inputs` is a ReadonlyMap keyed by handle *name*; read a connected value
|
|
438
|
+
// via inputs.get('Name')?.connections[0]?.value. Return a Map of output
|
|
439
|
+
// handle *names* to computed values (sync Map or Promise<Map>).
|
|
440
|
+
const functionImplementations = makeFunctionImplementationsWithAutoInfer({
|
|
441
|
+
myNodeType: async (inputs) => {
|
|
442
|
+
const value = Number(inputs.get('Input')?.connections[0]?.value ?? 0);
|
|
443
|
+
return new Map([['Output', value * 2]]);
|
|
444
|
+
},
|
|
445
|
+
});
|
|
261
446
|
|
|
262
447
|
function MyExecutableGraph() {
|
|
263
448
|
const { state, dispatch } = useFullGraph(initialState);
|
|
264
449
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
450
|
+
// Pass implementations to FullGraph to enable the runner
|
|
451
|
+
return (
|
|
452
|
+
<FullGraph
|
|
453
|
+
state={state}
|
|
454
|
+
dispatch={dispatch}
|
|
455
|
+
functionImplementations={functionImplementations}
|
|
456
|
+
/>
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Controlling execution
|
|
462
|
+
|
|
463
|
+
Execution is driven through `FullGraph`'s runner props — the runner hook itself
|
|
464
|
+
(`useNodeRunner`) is internal to `FullGraph` and is not exported. Register run
|
|
465
|
+
targets, pick the default, seed the root Graph Input, and observe or control the
|
|
466
|
+
execution record:
|
|
467
|
+
|
|
468
|
+
```tsx
|
|
469
|
+
import { useState } from 'react';
|
|
470
|
+
import {
|
|
471
|
+
FullGraph,
|
|
472
|
+
useFullGraph,
|
|
473
|
+
jsonIrRunTarget,
|
|
474
|
+
type ExecutionRecord,
|
|
475
|
+
} from '@theclearsky/react-blender-nodes';
|
|
476
|
+
|
|
477
|
+
function MyExecutableGraph() {
|
|
478
|
+
const { state, dispatch } = useFullGraph(initialState);
|
|
479
|
+
const [record, setRecord] = useState<ExecutionRecord | undefined>();
|
|
292
480
|
|
|
293
481
|
return (
|
|
294
|
-
<
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
</div>
|
|
482
|
+
<FullGraph
|
|
483
|
+
state={state}
|
|
484
|
+
dispatch={dispatch}
|
|
485
|
+
functionImplementations={functionImplementations}
|
|
486
|
+
runTargets={[jsonIrRunTarget]} // the Run button becomes a split control
|
|
487
|
+
rootInputs={{ a: 2, b: 3 }} // seeds the root Graph Input by handle name (or id)
|
|
488
|
+
executionRecord={record} // controlled record — you own its lifecycle
|
|
489
|
+
onExecutionRecordChange={setRecord}
|
|
490
|
+
/>
|
|
304
491
|
);
|
|
305
492
|
}
|
|
306
493
|
```
|
|
307
494
|
|
|
495
|
+
For headless use — CI checks, tooling, or inspecting what the runner will do —
|
|
496
|
+
compile a graph directly and serialize the plan:
|
|
497
|
+
|
|
498
|
+
```ts
|
|
499
|
+
import {
|
|
500
|
+
compile,
|
|
501
|
+
serializeExecutionPlan,
|
|
502
|
+
} from '@theclearsky/react-blender-nodes';
|
|
503
|
+
|
|
504
|
+
const plan = compile(state, functionImplementations, {
|
|
505
|
+
maxLoopIterations: 100,
|
|
506
|
+
});
|
|
507
|
+
console.log(JSON.stringify(serializeExecutionPlan(plan), null, 2));
|
|
508
|
+
```
|
|
509
|
+
|
|
308
510
|
### Import/Export & Automatic Repair
|
|
309
511
|
|
|
310
512
|
Graph state and execution recordings can be exported to JSON and re-imported
|
|
@@ -316,7 +518,7 @@ repair common issues via opt-in repair strategies.
|
|
|
316
518
|
Pass a `repair` object to `importGraphState` to enable automatic fixes:
|
|
317
519
|
|
|
318
520
|
```tsx
|
|
319
|
-
import { importGraphState } from 'react-blender-nodes';
|
|
521
|
+
import { importGraphState } from '@theclearsky/react-blender-nodes';
|
|
320
522
|
|
|
321
523
|
const result = importGraphState(json, {
|
|
322
524
|
dataTypes: myDataTypes,
|
|
@@ -326,7 +528,8 @@ const result = importGraphState(json, {
|
|
|
326
528
|
removeDuplicateNodeIds: true, // Deduplicate nodes with the same ID (keep first)
|
|
327
529
|
removeDuplicateEdgeIds: true, // Deduplicate edges with the same ID (keep first)
|
|
328
530
|
fillMissingDefaults: true, // Fill missing optional fields (viewport, etc.) with defaults
|
|
329
|
-
rehydrateDataTypeObjects: true, //
|
|
531
|
+
rehydrateDataTypeObjects: true, // Effectively always-on — the importer always rebuilds handle dataType objects from provided dataTypes (this flag is not read)
|
|
532
|
+
normalizeConnectionOrder: true, // Repack imported fan-in connection orders to contiguous 0..n-1
|
|
330
533
|
},
|
|
331
534
|
});
|
|
332
535
|
|
|
@@ -343,12 +546,12 @@ if (result.success) {
|
|
|
343
546
|
Pass a `repair` object to `importExecutionRecord` for recording-specific fixes:
|
|
344
547
|
|
|
345
548
|
```tsx
|
|
346
|
-
import { importExecutionRecord } from 'react-blender-nodes';
|
|
549
|
+
import { importExecutionRecord } from '@theclearsky/react-blender-nodes';
|
|
347
550
|
|
|
348
551
|
const result = importExecutionRecord(json, {
|
|
349
552
|
repair: {
|
|
350
|
-
sanitizeNonSerializableValues: true, //
|
|
351
|
-
removeOrphanSteps: true, // Remove steps
|
|
553
|
+
sanitizeNonSerializableValues: true, // No-op — values parsed from JSON are already serializable; kept for API symmetry
|
|
554
|
+
removeOrphanSteps: true, // Remove malformed steps missing nodeId, nodeTypeId, or stepIndex
|
|
352
555
|
},
|
|
353
556
|
});
|
|
354
557
|
```
|
|
@@ -498,24 +701,52 @@ const handleShapes = [
|
|
|
498
701
|
// Clicking a node type adds it at the cursor position
|
|
499
702
|
```
|
|
500
703
|
|
|
501
|
-
## 🎨 Styling
|
|
704
|
+
## 🎨 Styling & Theming
|
|
705
|
+
|
|
706
|
+
The whole graph — canvas, nodes, handles, menus, runner panel, timeline, and the
|
|
707
|
+
in-canvas editor drawers — is retheme-able from a single typed theme object.
|
|
708
|
+
Below: the same graph under the built-in path plus two custom presets (a
|
|
709
|
+
cyberpunk "Neon Heist" and a comic "Halftone Pop") — nodes, edges, run button,
|
|
710
|
+
background grid, and accents all follow the active theme.
|
|
711
|
+
|
|
712
|
+

|
|
502
713
|
|
|
503
|
-
|
|
504
|
-
|
|
714
|
+

|
|
715
|
+
|
|
716
|
+
Import the stylesheet once; the graph ships with the default Blender-style dark
|
|
717
|
+
look:
|
|
505
718
|
|
|
506
719
|
```css
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
720
|
+
@import '@theclearsky/react-blender-nodes/style.css';
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
To retheme the graph, wrap it in the optional `GraphThemeProvider` — a theme is
|
|
724
|
+
a typed map of per-component/per-slot Tailwind className overrides plus a
|
|
725
|
+
`reactFlow` section, deep-merged over a named preset (`'blenderDark'` |
|
|
726
|
+
`'light'`):
|
|
727
|
+
|
|
728
|
+
```tsx
|
|
729
|
+
import {
|
|
730
|
+
FullGraph,
|
|
731
|
+
GraphThemeProvider,
|
|
732
|
+
} from '@theclearsky/react-blender-nodes';
|
|
733
|
+
|
|
734
|
+
<GraphThemeProvider preset='light' theme={{ node: { header: 'rounded-none' } }}>
|
|
735
|
+
<FullGraph state={state} dispatch={dispatch} />
|
|
736
|
+
</GraphThemeProvider>;
|
|
517
737
|
```
|
|
518
738
|
|
|
739
|
+
Without a provider the graph keeps its default look — theming is purely
|
|
740
|
+
additive. The built-in presets work out of the box (their classes ship in
|
|
741
|
+
`style.css`); classes you write yourself need your own Tailwind v4 build
|
|
742
|
+
scanning the files that contain them. Var-driven surfaces (scrollbars, glows,
|
|
743
|
+
timeline accents) are recolored through CSS variables on the `root` slot, e.g.
|
|
744
|
+
`theme={{ root: '[--color-graph-menu-bg:#f5f5f5]' }}` — re-declaring the
|
|
745
|
+
`@theme inline` tokens from your own CSS does NOT restyle the pre-built
|
|
746
|
+
stylesheet (those utilities inline their values at build time). See
|
|
747
|
+
[docs/ui/themingDoc.md](docs/ui/themingDoc.md) for the full slot map, the three
|
|
748
|
+
theming mechanisms, and the portal caveats.
|
|
749
|
+
|
|
519
750
|
## 📚 Documentation
|
|
520
751
|
|
|
521
752
|
### Interactive Documentation
|
|
@@ -549,8 +780,8 @@ includes an ASCII architecture diagram, cross-feature dependency maps, and a
|
|
|
549
780
|
| Modifying graph editor UI | `fullGraphDoc`, `configurableNodeDoc`, `contextMenuDoc` |
|
|
550
781
|
| Working with state/reducer | `stateManagementDoc`, `immerDoc`, `edgesDoc` |
|
|
551
782
|
|
|
552
|
-
See the [full index](./docs/index.md) for all
|
|
553
|
-
links organized by tier.
|
|
783
|
+
See the [full index](./docs/index.md) for all 39 documentation files with
|
|
784
|
+
relative links organized by tier.
|
|
554
785
|
|
|
555
786
|
### Component API
|
|
556
787
|
|
|
@@ -576,6 +807,12 @@ interface FullGraphProps {
|
|
|
576
807
|
executionRecord?: ExecutionRecord | null;
|
|
577
808
|
/** Called whenever the execution record changes (run completes, reset, load, etc.). */
|
|
578
809
|
onExecutionRecordChange?: (record: ExecutionRecord | null) => void;
|
|
810
|
+
/** Unified observability stream for UI lifecycle events (drag end, delete-attempt verdict, import outcomes). Pair with the same handler on useFullGraph for reducer-layer events. */
|
|
811
|
+
onGraphEvent?: (event: GraphEvent) => void;
|
|
812
|
+
/** Registry of custom input components keyed by DataTypeUniqueId, for data types whose underlyingType resolves to 'unsupportedDirectly'. */
|
|
813
|
+
inputComponents?: InputComponentRegistry;
|
|
814
|
+
/** Whether to listen for Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y undo/redo keyboard shortcuts. Defaults to true. */
|
|
815
|
+
enableUndoRedoShortcuts?: boolean;
|
|
579
816
|
}
|
|
580
817
|
```
|
|
581
818
|
|
|
@@ -601,8 +838,6 @@ interface ConfigurableNodeProps {
|
|
|
601
838
|
nodeResizerProps?: NodeResizerWithMoreControlsProps;
|
|
602
839
|
/** Node type unique id */
|
|
603
840
|
nodeTypeUniqueId?: string;
|
|
604
|
-
/** Whether to show the node open button (used by node groups) */
|
|
605
|
-
showNodeOpenButton?: boolean;
|
|
606
841
|
/** Runner visual state for this node (undefined = no runner overlay) */
|
|
607
842
|
runnerVisualState?: NodeVisualState;
|
|
608
843
|
/** Errors from the runner for this node */
|