html-overlay-node 0.1.9 โ†’ 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.md CHANGED
@@ -1,440 +1,143 @@
1
- # HTML-overlay-Node
2
-
3
- [![npm version](https://img.shields.io/npm/v/html-overlay-node.svg)](https://www.npmjs.com/package/html-overlay-node)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
-
6
- **HTML-overlay-Node** is a customizable, LiteGraph-style node editor library for building visual programming interfaces. It uses **Canvas rendering** for fast performance and supports node type registration, execution cycle control, custom drawing, HTML overlays, and group management.
7
-
8
- ---
9
-
10
- ## โœจ Features
11
-
12
- - ๐ŸŽจ **Production-quality design** - Professional dark theme with refined aesthetics
13
- - ๐Ÿ”ง **Type registration system** - Easily create custom node types
14
- - ๐Ÿ”Œ **Dual port system** - Exec ports (flow control) and data ports (values)
15
- - โšก **Flexible execution** - Automatic or manual execution modes with trigger nodes
16
- - ๐Ÿ”— **Multiple edge styles** - Curved, orthogonal, or straight connections
17
- - ๐Ÿ’พ **Serialization** - Save and load graphs with `toJSON`/`fromJSON`
18
- - ๐Ÿ–ฑ๏ธ **Rich interactions** - Zoom, pan, drag, box select, and snap-to-grid
19
- - โŒจ๏ธ **Keyboard shortcuts** - Undo/redo, align, group, and more
20
- - ๐ŸŽฏ **Custom drawing** - Per-node custom rendering with `onDraw`
21
- - ๐ŸŒ **HTML overlays** - Embed interactive HTML UIs with proper port layering
22
- - ๐Ÿ“ฆ **Group nodes** - Organize nodes in hierarchical groups
23
- - ๐Ÿช **Event hooks** - Subscribe to graph events for extensibility
24
- - ๐ŸŽฅ **Visual feedback** - Smooth animations and hover states
25
-
26
- ---
27
-
28
- ## Demo
29
-
30
- - ๐Ÿ  [Demo](https://cheonghakim.github.io/HTML-overlay-node/)
31
-
32
- ## ๐Ÿš€ Quick Start
33
-
34
- ```javascript
35
- import { createGraphEditor } from "html-overlay-node";
36
-
37
- // One-liner Initialization!
38
- // Pass a selector or HTMLElement. Canvas and overlays are created automatically.
39
- const editor = createGraphEditor("#editor-container", {
40
- autorun: true,
41
- enablePropertyPanel: true, // Integrated property panel (default: true)
42
- });
43
-
44
- const { graph, registry, addGroup, start } = editor;
45
- ```
46
-
47
- ### ๐Ÿ’… Styles (Required)
48
-
49
- Make sure to import the necessary CSS files for the editor and Property Panel to look correctly:
50
-
51
- ```javascript
52
- import "html-overlay-node/index.css";
53
- import "html-overlay-node/src/ui/PropertyPanel.css";
54
- ```
55
-
56
- // Register and add nodes
57
- registry.register("math/Add", {
58
- title: "Add",
59
- size: { w: 180, h: 80 },
60
- inputs: [
61
- { name: "a", datatype: "number" },
62
- { name: "b", datatype: "number" },
63
- ],
64
- outputs: [{ name: "result", datatype: "number" }],
65
- onCreate(node) {
66
- node.state.a = 0;
67
- node.state.b = 0;
68
- },
69
- onExecute(node, { getInput, setOutput }) {
70
- const a = getInput("a") ?? node.state.a;
71
- const b = getInput("b") ?? node.state.b;
72
- setOutput("result", a + b);
73
- },
74
- });
75
-
76
- // Add nodes
77
- const node1 = graph.addNode("math/Add", { x: 100, y: 100 });
78
- const node2 = graph.addNode("math/Add", { x: 100, y: 200 });
79
-
80
- // Create a group
81
- addGroup({
82
- title: "Math Operations",
83
- x: 50,
84
- y: 50,
85
- width: 300,
86
- height: 300,
87
- color: "#4a5568",
88
- members: [node1.id, node2.id],
89
- });
90
-
91
- start();
92
-
93
- ````
94
-
95
- ---
96
-
97
- ## ๐Ÿ“ฆ Group Management
98
-
99
- HTML-overlay-Node supports organizing nodes into hierarchical groups for better organization.
100
-
101
- ### Creating Groups
102
-
103
- ```javascript
104
- const { addGroup } = editor;
105
-
106
- // Create a group
107
- const group = addGroup({
108
- title: "My Group", // Group name
109
- x: 0, // X position
110
- y: 0, // Y position
111
- width: 400, // Width (min: 100)
112
- height: 300, // Height (min: 60)
113
- color: "#2d3748", // Background color
114
- members: [node1.id, node2.id], // Nodes to include
115
- });
116
- ````
117
-
118
- ### Group Features
119
-
120
- - **Hierarchical Structure**: Groups can contain multiple nodes
121
- - **Automatic Movement**: Nodes inside the group move with the group
122
- - **Resizable**: Resize groups using the handle in the bottom-right corner
123
- - **Custom Colors**: Set group colors for visual organization
124
- - **Local Coordinates**: World and local coordinates automatically managed
125
-
126
- ### Advanced Group Operations
127
-
128
- ```javascript
129
- // Access GroupManager
130
- const groupManager = graph.groupManager;
131
-
132
- // Reparent a node to a group
133
- graph.reparent(node, group);
134
-
135
- // Remove node from group (reparent to root)
136
- graph.reparent(node, null);
137
-
138
- // Resize a group
139
- groupManager.resizeGroup(group.id, 50, 50); // add 50px to width and height
140
-
141
- // Remove a group (children are un-grouped)
142
- groupManager.removeGroup(group.id);
143
-
144
- // Listen to group events
145
- hooks.on("group:change", () => {
146
- console.log("Group structure changed");
147
- });
148
- ```
149
-
150
- ---
151
-
152
- ## ๐ŸŒ HTML Overlays
153
-
154
- Create interactive HTML UIs inside nodes.
155
-
156
- ### Basic Example
157
-
158
- ```javascript
159
- registry.register("ui/TextInput", {
160
- title: "Text Input",
161
- size: { w: 220, h: 100 },
162
- outputs: [{ name: "text", datatype: "string" }],
163
-
164
- html: {
165
- init(node, el, { header, body }) {
166
- el.style.backgroundColor = "#1a1a1a";
167
- el.style.borderRadius = "8px";
168
-
169
- const input = document.createElement("input");
170
- input.type = "text";
171
- input.placeholder = "Enter text...";
172
- Object.assign(input.style, {
173
- width: "100%",
174
- padding: "8px",
175
- background: "#111",
176
- border: "1px solid #444",
177
- color: "#fff",
178
- pointerEvents: "auto", // IMPORTANT: Enable interaction
179
- });
180
-
181
- input.addEventListener("input", (e) => {
182
- node.state.text = e.target.value;
183
- hooks.emit("node:updated", node);
184
- });
185
-
186
- input.addEventListener("mousedown", (e) => e.stopPropagation()); // Prevent drag
187
-
188
- body.appendChild(input);
189
- el._input = input;
190
- },
191
-
192
- update(node, el, { selected }) {
193
- el.style.borderColor = selected ? "#3b82f6" : "#333";
194
- if (el._input.value !== (node.state.text || "")) {
195
- el._input.value = node.state.text || "";
196
- }
197
- },
198
- },
199
-
200
- onCreate(node) {
201
- node.state.text = "";
202
- },
203
-
204
- onExecute(node, { setOutput }) {
205
- setOutput("text", node.state.text || "");
206
- },
207
- });
208
- ```
209
-
210
- ### Best Practices
211
-
212
- 1. **Enable Interaction**: Set `pointerEvents: "auto"` on interactive elements
213
- 2. **Stop Propagation**: Prevent canvas drag with `e.stopPropagation()` on `mousedown`
214
- 3. **Update State**: Emit `"node:updated"` when state changes
215
- 4. **Store References**: Cache DOM elements in `el._refs` for performance
216
- 5. **Port Visibility**: HTML overlays are rendered below ports for proper visibility
217
-
218
- ---
219
-
220
- ## ๐Ÿ”Œ Port Types
221
-
222
- HTML-overlay-Node supports two types of ports for different purposes:
223
-
224
- ### Exec Ports (Flow Control)
225
-
226
- Exec ports control the execution flow between nodes.
227
-
228
- ```javascript
229
- registry.register("util/Print", {
230
- title: "Print",
231
- inputs: [
232
- { name: "exec", portType: "exec" }, // Execution input
233
- { name: "value", portType: "data", datatype: "any" },
234
- ],
235
- outputs: [
236
- { name: "exec", portType: "exec" }, // Execution output
237
- ],
238
- onExecute(node, { getInput }) {
239
- console.log("[Print]", getInput("value"));
240
- },
241
- });
242
- ```
243
-
244
- ### Data Ports (Values)
245
-
246
- Data ports transfer values between nodes.
247
-
248
- ```javascript
249
- registry.register("math/Add", {
250
- title: "Add",
251
- inputs: [
252
- { name: "exec", portType: "exec" },
253
- { name: "a", portType: "data", datatype: "number" },
254
- { name: "b", portType: "data", datatype: "number" },
255
- ],
256
- outputs: [
257
- { name: "exec", portType: "exec" },
258
- { name: "result", portType: "data", datatype: "number" },
259
- ],
260
- onExecute(node, { getInput, setOutput }) {
261
- const result = (getInput("a") ?? 0) + (getInput("b") ?? 0);
262
- setOutput("result", result);
263
- },
264
- });
265
- ```
266
-
267
- ### Visual Style
268
-
269
- - **Exec ports**: Emerald green rounded squares (8ร—8px)
270
- - **Data ports**: Indigo blue circles (10px diameter)
271
- - Both have subtle outlines for depth
272
-
273
- ---
274
-
275
- ## โŒจ๏ธ Keyboard Shortcuts
276
-
277
- | Shortcut | Action |
278
- | ------------------------------- | --------------------------- |
279
- | **Selection** | |
280
- | `Click` | Select node |
281
- | `Shift + Click` | Add to selection |
282
- | `Ctrl + Drag` | Box select |
283
- | **Editing** | |
284
- | `Delete` | Delete selected nodes |
285
- | `Ctrl + Z` | Undo |
286
- | `Ctrl + Y` / `Ctrl + Shift + Z` | Redo |
287
- | **Grouping** | |
288
- | `Ctrl + G` | Create group from selection |
289
- | **Alignment** | |
290
- | `A` | Align nodes horizontally |
291
- | `Shift + A` | Align nodes vertically |
292
- | **Tools** | |
293
- | `G` | Toggle snap-to-grid |
294
- | `?` | Toggle shortcuts help |
295
- | **Navigation** | |
296
- | `Middle Click + Drag` | Pan canvas |
297
- | `Mouse Wheel` | Zoom in/out |
298
- | `Right Click` | Context menu |
299
-
300
- ---
301
-
302
- ## ๐Ÿ“š Complete API
303
-
304
- For full API documentation, see the comments in [src/index.js](src/index.js).
305
-
306
- ### Editor API
307
-
308
- | Property | Description |
309
- | ------------------- | --------------------- |
310
- | `graph` | Graph instance |
311
- | `registry` | Node type registry |
312
- | `hooks` | Event system |
313
- | `render()` | Trigger manual render |
314
- | `start()` | Start execution loop |
315
- | `stop()` | Stop execution loop |
316
- | `addGroup(options)` | Create a group |
317
- | `destroy()` | Cleanup |
318
-
319
- ### Key Methods
320
-
321
- - `registry.register(type, definition)` - Register node type
322
- - `graph.addNode(type, options)` - Create node
323
- - `graph.addEdge(from, fromPort, to, toPort)` - Connect nodes
324
- - `graph.toJSON()` / `graph.fromJSON(json)` - Serialize/deserialize
325
- - `hooks.on(event, callback)` - Subscribe to events
326
-
327
- ### Available Events
328
-
329
- - `node:create` | `node:move` | `node:resize` | `node:updated`
330
- - `edge:create` | `edge:delete`
331
- - `group:change`
332
- - `runner:start` | `runner:stop` | `runner:tick`
333
- - `error`
334
-
335
- ---
336
-
337
- ## ๐ŸŽจ Customization
338
-
339
- ### Theme Colors
340
-
341
- ```javascript
342
- const editor = createGraphEditor(canvas, {
343
- theme: {
344
- bg: "#0d0d0f", // Canvas background
345
- grid: "#1a1a1d", // Grid lines
346
- node: "#16161a", // Node background
347
- nodeBorder: "#2a2a2f", // Node border
348
- title: "#1f1f24", // Node header
349
- text: "#e4e4e7", // Primary text
350
- textMuted: "#a1a1aa", // Secondary text
351
- port: "#6366f1", // Data port color (indigo)
352
- portExec: "#10b981", // Exec port color (emerald)
353
- edge: "#52525b", // Edge color
354
- edgeActive: "#8b5cf6", // Active edge (purple)
355
- accent: "#6366f1", // Accent color
356
- accentBright: "#818cf8", // Bright accent
357
- },
358
- });
359
- ```
360
-
361
- ### Edge Styles
362
-
363
- ```javascript
364
- // Set edge style
365
- editor.renderer.setEdgeStyle("orthogonal"); // or "curved", "line"
366
- ```
367
-
368
- ### Custom Node Drawing
369
-
370
- ```javascript
371
- registry.register("visual/Circle", {
372
- title: "Circle",
373
- size: { w: 120, h: 120 },
374
- onDraw(node, { ctx, theme }) {
375
- const { x, y, width, height } = node.computed;
376
- const centerX = x + width / 2;
377
- const centerY = y + height / 2;
378
- const radius = Math.min(width, height) / 3;
379
-
380
- ctx.beginPath();
381
- ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
382
- ctx.fillStyle = theme.wire;
383
- ctx.fill();
384
- },
385
- });
386
- ```
387
-
388
- ---
389
-
390
- ## ๐Ÿ’พ Serialization
391
-
392
- ```javascript
393
- // Save
394
- const json = graph.toJSON();
395
- localStorage.setItem("myGraph", JSON.stringify(json));
396
-
397
- // Load
398
- const saved = JSON.parse(localStorage.getItem("myGraph"));
399
- graph.fromJSON(saved);
400
- ```
401
-
402
- ---
403
-
404
- ## ๐Ÿ› Troubleshooting
405
-
406
- | Issue | Solution |
407
- | ---------------------------- | --------------------------------------------------- |
408
- | Canvas not rendering | Ensure canvas has explicit width/height |
409
- | Nodes not executing | Call `start()` or set `autorun: true` |
410
- | Type errors | Register node types before using them |
411
- | HTML overlay not interactive | Set `pointerEvents: "auto"` on elements |
412
- | Performance issues | Limit to <1000 nodes, optimize `onExecute`/`onDraw` |
413
-
414
- ---
415
-
416
- ## ๐Ÿค Contributing
417
-
418
- Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md).
419
-
420
- ```bash
421
- npm install # Install dependencies
422
- npm run dev # Start dev server
423
- npm test # Run tests
424
- npm run lint # Check code quality
425
- npm run build # Build library
426
- ```
427
-
428
- ---
429
-
430
- ## ๐Ÿ“„ License
431
-
432
- [MIT](LICENSE) ยฉ cheonghakim
433
-
434
- ---
435
-
436
- ## ๐Ÿ”— Links
437
-
438
- - [GitHub Repository](https://github.com/cheonghakim/html-overlay-node)
439
- - [Issue Tracker](https://github.com/cheonghakim/html-overlay-node/issues)
440
- - [Changelog](CHANGELOG.md)
1
+ # HTML-overlay-Node
2
+
3
+ [![npm version](https://img.shields.io/npm/v/html-overlay-node.svg)](https://www.npmjs.com/package/html-overlay-node)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ **HTML-overlay-Node**๋Š” Canvas์˜ ๊ณ ์„ฑ๋Šฅ ๋ Œ๋”๋ง๊ณผ HTML์˜ ์œ ์—ฐํ•œ UI ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ฒฐํ•ฉํ•œ ์ „๋ฌธ๊ฐ€์šฉ ๋…ธ๋“œ ์—๋””ํ„ฐ ์—”์ง„์ž…๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•œ ์‹œ๊ฐํ™”๋ฅผ ๋„˜์–ด, ๋ณต์žกํ•œ ๋กœ์ง ์„ค๊ณ„์™€ ์‹ค์‹œ๊ฐ„ ์‹คํ–‰ ํ™˜๊ฒฝ์— ์ตœ์ ํ™”๋œ ๋‚ ์นด๋กญ๊ณ  ์„ธ๋ จ๋œ ์—”์ง€๋‹ˆ์–ด๋ง ๊ฒฝํ—˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
7
+
8
+ ---
9
+
10
+ ## โœจ ํ•ต์‹ฌ ๊ธฐ๋Šฅ (Key Features)
11
+
12
+ - **ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๋ Œ๋”๋ง**: ๋…ธ๋“œ ๋ณธ์ฒด์™€ ๋ณต์žกํ•œ ์œ„์ ฏ์€ HTML๋กœ, ์—ฐ๊ฒฐ์„ (Edge)๊ณผ ๊ณ ์ฃผํŒŒ ์• ๋‹ˆ๋ฉ”์ด์…˜์€ Canvas๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ ์„ฑ๋Šฅ๊ณผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์˜ ๊ท ํ˜•์„ ์žก์•˜์Šต๋‹ˆ๋‹ค.
13
+ - **์‹ค์‹œ๊ฐ„ ์‹คํ–‰ ํ๋ฆ„ ์‹œ๊ฐํ™”**: ๋…ธ๋“œ ์‹คํ–‰ ์ƒํƒœ์™€ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ 'Marching Ants' ํ…Œ๋‘๋ฆฌ ๋ฐ ํ๋ฅด๋Š” ์  ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ๊ฐ€์‹œํ™”ํ•˜์—ฌ ๋กœ์ง์˜ ํ๋ฆ„์„ ์ง๊ด€์ ์œผ๋กœ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
14
+ - **์ „๋ฌธ๊ฐ€๊ธ‰ ๋””์ž์ธ ์‹œ์Šคํ…œ**: 2px์˜ ์ •๋ฐ€ํ•œ ๋ผ์šด๋”ฉ, ๊นŠ์ด๊ฐ ์žˆ๋Š” ๋‹คํฌ ํ…Œ๋งˆ, ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์ปฌ๋Ÿฌ ์ฝ”๋”ฉ์œผ๋กœ ๋Œ€๊ทœ๋ชจ ๊ทธ๋ž˜ํ”„์—์„œ๋„ ๋†’์€ ๊ฐ€๋…์„ฑ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
15
+ - **๊ฐ•๋ ฅํ•œ ๋“€์–ผ ํฌํŠธ ์‹œ์Šคํ…œ**: ์‹คํ–‰ ์ œ์–ด(Execution Flow)์™€ ๋ฐ์ดํ„ฐ ์ „์šฉ(Data Flow) ํฌํŠธ๋ฅผ ๋ถ„๋ฆฌ ์ง€์›ํ•˜์—ฌ ํ•จ์ˆ˜ํ˜•/์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋กœ์ง ์„ค๊ณ„๋ฅผ ์™„๋ฒฝํžˆ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
16
+ - **์ƒ์‚ฐ์„ฑ ๋„๊ตฌ**: Undo/Redo, ๊ทธ๋ฃนํ™”(Group), ์ž๋™ ์ •๋ ฌ(Aligning), ๊ทธ๋ฆฌ๋“œ ์Šค๋ƒ…, ๋‹ค์ค‘ ์„ ํƒ ๋“ฑ ์‹ค์ œ ์ž‘์—… ํšจ์œจ์„ ๊ทน๋Œ€ํ™”ํ•˜๋Š” ํŒŒ์›Œ ์œ ์ € ๊ธฐ๋Šฅ์„ ๋‚ด์žฅํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
17
+
18
+ ---
19
+
20
+ ## ๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘ (Quick Start)
21
+
22
+ Vite๋‚˜ Webpack ํ™˜๊ฒฝ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์†์‰ฝ๊ฒŒ ์—๋””ํ„ฐ๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
23
+
24
+ ```javascript
25
+ import { createGraphEditor } from "html-overlay-node";
26
+ import "html-overlay-node/index.css";
27
+
28
+ const editor = createGraphEditor("#editor-container", {
29
+ theme: {
30
+ accent: "#6366f1", // ์ฃผ์š” ๊ฐ•์กฐ ์ƒ‰์ƒ (Indigo)
31
+ flowSpeed: 150 // ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ๋ฆ„ ์†๋„ (px/sec)
32
+ }
33
+ });
34
+
35
+ const { graph, registry, start } = editor;
36
+
37
+ // ๋…ธ๋“œ ๋“ฑ๋ก ๋ฐ ์‹œ์ž‘
38
+ registry.register("math/Add", { ... });
39
+ start();
40
+ ```
41
+
42
+ ---
43
+
44
+ ## ๐Ÿงฉ ํ”Œ๋Ÿฌ๊ทธ์ธ ์‹œ์Šคํ…œ (Plugins & Node Registration)
45
+
46
+ `registry`๋ฅผ ํ†ตํ•ด ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ๋™์ ์œผ๋กœ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
47
+
48
+ ```javascript
49
+ registry.register("math/Multiply", {
50
+ title: "Multiply",
51
+ color: "#f43f5e", // ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿฌ
52
+ inputs: [
53
+ { name: "a", portType: "data" },
54
+ { name: "b", portType: "data" }
55
+ ],
56
+ outputs: [{ name: "result", portType: "data" }],
57
+ // ์‹คํ–‰ ๋กœ์ง ์ •์˜
58
+ onExecute(node, { getInput, setOutput }) {
59
+ const a = getInput("a") ?? 1;
60
+ const b = getInput("b") ?? 1;
61
+ setOutput("result", a * b);
62
+ }
63
+ });
64
+ ```
65
+
66
+ ---
67
+
68
+ ## ๐Ÿ–ผ๏ธ UI & ์œ„์ ฏ (HTML Overlays & Widgets)
69
+
70
+ HTML์˜ ๊ฐ•์ ์„ ์‚ด๋ ค ๋…ธ๋“œ ๋‚ด๋ถ€์— ๋ณต์žกํ•œ UI๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. `renderHtml` ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด DOM ์š”์†Œ๋ฅผ ์ง์ ‘ ์ œ์–ดํ•˜๊ฑฐ๋‚˜ ๋‚ด์žฅ ์œ„์ ฏ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
71
+
72
+ ```javascript
73
+ registry.register("ui/Slider", {
74
+ html: true, // HTML ์˜ค๋ฒ„๋ ˆ์ด ํ™œ์„ฑํ™”
75
+ renderHtml(node, container) {
76
+ container.innerHTML = `<input type="range" class="hon-slider" />`;
77
+ const input = container.querySelector("input");
78
+ input.oninput = (e) => {
79
+ node.state.value = e.target.value;
80
+ // ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ ๊ทธ๋ž˜ํ”„ ๊ฐฑ์‹  ์•Œ๋ฆผ
81
+ };
82
+ }
83
+ });
84
+ ```
85
+
86
+ ---
87
+
88
+ ## ๐ŸŽจ CSS ๋””์ž์ธ ํ† ํฐ (Design Tokens)
89
+
90
+ `index.css`์— ์ •์˜๋œ CSS ๋ณ€์ˆ˜๋ฅผ ๋ฎ์–ด์“ฐ๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋„ ์ „์ฒด ์—๋””ํ„ฐ์˜ ๋ฃฉ์•คํ•„์„ ๋ธŒ๋žœ๋“œ์— ๋งž๊ฒŒ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
91
+
92
+ | ๋ณ€์ˆ˜๋ช… | ์„ค๋ช… | ๊ธฐ๋ณธ๊ฐ’ |
93
+ | :--- | :--- | :--- |
94
+ | `--hon-bg` | ์บ”๋ฒ„์Šค ๋ฐฐ๊ฒฝ์ƒ‰ | `#0d0d0f` |
95
+ | `--hon-node-bg` | ๋…ธ๋“œ ๋‚ด๋ถ€ ๋ฐฐ๊ฒฝ์ƒ‰ | `#16161a` |
96
+ | `--hon-node-border` | ๋…ธ๋“œ ํ…Œ๋‘๋ฆฌ ์ƒ‰์ƒ | `rgba(255,255,255,0.08)` |
97
+ | `--hon-accent` | ๊ฐ•์กฐ ํฌ์ธํŠธ ์ปฌ๋Ÿฌ | `#4f46e5` |
98
+ | `--hon-text` | ๊ธฐ๋ณธ ํ…์ŠคํŠธ ์ƒ‰์ƒ | `#e2e8f0` |
99
+ | `--hon-grid` | ๊ทธ๋ฆฌ๋“œ ์  ์ƒ‰์ƒ | `rgba(255,255,255,0.03)` |
100
+
101
+ ---
102
+
103
+ ## โŒจ๏ธ ์ƒ์‚ฐ์„ฑ ๋‹จ์ถ•ํ‚ค
104
+
105
+ | ๊ธฐ๋Šฅ | ๋‹จ์ถ•ํ‚ค |
106
+ | :--- | :--- |
107
+ | **๋…ธ๋“œ ์‚ญ์ œ** | `Delete` |
108
+ | **์ˆ˜ํ‰/์ˆ˜์ง ์ •๋ ฌ** | `A` / `Shift + A` |
109
+ | **๊ทธ๋ฃน ์ƒ์„ฑ** | `Ctrl + G` |
110
+ | **๊ทธ๋ฆฌ๋“œ ์Šค๋ƒ… ํ† ๊ธ€**| `G` |
111
+ | **์‹คํ–‰ ์ทจ์†Œ/์žฌ์‹คํ–‰** | `Ctrl + Z` / `Ctrl + Y` |
112
+ | **๋ฏธ๋‹ˆ๋งต ํ† ๊ธ€** | `M` |
113
+
114
+ ---
115
+
116
+ ## ๐Ÿ’พ ๋ฐ์ดํ„ฐ ์ง๋ ฌํ™” (Serialization)
117
+
118
+ ๊ทธ๋ž˜ํ”„์˜ ๋ชจ๋“  ์ƒํƒœ๋Š” JSON ํ˜•์‹์œผ๋กœ ์ €์žฅํ•˜๊ณ  ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
119
+
120
+ ```javascript
121
+ // ํ˜„์žฌ ์บ”๋ฒ„์Šค ์ƒํƒœ ์ €์žฅ
122
+ const snapshot = graph.toJSON();
123
+
124
+ // ๋ฐ์ดํ„ฐ ๋ณต์› (ํŠธ๋žœ์ง€์…˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํฌํ•จ)
125
+ graph.fromJSON(snapshot);
126
+ ```
127
+
128
+ ---
129
+
130
+ ## ๐Ÿ› ๏ธ ๊ฐœ๋ฐœ ๊ฐ€์ด๋“œ (Development)
131
+
132
+ ```bash
133
+ npm install # ์˜์กด์„ฑ ์„ค์น˜
134
+ npm run dev # ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹œ์ž‘
135
+ npm test # ํ…Œ์ŠคํŠธ ์‹คํ–‰
136
+ npm run build # ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ
137
+ ```
138
+
139
+ ---
140
+
141
+ ## ๐Ÿ“„ ๋ผ์ด์„ ์Šค
142
+
143
+ [MIT](LICENSE) ยฉ cheonghakim
package/src/core/Graph.js CHANGED
@@ -51,11 +51,13 @@ export class Graph {
51
51
  const available = Array.from(this.registry.types.keys()).join(", ") || "none";
52
52
  throw new Error(`Unknown node type: "${type}". Available types: ${available}`);
53
53
  }
54
+ const height = opts.height || def.size?.h || this._calculateDefaultNodeHeight(def);
55
+
54
56
  const node = new Node({
55
57
  type,
56
58
  title: def.title,
57
- width: def.size?.w,
58
- height: def.size?.h,
59
+ width: opts.width || def.size?.w || 140,
60
+ height,
59
61
  ...opts,
60
62
  });
61
63
  for (const i of def.inputs || []) node.addInput(i.name, i.datatype, i.portType || "data");
@@ -212,11 +214,17 @@ export class Graph {
212
214
  this.hooks?.emit("graph:serialize", json);
213
215
  return json;
214
216
  }
217
+
215
218
  fromJSON(json) {
216
- this.clear();
219
+ this.nodes.clear();
220
+ this.edges.clear();
217
221
 
218
222
  // Restore nodes first
219
223
  for (const nd of json.nodes) {
224
+ const def = this.registry?.types?.get(nd.type);
225
+ const minH = def ? this._calculateDefaultNodeHeight(def) : 60;
226
+ const height = nd.h !== undefined ? nd.h : minH;
227
+
220
228
  const node = new Node({
221
229
  id: nd.id,
222
230
  type: nd.type,
@@ -224,10 +232,10 @@ export class Graph {
224
232
  x: nd.x,
225
233
  y: nd.y,
226
234
  width: nd.w,
227
- height: nd.h,
235
+ height: height,
228
236
  });
237
+
229
238
  // Call onCreate to initialize node with defaults first
230
- const def = this.registry?.types?.get(nd.type);
231
239
  if (def?.onCreate) {
232
240
  def.onCreate(node);
233
241
  }
@@ -264,4 +272,25 @@ export class Graph {
264
272
 
265
273
  return this;
266
274
  }
275
+
276
+ _calculateDefaultNodeHeight(def) {
277
+ const inCount = def.inputs?.length || 0;
278
+ const outCount = def.outputs?.length || 0;
279
+ const maxPorts = Math.max(inCount, outCount);
280
+ const headerHeight = 26;
281
+ const portSpacing = 20;
282
+
283
+ if (def.html) {
284
+ // For HTML overlay nodes: reserve space for content BELOW all port rows.
285
+ // Port idx N bottom: headerHeight + 8 + N*portSpacing + portSpacing/2 + 6
286
+ // = 50 + N*portSpacing (for N=0: 50, N=1: 70, ...)
287
+ // Add ~50px for HTML content + bottom padding.
288
+ const lastPortBottom = maxPorts > 0 ? 50 + (maxPorts - 1) * portSpacing : 26;
289
+ return Math.max(lastPortBottom + 50, 90);
290
+ }
291
+
292
+ const padding = 8;
293
+ let h = headerHeight + padding + (maxPorts * portSpacing) + padding;
294
+ return Math.max(h, 40);
295
+ }
267
296
  }