@bemedev/mind-flow 0.1.1 → 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.
Files changed (51) hide show
  1. package/README.md +10 -10
  2. package/lib/helpers/createContext.d.ts +7 -2
  3. package/lib/helpers/createContext.d.ts.map +1 -1
  4. package/lib/index.cjs.js +1 -1
  5. package/lib/index.es.js +556 -510
  6. package/lib/output.css +1 -1
  7. package/lib/server.cjs.js +645 -418
  8. package/lib/server.es.js +649 -422
  9. package/lib/services/main.machine.d.ts +4857 -218
  10. package/lib/services/main.machine.d.ts.map +1 -1
  11. package/lib/{ui/components/FlowChart.data.d.ts → services/main.machine.data.d.ts} +4 -2
  12. package/lib/services/main.machine.data.d.ts.map +1 -0
  13. package/lib/services/main.machine.helpers.d.ts +32 -0
  14. package/lib/services/main.machine.helpers.d.ts.map +1 -0
  15. package/lib/services/main.machine.typings.d.ts +126 -0
  16. package/lib/services/main.machine.typings.d.ts.map +1 -0
  17. package/lib/ui/Flow.d.ts +2 -0
  18. package/lib/ui/Flow.d.ts.map +1 -1
  19. package/lib/ui/components/Bounds.d.ts +2 -0
  20. package/lib/ui/components/Bounds.d.ts.map +1 -1
  21. package/lib/ui/components/EdgeComponent.d.ts +3 -2
  22. package/lib/ui/components/EdgeComponent.d.ts.map +1 -1
  23. package/lib/ui/components/EdgesBoard.d.ts +2 -0
  24. package/lib/ui/components/EdgesBoard.d.ts.map +1 -1
  25. package/lib/ui/components/FlowChart.context.d.ts +5608 -996
  26. package/lib/ui/components/FlowChart.context.d.ts.map +1 -1
  27. package/lib/ui/components/FlowChart.d.ts +3 -1
  28. package/lib/ui/components/FlowChart.d.ts.map +1 -1
  29. package/lib/ui/components/NodeComponent.d.ts +2 -10
  30. package/lib/ui/components/NodeComponent.d.ts.map +1 -1
  31. package/lib/ui/components/NodesBoard.d.ts +2 -0
  32. package/lib/ui/components/NodesBoard.d.ts.map +1 -1
  33. package/package.json +8 -8
  34. package/src/helpers/createContext.ts +7 -2
  35. package/src/{ui/components/FlowChart.data.ts → services/main.machine.data.ts} +3 -1
  36. package/src/services/main.machine.helpers.ts +55 -0
  37. package/src/services/main.machine.ts +288 -70
  38. package/src/services/main.machine.typings.ts +98 -0
  39. package/src/ui/Flow.tsx +2 -0
  40. package/src/ui/components/Bounds.tsx +13 -8
  41. package/src/ui/components/EdgeComponent.tsx +78 -56
  42. package/src/ui/components/EdgesBoard.tsx +13 -36
  43. package/src/ui/components/FlowChart.context.ts +204 -310
  44. package/src/ui/components/FlowChart.tsx +16 -16
  45. package/src/ui/components/NodeComponent.tsx +60 -104
  46. package/src/ui/components/NodesBoard.tsx +74 -47
  47. package/src/ui/components/classes.ts +1 -1
  48. package/lib/services/main.typings.d.ts +0 -68
  49. package/lib/services/main.typings.d.ts.map +0 -1
  50. package/lib/ui/components/FlowChart.data.d.ts.map +0 -1
  51. package/src/services/main.typings.ts +0 -50
@@ -1,366 +1,260 @@
1
1
  import { interpret } from '@bemedev/app';
2
- import { dequal } from 'dequal/lite';
3
- import { createSignal } from 'solid-js';
4
- import { produce } from 'solid-js/store';
5
2
 
