@bemedev/mind-flow 0.1.0 → 0.3.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 +10 -10
- package/lib/helpers/createContext.d.ts +7 -2
- package/lib/helpers/createContext.d.ts.map +1 -1
- package/lib/index.cjs.js +1 -1
- package/lib/index.es.js +556 -510
- package/lib/output.css +1 -1
- package/lib/server.cjs.js +653 -397
- package/lib/server.es.js +657 -401
- package/lib/services/main.machine.d.ts +4857 -218
- package/lib/services/main.machine.d.ts.map +1 -1
- package/lib/{ui/components/FlowChart.data.d.ts → services/main.machine.data.d.ts} +8 -3
- package/lib/services/main.machine.data.d.ts.map +1 -0
- package/lib/services/main.machine.helpers.d.ts +32 -0
- package/lib/services/main.machine.helpers.d.ts.map +1 -0
- package/lib/services/main.machine.typings.d.ts +126 -0
- package/lib/services/main.machine.typings.d.ts.map +1 -0
- package/lib/ui/Flow.d.ts +2 -0
- package/lib/ui/Flow.d.ts.map +1 -1
- package/lib/ui/components/Bounds.d.ts +2 -0
- package/lib/ui/components/Bounds.d.ts.map +1 -1
- package/lib/ui/components/EdgeComponent.d.ts +3 -2
- package/lib/ui/components/EdgeComponent.d.ts.map +1 -1
- package/lib/ui/components/EdgesBoard.d.ts +2 -0
- package/lib/ui/components/EdgesBoard.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.context.d.ts +5608 -996
- package/lib/ui/components/FlowChart.context.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.d.ts +3 -1
- package/lib/ui/components/FlowChart.d.ts.map +1 -1
- package/lib/ui/components/NodeComponent.d.ts +2 -10
- package/lib/ui/components/NodeComponent.d.ts.map +1 -1
- package/lib/ui/components/NodesBoard.d.ts +2 -0
- package/lib/ui/components/NodesBoard.d.ts.map +1 -1
- package/package.json +9 -15
- package/src/helpers/createContext.ts +7 -2
- package/src/{ui/components/FlowChart.data.ts → services/main.machine.data.ts} +7 -2
- package/src/services/main.machine.helpers.ts +55 -0
- package/src/services/main.machine.ts +288 -70
- package/src/services/main.machine.typings.ts +98 -0
- package/src/ui/Flow.tsx +2 -0
- package/src/ui/components/Bounds.tsx +13 -8
- package/src/ui/components/EdgeComponent.tsx +78 -56
- package/src/ui/components/EdgesBoard.tsx +13 -36
- package/src/ui/components/FlowChart.context.ts +204 -291
- package/src/ui/components/FlowChart.tsx +16 -16
- package/src/ui/components/NodeComponent.tsx +60 -104
- package/src/ui/components/NodesBoard.tsx +85 -47
- package/src/ui/components/classes.ts +1 -1
- package/lib/services/main.typings.d.ts +0 -68
- package/lib/services/main.typings.d.ts.map +0 -1
- package/lib/ui/components/FlowChart.data.d.ts.map +0 -1
- package/src/services/main.typings.ts +0 -50
|
@@ -1,39 +1,42 @@
|
|
|
1
1
|
import { createMachine } from '@bemedev/app';
|
|
2
|
-
import { type } from '@bemedev/app/bemedev';
|
|
2
|
+
import { toArray, type } from '@bemedev/app/bemedev';
|
|
3
|
+
import type { useDragDropContext } from '@thisbeyond/solid-dnd';
|
|
3
4
|
import { nanoid } from 'nanoid';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
/** Type alias for drag-drop state extracted from {@linkcode useDragDropContext}. */
|
|
7
|
+
export type DragDropState = Exclude<ReturnType<typeof useDragDropContext>, null>[0];
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export const buildNodeID = (generated?: string | null) => {
|
|
27
|
-
return `node-${generated}`;
|
|
28
|
-
};
|
|
9
|
+
import { DEFAULT_INPUT_OFFSET, getDefaultOutputOffset } from './main.machine.data';
|
|
10
|
+
import {
|
|
11
|
+
buildEdgeId,
|
|
12
|
+
buildNodeID,
|
|
13
|
+
calculateDimensions,
|
|
14
|
+
} from './main.machine.helpers';
|
|
15
|
+
import {
|
|
16
|
+
dimension,
|
|
17
|
+
edgeJSON,
|
|
18
|
+
extremities,
|
|
19
|
+
newEdge,
|
|
20
|
+
nodeJSON,
|
|
21
|
+
point,
|
|
22
|
+
vector,
|
|
23
|
+
board,
|
|
24
|
+
type Dimension,
|
|
25
|
+
type Point,
|
|
26
|
+
type Board,
|
|
27
|
+
} from './main.machine.typings';
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
30
|
* State machine managing flowchart state transitions, nodes, edges, selection, and
|
|
32
31
|
* layout actions.
|
|
32
|
+
*
|
|
33
|
+
* @see {@linkcode calculateDimensions}, {@linkcode buildEdgeId}, {@linkcode buildNodeID}, {@linkcode getDefaultOutputOffset}, {@linkcode DEFAULT_INPUT_OFFSET}
|
|
33
34
|
*/
|
|
34
35
|
export const machine = createMachine(
|
|
35
36
|
{
|
|
36
37
|
initial: 'idle',
|
|
38
|
+
|
|
39
|
+
on: { SET_BOARD: { actions: ['setBoard'] } },
|
|
37
40
|
states: {
|
|
38
41
|
idle: {
|
|
39
42
|
on: {
|
|
@@ -47,9 +50,7 @@ export const machine = createMachine(
|
|
|
47
50
|
working: {
|
|
48
51
|
on: {
|
|
49
52
|
MOVE_IMMEDIATE: {
|
|
50
|
-
actions: [
|
|
51
|
-
{ name: 'buildImmediateUI', description: 'Must be in the ui' },
|
|
52
|
-
],
|
|
53
|
+
actions: [{ name: 'buildUI', description: 'Must be in the ui' }],
|
|
53
54
|
},
|
|
54
55
|
|
|
55
56
|
ADD_CHILD: {
|
|
@@ -70,6 +71,8 @@ export const machine = createMachine(
|
|
|
70
71
|
target: '/construction',
|
|
71
72
|
},
|
|
72
73
|
|
|
74
|
+
ADD_DIMENSION: { actions: ['addDimension'] },
|
|
75
|
+
|
|
73
76
|
ADD_SIBLING: {
|
|
74
77
|
actions: [
|
|
75
78
|
'generateID',
|
|
@@ -81,9 +84,15 @@ export const machine = createMachine(
|
|
|
81
84
|
|
|
82
85
|
MOVE: { actions: ['moveNode', 'buildUI'], target: '/construction' },
|
|
83
86
|
ADD_EDGE: { actions: ['addEdge'], target: '/construction' },
|
|
87
|
+
START_NEW_EDGE: { actions: ['startNewEdge'] },
|
|
88
|
+
MOVE_NEW_EDGE: { actions: ['moveNewEdge'] },
|
|
89
|
+
CLEAR_NEW_EDGE: { actions: ['clearNewEdge'] },
|
|
84
90
|
DELETE: { actions: ['delete'], target: '/construction' },
|
|
85
91
|
SELECT: { actions: ['select'] },
|
|
86
92
|
DESELECT: { actions: ['deselect'] },
|
|
93
|
+
ZOOM: { actions: ['zoom'] },
|
|
94
|
+
TOGGLE_ZOOM: { actions: ['toggleZoom'] },
|
|
95
|
+
SET_BOARD: { actions: ['setBoard'] },
|
|
87
96
|
},
|
|
88
97
|
},
|
|
89
98
|
},
|
|
@@ -95,6 +104,7 @@ export const machine = createMachine(
|
|
|
95
104
|
edges: array(intersection(use(edgeJSON), { id: 'string' })),
|
|
96
105
|
},
|
|
97
106
|
|
|
107
|
+
SET_BOARD: use(board),
|
|
98
108
|
CONFIGURE_EMPTY: 'never',
|
|
99
109
|
MOVE: { id: 'string', x: 'number', y: 'number' },
|
|
100
110
|
MOVE_IMMEDIATE: { id: 'string', x: 'number', y: 'number' },
|
|
@@ -105,79 +115,261 @@ export const machine = createMachine(
|
|
|
105
115
|
SELECT: 'string',
|
|
106
116
|
DESELECT: 'never',
|
|
107
117
|
ADD_EDGE: use(extremities),
|
|
118
|
+
START_NEW_EDGE: 'string',
|
|
119
|
+
MOVE_NEW_EDGE: use(point),
|
|
120
|
+
CLEAR_NEW_EDGE: 'never',
|
|
121
|
+
ZOOM: 'number',
|
|
122
|
+
TOGGLE_ZOOM: 'never',
|
|
123
|
+
ADD_DIMENSION: { id: 'string', dimension: use(dimension) },
|
|
108
124
|
})),
|
|
109
125
|
|
|
110
126
|
sync: true,
|
|
111
|
-
pContext: type(({ union }) => ({
|
|
127
|
+
pContext: type(({ union, optional, record, use, custom }) => ({
|
|
128
|
+
generatedId: union('string', 'null'),
|
|
129
|
+
previousZoom: optional('number'),
|
|
130
|
+
dimensions: record(use(dimension)),
|
|
131
|
+
|
|
132
|
+
getBoardPosition:
|
|
133
|
+
custom<(clientX: number, clientY: number, board: Board) => Point>(),
|
|
112
134
|
|
|
113
|
-
|
|
135
|
+
clampPosition:
|
|
136
|
+
custom<
|
|
137
|
+
(
|
|
138
|
+
board: Board,
|
|
139
|
+
x: number,
|
|
140
|
+
y: number,
|
|
141
|
+
nodeWidth?: number,
|
|
142
|
+
nodeHeight?: number,
|
|
143
|
+
) => Point
|
|
144
|
+
>(),
|
|
145
|
+
|
|
146
|
+
calculateDimensions:
|
|
147
|
+
custom<
|
|
148
|
+
(
|
|
149
|
+
position: { x: number; y: number },
|
|
150
|
+
parentDimension?: Pick<
|
|
151
|
+
{
|
|
152
|
+
width: number;
|
|
153
|
+
height: number;
|
|
154
|
+
output: { x: number; y: number };
|
|
155
|
+
input?: { x: number; y: number } | undefined;
|
|
156
|
+
inputOffset?: { x: number; y: number } | undefined;
|
|
157
|
+
outputOffset?: { x: number; y: number } | undefined;
|
|
158
|
+
},
|
|
159
|
+
'width' | 'height' | 'inputOffset' | 'outputOffset'
|
|
160
|
+
>,
|
|
161
|
+
) => Dimension
|
|
162
|
+
>(),
|
|
163
|
+
})),
|
|
164
|
+
|
|
165
|
+
context: type(({ optional, use, array, record }) => ({
|
|
114
166
|
data: optional({
|
|
115
167
|
nodes: array({ ...use(nodeJSON), id: 'string' }),
|
|
116
168
|
edges: array({ ...use(edgeJSON), id: 'string' }),
|
|
117
169
|
}),
|
|
170
|
+
board: optional(use(board)),
|
|
118
171
|
|
|
172
|
+
edgesPositions: record(use(vector)),
|
|
173
|
+
newEdge: optional(use(newEdge)),
|
|
119
174
|
selected: optional('string'),
|
|
120
175
|
updatingUI: optional('boolean'),
|
|
176
|
+
zoom: 'number',
|
|
177
|
+
bounds: use(point),
|
|
121
178
|
})),
|
|
122
179
|
},
|
|
123
|
-
).provideOptions(({ assign, batch, erase }) => ({
|
|
180
|
+
).provideOptions(({ assign, batch, erase, filter, action }) => ({
|
|
124
181
|
actions: {
|
|
125
182
|
configure: batch(
|
|
126
|
-
assign('
|
|
127
|
-
assign('
|
|
128
|
-
assign('
|
|
129
|
-
|
|
130
|
-
|
|
183
|
+
assign('data', { CONFIGURE: ({ payload }) => payload }),
|
|
184
|
+
assign('newEdge', () => undefined),
|
|
185
|
+
assign('updatingUI', () => false),
|
|
186
|
+
action(({ pContext }) => {
|
|
187
|
+
pContext.generatedId = null;
|
|
188
|
+
}),
|
|
189
|
+
action({
|
|
190
|
+
CONFIGURE: ({ payload: { nodes }, pContext: { dimensions } }) => {
|
|
191
|
+
nodes.forEach(({ id, position }) => {
|
|
192
|
+
dimensions[id] = calculateDimensions(position);
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
}),
|
|
196
|
+
),
|
|
197
|
+
|
|
198
|
+
startNewEdge: assign('newEdge', {
|
|
199
|
+
START_NEW_EDGE: ({ payload: from, pContext: { dimensions } }) => {
|
|
200
|
+
const { x, y } = dimensions[from].output;
|
|
201
|
+
return { from, x0: x, y0: y, x1: x, y1: y };
|
|
202
|
+
},
|
|
203
|
+
}),
|
|
204
|
+
|
|
205
|
+
setBoard: assign('board', { SET_BOARD: ({ payload }) => payload }),
|
|
206
|
+
|
|
207
|
+
buildUI: batch(
|
|
208
|
+
assign('edgesPositions', {
|
|
209
|
+
MOVE: ({
|
|
210
|
+
context: { data, edgesPositions },
|
|
211
|
+
payload,
|
|
212
|
+
pContext: { dimensions },
|
|
213
|
+
}) => {
|
|
214
|
+
const edges = data?.edges;
|
|
215
|
+
const dimension = dimensions[payload.id];
|
|
216
|
+
|
|
217
|
+
const outputOffset =
|
|
218
|
+
dimension?.outputOffset ?? getDefaultOutputOffset(dimension?.width);
|
|
219
|
+
|
|
220
|
+
const inputOffset = dimension?.inputOffset ?? DEFAULT_INPUT_OFFSET;
|
|
221
|
+
|
|
222
|
+
const output = {
|
|
223
|
+
x: payload.x + outputOffset.x,
|
|
224
|
+
y: payload.y + outputOffset.y,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const input = {
|
|
228
|
+
x: payload.x + inputOffset.x,
|
|
229
|
+
y: payload.y + inputOffset.y,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
if (dimension) {
|
|
233
|
+
dimension.output = output;
|
|
234
|
+
dimension.input = input;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
edges?.forEach(({ from, to, id }) => {
|
|
238
|
+
if (from === payload.id) {
|
|
239
|
+
edgesPositions[id].x0 = output.x;
|
|
240
|
+
edgesPositions[id].y0 = output.y;
|
|
241
|
+
}
|
|
242
|
+
if (to === payload.id) {
|
|
243
|
+
edgesPositions[id].x1 = input.x;
|
|
244
|
+
edgesPositions[id].y1 = input.y;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
return edgesPositions;
|
|
249
|
+
},
|
|
250
|
+
MOVE_IMMEDIATE: ({
|
|
251
|
+
context: { data, edgesPositions },
|
|
252
|
+
payload,
|
|
253
|
+
pContext: { dimensions },
|
|
254
|
+
}) => {
|
|
255
|
+
const edges = data?.edges;
|
|
256
|
+
|
|
257
|
+
edges?.forEach(({ from, to, id }) => {
|
|
258
|
+
if (from === payload.id) {
|
|
259
|
+
const dimension = dimensions[payload.id];
|
|
260
|
+
const offset =
|
|
261
|
+
dimension?.outputOffset ?? getDefaultOutputOffset(dimension?.width);
|
|
262
|
+
|
|
263
|
+
const x0 = payload.x + offset.x;
|
|
264
|
+
const y0 = payload.y + offset.y;
|
|
265
|
+
edgesPositions[id].x0 = x0;
|
|
266
|
+
edgesPositions[id].y0 = y0;
|
|
267
|
+
|
|
268
|
+
if (dimension) {
|
|
269
|
+
dimension.output.x = x0;
|
|
270
|
+
dimension.output.y = y0;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (to === payload.id) {
|
|
275
|
+
const dimension = dimensions[payload.id];
|
|
276
|
+
const offset = dimension?.inputOffset ?? DEFAULT_INPUT_OFFSET;
|
|
277
|
+
|
|
278
|
+
const x1 = payload.x + offset.x;
|
|
279
|
+
const y1 = payload.y + offset.y;
|
|
280
|
+
edgesPositions[id].x1 = x1;
|
|
281
|
+
edgesPositions[id].y1 = y1;
|
|
282
|
+
|
|
283
|
+
if (dimension) {
|
|
284
|
+
dimension.input = { x: x1, y: y1 };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
return edgesPositions;
|
|
290
|
+
},
|
|
291
|
+
else: ({ context: { data, edgesPositions }, pContext: { dimensions } }) => {
|
|
292
|
+
const edges = data?.edges ?? [];
|
|
293
|
+
edgesPositions = {};
|
|
294
|
+
|
|
295
|
+
edges.forEach(({ from, id, to }) => {
|
|
296
|
+
const output = dimensions[from]?.output;
|
|
297
|
+
const input = dimensions[to]?.input;
|
|
298
|
+
|
|
299
|
+
if (output && input) {
|
|
300
|
+
edgesPositions[id] = {
|
|
301
|
+
x0: output.x,
|
|
302
|
+
y0: output.y,
|
|
303
|
+
x1: input.x,
|
|
304
|
+
y1: input.y,
|
|
305
|
+
};
|
|
306
|
+
} else {
|
|
307
|
+
delete edgesPositions[id];
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return edgesPositions;
|
|
312
|
+
},
|
|
313
|
+
}),
|
|
314
|
+
|
|
315
|
+
assign('updatingUI', () => true),
|
|
131
316
|
),
|
|
132
317
|
|
|
133
|
-
|
|
318
|
+
addDimension: action({
|
|
319
|
+
ADD_DIMENSION: ({ payload: { id, dimension }, pContext: { dimensions } }) => {
|
|
320
|
+
dimensions[id] = dimension;
|
|
321
|
+
},
|
|
322
|
+
}),
|
|
323
|
+
|
|
324
|
+
generateID: action(({ pContext }) => {
|
|
325
|
+
pContext.generatedId = nanoid();
|
|
326
|
+
}),
|
|
134
327
|
|
|
135
328
|
linkChild: batch(
|
|
136
|
-
assign('
|
|
137
|
-
ADD_CHILD: ({ context, pContext, payload }) => {
|
|
138
|
-
const
|
|
329
|
+
assign('data.edges', {
|
|
330
|
+
ADD_CHILD: ({ context: { data }, pContext, payload }) => {
|
|
331
|
+
const edges = toArray.typed(data?.edges);
|
|
139
332
|
const from = payload;
|
|
140
333
|
const generatedId = pContext?.generatedId;
|
|
141
334
|
const to = buildNodeID(generatedId);
|
|
142
335
|
const id = buildEdgeId(from, to);
|
|
143
|
-
|
|
336
|
+
edges.push({ id, from, to });
|
|
337
|
+
return edges;
|
|
144
338
|
},
|
|
145
339
|
}),
|
|
146
340
|
|
|
147
|
-
assign('
|
|
341
|
+
assign('selected', ({ pContext: { generatedId } }) =>
|
|
148
342
|
buildNodeID(generatedId),
|
|
149
343
|
),
|
|
150
344
|
),
|
|
151
345
|
|
|
152
346
|
linkSibling: batch(
|
|
153
|
-
assign('
|
|
154
|
-
ADD_SIBLING: ({ pContext, payload, context }) => {
|
|
155
|
-
const edges =
|
|
347
|
+
assign('data.edges', {
|
|
348
|
+
ADD_SIBLING: ({ pContext, payload, context: { data } }) => {
|
|
349
|
+
const edges = toArray.typed(data?.edges);
|
|
156
350
|
const generatedId = pContext?.generatedId;
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
if (!from) return out;
|
|
351
|
+
const from = edges.find(({ to }) => to === payload)?.from;
|
|
352
|
+
if (!from) return edges;
|
|
160
353
|
|
|
161
354
|
const to = buildNodeID(generatedId);
|
|
162
355
|
const id = buildEdgeId(from, to);
|
|
163
|
-
|
|
164
|
-
return
|
|
356
|
+
edges.push({ from, to, id });
|
|
357
|
+
return edges;
|
|
165
358
|
},
|
|
166
359
|
}),
|
|
167
360
|
|
|
168
|
-
assign('
|
|
361
|
+
assign('selected', ({ pContext: { generatedId } }) =>
|
|
169
362
|
buildNodeID(generatedId),
|
|
170
363
|
),
|
|
171
364
|
),
|
|
172
365
|
|
|
173
|
-
selectParent: assign('
|
|
366
|
+
selectParent: assign('selected', ({ pContext: { generatedId } }) =>
|
|
174
367
|
buildNodeID(generatedId),
|
|
175
368
|
),
|
|
176
369
|
|
|
177
|
-
moveNode: assign('
|
|
370
|
+
moveNode: assign('data.nodes', {
|
|
178
371
|
MOVE: ({ context: { data }, payload }) => {
|
|
179
372
|
const { id, x, y } = payload;
|
|
180
|
-
if (!id) return data?.nodes ?? [];
|
|
181
373
|
|
|
182
374
|
return (
|
|
183
375
|
data?.nodes?.map(d => {
|
|
@@ -190,30 +382,56 @@ export const machine = createMachine(
|
|
|
190
382
|
},
|
|
191
383
|
}),
|
|
192
384
|
|
|
193
|
-
select: assign('
|
|
385
|
+
select: assign('selected', { SELECT: ({ payload }) => payload }),
|
|
194
386
|
|
|
195
|
-
delete:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
387
|
+
delete: batch(
|
|
388
|
+
filter('data.edges', {
|
|
389
|
+
DELETE: ({ id, from, to }, _, { payload }) => {
|
|
390
|
+
return id !== payload && from !== payload && to !== payload;
|
|
391
|
+
},
|
|
392
|
+
}),
|
|
393
|
+
|
|
394
|
+
filter('data.nodes', { DELETE: ({ id }, _, { payload }) => id !== payload }),
|
|
395
|
+
),
|
|
396
|
+
|
|
397
|
+
addEdge: batch(
|
|
398
|
+
assign('data.edges', {
|
|
399
|
+
ADD_EDGE: ({ context, payload: { from, to } }) => {
|
|
400
|
+
const edges = context.data?.edges ?? [];
|
|
401
|
+
const id = buildEdgeId(from, to);
|
|
402
|
+
if (edges.some(e => e.id === id)) return edges;
|
|
201
403
|
|
|
202
|
-
|
|
404
|
+
const out = [...edges, { id, from, to }];
|
|
405
|
+
return out;
|
|
406
|
+
},
|
|
407
|
+
}),
|
|
408
|
+
erase('newEdge'),
|
|
409
|
+
),
|
|
410
|
+
|
|
411
|
+
clearNewEdge: erase('newEdge'),
|
|
412
|
+
deselect: erase('selected'),
|
|
413
|
+
|
|
414
|
+
zoom: assign('zoom', {
|
|
415
|
+
ZOOM: ({ context: { zoom }, payload, pContext }) => {
|
|
416
|
+
const next = zoom + payload;
|
|
417
|
+
const clamped = Math.min(Math.max(Number(next.toFixed(2)), 0.2), 3);
|
|
418
|
+
pContext.previousZoom = undefined;
|
|
419
|
+
return clamped;
|
|
203
420
|
},
|
|
204
421
|
}),
|
|
205
422
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
const id = buildEdgeId(from, to);
|
|
210
|
-
if (edges.some(e => e.id === id)) return edges;
|
|
423
|
+
toggleZoom: assign('zoom', {
|
|
424
|
+
TOGGLE_ZOOM: ({ context: { zoom }, pContext }) => {
|
|
425
|
+
const previous = pContext.previousZoom;
|
|
211
426
|
|
|
212
|
-
|
|
213
|
-
|
|
427
|
+
if (previous !== undefined) {
|
|
428
|
+
pContext.previousZoom = undefined;
|
|
429
|
+
return previous;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
pContext.previousZoom = zoom;
|
|
433
|
+
return 1;
|
|
214
434
|
},
|
|
215
435
|
}),
|
|
216
|
-
|
|
217
|
-
deselect: erase('context.selected'),
|
|
218
436
|
},
|
|
219
437
|
}));
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { type } from '@bemedev/app/bemedev';
|
|
2
|
+
import type { inferT } from '@bemedev/app/typings';
|
|
3
|
+
|
|
4
|
+
/** Schema definition for 2D coordinates `(x, y)`. */
|
|
5
|
+
export const point = type({ x: 'number', y: 'number' });
|
|
6
|
+
|
|
7
|
+
/** 2D coordinate point type inferred from schema {@linkcode point}. */
|
|
8
|
+
export type Point = inferT<typeof point>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Schema definition for node handle offsets (input and output).
|
|
12
|
+
*
|
|
13
|
+
* @see {@linkcode point}
|
|
14
|
+
*/
|
|
15
|
+
export const nodeOffset = type(({ optional, use }) => ({
|
|
16
|
+
input: optional(use(point)),
|
|
17
|
+
output: use(point),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
/** Schema definition for edge extremities. */
|
|
21
|
+
export const extremities = type({ from: 'string', to: 'string' });
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Schema definition for a serialized flowchart node entity.
|
|
25
|
+
*
|
|
26
|
+
* @see {@linkcode point}
|
|
27
|
+
*/
|
|
28
|
+
export const nodeJSON = type(({ optional, use }) => ({
|
|
29
|
+
position: use(point),
|
|
30
|
+
data: { label: optional('string'), content: 'string' },
|
|
31
|
+
input: 'boolean',
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Schema definition for a serialized flowchart edge entity.
|
|
36
|
+
*
|
|
37
|
+
* @see {@linkcode extremities}
|
|
38
|
+
*/
|
|
39
|
+
export const edgeJSON = extremities;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Schema definition for layout dimensions and connection points of a node.
|
|
43
|
+
*
|
|
44
|
+
* @see {@linkcode point}
|
|
45
|
+
*/
|
|
46
|
+
export const dimension = type(({ optional, use }) => ({
|
|
47
|
+
width: 'number',
|
|
48
|
+
height: 'number',
|
|
49
|
+
output: use(point),
|
|
50
|
+
input: optional(use(point)),
|
|
51
|
+
inputOffset: optional(use(point)),
|
|
52
|
+
outputOffset: optional(use(point)),
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
/** Node layout dimension type inferred from schema {@linkcode dimension}. */
|
|
56
|
+
export type Dimension = inferT<typeof dimension>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Schema definition for a 2D line vector representing edge coordinates `(x0, y0)` to
|
|
60
|
+
* `(x1, y1)`.
|
|
61
|
+
*/
|
|
62
|
+
export const vector = type({
|
|
63
|
+
x0: 'number',
|
|
64
|
+
y0: 'number',
|
|
65
|
+
x1: 'number',
|
|
66
|
+
y1: 'number',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/** 2D vector coordinate type inferred from schema {@linkcode vector}. */
|
|
70
|
+
export type Vector = inferT<typeof vector>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Schema definition for an ongoing new connection edge creation preview between
|
|
74
|
+
* source node and cursor position.
|
|
75
|
+
*
|
|
76
|
+
* @see {@linkcode vector}
|
|
77
|
+
*/
|
|
78
|
+
export const newEdge = type(({ intersection, use }) =>
|
|
79
|
+
intersection({ from: 'string' }, use(vector)),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
/** Ongoing new connection edge preview type inferred from schema {@linkcode newEdge}. */
|
|
83
|
+
export type Edge = inferT<typeof newEdge>;
|
|
84
|
+
|
|
85
|
+
/** Schema definition for flowchart board geometry and container scroll dimensions. */
|
|
86
|
+
export const board = type(({ optional }) => ({
|
|
87
|
+
self: { left: 'number', top: 'number', width: 'number', height: 'number' },
|
|
88
|
+
|
|
89
|
+
parent: optional({
|
|
90
|
+
scrollLeft: 'number',
|
|
91
|
+
scrollTop: 'number',
|
|
92
|
+
height: 'number',
|
|
93
|
+
width: 'number',
|
|
94
|
+
}),
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
/** Flowchart board layout type inferred from schema {@linkcode board}. */
|
|
98
|
+
export type Board = inferT<typeof board>;
|
package/src/ui/Flow.tsx
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { createState } from '@bemedev/app-solidjs';
|
|
1
2
|
import { useDragDropContext, type Transformer } from '@thisbeyond/solid-dnd';
|
|
2
3
|
import { type Component } from 'solid-js';
|
|
3
4
|
|
|
5
|
+
import { BOUNDS_CONSTRAINTS } from '#services/main.machine.data';
|
|
6
|
+
|
|
4
7
|
import { useFlow } from './FlowChart.context';
|
|
5
|
-
import { BOUNDS_CONSTRAINTS } from './FlowChart.data';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Drag boundary transformer component that clamps draggable nodes within container
|
|
@@ -10,12 +12,15 @@ import { BOUNDS_CONSTRAINTS } from './FlowChart.data';
|
|
|
10
12
|
*
|
|
11
13
|
* @returns `null` as this component performs side-effect transformer registrations
|
|
12
14
|
* only.
|
|
15
|
+
*
|
|
16
|
+
* @see {@linkcode useFlow}, {@linkcode BOUNDS_CONSTRAINTS}
|
|
13
17
|
*/
|
|
14
18
|
export const DragBounds: Component = () => {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
const service = useFlow();
|
|
20
|
+
|
|
21
|
+
const zoom = createState(service, { selector: ({ context }) => context.zoom });
|
|
22
|
+
|
|
23
|
+
const board = createState(service, { selector: ({ context }) => context.board });
|
|
19
24
|
|
|
20
25
|
const [state, { addTransformer, removeTransformer, onDragStart, onDragEnd }] =
|
|
21
26
|
useDragDropContext()!;
|
|
@@ -24,7 +29,7 @@ export const DragBounds: Component = () => {
|
|
|
24
29
|
id: 'clamp-to-container',
|
|
25
30
|
order: 100,
|
|
26
31
|
callback: transform => {
|
|
27
|
-
const container =
|
|
32
|
+
const container = board()?.self;
|
|
28
33
|
const activeDraggable = state.active.draggable;
|
|
29
34
|
if (!container || !activeDraggable) return transform;
|
|
30
35
|
|
|
@@ -37,7 +42,7 @@ export const DragBounds: Component = () => {
|
|
|
37
42
|
const minBoardX = BOUNDS_CONSTRAINTS.horizontal;
|
|
38
43
|
const maxBoardX = Math.max(
|
|
39
44
|
minBoardX,
|
|
40
|
-
container.
|
|
45
|
+
container.width / currentZoom -
|
|
41
46
|
node.offsetWidth -
|
|
42
47
|
BOUNDS_CONSTRAINTS.horizontal,
|
|
43
48
|
);
|
|
@@ -45,7 +50,7 @@ export const DragBounds: Component = () => {
|
|
|
45
50
|
const minBoardY = BOUNDS_CONSTRAINTS.vertical;
|
|
46
51
|
const maxBoardY = Math.max(
|
|
47
52
|
minBoardY,
|
|
48
|
-
container.
|
|
53
|
+
container.height / currentZoom -
|
|
49
54
|
node.offsetHeight -
|
|
50
55
|
BOUNDS_CONSTRAINTS.vertical,
|
|
51
56
|
);
|