@bemedev/mind-flow 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +95 -0
  2. package/lib/helpers/createContext.d.ts +5 -8
  3. package/lib/helpers/createContext.d.ts.map +1 -1
  4. package/lib/index.cjs.js +1 -1
  5. package/lib/index.es.js +240 -235
  6. package/lib/server.cjs.js +148 -117
  7. package/lib/server.es.js +148 -117
  8. package/lib/services/main.machine.d.ts +7 -9
  9. package/lib/services/main.machine.d.ts.map +1 -1
  10. package/lib/services/main.typings.d.ts +2 -2
  11. package/lib/ui/Flow.d.ts +2 -2
  12. package/lib/ui/Flow.d.ts.map +1 -1
  13. package/lib/ui/components/Bounds.d.ts +4 -4
  14. package/lib/ui/components/Bounds.d.ts.map +1 -1
  15. package/lib/ui/components/EdgeComponent.d.ts +2 -2
  16. package/lib/ui/components/EdgeComponent.d.ts.map +1 -1
  17. package/lib/ui/components/EdgesBoard.d.ts +2 -2
  18. package/lib/ui/components/EdgesBoard.d.ts.map +1 -1
  19. package/lib/ui/components/FlowChart.context.d.ts +10 -18
  20. package/lib/ui/components/FlowChart.context.d.ts.map +1 -1
  21. package/lib/ui/components/FlowChart.d.ts +4 -10
  22. package/lib/ui/components/FlowChart.d.ts.map +1 -1
  23. package/lib/ui/components/FlowChart.data.d.ts +6 -10
  24. package/lib/ui/components/FlowChart.data.d.ts.map +1 -1
  25. package/lib/ui/components/NodeComponent.d.ts +2 -2
  26. package/lib/ui/components/NodeComponent.d.ts.map +1 -1
  27. package/lib/ui/components/NodesBoard.d.ts +2 -2
  28. package/lib/ui/components/NodesBoard.d.ts.map +1 -1
  29. package/lib/ui/components/classes.d.ts +2 -2
  30. package/package.json +2 -2
  31. package/src/README.md +2 -2
  32. package/src/helpers/createContext.ts +5 -8
  33. package/src/services/main.machine.ts +18 -46
  34. package/src/services/main.typings.ts +2 -2
  35. package/src/ui/Flow.tsx +3 -2
  36. package/src/ui/components/Bounds.tsx +35 -44
  37. package/src/ui/components/EdgeComponent.tsx +7 -8
  38. package/src/ui/components/EdgesBoard.tsx +3 -2
  39. package/src/ui/components/FlowChart.context.ts +56 -82
  40. package/src/ui/components/FlowChart.data.ts +8 -14
  41. package/src/ui/components/FlowChart.tsx +6 -11
  42. package/src/ui/components/NodeComponent.tsx +13 -25
  43. package/src/ui/components/NodesBoard.tsx +109 -111
  44. package/src/ui/components/classes.ts +2 -2
@@ -5,35 +5,26 @@ import {
5
5
  DragOverlay,
6
6
  } from '@thisbeyond/solid-dnd';
7
7
  import { dequal } from 'dequal';
8
- import {
9
- Component,
10
- createEffect,
11
- createSignal,
12
- For,
13
- on,
14
- onCleanup,
15
- Show,
16
- } from 'solid-js';
8
+ import { Component, createEffect, createSignal, For, on, Show } from 'solid-js';
9
+
17
10
  import { DragBounds } from './Bounds';
18
11
  import { EdgesBoard } from './EdgesBoard';
19
12
  import { useFlow } from './FlowChart.context';
13
+ import { CANVAS_FACTOR, SCROLL_MULTIPLIER } from './FlowChart.data';
20
14
  import { NodeComponent } from './NodeComponent';
21
- import { CANVAS_FACTOR } from './FlowChart.data';
22
15
 
23
16
  /**
24
- * Interactive board component containing the drag-drop viewport, zoom
25
- * controls, panning gestures, and rendered nodes/edges.
17
+ * Interactive board component containing the drag-drop viewport, zoom controls,
18
+ * panning gestures, and rendered nodes/edges.
26
19
  *
27
20
  * @returns The rendered Solid component.
28
21
  */