6
- import { createContext } from '../../helpers/createContext';
7
- import { machine } from '../../services/main.machine';
8
- import type { Point, Vector } from '../../services/main.typings';
3
+ import { createContext } from '#helpers/createContext';
4
+ import { machine } from '#main-machine';
9
5
  import {
10
6
  BOUNDS_CONSTRAINTS,
11
7
  DEFAULT_INPUT_OFFSET,
12
8
  getDefaultOutputOffset,
13
9
  PARENT_CHILD_GAP_WIDTH,
14
- } from './FlowChart.data';
15
-
16
- /** Layout dimensions and handle offset coordinates for a flowchart node. */
17
- type Dimensions = {
18
- /** Node width in pixels. */
19
- width: number;
20
- /** Node height in pixels. */
21
- height: number;
22
- /** Output handle point coordinates of type {@linkcode Point}. */
23
- output: Point;
24
- /** Input handle point coordinates of type {@linkcode Point}. */
25
- input?: Point;
26
- /** Output handle relative offset point of type {@linkcode Point}. */
27
- outputOffset?: Point;
28
- /** Input handle relative offset point of type {@linkcode Point}. */
29
- inputOffset?: Point;
30
- };
31
-
32
- /** Connection edge coordinate representation between two endpoints. */
33
- type Edge = {
34
- /** Source node identifier. */
35
- from: string;
36
- } & Vector;
10
+ } from '#services/main.machine.data';
11
+ import type { Dimension, Point } from '#services/main.machine.typings';
37
12
 
38
13
  /**
39
14
  * Solid Context Provider component and hook for accessing flowchart board state,
40
15
  * services, and zoom.
16
+ *
17
+ * @see {@linkcode machine}, {@linkcode createContext}
41
18
  */
