@kabel-project/kabel 1.0.7

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.
Files changed (152) hide show
  1. package/(1.0.7)kabel.md +18 -0
  2. package/README.md +96 -0
  3. package/_READ_ME_MEDIA_/documentation/docs.md +293 -0
  4. package/_READ_ME_MEDIA_/workspace.png +0 -0
  5. package/comment-renderer/renderer.ts +228 -0
  6. package/controllers/base.ts +186 -0
  7. package/controllers/wasd.ts +132 -0
  8. package/docs/README.md +98 -0
  9. package/docs/_media/docs.md +289 -0
  10. package/docs/_media/index.html +168 -0
  11. package/docs/_media/workspace.png +0 -0
  12. package/docs/classes/CommentModel.md +271 -0
  13. package/docs/classes/CommentRenderer.md +457 -0
  14. package/docs/classes/ConnectableField.md +597 -0
  15. package/docs/classes/Connection.md +191 -0
  16. package/docs/classes/ContextMenuHTML.md +163 -0
  17. package/docs/classes/Coordinates.md +187 -0
  18. package/docs/classes/DropdownContainer.md +300 -0
  19. package/docs/classes/DummyField.md +393 -0
  20. package/docs/classes/Eventer.md +185 -0
  21. package/docs/classes/Field.md +461 -0
  22. package/docs/classes/InjectMsg.md +85 -0
  23. package/docs/classes/NodeSvg.md +1011 -0
  24. package/docs/classes/NumberField.md +559 -0
  25. package/docs/classes/OptConnectField.md +624 -0
  26. package/docs/classes/Renderer.md +1636 -0
  27. package/docs/classes/RendererConstants.md +343 -0
  28. package/docs/classes/Representer.md +95 -0
  29. package/docs/classes/RepresenterNode.md +175 -0
  30. package/docs/classes/TextField.md +559 -0
  31. package/docs/classes/Toolbox.md +172 -0
  32. package/docs/classes/WASDController.md +616 -0
  33. package/docs/classes/Widget.md +195 -0
  34. package/docs/classes/WorkspaceController.md +385 -0
  35. package/docs/classes/WorkspaceCoords.md +218 -0
  36. package/docs/classes/WorkspaceSvg.md +1380 -0
  37. package/docs/functions/clearMainWorkspace.md +20 -0
  38. package/docs/functions/getMainWorkspace.md +19 -0
  39. package/docs/functions/inject.md +35 -0
  40. package/docs/functions/setMainWorkspace.md +28 -0
  41. package/docs/globals.md +95 -0
  42. package/docs/interfaces/ColorStyle.md +43 -0
  43. package/docs/interfaces/ConnectorToFrom.md +57 -0
  44. package/docs/interfaces/DrawState.md +81 -0
  45. package/docs/interfaces/FieldConnectionData.md +25 -0
  46. package/docs/interfaces/FieldOptions.md +63 -0
  47. package/docs/interfaces/FieldRawBoxData.md +25 -0
  48. package/docs/interfaces/FieldVisualInfo.md +65 -0
  49. package/docs/interfaces/GridOptions.md +61 -0
  50. package/docs/interfaces/InjectOptions.md +133 -0
  51. package/docs/interfaces/InputFieldJson.md +50 -0
  52. package/docs/interfaces/KabelCommentRendering.md +31 -0
  53. package/docs/interfaces/KabelInterface.md +469 -0
  54. package/docs/interfaces/KabelNodeRendering.md +77 -0
  55. package/docs/interfaces/KabelUIX.md +105 -0
  56. package/docs/interfaces/KabelUtils.md +215 -0
  57. package/docs/interfaces/NodeEvents.md +42 -0
  58. package/docs/interfaces/NodeJson.md +104 -0
  59. package/docs/interfaces/NodePrototype.md +82 -0
  60. package/docs/interfaces/RegisteredEl.md +53 -0
  61. package/docs/interfaces/SerializedNode.md +128 -0
  62. package/docs/interfaces/TblxCategoryStruct.md +41 -0
  63. package/docs/interfaces/TblxFieldStruct.md +28 -0
  64. package/docs/interfaces/TblxNodeStruct.md +35 -0
  65. package/docs/interfaces/WidgetOptions.md +115 -0
  66. package/docs/interfaces/WidgetPrototypeList.md +15 -0
  67. package/docs/type-aliases/AnyField.md +13 -0
  68. package/docs/type-aliases/AnyFieldCls.md +13 -0
  69. package/docs/type-aliases/Color.md +13 -0
  70. package/docs/type-aliases/Connectable.md +13 -0
  71. package/docs/type-aliases/EventArgs.md +11 -0
  72. package/docs/type-aliases/EventSetupFn.md +25 -0
  73. package/docs/type-aliases/Hex.md +13 -0
  74. package/docs/type-aliases/RGBObject.md +37 -0
  75. package/docs/type-aliases/RGBString.md +13 -0
  76. package/docs/type-aliases/RGBTuple.md +13 -0
  77. package/docs/type-aliases/TblxObjStruct.md +52 -0
  78. package/docs/variables/CategoryColors.md +29 -0
  79. package/docs/variables/FieldMap.md +41 -0
  80. package/docs/variables/NodePrototypes.md +18 -0
  81. package/docs/variables/default.md +11 -0
  82. package/events/comment-drag-handle.ts +61 -0
  83. package/events/comment-input.ts +291 -0
  84. package/events/connection-line.ts +68 -0
  85. package/events/connector.ts +116 -0
  86. package/events/draggable.ts +119 -0
  87. package/events/events.ts +7 -0
  88. package/events/input-box.ts +213 -0
  89. package/events/node-x-btn.ts +25 -0
  90. package/index.d.ts +4 -0
  91. package/package.json +49 -0
  92. package/renderers/apollo/apollo.ts +21 -0
  93. package/renderers/apollo/constants.ts +40 -0
  94. package/renderers/apollo/renderer.ts +331 -0
  95. package/renderers/atlas/atlas.ts +15 -0
  96. package/renderers/constants.ts +87 -0
  97. package/renderers/renderer.ts +1288 -0
  98. package/renderers/representer-node.ts +52 -0
  99. package/renderers/representer.ts +25 -0
  100. package/src/category.ts +107 -0
  101. package/src/colors.ts +20 -0
  102. package/src/comment.ts +142 -0
  103. package/src/connection.ts +114 -0
  104. package/src/context-menu.ts +194 -0
  105. package/src/coordinates.ts +74 -0
  106. package/src/core.ts +202 -0
  107. package/src/ctx-menu-registry.ts +143 -0
  108. package/src/dropdown-menu.ts +215 -0
  109. package/src/field.ts +595 -0
  110. package/src/flyout.ts +165 -0
  111. package/src/fonts-manager.ts +38 -0
  112. package/src/grid.ts +162 -0
  113. package/src/headless-node.ts +27 -0
  114. package/src/index.ts +115 -0
  115. package/src/inject-headless.ts +18 -0
  116. package/src/inject.ts +213 -0
  117. package/src/main-workspace.ts +51 -0
  118. package/src/mutator.ts +40 -0
  119. package/src/node-types.ts +27 -0
  120. package/src/nodesvg.ts +756 -0
  121. package/src/prototypes.ts +9 -0
  122. package/src/renderer-map.ts +86 -0
  123. package/src/styles.css +224 -0
  124. package/src/toolbox.ts +125 -0
  125. package/src/types.ts +205 -0
  126. package/src/undo-redo.ts +87 -0
  127. package/src/visual-types.ts +29 -0
  128. package/src/widget-prototypes.ts +11 -0
  129. package/src/widget.ts +139 -0
  130. package/src/workspace-coords.ts +14 -0
  131. package/src/workspace-svg.ts +736 -0
  132. package/src/workspace.ts +155 -0
  133. package/test-server.js +61 -0
  134. package/themes/dark.ts +32 -0
  135. package/themes/default.ts +28 -0
  136. package/themes/themes.ts +9 -0
  137. package/tsconfig.json +25 -0
  138. package/typedoc.json +10 -0
  139. package/util/emitter.ts +33 -0
  140. package/util/env.ts +11 -0
  141. package/util/escape-html.ts +22 -0
  142. package/util/eventer.ts +108 -0
  143. package/util/has-prop.ts +4 -0
  144. package/util/parse-color.ts +42 -0
  145. package/util/path.ts +99 -0
  146. package/util/styler.ts +41 -0
  147. package/util/uid.ts +184 -0
  148. package/util/unescape-html.ts +22 -0
  149. package/util/user-state.ts +68 -0
  150. package/util/wait-anim-frames.ts +24 -0
  151. package/util/window-listeners.ts +62 -0
  152. package/webpack.config.js +80 -0