29
22
  export const NodesBoard: Component = () => {
30
- let containerRef: HTMLDivElement | undefined;
23
+ let containerRef: HTMLDivElement;
31
24
  const [isPanning, setIsPanning] = createSignal(false);
32
25
  const [transform, setTransform] = createSignal({ x: 0, y: 0 });
33
26
  const [id, setId] = createSignal<string | number>('');
34
27
  const [previousZoom, setPreviousZoom] = createSignal<number>();
35
- let cleanupPanning = () => {};
36
- onCleanup(cleanupPanning);
37
28
  let percentX = 0;
38
29
  let percentY = 0;
39
30
 
@@ -45,10 +36,8 @@ export const NodesBoard: Component = () => {
45
36
  } = useFlow();
46
37
 
47
38
  const updateScrollPercentages = () => {
48
- if (!containerRef) return;
49
39
  const maxScrollX = containerRef.scrollWidth - containerRef.clientWidth;
50
- const maxScrollY =
51
- containerRef.scrollHeight - containerRef.clientHeight;
40
+ const maxScrollY = containerRef.scrollHeight - containerRef.clientHeight;
52
41
  percentX = maxScrollX > 0 ? containerRef.scrollLeft / maxScrollX : 0;
53
42
  percentY = maxScrollY > 0 ? containerRef.scrollTop / maxScrollY : 0;
54
43
  };
@@ -57,22 +46,16 @@ export const NodesBoard: Component = () => {
57
46
  on(
58
47
  zoom,
59
48
  () => {
60
- if (!containerRef) return;
61
- const maxScrollX =
62
- containerRef.scrollWidth - containerRef.clientWidth;
63
- const maxScrollY =
64
- containerRef.scrollHeight - containerRef.clientHeight;
65
- if (maxScrollX > 0)
66
- containerRef.scrollLeft = percentX * maxScrollX;
49
+ const maxScrollX = containerRef.scrollWidth - containerRef.clientWidth;
50
+ const maxScrollY = containerRef.scrollHeight - containerRef.clientHeight;
51
+ if (maxScrollX > 0) containerRef.scrollLeft = percentX * maxScrollX;
67
52
  if (maxScrollY > 0) containerRef.scrollTop = percentY * maxScrollY;
68
53
  },
69
54
  { defer: true },
70
55
  ),
71
56
  );
72
57
 
73
- const selectedId = useState(service, {
74
- selector: s => s.context?.selected,
75
- });
58
+ const selectedId = useState(service, { selector: s => s.context?.selected });
76
59
 
77
60
  const selected = (id: string | number) => selectedId() === id;
78
61
 
@@ -91,76 +74,93 @@ export const NodesBoard: Component = () => {
91
74
  equals: dequal,
92
75
  });
93
76
 
94
- const cDim = () => {
95
- const _zoom = zoom();
96
- if (_zoom < 1) {
97
- return CANVAS_FACTOR * 100 * _zoom;
98
- }
99
- return CANVAS_FACTOR * 100;
100
- };
77
+ const CANVAS_SIZE = CANVAS_FACTOR * 100;
78
+ const MARGIN_X = 53 * CANVAS_FACTOR;
79
+ const MARGIN_Y = 85 * CANVAS_FACTOR;
80
+ const cWidth = () => `calc((${CANVAS_SIZE}vw - ${MARGIN_X}px) * ${zoom()})`;
81
+ const cHeight = () => `calc((${CANVAS_SIZE}vh - ${MARGIN_Y}px) * ${zoom()})`;
101
82
 