42
19
  export const [Provider, useFlow] = createContext(
43
20
  () => {
44
- /**
45
- * Shared state machine interpreter service instance for flowchart state
46
- * management.
47
- */
21
+ /** Shared service for flowchart state management. */
48
22
  const service = interpret(machine, {
49
- context: {},
50
- pContext: { generatedId: null },
23
+ context: { zoom: 1, edgesPositions: {}, bounds: { x: 0, y: 0 } },
24
+ pContext: {
25
+ generatedId: null,
26
+ dimensions: {},
27
+
28
+ /**
29
+ * Converts viewport screen coordinates to unscaled board coordinates.
30
+ *
31
+ * @param clientX - Client X coordinate in pixels.
32
+ * @param clientY - Client Y coordinate in pixels.
33
+ * @param el - Board layout details of type {@linkcode Board}.
34
+ *
35
+ * @returns Calculated 2D coordinate of type {@linkcode Point}.
36
+ */
37
+ getBoardPosition(clientX, clientY, el): Point {
38
+ if (!el) return { x: clientX, y: clientY };
39
+ const currentZoom = service.state.context.zoom;
40
+ const x = (clientX - el.self.left) / currentZoom;
41
+ const y = (clientY - el.self.top) / currentZoom;
42
+ return { x, y };
43
+ },
44
+
45
+ /**
46
+ * Clamps given coordinates within board boundaries.
47
+ *
48
+ * @param board - Board layout and container details of type
49
+ * {@linkcode Board}.
50
+ * @param x - Desired X position in pixels.
51
+ * @param y - Desired Y position in pixels.
52
+ * @param nodeWidth - Node width in pixels, defaults to `192`.
53
+ * @param nodeHeight - Node height in pixels, defaults to `50`.
54
+ *
55
+ * @returns Clamped coordinate of type {@linkcode Point}.
56
+ *
57
+ * @see {@linkcode BOUNDS_CONSTRAINTS}
58
+ */
59
+ clampPosition(board, x, y, nodeWidth = 192, nodeHeight = 50): Point {
60
+ const container = board?.parent;
61
+ if (!container) return { x, y };
62
+
63
+ const currentZoom = service.state.context.zoom ?? 1;
64
+
65
+ const minX =
66
+ container.scrollLeft / currentZoom + BOUNDS_CONSTRAINTS.horizontal;
67
+
68
+ const maxX = Math.max(
69
+ minX,
70
+ (container.scrollLeft + container.width) / currentZoom -
71
+ BOUNDS_CONSTRAINTS.horizontal -
72
+ nodeWidth,
73
+ );
74
+
75
+ const minY =
76
+ container.scrollTop / currentZoom + BOUNDS_CONSTRAINTS.vertical;
77
+
78
+ const maxY = Math.max(
79
+ minY,
80
+ (container.scrollTop + container.height) / currentZoom -
81
+ BOUNDS_CONSTRAINTS.vertical -
82
+ nodeHeight,
83
+ );
84
+
85
+ return {
86
+ x: Math.min(Math.max(x, minX), maxX),
87
+ y: Math.min(Math.max(y, minY), maxY),
88
+ };
89
+ },
90
+
91
+ /**
92
+ * Computes node dimension, connection points, and handle offsets.
93
+ *
94
+ * @param position - Node position of type {@linkcode Point}.
95
+ * @param parentDimension - Optional sizing and offsets to inherit.
96
+ *
97
+ * @returns Calculated dimension object of type {@linkcode Dimension}.
98
+ *
99
+ * @see {@linkcode DEFAULT_INPUT_OFFSET}, {@linkcode getDefaultOutputOffset}
100
+ */
101
+ calculateDimensions: (
102
+ position: Point,
103
+ parentDimension: Pick<
104
+ Dimension,
105
+ 'width' | 'height' | 'outputOffset' | 'inputOffset'
106
+ > = { width: 192, height: 50, inputOffset: DEFAULT_INPUT_OFFSET },
107
+ ): Dimension => {
108
+ const width = parentDimension.width;
109
+ const height = parentDimension.height;
110
+ const outputOffset =
111
+ parentDimension.outputOffset ?? getDefaultOutputOffset(width);
112
+ const inputOffset = parentDimension.inputOffset!;
113
+
114
+ const output = {
115
+ x: position.x + outputOffset.x,
116
+ y: position.y + outputOffset.y,
117
+ };
118
+ const input = {
119
+ x: position.x + inputOffset.x,
120
+ y: position.y + inputOffset.y,
121
+ };
122
+
123
+ return { width, height, output, input, outputOffset, inputOffset };
124
+ },
125
+ },
51
126
  });
52
127
 
53
- const zoom = createSignal(1);
54
- const newEdge = createSignal<Edge>();
55
- const [boardRef, setBoardRef] = createSignal<HTMLDivElement>();
56
-
57
- const [dimensions, setDimensions] = createSignal<Record<string, Dimensions>>(
58
- {},
59
- { equals: dequal },
60
- );
61
-
62
- /**
63
- * Converts client viewport coordinates to board canvas coordinates adjusted for
64
- * zoom and scroll.
65
- *
66
- * @param clientX - Viewport horizontal coordinate.
67
- * @param clientY - Viewport vertical coordinate.
68
- *
69
- * @returns Board coordinate point of type {@linkcode Point}.
70
- */
71
- const getBoardPoint = (clientX: number, clientY: number): Point => {
72
- const el = boardRef();
73
- if (!el) return { x: clientX, y: clientY };
74
-
75
- const rect = el.getBoundingClientRect();
76
- const currentZoom = zoom[0]();
77
- const x = clientX - rect.left / currentZoom;
78
- const y = clientY - rect.top / currentZoom;
79
- return { x, y };
80
- };
81
-
82
- const [edgesPositions, setEdgesPositions] = createSignal<Record<string, Vector>>(
83
- {},
84
- { equals: false },
85
- );
86
-
87
- /**
88
- * Clamps node coordinates within the visible container boundaries.
89
- *
90
- * @param x - Desired horizontal X coordinate.
91
- * @param y - Desired vertical Y coordinate.
92
- * @param nodeWidth - Width of the node element in pixels, defaults to `192`.
93
- * @param nodeHeight - Height of the node element in pixels, defaults to `50`.
94
- *
95
- * @returns Clamped coordinate point of type {@linkcode Point}.
96
- */
97
- const clampPosition = (
98
- x: number,
99
- y: number,
100
- nodeWidth = 192,
101
- nodeHeight = 50,
102
- ): Point => {
103
- const container = boardRef()?.parentElement;
104
- if (!container) return { x, y };
105
-
106
- const currentZoom = zoom[0]();
107
-
108
- const minX =
109
- container.scrollLeft / currentZoom + BOUNDS_CONSTRAINTS.horizontal;
110
-
111
- const maxX = Math.max(
112
- minX,
113
- (container.scrollLeft + container.clientWidth) / currentZoom -
114
- BOUNDS_CONSTRAINTS.horizontal -
115
- nodeWidth,
116
- );
128
+ // #endregion
117
129
 
118
- const minY = container.scrollTop / currentZoom + BOUNDS_CONSTRAINTS.vertical;
119
-
120
- const maxY = Math.max(
121
- minY,
122
- (container.scrollTop + container.clientHeight) / currentZoom -
123
- BOUNDS_CONSTRAINTS.vertical -
124
- nodeHeight,
125
- );
126
-
127
- return {
128
- x: Math.min(Math.max(x, minX), maxX),
129
- y: Math.min(Math.max(y, minY), maxY),
130
- };
131
- };
132
-
133
- service.addOptions(({ voidAction, batch, assign }) => ({
130
+ service.addOptions(({ assign }) => ({
134
131
  actions: {
135
- placeChild: assign('context.data.nodes', {
136
- ADD_CHILD: ({ payload, context: { data }, pContext: { generatedId } }) => {
137
- const nodes = data?.nodes ?? [];
132
+ placeChild: assign('data.nodes', {
133
+ ADD_CHILD: ({ payload, context: { data, board }, pContext }) => {
134
+ if (!board) return data?.nodes;
135
+ const nodes = data?.nodes;
138
136
  if (!payload) return nodes;
139
137
 
140
- const parentNode = nodes.find(node => node.id === payload);
138
+ const parentNode = nodes?.find(node => node.id === payload);
141
139
  if (!parentNode) return nodes;
142
140
 
143
- const id = `node-${generatedId}`;
144
- const width = dimensions()[payload]?.width ?? 0;
145
- const height = dimensions()[payload]?.height ?? 0;
141
+ const parentDimension = pContext.dimensions[payload];
142
+
143
+ const id = `node-${pContext.generatedId}`;
144
+ const width = parentDimension?.width ?? 192;
145
+ const height = parentDimension?.height ?? 50;
146
146
  const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
147
147
  const initialY = parentNode.position.y;
148
- const position = clampPosition(initialX, initialY, width, height);
148
+ const position = pContext.clampPosition(
149
+ board,
150
+ initialX,
151
+ initialY,
152
+ width,
153
+ height,
154
+ );
155
+ const dimension = pContext.calculateDimensions(
156
+ position,
157
+ parentDimension,
158
+ );
159
+
160
+ nodes?.push({
161
+ id,
162
+ data: { content: '<Nouveau nœud>' },
163
+ input: true,
164
+ position,
165
+ });
166
+ pContext.dimensions[id] = dimension;
149
167
 
150
- return [
151
- ...nodes,
152
- { id, data: { content: '<Nouveau nœud>' }, input: true, position },
153
- ];
168
+ return nodes;
154
169
  },
155
170
  }),
156
171
 
157
- placeParent: assign('context.data.nodes', {
158
- ADD_PARENT: ({ context: { data }, pContext: { generatedId } }) => {
159
- const nodes = data?.nodes ?? [];
160
- const id = `node-${generatedId}`;
161
- const container = boardRef()?.parentElement;
172
+ placeParent: assign('data.nodes', {
173
+ ADD_PARENT: ({ context: { data, zoom = 1, board }, pContext }) => {
174
+ if (!board) return data?.nodes;
175
+ const nodes = data?.nodes;
176
+ const id = `node-${pContext.generatedId}`;
177
+ const container = board.parent;
162
178
  const scrollLeft = container?.scrollLeft ?? 0;
163
179
  const scrollTop = container?.scrollTop ?? 0;
164
- const width = container?.clientWidth ?? 0;
165
- const height = container?.clientHeight ?? 0;
166
- const currentZoom = zoom[0]();
180
+ const width = container?.width ?? 0;
181
+ const height = container?.height ?? 0;
182
+ const currentZoom = zoom;
167
183
  const x = (scrollLeft + width / 2) / currentZoom;
168
184
  const y = (scrollTop + height / 2) / currentZoom;
185
+ const position = pContext.clampPosition(board, x, y);
186
+ const dimension = pContext.calculateDimensions(position);
187
+
188
+ nodes?.push({
189
+ id,
190
+ data: { content: '<Nouveau nœud>' },
191
+ input: false,
192
+ position,
193
+ });
169
194
 
170
- return [
171
- ...nodes,
172
- {
173
- id,
174
- data: { content: '<Nouveau nœud>' },
175
- input: false,
176
- position: { x, y },
177
- },
178
- ];
195
+ pContext.dimensions[id] = dimension;
196
+ return nodes;
179
197
  },
180
198
  }),
181
199
 
182
- placeSibling: assign('context.data.nodes', {
183
- ADD_SIBLING: ({
184
- payload,
185
- context: { data },
186
- pContext: { generatedId },
187
- }) => {
188
- const edges = data?.edges ?? [];
189
- const nodes = data?.nodes ?? [];
190
- const parentID = edges.find(edge => edge.to === payload)?.from;
200
+ placeSibling: assign('data.nodes', {
201
+ ADD_SIBLING: ({ payload, context: { data, board }, pContext }) => {
202
+ if (!board) return data?.nodes;
203
+ const edges = data?.edges;
204
+ const nodes = data?.nodes;
205
+ const parentID = edges?.find(edge => edge.to === payload)?.from;
191
206
  if (!parentID) return nodes;
192
207
 
193
- const parentNode = nodes.find(node => node.id === parentID);
208
+ const parentNode = nodes?.find(node => node.id === parentID);
194
209
  if (!parentNode) return nodes;
195
210
 
196
- const id = `node-${generatedId}`;
197
- const width = dimensions()[parentID]?.width ?? 0;
198
- const height = dimensions()[parentID]?.height ?? 0;
211
+ const parentDimension = pContext.dimensions[parentID];
212
+ const id = `node-${pContext.generatedId}`;
213
+ const width = parentDimension?.width ?? 192;
214
+ const height = parentDimension?.height ?? 50;
199
215
  const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
200
- const initialY = parentNode.position.y + 100;
201
- const position = clampPosition(initialX, initialY, width, height);
202
-
203
- return [
204
- ...nodes,
205
- { id, data: { content: '<Nouveau nœud>' }, input: true, position },
206
- ];
207
- },
208
- }),
209
-
210
- buildUI: batch(
211
- voidAction(({ context: { data } }) => {
212
- const edges = data?.edges ?? [];
213
- setEdgesPositions(data => {
214
- const array = Object.entries({ ...data }).filter(([id]) =>
215
- edges.some(edge => edge.id === id),
216
- );
216
+ const initialY = parentNode.position.y + PARENT_CHILD_GAP_WIDTH;
217
+ const position = pContext.clampPosition(
218
+ board,
219
+ initialX,
220
+ initialY,
221
+ width,
222
+ height,
223
+ );
224
+ const dimension = pContext.calculateDimensions(
225
+ position,
226
+ parentDimension,
227
+ );
217
228
 
218
- return Object.fromEntries(array);
229
+ nodes?.push({
230
+ id,
231
+ data: { content: '<Nouveau nœud>' },
232
+ input: true,
233
+ position,
219
234
  });
220
- }),
221
- voidAction({
222
- else: ({ context: { data } }) => {
223
- const edges = data?.edges ?? [];
224
- setEdgesPositions(
225
- produce(next => {
226
- edges?.forEach(({ from, id, to }) => {
227
- const output = dimensions()[from]?.output;
228
- const input = dimensions()[to]?.input;
229
-
230
- if (output && input) {
231
- next[id] = {
232
- x0: output.x,
233
- y0: output.y,
234
- x1: input.x,
235
- y1: input.y,
236
- };
237
- }
238
- });
239
- }),
240
- );
241
- },
242
- MOVE: ({ context: { data }, payload }) => {
243
- const edges = data?.edges ?? [];
244
-
245
- setEdgesPositions(
246
- produce(next => {
247
- edges?.forEach(({ from, to, id }) => {
248
- if (from === payload.id) {
249
- const offset =
250
- dimensions()[payload.id]?.outputOffset ??
251
- getDefaultOutputOffset(dimensions()[payload.id]?.width);
252
-
253
- const x0 = payload.x + offset.x;
254
- const y0 = payload.y + offset.y;
255
- next[id] = { ...next[id], x0, y0 };
256
-
257
- setDimensions(
258
- produce(data => {
259
- if (data[payload.id]) {
260
- data[payload.id] = {
261
- ...data[payload.id],
262
- output: { x: x0, y: y0 },
263
- };
264
- }
265
- }),
266
- );
267
- }
268
- if (to === payload.id) {
269
- const offset =
270
- dimensions()[payload.id]?.inputOffset ??
271
- DEFAULT_INPUT_OFFSET;
272
-
273
- const x1 = payload.x + offset.x;
274
- const y1 = payload.y + offset.y;
275
- next[id] = { ...next[id], x1, y1 };
276
235
 
277
- setDimensions(
278
- produce(data => {
279
- if (data[payload.id]) {
280
- data[payload.id] = {
281
- ...data[payload.id],
282
- input: { x: x1, y: y1 },
283
- };
284
- }
285
- }),
286
- );
287
- }
288
- });
289
- }),
290
- );
291
- },
292
- }),
293
-
294
- assign('context.updatingUI', () => true),
295
- ),
296
-
297
- buildImmediateUI: voidAction({
298
- MOVE_IMMEDIATE: ({ context: { data }, payload }) => {
299
- const edges = data?.edges ?? [];
300
-
301
- setEdgesPositions(
302
- produce(next => {
303
- edges?.forEach(({ from, to, id }) => {
304
- if (from === payload.id) {
305
- const offset =
306
- dimensions()[payload.id]?.outputOffset ??
307
- getDefaultOutputOffset(dimensions()[payload.id]?.width);
308
-
309
- const x0 = payload.x + offset.x;
310
- const y0 = payload.y + offset.y;
311
- next[id] = { ...next[id], x0, y0 };
312
-
313
- setDimensions(
314
- produce(data => {
315
- if (data[payload.id]) {
316
- data[payload.id] = {
317
- ...data[payload.id],
318
- output: { x: x0, y: y0 },
319
- };
320
- }
321
- }),
322
- );
323
- }
324
-
325
- if (to === payload.id) {
326
- const offset =
327
- dimensions()[payload.id]?.inputOffset ?? DEFAULT_INPUT_OFFSET;
328
-
329
- const x1 = payload.x + offset.x;
330
- const y1 = payload.y + offset.y;
331
- next[id] = { ...next[id], x1, y1 };
236
+ pContext.dimensions[id] = dimension;
237
+ return nodes;
238
+ },
239
+ }),
332
240
 
333
- setDimensions(
334
- produce(data => {
335
- if (data[payload.id]) {
336
- data[payload.id] = {
337
- ...data[payload.id],
338
- input: { x: x1, y: y1 },
339
- };
340
- }
341
- }),
342
- );
343
- }
344
- });
345
- }),
241
+ moveNewEdge: assign('newEdge', {
242
+ MOVE_NEW_EDGE: ({ context: { newEdge, board }, payload, pContext }) => {
243
+ if (!board) return undefined;
244
+ if (!newEdge) return undefined;
245
+ const { x: x1, y: y1 } = pContext.getBoardPosition(
246
+ payload.x,
247
+ payload.y,
248
+ board,
346
249
  );
250
+ return { ...newEdge, x1, y1 };
347
251
  },
348
252
  }),
349
253
  },
350
254
  }));
351
255
 
352
256
  service.start();
353
-
354
- return {
355
- dimensions: [dimensions, setDimensions] as const,
356
- newEdge,
357
- board: [boardRef, setBoardRef] as const,
358
- getBoardPoint,
359
- edgesPositions: [edgesPositions, setEdgesPositions] as const,
360
- service,
361
- zoom,
362
- clampPosition,
363
- };
257
+ return service;
364
258
  },
365
259
  { name: 'FlowContext' },
366
260
  );
@@ -1,9 +1,10 @@
1
+ import { createState } from '@bemedev/app-solidjs';
1
2
  import type { inferT } from '@bemedev/app/typings';
2
3
  import { Component, onCleanup, onMount } from 'solid-js';
3
4
 
4
- import type { edgeJSON, nodeJSON } from '../../services/main.typings';
5
+ import { DEFAULT_NODES } from '../../services/main.machine.data';
6
+ import type { edgeJSON, nodeJSON } from '../../services/main.machine.typings';
5
7
  import { useFlow } from './FlowChart.context';
6
- import { DEFAULT_NODES } from './FlowChart.data';
7
8
  import { NodesBoard } from './NodesBoard';
8
9
 
9
10
  /** Serialized node properties type inferred from schema {@linkcode nodeJSON}. */
@@ -58,44 +59,43 @@ export type FlowProps = {
58
59
  * {@linkcode FlowProps}.
59
60
  *
60
61
  * @returns The rendered Solid component.
62
+ *
63
+ * @see {@linkcode NodesBoard}, {@linkcode useFlow}, {@linkcode DEFAULT_NODES}
61
64
  */
62
65
  export const FlowChart: Component<FlowProps> = props => {
63
66
  const primaryNodes = props.config?.nodes ?? DEFAULT_NODES;
64
67
  const primaryEdges = props.config?.edges;
65
68
 
66
- const {
67
- service,
68
- newEdge: [newEdge, setNewEdge],
69
- getBoardPoint,
70
- } = useFlow();
69
+ const service = useFlow();
70
+
71
+ const hasNewEdge = createState(service, { selector: s => !!s.context.newEdge });
71
72
 
72
73
  onMount(() => {
73
- service.start();
74
+ service.resume();
74
75
  service.send({
75
76
  type: 'CONFIGURE',
76
77
  payload: { nodes: primaryNodes, edges: primaryEdges ?? [] },
77
78
  });
78
79
  });
79
80
 
80
- onCleanup(service.stop);
81
+ onCleanup(service.pause);
81
82
 
82
83
  return (
83
84
  <div
84
85
  class='relative h-full w-full'
85
- onMouseUp={() => setNewEdge()}
86
+ onMouseUp={() => service.send('CLEAR_NEW_EDGE')}
86
87
  onMouseMove={event => {
87
- const edge = newEdge();
88
- if (edge) {
89
- const boardPoint = getBoardPoint(event.clientX, event.clientY);
90
- setNewEdge({ ...edge, x1: boardPoint.x, y1: boardPoint.y });
91
- }
88
+ service.send({
89
+ type: 'MOVE_NEW_EDGE',
90
+ payload: { x: event.clientX, y: event.clientY },
91
+ });
92
92
  }}
93
93
  style={{}}
94
94
  >
95
95
  <div
96
96
  class='relative h-full w-full bg-white bg-size-[30px_30px]'
97
97
  style={{
98
- cursor: newEdge() ? 'inherit' : 'crosshair',
98
+ cursor: hasNewEdge() ? 'inherit' : 'crosshair',
99
99
  'background-image':
100
100
  'radial-gradient(circle, #b8b8b8bf 1px, rgba(0, 0, 0, 0) 1px)',
101
101
  }}