@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.
- package/README.md +95 -0
- package/lib/helpers/createContext.d.ts +5 -8
- package/lib/helpers/createContext.d.ts.map +1 -1
- package/lib/index.cjs.js +1 -1
- package/lib/index.es.js +240 -235
- package/lib/server.cjs.js +148 -117
- package/lib/server.es.js +148 -117
- package/lib/services/main.machine.d.ts +7 -9
- package/lib/services/main.machine.d.ts.map +1 -1
- package/lib/services/main.typings.d.ts +2 -2
- package/lib/ui/Flow.d.ts +2 -2
- package/lib/ui/Flow.d.ts.map +1 -1
- package/lib/ui/components/Bounds.d.ts +4 -4
- package/lib/ui/components/Bounds.d.ts.map +1 -1
- package/lib/ui/components/EdgeComponent.d.ts +2 -2
- package/lib/ui/components/EdgeComponent.d.ts.map +1 -1
- package/lib/ui/components/EdgesBoard.d.ts +2 -2
- package/lib/ui/components/EdgesBoard.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.context.d.ts +10 -18
- package/lib/ui/components/FlowChart.context.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.d.ts +4 -10
- package/lib/ui/components/FlowChart.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.data.d.ts +6 -10
- package/lib/ui/components/FlowChart.data.d.ts.map +1 -1
- package/lib/ui/components/NodeComponent.d.ts +2 -2
- package/lib/ui/components/NodeComponent.d.ts.map +1 -1
- package/lib/ui/components/NodesBoard.d.ts +2 -2
- package/lib/ui/components/NodesBoard.d.ts.map +1 -1
- package/lib/ui/components/classes.d.ts +2 -2
- package/package.json +2 -2
- package/src/README.md +2 -2
- package/src/helpers/createContext.ts +5 -8
- package/src/services/main.machine.ts +18 -46
- package/src/services/main.typings.ts +2 -2
- package/src/ui/Flow.tsx +3 -2
- package/src/ui/components/Bounds.tsx +35 -44
- package/src/ui/components/EdgeComponent.tsx +7 -8
- package/src/ui/components/EdgesBoard.tsx +3 -2
- package/src/ui/components/FlowChart.context.ts +56 -82
- package/src/ui/components/FlowChart.data.ts +8 -14
- package/src/ui/components/FlowChart.tsx +6 -11
- package/src/ui/components/NodeComponent.tsx +13 -25
- package/src/ui/components/NodesBoard.tsx +109 -111
- 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
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 *
|
|
204
|
-
containerRef.scrollTop = startScrollTop - dy *
|
|
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
|
-
<
|
|
223
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
*
|
|
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',
|