102
83
  return (
103
- <DragDropProvider
104
- onDragMove={({
105
- draggable: { transform: _transform, node, id },
106
- overlay,
107
- }) => {
108
- setId(id);
109
- if (selected(id)) {
110
- const x = node.offsetLeft + _transform.x / zoom();
111
- const y = node.offsetTop + _transform.y / zoom();
112
- overlay?.node.style.setProperty('top', y * 2 + 'px');
113
- overlay?.node.style.setProperty('left', x * 2 + 'px');
114
- const deltaX = _transform.x / zoom();
115
- const deltaY = _transform.y / zoom();
116
- // Directly update the draggable node's CSS transform adjusted for zoom:
117
- node.style.setProperty(
118
- 'transform',
119
- `translate3d(${deltaX}px, ${deltaY}px, 0)`,
84
+ <div
85
+ onWheel={e => {
86
+ if (e.ctrlKey || e.metaKey) {
87
+ e.preventDefault();
88
+ updateScrollPercentages();
89
+ const delta = e.deltaY < 0 ? 0.1 : -0.1;
90
+ setZoom(prev =>
91
+ Math.min(Math.max(Number((prev + delta).toFixed(2)), 0.2), 3),
120
92
  );
121
-
122
- service.send({
123
- type: 'MOVE_IMMEDIATE',
124
- payload: { id: `${id}`, x, y },
125
- });
126
- setTransform({ ..._transform });
127
93
  }
128
94
  }}
129
95
 
130
- onDragEnd={({ draggable: { node, id } }) => {
131
- if (!selected(id)) return;
96
+ class='relative mx-auto h-[calc(100vh-64px)] w-[calc(100vw-32px)] overflow-hidden'
97
+ >
98
+ <DragDropProvider
99
+ onDragMove={({ draggable: { transform: _transform, node, id } }) => {
100
+ setId(id);
101
+ if (selected(id)) {
102
+ const grandParent = node.parentElement?.parentElement;
103
+ const currentZoom = zoom();
104
+ const minX = 0;
105
+ const maxX = grandParent
106
+ ? Math.max(0, grandParent.clientWidth / currentZoom - node.offsetWidth)
107
+ : Infinity;
108
+ const minY = 0;
109
+ const maxY = grandParent
110
+ ? Math.max(
111
+ 0,
112
+ grandParent.clientHeight / currentZoom - node.offsetHeight,
113
+ )
114
+ : Infinity;
132
115
 
133
- const X = node.offsetLeft + transform().x / zoom();
134
- const Y = node.offsetTop + transform().y / zoom();
135
- node.style.setProperty('top', Y + 'px');
136
- node.style.setProperty('left', X + 'px');
137
- node.style.removeProperty('transform');
116
+ const targetX = node.offsetLeft + _transform.x / currentZoom;
117
+ const targetY = node.offsetTop + _transform.y / currentZoom;
138
118
 
139
- setTimeout(() => {
140
- service.send({
141
- type: 'MOVE',
142
- payload: { id: `${id}`, x: X, y: Y },
143
- });
144
- setTransform({ x: 0, y: 0 });
145
- }, 0);
146
- }}
147
- >
148
- <div
149
- onWheel={e => {
150
- if (e.ctrlKey || e.metaKey) {
151
- e.preventDefault();
152
- updateScrollPercentages();
153
- const delta = e.deltaY < 0 ? 0.1 : -0.1;
154
- setZoom(prev =>
155
- Math.min(
156
- Math.max(Number((prev + delta).toFixed(2)), 0.2),
157
- 3,
158
- ),
119
+ const x = Math.min(Math.max(targetX, minX), maxX);
120
+ const y = Math.min(Math.max(targetY, minY), maxY);
121
+
122
+ const deltaX = x - node.offsetLeft;
123
+ const deltaY = y - node.offsetTop;
124
+
125
+ // Directly update the draggable node's CSS transform adjusted for zoom:
126
+ node.style.setProperty(
127
+ 'transform',
128
+ `translate3d(${deltaX}px, ${deltaY}px, 0)`,
159
129
  );
130
+
131
+ service.send({ type: 'MOVE_IMMEDIATE', payload: { id: `${id}`, x, y } });
132
+ setTransform({ x: deltaX * currentZoom, y: deltaY * currentZoom });
160
133
  }
161
134
  }}
162
135
 
163
- class='relative mx-auto h-[calc(100vh-64px)] w-[calc(100vw-32px)]'
136
+ onDragEnd={({ draggable: { node, id } }) => {
137
+ if (!selected(id)) return;
138
+
139
+ const grandParent = node.parentElement?.parentElement;
140
+ const currentZoom = zoom();
141
+ const minX = 0;
142
+ const maxX = grandParent
143
+ ? Math.max(0, grandParent.clientWidth / currentZoom - node.offsetWidth)
144
+ : Infinity;
145
+ const minY = 0;
146
+ const maxY = grandParent
147
+ ? Math.max(0, grandParent.clientHeight / currentZoom - node.offsetHeight)
148
+ : Infinity;
149
+
150
+ const rawX = node.offsetLeft + transform().x / currentZoom;
151
+ const rawY = node.offsetTop + transform().y / currentZoom;
152
+ const X = Math.min(Math.max(rawX, minX), maxX);
153
+ const Y = Math.min(Math.max(rawY, minY), maxY);
154
+
155
+ node.style.setProperty('top', Y + 'px');
156
+ node.style.setProperty('left', X + 'px');
157
+ node.style.removeProperty('transform');
158
+
159
+ setTimeout(() => {
160
+ service.send({ type: 'MOVE', payload: { id: `${id}`, x: X, y: Y } });
161
+ setTransform({ x: 0, y: 0 });
162
+ }, 0);
163
+ }}
164
164
  >
165
165
  <div
166
166
  ref={el => {
@@ -170,21 +170,14 @@ export const NodesBoard: Component = () => {
170
170
  class='relative h-full w-full overflow-scroll rounded-lg border-2 border-gray-600'
171
171
  >
172
172
  <DragDropSensors />
173
- <DragBounds />
174
173
  <div
175
174
  ref={setRef}
176
- class='relative cursor-crosshair'
175
+ class='relative cursor-crosshair overflow-hidden'
177
176
  classList={{ 'cursor-grabbing': isPanning() }}
178
- style={{
179
- height: `calc(${cDim()}vh - 85px)`,
180
- width: `calc(${cDim()}vw - 53px)`,
181
- scale: zoom(),
182
- 'transform-origin': 'top left',
183
- }}
177
+ style={{ height: cHeight(), width: cWidth() }}
178
+
184
179
  onMouseDown={e => {
185
180
  if (newEdge() || e.button !== 0) return;
186
- if (!containerRef) return;
187
-
188
181
  service.send('DESELECT');
189
182
 
190
183
  // #region Props
@@ -197,11 +190,10 @@ export const NodesBoard: Component = () => {
197
190
  setIsPanning(true);
198
191
 
199
192
  const handleMouseMove = (moveEvent: MouseEvent) => {
200
- if (!containerRef) return;
201
193
  const dx = moveEvent.clientX - startX;
202
194
  const dy = moveEvent.clientY - startY;
203
- containerRef.scrollLeft = startScrollLeft - dx * 3;
204
- containerRef.scrollTop = startScrollTop - dy * 3;
195
+ containerRef.scrollLeft = startScrollLeft - dx * SCROLL_MULTIPLIER;
196
+ containerRef.scrollTop = startScrollTop - dy * SCROLL_MULTIPLIER;
205
197
  updateScrollPercentages();
206
198
  };
207
199
 
@@ -209,18 +201,22 @@ export const NodesBoard: Component = () => {
209
201
  window.removeEventListener('mousemove', handleMouseMove);
210
202
  window.removeEventListener('mouseup', handleMouseUp);
211
203
  setTimeout(() => setIsPanning(false), 200);
212
- (cleanupPanning as any) = undefined;
213
204
  };
214
205
 
215
206
  // #region Attach Windows listeners
216
- cleanupPanning = handleMouseUp;
217
207
  window.addEventListener('mousemove', handleMouseMove);
218
208
  window.addEventListener('mouseup', handleMouseUp);
219
209
  // #endregion
220
210
  }}
221
211
  >
222
- <EdgesBoard />
223
- <For each={nodes()} children={NodeComponent} />
212
+ <div
213
+ class='relative h-full w-full'
214
+ style={{ scale: zoom(), 'transform-origin': 'top left' }}
215
+ >
216
+ <DragBounds />
217
+ <EdgesBoard />
218
+ <For each={nodes()} children={NodeComponent} />
219
+ </div>
224
220
  </div>
225
221
  </div>
226
222
 
@@ -233,7 +229,7 @@ export const NodesBoard: Component = () => {
233
229
  updateScrollPercentages();
234
230
  setPreviousZoom(undefined);
235
231
  setZoom(prev =>
236
- Math.max(0.2, Number((prev - 0.1).toFixed(2))),
232
+ Math.max(1 / CANVAS_FACTOR, Number((prev - 0.1).toFixed(2))),
237
233
  );
238
234
  }}
239
235
  title='Zoom out'
@@ -266,7 +262,10 @@ export const NodesBoard: Component = () => {
266
262
  updateScrollPercentages();
267
263
  setPreviousZoom(undefined);
268
264
  setZoom(prev =>
269
- Math.min(3, Number((prev + 0.1).toFixed(2))),
265
+ Math.min(
266
+ Math.min(3, CANVAS_FACTOR),
267
+ Number((prev + 0.1).toFixed(2)),
268
+ ),
270
269
  );
271
270
  }}
272
271
  title='Zoom in'
@@ -288,11 +287,10 @@ export const NodesBoard: Component = () => {
288
287
  </svg>
289
288
  </button>
290
289
  </div>
291
- </div>
292
-
293
- <Show when={!id() || !selected(id())}>
294
- <DragOverlay children='' />
295
- </Show>
296
- </DragDropProvider>
290
+ <Show when={!id() || !selected(id())}>
291
+ <DragOverlay children='' />
292
+ </Show>
293
+ </DragDropProvider>
294
+ </div>
297
295
  );
298
296
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
- * List of Tailwind CSS class names used across flowchart UI components to
3
- * ensure compilation safelist.
2
+ * List of Tailwind CSS class names used across flowchart UI components to ensure
3
+ * compilation safelist.
4
4
  */
5
5
  export const CLASSES = [
6
6
  'fill-transparent',