@bemedev/mind-flow 0.3.1 → 1.0.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 +55 -12
- package/lib/helpers/clamp.d.ts +2 -0
- package/lib/helpers/clamp.d.ts.map +1 -0
- package/lib/helpers/createContext.d.ts +1 -1
- package/lib/helpers/createContext.d.ts.map +1 -1
- package/lib/helpers/index.d.ts +3 -0
- package/lib/helpers/index.d.ts.map +1 -0
- package/lib/index.cjs.js +1 -1
- package/lib/index.d.ts +9 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.es.js +2543 -448
- package/lib/output.css +1 -1
- package/lib/server.cjs.js +1129 -397
- package/lib/server.es.js +1095 -407
- package/lib/services/main.machine.d.ts +2453 -576
- package/lib/services/main.machine.d.ts.map +1 -1
- package/lib/services/main.machine.data.d.ts +14 -1
- package/lib/services/main.machine.data.d.ts.map +1 -1
- package/lib/services/main.machine.helpers.d.ts +1 -1
- package/lib/services/main.machine.helpers.d.ts.map +1 -1
- package/lib/services/main.machine.test.d.ts +2 -0
- package/lib/services/main.machine.test.d.ts.map +1 -0
- package/lib/services/main.machine.typings.d.ts +26 -8
- package/lib/services/main.machine.typings.d.ts.map +1 -1
- package/lib/ui/Flow.d.ts +8 -4
- package/lib/ui/Flow.d.ts.map +1 -1
- package/lib/ui/components/Bounds.d.ts.map +1 -1
- package/lib/ui/components/EdgeComponent.d.ts.map +1 -1
- package/lib/ui/components/EdgesBoard.d.ts.map +1 -1
- package/lib/ui/components/EditPanel.d.ts +19 -0
- package/lib/ui/components/EditPanel.d.ts.map +1 -0
- package/lib/ui/components/EditPanel.hooks.d.ts +29 -0
- package/lib/ui/components/EditPanel.hooks.d.ts.map +1 -0
- package/lib/ui/components/EditPanel.types.d.ts +47 -0
- package/lib/ui/components/EditPanel.types.d.ts.map +1 -0
- package/lib/ui/components/FlowChart.context.d.ts +2452 -575
- package/lib/ui/components/FlowChart.context.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.d.ts +8 -47
- package/lib/ui/components/FlowChart.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.types.d.ts +87 -0
- package/lib/ui/components/FlowChart.types.d.ts.map +1 -0
- package/lib/ui/components/NodeComponent.d.ts +10 -12
- package/lib/ui/components/NodeComponent.d.ts.map +1 -1
- package/lib/ui/components/NodesBoard.d.ts +17 -2
- package/lib/ui/components/NodesBoard.d.ts.map +1 -1
- package/lib/ui/components/Panels.d.ts +12 -0
- package/lib/ui/components/Panels.d.ts.map +1 -0
- package/lib/ui/globals/components/atoms/TypingText.d.ts +29 -0
- package/lib/ui/globals/components/atoms/TypingText.d.ts.map +1 -0
- package/lib/ui/globals/components/molecules/FadingDots.d.ts +21 -0
- package/lib/ui/globals/components/molecules/FadingDots.d.ts.map +1 -0
- package/lib/ui/globals/components/organisms/LoadingFallback.d.ts +46 -0
- package/lib/ui/globals/components/organisms/LoadingFallback.d.ts.map +1 -0
- package/lib/ui/globals/constants/index.d.ts +3 -0
- package/lib/ui/globals/constants/index.d.ts.map +1 -0
- package/lib/ui/globals/constants/links.d.ts +4 -0
- package/lib/ui/globals/constants/links.d.ts.map +1 -0
- package/lib/ui/globals/constants/strings.d.ts +5 -0
- package/lib/ui/globals/constants/strings.d.ts.map +1 -0
- package/lib/ui/globals/directives/clickOutside.d.ts +9 -0
- package/lib/ui/globals/directives/clickOutside.d.ts.map +1 -0
- package/lib/ui/globals/directives/index.d.ts +16 -0
- package/lib/ui/globals/directives/index.d.ts.map +1 -0
- package/lib/ui/globals/directives/mouseOut.d.ts +33 -0
- package/lib/ui/globals/directives/mouseOut.d.ts.map +1 -0
- package/lib/ui/globals/directives/mouseOut.test.d.ts +2 -0
- package/lib/ui/globals/directives/mouseOut.test.d.ts.map +1 -0
- package/lib/ui/globals/directives/resize.d.ts +11 -0
- package/lib/ui/globals/directives/resize.d.ts.map +1 -0
- package/lib/ui/globals/helpers/espace.d.ts +17 -0
- package/lib/ui/globals/helpers/espace.d.ts.map +1 -0
- package/lib/ui/globals/signals/createTyping.d.ts +27 -0
- package/lib/ui/globals/signals/createTyping.d.ts.map +1 -0
- package/lib/ui/globals/types.d.ts +54 -0
- package/lib/ui/globals/types.d.ts.map +1 -0
- package/lib/ui/utils.d.ts +11 -0
- package/lib/ui/utils.d.ts.map +1 -0
- package/package.json +2 -3
- package/src/helpers/clamp.ts +3 -0
- package/src/helpers/createContext.ts +20 -4
- package/src/helpers/index.ts +2 -0
- package/src/index.ts +9 -1
- package/src/services/main.machine.data.ts +13 -2
- package/src/services/main.machine.helpers.ts +12 -4
- package/src/services/main.machine.test.ts +205 -0
- package/src/services/main.machine.ts +253 -75
- package/src/services/main.machine.typings.ts +31 -6
- package/src/ui/Flow.tsx +47 -8
- package/src/ui/components/Bounds.tsx +0 -2
- package/src/ui/components/EdgeComponent.tsx +60 -46
- package/src/ui/components/EdgesBoard.tsx +1 -2
- package/src/ui/components/EditPanel.hooks.ts +69 -0
- package/src/ui/components/EditPanel.tsx +89 -0
- package/src/ui/components/EditPanel.types.ts +49 -0
- package/src/ui/components/FlowChart.context.ts +22 -141
- package/src/ui/components/FlowChart.tsx +27 -64
- package/src/ui/components/FlowChart.types.ts +92 -0
- package/src/ui/components/NodeComponent.tsx +88 -65
- package/src/ui/components/NodesBoard.tsx +61 -56
- package/src/ui/components/Panels.tsx +40 -0
- package/src/ui/globals/components/atoms/TypingText.tsx +63 -0
- package/src/ui/globals/components/molecules/FadingDots.tsx +45 -0
- package/src/ui/globals/components/organisms/LoadingFallback.tsx +116 -0
- package/src/ui/globals/constants/index.ts +2 -0
- package/src/ui/globals/constants/links.ts +15 -0
- package/src/ui/globals/constants/strings.ts +5 -0
- package/src/ui/globals/directives/clickOutside.ts +18 -0
- package/src/ui/globals/directives/index.ts +16 -0
- package/src/ui/globals/directives/mouseOut.test.ts +115 -0
- package/src/ui/globals/directives/mouseOut.ts +98 -0
- package/src/ui/globals/directives/resize.ts +33 -0
- package/src/ui/globals/helpers/espace.ts +26 -0
- package/src/ui/globals/signals/createTyping.ts +82 -0
- package/src/ui/globals/types.ts +83 -0
- package/src/ui/utils.ts +14 -0
|
@@ -6,7 +6,14 @@ import { nanoid } from 'nanoid';
|
|
|
6
6
|
/** Type alias for drag-drop state extracted from {@linkcode useDragDropContext}. */
|
|
7
7
|
export type DragDropState = Exclude<ReturnType<typeof useDragDropContext>, null>[0];
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { clamp } from '..';
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_DATA,
|
|
12
|
+
DEFAULT_INPUT_OFFSET,
|
|
13
|
+
DEFAULT_SIZE,
|
|
14
|
+
getDefaultOutputOffset,
|
|
15
|
+
PARENT_CHILD_GAP_WIDTH,
|
|
16
|
+
} from './main.machine.data';
|
|
10
17
|
import {
|
|
11
18
|
buildEdgeId,
|
|
12
19
|
buildNodeID,
|
|
@@ -17,6 +24,7 @@ import {
|
|
|
17
24
|
edgeJSON,
|
|
18
25
|
extremities,
|
|
19
26
|
newEdge,
|
|
27
|
+
data,
|
|
20
28
|
nodeJSON,
|
|
21
29
|
point,
|
|
22
30
|
vector,
|
|
@@ -24,13 +32,14 @@ import {
|
|
|
24
32
|
type Dimension,
|
|
25
33
|
type Point,
|
|
26
34
|
type Board,
|
|
35
|
+
type Vector,
|
|
27
36
|
} from './main.machine.typings';
|
|
28
37
|
|
|
29
38
|
/**
|
|
30
39
|
* State machine managing flowchart state transitions, nodes, edges, selection, and
|
|
31
40
|
* layout actions.
|
|
32
41
|
*
|
|
33
|
-
* @see {@linkcode calculateDimensions}, {@linkcode buildEdgeId}, {@linkcode buildNodeID}, {@linkcode getDefaultOutputOffset}, {@linkcode DEFAULT_INPUT_OFFSET}
|
|
42
|
+
* @see {@linkcode calculateDimensions}, {@linkcode buildEdgeId}, {@linkcode buildNodeID}, {@linkcode getDefaultOutputOffset}, {@linkcode DEFAULT_INPUT_OFFSET}, {@linkcode DEFAULT_SIZE}, {@linkcode DEFAULT_DATA}
|
|
34
43
|
*/
|
|
35
44
|
export const machine = createMachine(
|
|
36
45
|
{
|
|
@@ -40,7 +49,7 @@ export const machine = createMachine(
|
|
|
40
49
|
states: {
|
|
41
50
|
idle: {
|
|
42
51
|
on: {
|
|
43
|
-
CONFIGURE: { actions: ['configure'], target: '/
|
|
52
|
+
CONFIGURE: { actions: ['configure'], target: '/construction' },
|
|
44
53
|
CONFIGURE_EMPTY: '/working',
|
|
45
54
|
},
|
|
46
55
|
},
|
|
@@ -49,6 +58,7 @@ export const machine = createMachine(
|
|
|
49
58
|
|
|
50
59
|
working: {
|
|
51
60
|
on: {
|
|
61
|
+
CONFIGURE: { actions: ['configure'], target: '/construction' },
|
|
52
62
|
MOVE_IMMEDIATE: {
|
|
53
63
|
actions: [{ name: 'buildUI', description: 'Must be in the ui' }],
|
|
54
64
|
},
|
|
@@ -71,8 +81,6 @@ export const machine = createMachine(
|
|
|
71
81
|
target: '/construction',
|
|
72
82
|
},
|
|
73
83
|
|
|
74
|
-
ADD_DIMENSION: { actions: ['addDimension'] },
|
|
75
|
-
|
|
76
84
|
ADD_SIBLING: {
|
|
77
85
|
actions: [
|
|
78
86
|
'generateID',
|
|
@@ -82,6 +90,7 @@ export const machine = createMachine(
|
|
|
82
90
|
target: '/construction',
|
|
83
91
|
},
|
|
84
92
|
|
|
93
|
+
RESIZE: { actions: ['resize', 'buildUI'] },
|
|
85
94
|
MOVE: { actions: ['moveNode', 'buildUI'], target: '/construction' },
|
|
86
95
|
ADD_EDGE: { actions: ['addEdge'], target: '/construction' },
|
|
87
96
|
START_NEW_EDGE: { actions: ['startNewEdge'] },
|
|
@@ -93,15 +102,19 @@ export const machine = createMachine(
|
|
|
93
102
|
ZOOM: { actions: ['zoom'] },
|
|
94
103
|
TOGGLE_ZOOM: { actions: ['toggleZoom'] },
|
|
95
104
|
SET_BOARD: { actions: ['setBoard'] },
|
|
105
|
+
SET_NODE_DATA: { actions: ['setNodeData'] },
|
|
106
|
+
EDIT: { actions: ['edit'] },
|
|
107
|
+
STOP_EDIT: { actions: ['stopEdit'] },
|
|
96
108
|
},
|
|
97
109
|
},
|
|
98
110
|
},
|
|
99
111
|
},
|
|
100
112
|
{
|
|
101
|
-
eventsMap: type(({ intersection, use, array }) => ({
|
|
113
|
+
eventsMap: type(({ intersection, use, array, optional }) => ({
|
|
102
114
|
CONFIGURE: {
|
|
103
115
|
nodes: array(intersection(use(nodeJSON), { id: 'string' })),
|
|
104
116
|
edges: array(intersection(use(edgeJSON), { id: 'string' })),
|
|
117
|
+
defaultData: optional(use(data)),
|
|
105
118
|
},
|
|
106
119
|
|
|
107
120
|
SET_BOARD: use(board),
|
|
@@ -114,13 +127,16 @@ export const machine = createMachine(
|
|
|
114
127
|
DELETE: 'string',
|
|
115
128
|
SELECT: 'string',
|
|
116
129
|
DESELECT: 'never',
|
|
130
|
+
EDIT: 'string',
|
|
131
|
+
STOP_EDIT: 'never',
|
|
117
132
|
ADD_EDGE: use(extremities),
|
|
118
133
|
START_NEW_EDGE: 'string',
|
|
119
134
|
MOVE_NEW_EDGE: use(point),
|
|
120
135
|
CLEAR_NEW_EDGE: 'never',
|
|
121
136
|
ZOOM: 'number',
|
|
122
137
|
TOGGLE_ZOOM: 'never',
|
|
123
|
-
|
|
138
|
+
RESIZE: { id: 'string', size: { width: 'number', height: 'number' } },
|
|
139
|
+
SET_NODE_DATA: { id: 'string', data: use(data) },
|
|
124
140
|
})),
|
|
125
141
|
|
|
126
142
|
sync: true,
|
|
@@ -128,6 +144,7 @@ export const machine = createMachine(
|
|
|
128
144
|
generatedId: union('string', 'null'),
|
|
129
145
|
previousZoom: optional('number'),
|
|
130
146
|
dimensions: record(use(dimension)),
|
|
147
|
+
defaultData: optional(use(data)),
|
|
131
148
|
|
|
132
149
|
getBoardPosition:
|
|
133
150
|
custom<(clientX: number, clientY: number, board: Board) => Point>(),
|
|
@@ -172,6 +189,7 @@ export const machine = createMachine(
|
|
|
172
189
|
edgesPositions: record(use(vector)),
|
|
173
190
|
newEdge: optional(use(newEdge)),
|
|
174
191
|
selected: optional('string'),
|
|
192
|
+
editing: optional('string'),
|
|
175
193
|
updatingUI: optional('boolean'),
|
|
176
194
|
zoom: 'number',
|
|
177
195
|
bounds: use(point),
|
|
@@ -180,21 +198,46 @@ export const machine = createMachine(
|
|
|
180
198
|
).provideOptions(({ assign, batch, erase, filter, action }) => ({
|
|
181
199
|
actions: {
|
|
182
200
|
configure: batch(
|
|
183
|
-
assign('data', {
|
|
201
|
+
assign('data', {
|
|
202
|
+
CONFIGURE: ({ payload: { nodes, edges } }) => ({ nodes, edges }),
|
|
203
|
+
}),
|
|
184
204
|
assign('newEdge', () => undefined),
|
|
185
205
|
assign('updatingUI', () => false),
|
|
186
206
|
action(({ pContext }) => {
|
|
187
207
|
pContext.generatedId = null;
|
|
188
208
|
}),
|
|
189
209
|
action({
|
|
190
|
-
CONFIGURE: ({ payload: { nodes }, pContext
|
|
210
|
+
CONFIGURE: ({ payload: { nodes, defaultData }, pContext }) => {
|
|
211
|
+
pContext.defaultData = defaultData;
|
|
191
212
|
nodes.forEach(({ id, position }) => {
|
|
192
|
-
dimensions[id] = calculateDimensions(position);
|
|
213
|
+
pContext.dimensions[id] = calculateDimensions(position);
|
|
193
214
|
});
|
|
194
215
|
},
|
|
195
216
|
}),
|
|
196
217
|
),
|
|
197
218
|
|
|
219
|
+
setNodeData: assign('data.nodes', {
|
|
220
|
+
SET_NODE_DATA: ({ context: { data }, payload: { id, data: newData } }) => {
|
|
221
|
+
return data?.nodes?.map(node => {
|
|
222
|
+
if (node.id === id) {
|
|
223
|
+
return { ...node, data: { ...node.data, ...newData } };
|
|
224
|
+
}
|
|
225
|
+
return node;
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
}),
|
|
229
|
+
|
|
230
|
+
setBoard: assign('board', { SET_BOARD: ({ payload }) => payload }),
|
|
231
|
+
generateID: action(({ pContext }) => (pContext.generatedId = nanoid())),
|
|
232
|
+
select: assign('selected', { SELECT: ({ payload }) => payload }),
|
|
233
|
+
clearNewEdge: erase('newEdge'),
|
|
234
|
+
deselect: batch(erase('selected'), erase('editing')),
|
|
235
|
+
edit: batch(
|
|
236
|
+
assign('editing', { EDIT: ({ payload }) => payload }),
|
|
237
|
+
assign('selected', { EDIT: ({ payload }) => payload }),
|
|
238
|
+
),
|
|
239
|
+
stopEdit: erase('editing'),
|
|
240
|
+
|
|
198
241
|
startNewEdge: assign('newEdge', {
|
|
199
242
|
START_NEW_EDGE: ({ payload: from, pContext: { dimensions } }) => {
|
|
200
243
|
const { x, y } = dimensions[from].output;
|
|
@@ -202,8 +245,6 @@ export const machine = createMachine(
|
|
|
202
245
|
},
|
|
203
246
|
}),
|
|
204
247
|
|
|
205
|
-
setBoard: assign('board', { SET_BOARD: ({ payload }) => payload }),
|
|
206
|
-
|
|
207
248
|
buildUI: batch(
|
|
208
249
|
assign('edgesPositions', {
|
|
209
250
|
MOVE: ({
|
|
@@ -213,11 +254,12 @@ export const machine = createMachine(
|
|
|
213
254
|
}) => {
|
|
214
255
|
const edges = data?.edges;
|
|
215
256
|
const dimension = dimensions[payload.id];
|
|
257
|
+
if (!dimension) return;
|
|
216
258
|
|
|
217
259
|
const outputOffset =
|
|
218
|
-
dimension
|
|
260
|
+
dimension.outputOffset ?? getDefaultOutputOffset(dimension.width);
|
|
219
261
|
|
|
220
|
-
const inputOffset = dimension
|
|
262
|
+
const inputOffset = dimension.inputOffset ?? DEFAULT_INPUT_OFFSET;
|
|
221
263
|
|
|
222
264
|
const output = {
|
|
223
265
|
x: payload.x + outputOffset.x,
|
|
@@ -229,107 +271,125 @@ export const machine = createMachine(
|
|
|
229
271
|
y: payload.y + inputOffset.y,
|
|
230
272
|
};
|
|
231
273
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
dimension.input = input;
|
|
235
|
-
}
|
|
274
|
+
dimension.output = output;
|
|
275
|
+
dimension.input = input;
|
|
236
276
|
|
|
237
277
|
edges?.forEach(({ from, to, id }) => {
|
|
278
|
+
const edgePosition = edgesPositions[id];
|
|
279
|
+
if (!edgePosition) return;
|
|
280
|
+
|
|
238
281
|
if (from === payload.id) {
|
|
239
|
-
|
|
240
|
-
|
|
282
|
+
edgePosition.x0 = output.x;
|
|
283
|
+
edgePosition.y0 = output.y;
|
|
241
284
|
}
|
|
242
285
|
if (to === payload.id) {
|
|
243
|
-
|
|
244
|
-
|
|
286
|
+
edgePosition.x1 = input.x;
|
|
287
|
+
edgePosition.y1 = input.y;
|
|
245
288
|
}
|
|
246
289
|
});
|
|
247
290
|
|
|
248
291
|
return edgesPositions;
|
|
249
292
|
},
|
|
293
|
+
|
|
250
294
|
MOVE_IMMEDIATE: ({
|
|
251
295
|
context: { data, edgesPositions },
|
|
252
296
|
payload,
|
|
253
297
|
pContext: { dimensions },
|
|
254
298
|
}) => {
|
|
255
|
-
|
|
299
|
+
data?.edges?.forEach(({ from, to, id }) => {
|
|
300
|
+
const dimension = dimensions[payload.id];
|
|
301
|
+
const edgePosition = edgesPositions[id];
|
|
302
|
+
if (!edgePosition || !dimension) return;
|
|
256
303
|
|
|
257
|
-
edges?.forEach(({ from, to, id }) => {
|
|
258
304
|
if (from === payload.id) {
|
|
259
|
-
const dimension = dimensions[payload.id];
|
|
260
305
|
const offset =
|
|
261
|
-
dimension
|
|
306
|
+
dimension.outputOffset ?? getDefaultOutputOffset(dimension.width);
|
|
262
307
|
|
|
263
308
|
const x0 = payload.x + offset.x;
|
|
264
309
|
const y0 = payload.y + offset.y;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (dimension) {
|
|
269
|
-
dimension.output.x = x0;
|
|
270
|
-
dimension.output.y = y0;
|
|
271
|
-
}
|
|
310
|
+
edgePosition.x0 = x0;
|
|
311
|
+
edgePosition.y0 = y0;
|
|
312
|
+
dimension.output = { x: x0, y: y0 };
|
|
272
313
|
}
|
|
273
314
|
|
|
274
315
|
if (to === payload.id) {
|
|
275
|
-
const dimension = dimensions[payload.id];
|
|
276
316
|
const offset = dimension?.inputOffset ?? DEFAULT_INPUT_OFFSET;
|
|
277
|
-
|
|
278
317
|
const x1 = payload.x + offset.x;
|
|
279
318
|
const y1 = payload.y + offset.y;
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (dimension) {
|
|
284
|
-
dimension.input = { x: x1, y: y1 };
|
|
285
|
-
}
|
|
319
|
+
edgePosition.x1 = x1;
|
|
320
|
+
edgePosition.y1 = y1;
|
|
321
|
+
dimension.input = { x: x1, y: y1 };
|
|
286
322
|
}
|
|
287
323
|
});
|
|
288
324
|
|
|
289
325
|
return edgesPositions;
|
|
290
326
|
},
|
|
291
|
-
else: ({ context: { data, edgesPositions }, pContext: { dimensions } }) => {
|
|
292
|
-
const edges = data?.edges ?? [];
|
|
293
|
-
edgesPositions = {};
|
|
294
327
|
|
|
295
|
-
|
|
328
|
+
else: ({ context: { data }, pContext: { dimensions } }) => {
|
|
329
|
+
const nextEdgesPositions: Record<string, Vector> = {};
|
|
330
|
+
|
|
331
|
+
data?.edges?.forEach(({ from, id, to }) => {
|
|
296
332
|
const output = dimensions[from]?.output;
|
|
297
333
|
const input = dimensions[to]?.input;
|
|
298
334
|
|
|
299
335
|
if (output && input) {
|
|
300
|
-
|
|
336
|
+
nextEdgesPositions[id] = {
|
|
301
337
|
x0: output.x,
|
|
302
338
|
y0: output.y,
|
|
303
339
|
x1: input.x,
|
|
304
340
|
y1: input.y,
|
|
305
341
|
};
|
|
306
|
-
} else {
|
|
307
|
-
delete edgesPositions[id];
|
|
308
342
|
}
|
|
309
343
|
});
|
|
310
344
|
|
|
311
|
-
return
|
|
345
|
+
return nextEdgesPositions;
|
|
312
346
|
},
|
|
313
347
|
}),
|
|
314
348
|
|
|
315
349
|
assign('updatingUI', () => true),
|
|
316
350
|
),
|
|
317
351
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
352
|
+
resize: action({
|
|
353
|
+
RESIZE: ({
|
|
354
|
+
payload: {
|
|
355
|
+
id,
|
|
356
|
+
size: { width, height },
|
|
357
|
+
},
|
|
358
|
+
context: { data },
|
|
359
|
+
pContext: { dimensions },
|
|
360
|
+
}) => {
|
|
361
|
+
const dimension = dimensions[id];
|
|
362
|
+
if (!dimension) return;
|
|
363
|
+
|
|
364
|
+
dimension.width = width;
|
|
365
|
+
dimension.height = height;
|
|
366
|
+
|
|
367
|
+
const node = data?.nodes?.find(n => n.id === id);
|
|
368
|
+
const outputOffset = getDefaultOutputOffset(width);
|
|
369
|
+
const inputOffset = dimension.inputOffset ?? DEFAULT_INPUT_OFFSET;
|
|
370
|
+
|
|
371
|
+
dimension.outputOffset = outputOffset;
|
|
372
|
+
dimension.inputOffset = inputOffset;
|
|
373
|
+
|
|
374
|
+
if (node) {
|
|
375
|
+
dimension.output = {
|
|
376
|
+
x: node.position.x + outputOffset.x,
|
|
377
|
+
y: node.position.y + outputOffset.y,
|
|
378
|
+
};
|
|
379
|
+
dimension.input = {
|
|
380
|
+
x: node.position.x + inputOffset.x,
|
|
381
|
+
y: node.position.y + inputOffset.y,
|
|
382
|
+
};
|
|
383
|
+
} else {
|
|
384
|
+
dimension.output = { x: dimension.output.x, y: dimension.output.y };
|
|
385
|
+
}
|
|
321
386
|
},
|
|
322
387
|
}),
|
|
323
388
|
|
|
324
|
-
generateID: action(({ pContext }) => {
|
|
325
|
-
pContext.generatedId = nanoid();
|
|
326
|
-
}),
|
|
327
|
-
|
|
328
389
|
linkChild: batch(
|
|
329
390
|
assign('data.edges', {
|
|
330
|
-
ADD_CHILD: ({ context: { data }, pContext, payload }) => {
|
|
391
|
+
ADD_CHILD: ({ context: { data }, pContext, payload: from }) => {
|
|
331
392
|
const edges = toArray.typed(data?.edges);
|
|
332
|
-
const from = payload;
|
|
333
393
|
const generatedId = pContext?.generatedId;
|
|
334
394
|
const to = buildNodeID(generatedId);
|
|
335
395
|
const id = buildEdgeId(from, to);
|
|
@@ -368,22 +428,14 @@ export const machine = createMachine(
|
|
|
368
428
|
),
|
|
369
429
|
|
|
370
430
|
moveNode: assign('data.nodes', {
|
|
371
|
-
MOVE: ({ context: { data }, payload }) => {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (d.id === id) {
|
|
377
|
-
return { ...d, position: { x, y } };
|
|
378
|
-
}
|
|
379
|
-
return d;
|
|
380
|
-
}) ?? []
|
|
381
|
-
);
|
|
431
|
+
MOVE: ({ context: { data }, payload: { id, x, y } }) => {
|
|
432
|
+
return data?.nodes?.map(node => {
|
|
433
|
+
if (node.id === id) return { ...node, position: { x, y } };
|
|
434
|
+
return node;
|
|
435
|
+
});
|
|
382
436
|
},
|
|
383
437
|
}),
|
|
384
438
|
|
|
385
|
-
select: assign('selected', { SELECT: ({ payload }) => payload }),
|
|
386
|
-
|
|
387
439
|
delete: batch(
|
|
388
440
|
filter('data.edges', {
|
|
389
441
|
DELETE: ({ id, from, to }, _, { payload }) => {
|
|
@@ -392,6 +444,7 @@ export const machine = createMachine(
|
|
|
392
444
|
}),
|
|
393
445
|
|
|
394
446
|
filter('data.nodes', { DELETE: ({ id }, _, { payload }) => id !== payload }),
|
|
447
|
+
erase('editing'),
|
|
395
448
|
),
|
|
396
449
|
|
|
397
450
|
addEdge: batch(
|
|
@@ -405,16 +458,14 @@ export const machine = createMachine(
|
|
|
405
458
|
return out;
|
|
406
459
|
},
|
|
407
460
|
}),
|
|
461
|
+
|
|
408
462
|
erase('newEdge'),
|
|
409
463
|
),
|
|
410
464
|
|
|
411
|
-
clearNewEdge: erase('newEdge'),
|
|
412
|
-
deselect: erase('selected'),
|
|
413
|
-
|
|
414
465
|
zoom: assign('zoom', {
|
|
415
466
|
ZOOM: ({ context: { zoom }, payload, pContext }) => {
|
|
416
467
|
const next = zoom + payload;
|
|
417
|
-
const clamped =
|
|
468
|
+
const clamped = clamp(next, 0.1, 3);
|
|
418
469
|
pContext.previousZoom = undefined;
|
|
419
470
|
return clamped;
|
|
420
471
|
},
|
|
@@ -433,5 +484,132 @@ export const machine = createMachine(
|
|
|
433
484
|
return 1;
|
|
434
485
|
},
|
|
435
486
|
}),
|
|
487
|
+
|
|
488
|
+
// #region UI
|
|
489
|
+
placeChild: assign('data.nodes', {
|
|
490
|
+
ADD_CHILD: ({ payload, context: { data, board }, pContext }) => {
|
|
491
|
+
if (!board) return data?.nodes;
|
|
492
|
+
|
|
493
|
+
const nodes = data?.nodes;
|
|
494
|
+
if (!payload) return nodes;
|
|
495
|
+
|
|
496
|
+
const parentNode = nodes?.find(node => node.id === payload);
|
|
497
|
+
if (!parentNode) return nodes;
|
|
498
|
+
|
|
499
|
+
const parentDimension = pContext.dimensions[payload];
|
|
500
|
+
const id = `node-${pContext.generatedId}`;
|
|
501
|
+
const width = parentDimension?.width ?? DEFAULT_SIZE.width;
|
|
502
|
+
const height = parentDimension?.height ?? DEFAULT_SIZE.height;
|
|
503
|
+
const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
|
|
504
|
+
const initialY = parentNode.position.y;
|
|
505
|
+
|
|
506
|
+
const position = pContext.clampPosition(
|
|
507
|
+
board,
|
|
508
|
+
initialX,
|
|
509
|
+
initialY,
|
|
510
|
+
width,
|
|
511
|
+
height,
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
const defaultData = pContext.defaultData ?? DEFAULT_DATA;
|
|
515
|
+
|
|
516
|
+
nodes?.push({ id, data: { ...defaultData }, position });
|
|
517
|
+
|
|
518
|
+
pContext.dimensions[id] = pContext.calculateDimensions(
|
|
519
|
+
position,
|
|
520
|
+
parentDimension ?? DEFAULT_SIZE,
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
return nodes;
|
|
524
|
+
},
|
|
525
|
+
}),
|
|
526
|
+
|
|
527
|
+
placeParent: assign('data.nodes', {
|
|
528
|
+
ADD_PARENT: ({ context: { data, zoom = 1, board }, pContext }) => {
|
|
529
|
+
if (!board) return data?.nodes;
|
|
530
|
+
|
|
531
|
+
const nodes = data?.nodes;
|
|
532
|
+
const id = `node-${pContext.generatedId}`;
|
|
533
|
+
const container = board.parent;
|
|
534
|
+
const scrollLeft = container?.scrollLeft ?? 0;
|
|
535
|
+
const scrollTop = container?.scrollTop ?? 0;
|
|
536
|
+
const width = container?.width ?? 0;
|
|
537
|
+
const height = container?.height ?? 0;
|
|
538
|
+
const currentZoom = zoom;
|
|
539
|
+
const x = (scrollLeft + width / 2) / currentZoom;
|
|
540
|
+
const y = (scrollTop + height / 2) / currentZoom;
|
|
541
|
+
const position = pContext.clampPosition(
|
|
542
|
+
board,
|
|
543
|
+
x,
|
|
544
|
+
y,
|
|
545
|
+
DEFAULT_SIZE.width,
|
|
546
|
+
DEFAULT_SIZE.height,
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
const defaultData = pContext.defaultData ?? DEFAULT_DATA;
|
|
550
|
+
|
|
551
|
+
nodes?.push({ id, data: { ...defaultData }, position });
|
|
552
|
+
|
|
553
|
+
pContext.dimensions[id] = pContext.calculateDimensions(
|
|
554
|
+
position,
|
|
555
|
+
DEFAULT_SIZE,
|
|
556
|
+
);
|
|
557
|
+
return nodes;
|
|
558
|
+
},
|
|
559
|
+
}),
|
|
560
|
+
|
|
561
|
+
placeSibling: assign('data.nodes', {
|
|
562
|
+
ADD_SIBLING: ({ payload, context: { data, board }, pContext }) => {
|
|
563
|
+
if (!board) return data?.nodes;
|
|
564
|
+
|
|
565
|
+
const edges = data?.edges;
|
|
566
|
+
const nodes = data?.nodes;
|
|
567
|
+
const parentID = edges?.find(edge => edge.to === payload)?.from;
|
|
568
|
+
if (!parentID) return nodes;
|
|
569
|
+
|
|
570
|
+
const parentNode = nodes?.find(node => node.id === parentID);
|
|
571
|
+
if (!parentNode) return nodes;
|
|
572
|
+
|
|
573
|
+
const parentDimension = pContext.dimensions[parentID];
|
|
574
|
+
const id = `node-${pContext.generatedId}`;
|
|
575
|
+
const width = parentDimension?.width ?? DEFAULT_SIZE.width;
|
|
576
|
+
const height = parentDimension?.height ?? DEFAULT_SIZE.height;
|
|
577
|
+
const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
|
|
578
|
+
const initialY = parentNode.position.y + PARENT_CHILD_GAP_WIDTH;
|
|
579
|
+
|
|
580
|
+
const position = pContext.clampPosition(
|
|
581
|
+
board,
|
|
582
|
+
initialX,
|
|
583
|
+
initialY,
|
|
584
|
+
width,
|
|
585
|
+
height,
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
const defaultData = pContext.defaultData ?? DEFAULT_DATA;
|
|
589
|
+
|
|
590
|
+
nodes?.push({ id, data: { ...defaultData }, position });
|
|
591
|
+
|
|
592
|
+
pContext.dimensions[id] = pContext.calculateDimensions(
|
|
593
|
+
position,
|
|
594
|
+
parentDimension ?? DEFAULT_SIZE,
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
return nodes;
|
|
598
|
+
},
|
|
599
|
+
}),
|
|
600
|
+
|
|
601
|
+
moveNewEdge: assign('newEdge', {
|
|
602
|
+
MOVE_NEW_EDGE: ({ context: { newEdge, board }, payload, pContext }) => {
|
|
603
|
+
if (!board) return undefined;
|
|
604
|
+
if (!newEdge) return undefined;
|
|
605
|
+
const { x: x1, y: y1 } = pContext.getBoardPosition(
|
|
606
|
+
payload.x,
|
|
607
|
+
payload.y,
|
|
608
|
+
board,
|
|
609
|
+
);
|
|
610
|
+
return { ...newEdge, x1, y1 };
|
|
611
|
+
},
|
|
612
|
+
}),
|
|
613
|
+
// #endregion
|
|
436
614
|
},
|
|
437
615
|
}));
|
|
@@ -12,25 +12,47 @@ export type Point = inferT<typeof point>;
|
|
|
12
12
|
*
|
|
13
13
|
* @see {@linkcode point}
|
|
14
14
|
*/
|
|
15
|
-
export const nodeOffset = type(({
|
|
16
|
-
input:
|
|
15
|
+
export const nodeOffset = type(({ use }) => ({
|
|
16
|
+
input: use(point),
|
|
17
17
|
output: use(point),
|
|
18
18
|
}));
|
|
19
19
|
|
|
20
20
|
/** Schema definition for edge extremities. */
|
|
21
21
|
export const extremities = type({ from: 'string', to: 'string' });
|
|
22
22
|
|
|
23
|
+
/** Schema definition for primitive values allowed in node data. */
|
|
24
|
+
export const nodeDataValue = type(({ union }) =>
|
|
25
|
+
union('string', 'boolean', 'number'),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Schema definition for serialized node data dictionary.
|
|
30
|
+
*
|
|
31
|
+
* @see {@linkcode nodeDataValue}
|
|
32
|
+
*/
|
|
33
|
+
export const data = type(({ record, use }) => record(use(nodeDataValue)));
|
|
34
|
+
|
|
35
|
+
/** Serialized node data dictionary type inferred from schema {@linkcode data}. */
|
|
36
|
+
export type NodeData = Record<string, string | boolean | number>;
|
|
37
|
+
|
|
23
38
|
/**
|
|
24
39
|
* Schema definition for a serialized flowchart node entity.
|
|
25
40
|
*
|
|
26
|
-
* @see {@linkcode point}
|
|
41
|
+
* @see {@linkcode point}, {@linkcode data}
|
|
27
42
|
*/
|
|
28
|
-
export const nodeJSON = type(({
|
|
43
|
+
export const nodeJSON = type(({ use }) => ({
|
|
29
44
|
position: use(point),
|
|
30
|
-
data:
|
|
31
|
-
input: 'boolean',
|
|
45
|
+
data: use(data),
|
|
32
46
|
}));
|
|
33
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Serialized node properties type inferred from schema {@linkcode nodeJSON}.
|
|
50
|
+
*
|
|
51
|
+
* @template | {@linkcode NodeData} `D` - Custom data properties type extending
|
|
52
|
+
* {@linkcode NodeData}.
|
|
53
|
+
*/
|
|
54
|
+
export type NodeProps<D extends NodeData = NodeData> = { position: Point; data: D };
|
|
55
|
+
|
|
34
56
|
/**
|
|
35
57
|
* Schema definition for a serialized flowchart edge entity.
|
|
36
58
|
*
|
|
@@ -38,6 +60,9 @@ export const nodeJSON = type(({ optional, use }) => ({
|
|
|
38
60
|
*/
|
|
39
61
|
export const edgeJSON = extremities;
|
|
40
62
|
|
|
63
|
+
/** Serialized edge properties type inferred from schema {@linkcode edgeJSON}. */
|
|
64
|
+
export type EdgeProps = inferT<typeof edgeJSON>;
|
|
65
|
+
|
|
41
66
|
/**
|
|
42
67
|
* Schema definition for layout dimensions and connection points of a node.
|
|
43
68
|
*
|
package/src/ui/Flow.tsx
CHANGED
|
@@ -1,21 +1,60 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { createSignal, onMount, Show, type JSX } from 'solid-js';
|
|
2
|
+
import {} from 'solid-js/web';
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import type { NodeData } from '../services/main.machine.typings';
|
|
5
|
+
import { FlowChart } from './components/FlowChart';
|
|
4
6
|
import { Provider } from './components/FlowChart.context';
|
|
7
|
+
import type { FlowProps } from './components/FlowChart.types';
|
|
8
|
+
import { LoadingFallback } from './globals/components/organisms/LoadingFallback';
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* Root Flow component wrapping the {@linkcode FlowChart} inside the flow context
|
|
8
12
|
* provider.
|
|
9
13
|
*
|
|
14
|
+
* @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
|
|
15
|
+
* {@linkcode NodeData}.
|
|
16
|
+
*
|
|
10
17
|
* @param props - Flow chart configuration and callbacks of type
|
|
11
18
|
* {@linkcode FlowProps}.
|
|
12
19
|
*
|
|
13
20
|
* @returns The rendered Solid component.
|
|
14
21
|
*
|
|
15
|
-
* @see {@linkcode Provider}
|
|
22
|
+
* @see {@linkcode Provider}, {@linkcode FlowChart}
|
|
16
23
|
*/
|
|
17
|
-
export const Flow
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
export const Flow = <D extends NodeData = NodeData>(
|
|
25
|
+
props: FlowProps<D>,
|
|
26
|
+
): JSX.Element => {
|
|
27
|
+
const [isMounted, setIsMounted] = createSignal(false);
|
|
28
|
+
|
|
29
|
+
// onMount never runs on the server, only in the browser
|
|
30
|
+
onMount(() => setTimeout(() => setIsMounted(true), props.delay));
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
style={{
|
|
34
|
+
'background-image':
|
|
35
|
+
'radial-gradient(circle, #b8b8b8bf 1px, rgba(0, 0, 0, 0) 1px)',
|
|
36
|
+
}}
|
|
37
|
+
|
|
38
|
+
class='relative flex h-full w-full rounded-lg border-2 border-gray-600 bg-white bg-size-[30px_30px]'
|
|
39
|
+
>
|
|
40
|
+
<Show
|
|
41
|
+
when={isMounted()}
|
|
42
|
+
keyed
|
|
43
|
+
fallback={
|
|
44
|
+
<div class='relative flex flex-1 items-center justify-center'>
|
|
45
|
+
<LoadingFallback
|
|
46
|
+
message='Chart is loading...'
|
|
47
|
+
interval={35}
|
|
48
|
+
rewind
|
|
49
|
+
rewindDelay={1_000}
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
}
|
|
53
|
+
>
|
|
54
|
+
<Provider>
|
|
55
|
+
<FlowChart {...props} />
|
|
56
|
+
</Provider>
|
|
57
|
+
</Show>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -17,9 +17,7 @@ import { useFlow } from './FlowChart.context';
|
|
|
17
17
|
*/
|
|
18
18
|
export const DragBounds: Component = () => {
|
|
19
19
|
const service = useFlow();
|
|
20
|
-
|
|
21
20
|
const zoom = createState(service, { selector: ({ context }) => context.zoom });
|
|
22
|
-
|
|
23
21
|
const board = createState(service, { selector: ({ context }) => context.board });
|
|
24
22
|
|
|
25
23
|
const [state, { addTransformer, removeTransformer, onDragStart, onDragEnd }] =
|