@@ -0,0 +1,186 @@
1
+ import WorkspaceSvg from '../src/workspace-svg';
2
+ import userState from '../util/user-state';
3
+
4
+ interface Vec2 { x: number; y: number; }
5
+
6
+ export default class WorkspaceController {
7
+ workspace: WorkspaceSvg;
8
+
9
+ keysDown: Set<string>;
10
+ mouseBtns: Set<number>;
11
+ mousePos: Vec2;
12
+ lastMousePos: Vec2;
13
+ isDragging: boolean;
14
+
15
+ wheelDelta: number;
16
+ movedListeners: (() => void)[];
17
+ _lastMoveFire = 0;
18
+ _moveThrottleMs = 100; // bump this to whatever doesn't lag you
19
+ _queuedMove = false;
20
+ _moveTimeout: any = null;
21
+ _updateInt: any;
22
+
23
+ constructor(workspace: WorkspaceSvg) {
24
+ this.workspace = workspace;
25
+
26
+ this.keysDown = new Set();
27
+ this.mouseBtns = new Set();
28
+ this.mousePos = { x: 0, y: 0 };
29
+ this.lastMousePos = { x: 0, y: 0 };
30
+ this.isDragging = false;
31
+ this.wheelDelta = 0;
32
+ this.movedListeners = [];
33
+
34
+ this._setupListeners();
35
+
36
+ this._updateInt = setInterval(() => this.update(), 16);
37
+ }
38
+ addMoveListener(cb: () => void) {
39
+ this.movedListeners.push(cb);
40
+ }
41
+
42
+ removeMoveListener(cb: () => void) {
43
+ const i = this.movedListeners.indexOf(cb);
44
+ if (i !== -1) this.movedListeners.splice(i, 1);
45
+ }
46
+
47
+ fireDidMove() {
48
+ if (typeof queueMicrotask !== 'undefined') {
49
+ queueMicrotask(() => {
50
+ for (let cb of this.movedListeners) {
51
+ try { cb(); } catch (e) { console.error(e); }
52
+ }
53
+ });
54
+ } else {
55
+ Promise.resolve().then(() => {
56
+ for (let cb of this.movedListeners) {
57
+ try { cb(); } catch (e) { console.error(e); }
58
+ }
59
+ });
60
+ }
61
+ }
62
+
63
+ _throttledFireDidMove() {
64
+ const now = performance.now();
65
+
66
+ // enough time passed → fire instantly
67
+ if (now - this._lastMoveFire >= this._moveThrottleMs) {
68
+ this._lastMoveFire = now;
69
+ this.fireDidMove();
70
+ return;
71
+ }
72
+
73
+ // otherwise queue ONE fire
74
+ if (!this._queuedMove) {
75
+ this._queuedMove = true;
76
+ const wait = this._moveThrottleMs - (now - this._lastMoveFire);
77
+
78
+ this._moveTimeout = setTimeout(() => {
79
+ this._queuedMove = false;
80
+ this._lastMoveFire = performance.now();
81
+ this.fireDidMove();
82
+ }, wait);
83
+ }
84
+ }
85
+ getZoom() {
86
+ return 1;
87
+ }
88
+ canMove() {
89
+ return true;
90
+ }
91
+ private _setupListeners() {
92
+ window.addEventListener('keydown', e => this.keysDown.add(e.key));
93
+ window.addEventListener('keyup', e => this.keysDown.delete(e.key));
94
+
95
+ window.addEventListener('mousedown', e => this.mouseBtns.add(e.button));
96
+ window.addEventListener('mouseup', e => this.mouseBtns.delete(e.button));
97
+
98
+ window.addEventListener('mousemove', e => {
99
+ this.lastMousePos = { ...this.mousePos };
100
+ this.mousePos = { x: e.clientX, y: e.clientY };
101
+ });
102
+
103
+ window.addEventListener('wheel', e => {
104
+ this.wheelDelta = e.deltaY;
105
+ });
106
+
107
+ window.addEventListener('mousedown', e => {
108
+ if (e.button === 0) this.isDragging = true;
109
+ });
110
+ window.addEventListener('mouseup', e => {
111
+ if (e.button === 0) this.isDragging = false;
112
+ });
113
+ }
114
+
115
+ update() {
116
+ // Can handle keyboard shortcuts or auto-pan here
117
+ }
118
+
119
+ // --- Camera methods ---
120
+ pan(dx: number, dy: number) {
121
+ const x = this.workspace._camera.x, y = this.workspace._camera.y;
122
+ this.workspace._camera.x += dx;
123
+ this.workspace._camera.y += dy;
124
+ if (x == this.workspace._camera.x && y == this.workspace._camera.y) {
125
+ return;
126
+ }
127
+ this._throttledFireDidMove();
128
+ this.workspace.didMove = true;
129
+ this.workspace.fireMoveListeners();
130
+ this.refreshPos();
131
+ }
132
+
133
+ setCamera(pos: Vec2) {
134
+ const x = this.workspace._camera.x, y = this.workspace._camera.y;
135
+ this.workspace._camera.x = pos.x;
136
+ this.workspace._camera.y = pos.y;
137
+ if (x == this.workspace._camera.x && y == this.workspace._camera.y) {
138
+ return;
139
+ }
140
+ this._throttledFireDidMove();
141
+ this.workspace.didMove = true;
142
+ this.workspace.fireMoveListeners();
143
+ this.refreshPos();
144
+ }
145
+
146
+ centerOn(pos: Vec2) {
147
+ const wsSize = this.workspace.getSize?.() ?? { width: 0, height: 0 };
148
+ this.setCamera({
149
+ x: pos.x - wsSize.width / 2,
150
+ y: pos.y - wsSize.height / 2
151
+ });
152
+ }
153
+
154
+
155
+
156
+ // --- Coordinate conversion ---
157
+ screenToWorkspace(x: number, y: number): Vec2 {
158
+ const cam = this.workspace._camera;
159
+ return {
160
+ x: x + cam.x,
161
+ y: y + cam.y
162
+ };
163
+ }
164
+
165
+ workspaceToScreen(x: number, y: number): Vec2 {
166
+ const cam = this.workspace._camera;
167
+ return {
168
+ x: (x - cam.x),
169
+ y: (y - cam.y)
170
+ };
171
+ }
172
+
173
+ // --- Refresh ---
174
+ refreshPos() {
175
+ this.workspace.refresh?.();
176
+ }
177
+
178
+ redraw() {
179
+ this.workspace.redraw?.();
180
+ }
181
+
182
+ // --- Cleanup ---
183
+ stop() {
184
+ clearInterval(this._updateInt);
185
+ }
186
+ }
@@ -0,0 +1,132 @@
1
+ import WorkspaceController from './base';
2
+ import WorkspaceSvg from '../src/workspace-svg';
3
+ import userState from '../util/user-state';
4
+
5
+ interface Vec2 { x: number; y: number; }
6
+
7
+ export default class WASDController extends WorkspaceController {
8
+ moveSpeed: number;
9
+ doAccelerate?: boolean;
10
+ accelSpeed: number;
11
+ friction: number;
12
+ velocity: Vec2;
13
+ zoom: number = 1;
14
+ zoomSpeed: number;
15
+ minZoom: number;
16
+ maxZoom: number;
17
+ isFalloff: boolean;
18
+ constructor(workspace: WorkspaceSvg, moveSpeed?: number) {
19
+ super(workspace);
20
+ this.moveSpeed = workspace.options.moveSpeed || moveSpeed || 5;
21
+
22
+ this.doAccelerate = workspace.options?.controls?.wasdSmooth ?? false;
23
+ this.accelSpeed = workspace.options?.controls?.wasdAccelerate ?? 0.2;
24
+ this.friction = workspace.options?.controls?.wasdFriction ?? 0.85;
25
+
26
+ this.velocity = { x: 0, y: 0 };
27
+ this.isFalloff = false;
28
+
29
+ // Zoom settings
30
+ this.zoomSpeed = workspace.options?.controls?.zoomSpeed ?? 0.1;
31
+ this.minZoom = workspace.options?.controls?.minZoom ?? 0.1;
32
+ this.maxZoom = workspace.options?.controls?.maxZoom ?? 4;
33
+ this.zoom = 1;
34
+ // Bind wheel for zoom
35
+ workspace._wsTop.addEventListener('wheel', this.onWheel.bind(this), { passive: false });
36
+ }
37
+
38
+ canMove() {
39
+ return !userState.hasState('typing');
40
+ }
41
+
42
+ update() {
43
+ super.update();
44
+ if (!this.canMove()) return;
45
+
46
+ let inputX = 0;
47
+ let inputY = 0;
48
+
49
+ if (this.keysDown.has('w') || this.keysDown.has('ArrowUp')) inputY -= 1;
50
+ if (this.keysDown.has('s') || this.keysDown.has('ArrowDown')) inputY += 1;
51
+ if (this.keysDown.has('a') || this.keysDown.has('ArrowLeft')) inputX -= 1;
52
+ if (this.keysDown.has('d') || this.keysDown.has('ArrowRight')) inputX += 1;
53
+
54
+ if (this.doAccelerate) {
55
+ // Accelerate velocity towards input direction
56
+ this.velocity.x += inputX * this.accelSpeed;
57
+ this.velocity.y += inputY * this.accelSpeed;
58
+
59
+ // Apply friction
60
+ this.velocity.x *= this.friction;
61
+ this.velocity.y *= this.friction;
62
+ if (inputX == 0 && inputY == 0) {
63
+ this.isFalloff = true;
64
+ } else {
65
+ this.isFalloff = false;
66
+ }
67
+ // Only pan if velocity is noticeable
68
+ if (Math.abs(this.velocity.x) > 0.01 || Math.abs(this.velocity.y) > 0.01) {
69
+ this.pan(this.velocity.x, this.velocity.y);
70
+ }
71
+ } else {
72
+ // Instant movement
73
+ const dx = inputX * this.moveSpeed;
74
+ const dy = inputY * this.moveSpeed;
75
+ if (dx !== 0 || dy !== 0) this.pan(dx, dy);
76
+ }
77
+ }
78
+ pan(dx: number, dy: number) {
79
+ const x = this.workspace._camera.x, y = this.workspace._camera.y;
80
+ this.workspace._camera.x += dx;
81
+ this.workspace._camera.y += dy;
82
+ if (x == this.workspace._camera.x && y == this.workspace._camera.y) {
83
+ return;
84
+ }
85
+ if (!this.isFalloff) {
86
+ this._throttledFireDidMove();
87
+ this.workspace.didMove = true;
88
+ this.workspace.fireMoveListeners();
89
+ this.refreshPos();
90
+ }
91
+ this.refreshPos();
92
+ }
93
+ /**
94
+ * Handles wheel events for zooming.
95
+ * Zooms around the mouse position for intuitive zooming.
96
+ */
97
+ onWheel(e: WheelEvent) {
98
+ e.preventDefault();
99
+
100
+ const oldZoom = this.getZoom();
101
+ let delta = -e.deltaY * this.zoomSpeed * 0.01; // normalize wheel
102
+ let newZoom = oldZoom * (1 + delta);
103
+
104
+ // Clamp zoom
105
+ newZoom = Math.max(this.minZoom, Math.min(this.maxZoom, newZoom));
106
+
107
+ // Zoom around cursor
108
+ const mouseX = e.clientX;
109
+ const mouseY = e.clientY;
110
+
111
+ const wsMouse = this.workspace.screenToWorkspace(mouseX, mouseY);
112
+
113
+ // Apply new zoom
114
+ this.setZoom(newZoom);
115
+
116
+ // Adjust camera so the point under cursor stays stable
117
+ this.workspace._camera.x = wsMouse.x - mouseX / newZoom;
118
+ this.workspace._camera.y = wsMouse.y - mouseY / newZoom;
119
+
120
+ this.workspace.refresh();
121
+ }
122
+
123
+ /** Returns current zoom level */
124
+ getZoom() {
125
+ return this.zoom;
126
+ }
127
+
128
+ /** Sets zoom directly */
129
+ setZoom(zoom: number) {
130
+ this.zoom = zoom;
131
+ }
132
+ }
package/docs/README.md ADDED
@@ -0,0 +1,98 @@
1
+ **Kabel Project Docs v1.0.6**
2
+
3
+ ***
4
+
5
+ # Kabel
6
+ [![npm version](https://img.shields.io/npm/v/@kabel-project/kabel)](https://www.npmjs.com/package/@kabel-project/kabel)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
+
9
+ Node-based visual editor framework with an API inspired by Google’s Blockly project.
10
+ Fully extensible with custom nodes, fields, and renderers.
11
+ Written in TypeScript and ready to use out of the box.
12
+
13
+ ![Kabel workspace screenshot](_media/workspace.png)
14
+
15
+ ---
16
+
17
+ ## Installation
18
+
19
+ You can install Kabel in two main ways:
20
+
21
+ **Using npm (recommended):**
22
+ ```bash
23
+ cd path/to/your/project
24
+ npm install @kabel-project/kabel
25
+ ```
26
+
27
+ **Cloning from GitHub (Experimental builds):**
28
+
29
+ ```bash
30
+ git clone https://github.com/FentFentFent/Kabel.git --depth 1
31
+ cd Kabel
32
+ npm install
33
+ npm run build
34
+ ```
35
+
36
+ Then import Kabel from the build:
37
+
38
+ ```js
39
+ import Kabel from './Kabel/dist/kabel.js';
40
+ ```
41
+
42
+ ##### Quick Starter HTML
43
+
44
+ ```html
45
+ <div id="workspace-container" style="width:800px;height:600px;"></div>
46
+ <script type="module">
47
+ import Kabel from 'kabel';
48
+ Kabel.inject('workspace-container', {
49
+ /* Your options here... */
50
+ });
51
+ </script>
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Features
57
+
58
+ * ⚡ Extensible: create custom nodes, fields, and renderers.
59
+ * 🛠️ TypeScript support out of the box.
60
+ * 🎨 Renderer override system for custom visuals.
61
+ * ⌨️ Built-in workspace controls (WASD movement, drag, etc.).
62
+
63
+ ---
64
+
65
+ ## Example
66
+
67
+ ```js
68
+ import Kabel from 'kabel';
69
+
70
+ const ws = Kabel.inject('workspace-container', {
71
+ controls: { wasd: true, wasdSmooth: true }
72
+ });
73
+
74
+ // Register a simple node
75
+ Kabel.Nodes['example'] = {
76
+ init() {
77
+ this.jsonInit({
78
+ labelText: 'Hello Kabel',
79
+ type: 'example_node',
80
+ primaryColor: '#cc0c00'
81
+ });
82
+ }
83
+ };
84
+ ```
85
+
86
+ For another example refer to [Kabel Test](_media/index.html)
87
+
88
+ ---
89
+
90
+ ## Documentation
91
+
92
+ Please refer to [Kabel Guide](_media/docs.md)
93
+
94
+ ---
95
+
96
+ ## License
97
+
98
+ MIT
@@ -0,0 +1,289 @@
1
+
2
+ # Kabel Documentation
3
+
4
+ **Kabel** is a node-based visual programming editor for building interactive, logic-driven UIs in the browser. It’s built around JavaScript and uses SVG for rendering. Kabel supports node creation, custom fields, widgets, toolboxes, and smooth workspace controls.
5
+
6
+ ---
7
+
8
+ ## Table of Contents
9
+
10
+ 1. Getting Started
11
+ 2. Setting Up Your First Workspace
12
+ 3. Creating Your First Node Prototype
13
+ 4. Creating a Toolbox
14
+ 5. Manipulating Nodes
15
+ 6. Serializing & Deserializing Nodes
16
+ 7. Finishing Up
17
+
18
+ ## In-depth Docs
19
+ [Kabel API Documentation](../../docs/globals.md)
20
+
21
+ ---
22
+
23
+ ## 1. Getting Started
24
+
25
+ You can include Kabel either via a script tag or import it as a module:
26
+
27
+ ```html
28
+ <script src="./kabel.js"></script>
29
+ ```
30
+
31
+ Once loaded:
32
+
33
+ ```js
34
+ if (typeof Kabel !== 'undefined') {
35
+ console.log('Kabel is ready!');
36
+ }
37
+ ```
38
+
39
+ That’s all you need to confirm it’s available before initializing a workspace.
40
+
41
+ ---
42
+
43
+ ## 2. Setting Up Your First Workspace
44
+
45
+ The **workspace** is where nodes live and interact. Start by creating an HTML container:
46
+
47
+ ```js
48
+ const el = document.createElement('div');
49
+ Object.assign(el.style, {
50
+ width: '100vw',
51
+ height: '100vh',
52
+ background: '#f0f0f0'
53
+ });
54
+ document.body.appendChild(el);
55
+ ```
56
+
57
+ Then inject Kabel into that container:
58
+
59
+ ```js
60
+ const ws = Kabel.inject(el, {
61
+ moveSpeed: 6,
62
+ controls: {
63
+ wasd: true,
64
+ wasdSmooth: true,
65
+ wasdAccelerate: 1,
66
+ wasdFriction: 0.9
67
+ },
68
+ toolbox: { /* defined later */ }
69
+ });
70
+ ```
71
+
72
+ This gives you a fullscreen workspace with smooth WASD movement controls.
73
+
74
+ ---
75
+
76
+ ## 3. Creating Your First Node Prototype
77
+
78
+ Nodes are defined once as **prototypes** under `Kabel.Nodes`. Here’s an example:
79
+
80
+ ```js
81
+ Kabel.Nodes['my_node'] = {
82
+ init() {
83
+ this.jsonInit({
84
+ previousConnection: true,
85
+ nextConnection: true,
86
+ labelText: 'TopbarText',
87
+ type: 'my_node',
88
+ primaryColor: '#cc0c00',
89
+ tertiaryColor: '#660500',
90
+ arguments: [
91
+ { name: 'FLD', type: 'field_str', label: 'LabelText: ', value: 'ValueText' },
92
+ { name: 'Dummy', type: 'field_dummy', label: 'Label with no input!' },
93
+ { name: 'Conn', type: 'connection', label: 'Label!' },
94
+ { name: 'Conn2', type: 'field_both', label: 'lowk: ', value: 'lowk' },
95
+ {
96
+ name: 'Dropdown',
97
+ type: 'dropdown',
98
+ label: 'lowk dropdown?: ',
99
+ options: ['Oooo option', { text: 'Opt', value: 'opt' }]
100
+ }
101
+ ]
102
+ });
103
+ }
104
+ };
105
+ ```
106
+
107
+ **Key options:**
108
+
109
+ * `previousConnection` / `nextConnection` → Whether the node can chain with others.
110
+ * `arguments` → Defines fields, inputs, and connection points.
111
+ * `jsonInit()` → Initializes node configuration.
112
+
113
+ ---
114
+
115
+ ## 4. Creating a Toolbox
116
+
117
+ Toolboxes organize your available nodes:
118
+
119
+ ```js
120
+ toolbox: {
121
+ type: 'category',
122
+ contents: [{
123
+ name: 'Cat',
124
+ color: '#cc0c00',
125
+ contents: [{
126
+ type: 'my_node',
127
+ arguments: {}
128
+ }]
129
+ }]
130
+ }
131
+ ```
132
+
133
+ **Toolbox types:**
134
+
135
+ * `'category'` → Groups nodes visually into collapsible sections.
136
+ * `'flyout'` → Displays all nodes in one scrollable list.
137
+
138
+ **Category JSON fields:**
139
+
140
+ * `name`: The category name.
141
+ * `color`: The display color.
142
+ * `contents`: Array of node definitions.
143
+
144
+ > Currently, categories and flyouts can only contain nodes. Support for buttons or UI elements may be added later.
145
+
146
+ ---
147
+
148
+ ## 5. Manipulating Node Properties
149
+
150
+ Once a node is created, you can move, modify, or connect it programmatically:
151
+
152
+ ```js
153
+ const node = new Kabel.NodeSvg(Kabel.Nodes['my_node'], ws);
154
+ node.init();
155
+
156
+ const node2 = new Kabel.NodeSvg(Kabel.Nodes['my_node'], ws);
157
+ node2.init();
158
+ node2.relativeCoords.set(20, 0);
159
+ ```
160
+
161
+ Connect them:
162
+
163
+ ```js
164
+ node.nextConnection.to = node2;
165
+ node2.previousConnection.from = node;
166
+ ws.redraw();
167
+ ```
168
+
169
+ You can also manipulate the SVG directly (temporary, not saved across rerenders):
170
+
171
+ ```js
172
+ node.svg.highlight('#ff0');
173
+ node.svg.setScale(1);
174
+ node.svg.moveTo(0, 1);
175
+ node.svg.applyTransform('translate(0, 1)');
176
+ node.svg.getRaw(); // Returns the svg.js G element
177
+ ```
178
+
179
+ ---
180
+
181
+ ### Optional: Widgets
182
+
183
+ **Widgets** are custom UI overlays within the workspace:
184
+
185
+ ```js
186
+ Kabel.Widgets['testWidget'] = {
187
+ name: 'Test Widget',
188
+ width: 150,
189
+ height: 100,
190
+ coords: new Kabel.Coordinates(50, 50),
191
+ html: '<button id="counterBtn">Count: 0</button>',
192
+ init(widget, container) {
193
+ let count = 0;
194
+ const btn = container.querySelector('#counterBtn');
195
+ if (btn) {
196
+ btn.addEventListener('click', () => {
197
+ count++;
198
+ btn.textContent = 'Count: ' + count;
199
+ });
200
+ }
201
+ }
202
+ };
203
+
204
+ const myWidget = ws.newWidget('testWidget');
205
+ if (myWidget) myWidget.show();
206
+ ```
207
+
208
+ Widgets let you add custom interactivity or information panels.
209
+
210
+ ---
211
+
212
+ ## 6. Serializing Nodes
213
+
214
+ Kabel supports both **circular** and **non-circular** JSON serialization.
215
+
216
+ ### Circular Serialization
217
+
218
+ Contains live object references (not exportable as plain JSON, but usable within JS).
219
+
220
+ ```js
221
+ const circular = node.serialize();
222
+ ```
223
+
224
+ Example (simplified):
225
+
226
+ ```json
227
+ {
228
+ "id": "sisDG24Gp6lc7*S#m#I9c",
229
+ "type": "my_node",
230
+ "nextConnection": {
231
+ "node": "[CircularNodeRef]"
232
+ },
233
+ "fields": [...]
234
+ }
235
+ ```
236
+
237
+ ---
238
+
239
+ ### Non-Circular Serialization
240
+
241
+ Safe for exporting or saving to files:
242
+
243
+ ```js
244
+ const nonCircular = node.toJson();
245
+ ```
246
+
247
+ Example:
248
+
249
+ ```json
250
+ {
251
+ "7f7#2Rrt16uFuH^2f2^ad": {
252
+ "id": "7f7#2Rrt16uFuH^2f2^ad",
253
+ "type": "my_node",
254
+ "colors": { "primary": "#cc0c00", "tertiary": "#660500" },
255
+ "label": "TopbarText",
256
+ "nextConnection": { "node": "jX^$kh)yHW5p^bx98vD#s" },
257
+ "fields": [...]
258
+ }
259
+ }
260
+ ```
261
+
262
+ ---
263
+
264
+ ### Workspace Serialization
265
+
266
+ ```js
267
+ // Serialize
268
+ const data = workspace.toJson(false); // false = non-circular
269
+
270
+ // Deserialize
271
+ workspace.fromJson(data);
272
+ ```
273
+
274
+ The serialization format automatically includes the circular/non-circular flag internally.
275
+
276
+ ---
277
+
278
+ ## 7. Finishing Up
279
+
280
+ You can expose internals for quick debugging:
281
+
282
+ ```js
283
+ window.ws = ws;
284
+ window.nodes = [node, node2];
285
+ ```
286
+
287
+ That’s it — you now have a working Kabel workspace with nodes, a toolbox, and serialization support.
288
+
289
+ ---