@bemedev/mind-flow 0.3.2 → 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.
Files changed (115) hide show
  1. package/README.md +55 -12
  2. package/lib/helpers/clamp.d.ts +2 -0
  3. package/lib/helpers/clamp.d.ts.map +1 -0
  4. package/lib/helpers/createContext.d.ts +1 -1
  5. package/lib/helpers/createContext.d.ts.map +1 -1
  6. package/lib/helpers/index.d.ts +3 -0
  7. package/lib/helpers/index.d.ts.map +1 -0
  8. package/lib/index.cjs.js +1 -1
  9. package/lib/index.d.ts +9 -1
  10. package/lib/index.d.ts.map +1 -1
  11. package/lib/index.es.js +2473 -373
  12. package/lib/output.css +1 -1
  13. package/lib/server.cjs.js +1011 -262
  14. package/lib/server.es.js +977 -272
  15. package/lib/services/main.machine.d.ts +1564 -667
  16. package/lib/services/main.machine.d.ts.map +1 -1
  17. package/lib/services/main.machine.data.d.ts +14 -1
  18. package/lib/services/main.machine.data.d.ts.map +1 -1
  19. package/lib/services/main.machine.helpers.d.ts +1 -1
  20. package/lib/services/main.machine.helpers.d.ts.map +1 -1
  21. package/lib/services/main.machine.test.d.ts +2 -0
  22. package/lib/services/main.machine.test.d.ts.map +1 -0
  23. package/lib/services/main.machine.typings.d.ts +26 -8
  24. package/lib/services/main.machine.typings.d.ts.map +1 -1
  25. package/lib/ui/Flow.d.ts +8 -4
  26. package/lib/ui/Flow.d.ts.map +1 -1
  27. package/lib/ui/components/Bounds.d.ts.map +1 -1
  28. package/lib/ui/components/EdgeComponent.d.ts.map +1 -1
  29. package/lib/ui/components/EdgesBoard.d.ts.map +1 -1
  30. package/lib/ui/components/EditPanel.d.ts +19 -0
  31. package/lib/ui/components/EditPanel.d.ts.map +1 -0
  32. package/lib/ui/components/EditPanel.hooks.d.ts +29 -0
  33. package/lib/ui/components/EditPanel.hooks.d.ts.map +1 -0
  34. package/lib/ui/components/EditPanel.types.d.ts +47 -0
  35. package/lib/ui/components/EditPanel.types.d.ts.map +1 -0
  36. package/lib/ui/components/FlowChart.context.d.ts +1563 -666
  37. package/lib/ui/components/FlowChart.context.d.ts.map +1 -1
  38. package/lib/ui/components/FlowChart.d.ts +8 -47
  39. package/lib/ui/components/FlowChart.d.ts.map +1 -1
  40. package/lib/ui/components/FlowChart.types.d.ts +87 -0
  41. package/lib/ui/components/FlowChart.types.d.ts.map +1 -0
  42. package/lib/ui/components/NodeComponent.d.ts +10 -12
  43. package/lib/ui/components/NodeComponent.d.ts.map +1 -1
  44. package/lib/ui/components/NodesBoard.d.ts +17 -2
  45. package/lib/ui/components/NodesBoard.d.ts.map +1 -1
  46. package/lib/ui/components/Panels.d.ts +12 -0
  47. package/lib/ui/components/Panels.d.ts.map +1 -0
  48. package/lib/ui/globals/components/atoms/TypingText.d.ts +29 -0
  49. package/lib/ui/globals/components/atoms/TypingText.d.ts.map +1 -0
  50. package/lib/ui/globals/components/molecules/FadingDots.d.ts +21 -0
  51. package/lib/ui/globals/components/molecules/FadingDots.d.ts.map +1 -0
  52. package/lib/ui/globals/components/organisms/LoadingFallback.d.ts +46 -0
  53. package/lib/ui/globals/components/organisms/LoadingFallback.d.ts.map +1 -0
  54. package/lib/ui/globals/constants/index.d.ts +3 -0
  55. package/lib/ui/globals/constants/index.d.ts.map +1 -0
  56. package/lib/ui/globals/constants/links.d.ts +4 -0
  57. package/lib/ui/globals/constants/links.d.ts.map +1 -0
  58. package/lib/ui/globals/constants/strings.d.ts +5 -0
  59. package/lib/ui/globals/constants/strings.d.ts.map +1 -0
  60. package/lib/ui/globals/directives/clickOutside.d.ts +9 -0
  61. package/lib/ui/globals/directives/clickOutside.d.ts.map +1 -0
  62. package/lib/ui/globals/directives/index.d.ts +16 -0
  63. package/lib/ui/globals/directives/index.d.ts.map +1 -0
  64. package/lib/ui/globals/directives/mouseOut.d.ts +33 -0
  65. package/lib/ui/globals/directives/mouseOut.d.ts.map +1 -0
  66. package/lib/ui/globals/directives/mouseOut.test.d.ts +2 -0
  67. package/lib/ui/globals/directives/mouseOut.test.d.ts.map +1 -0
  68. package/lib/ui/globals/directives/resize.d.ts +11 -0
  69. package/lib/ui/globals/directives/resize.d.ts.map +1 -0
  70. package/lib/ui/globals/helpers/espace.d.ts +17 -0
  71. package/lib/ui/globals/helpers/espace.d.ts.map +1 -0
  72. package/lib/ui/globals/signals/createTyping.d.ts +27 -0
  73. package/lib/ui/globals/signals/createTyping.d.ts.map +1 -0
  74. package/lib/ui/globals/types.d.ts +54 -0
  75. package/lib/ui/globals/types.d.ts.map +1 -0
  76. package/lib/ui/utils.d.ts +11 -0
  77. package/lib/ui/utils.d.ts.map +1 -0
  78. package/package.json +2 -3
  79. package/src/helpers/clamp.ts +3 -0
  80. package/src/helpers/createContext.ts +20 -4
  81. package/src/helpers/index.ts +2 -0
  82. package/src/index.ts +9 -1
  83. package/src/services/main.machine.data.ts +13 -2
  84. package/src/services/main.machine.helpers.ts +12 -4
  85. package/src/services/main.machine.test.ts +205 -0
  86. package/src/services/main.machine.ts +136 -65
  87. package/src/services/main.machine.typings.ts +31 -6
  88. package/src/ui/Flow.tsx +47 -8
  89. package/src/ui/components/Bounds.tsx +0 -2
  90. package/src/ui/components/EdgeComponent.tsx +60 -46
  91. package/src/ui/components/EdgesBoard.tsx +1 -2
  92. package/src/ui/components/EditPanel.hooks.ts +69 -0
  93. package/src/ui/components/EditPanel.tsx +89 -0
  94. package/src/ui/components/EditPanel.types.ts +49 -0
  95. package/src/ui/components/FlowChart.context.ts +21 -11
  96. package/src/ui/components/FlowChart.tsx +20 -57
  97. package/src/ui/components/FlowChart.types.ts +92 -0
  98. package/src/ui/components/NodeComponent.tsx +81 -58
  99. package/src/ui/components/NodesBoard.tsx +29 -7
  100. package/src/ui/components/Panels.tsx +40 -0
  101. package/src/ui/globals/components/atoms/TypingText.tsx +63 -0
  102. package/src/ui/globals/components/molecules/FadingDots.tsx +45 -0
  103. package/src/ui/globals/components/organisms/LoadingFallback.tsx +116 -0
  104. package/src/ui/globals/constants/index.ts +2 -0
  105. package/src/ui/globals/constants/links.ts +15 -0
  106. package/src/ui/globals/constants/strings.ts +5 -0
  107. package/src/ui/globals/directives/clickOutside.ts +18 -0
  108. package/src/ui/globals/directives/index.ts +16 -0
  109. package/src/ui/globals/directives/mouseOut.test.ts +115 -0
  110. package/src/ui/globals/directives/mouseOut.ts +98 -0
  111. package/src/ui/globals/directives/resize.ts +33 -0
  112. package/src/ui/globals/helpers/espace.ts +26 -0
  113. package/src/ui/globals/signals/createTyping.ts +82 -0
  114. package/src/ui/globals/types.ts +83 -0
  115. package/src/ui/utils.ts +14 -0
@@ -6,8 +6,11 @@ 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 { clamp } from '..';
9
10
  import {
11
+ DEFAULT_DATA,
10
12
  DEFAULT_INPUT_OFFSET,
13
+ DEFAULT_SIZE,
11
14
  getDefaultOutputOffset,
12
15
  PARENT_CHILD_GAP_WIDTH,
13
16
  } from './main.machine.data';
@@ -21,6 +24,7 @@ import {
21
24
  edgeJSON,
22
25
  extremities,
23
26
  newEdge,
27
+ data,
24
28
  nodeJSON,
25
29
  point,
26
30
  vector,
@@ -28,13 +32,14 @@ import {
28
32
  type Dimension,
29
33
  type Point,
30
34
  type Board,
35
+ type Vector,
31
36
  } from './main.machine.typings';
32
37
 
33
38
  /**
34
39
  * State machine managing flowchart state transitions, nodes, edges, selection, and
35
40
  * layout actions.
36
41
  *
37
- * @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}
38
43
  */
39
44
  export const machine = createMachine(
40
45
  {
@@ -44,7 +49,7 @@ export const machine = createMachine(
44
49
  states: {
45
50
  idle: {
46
51
  on: {
47
- CONFIGURE: { actions: ['configure'], target: '/working' },
52
+ CONFIGURE: { actions: ['configure'], target: '/construction' },
48
53
  CONFIGURE_EMPTY: '/working',
49
54
  },
50
55
  },
@@ -53,6 +58,7 @@ export const machine = createMachine(
53
58
 
54
59
  working: {
55
60
  on: {
61
+ CONFIGURE: { actions: ['configure'], target: '/construction' },
56
62
  MOVE_IMMEDIATE: {
57
63
  actions: [{ name: 'buildUI', description: 'Must be in the ui' }],
58
64
  },
@@ -75,8 +81,6 @@ export const machine = createMachine(
75
81
  target: '/construction',
76
82
  },
77
83
 
78
- ADD_DIMENSION: { actions: ['addDimension'] },
79
-
80
84
  ADD_SIBLING: {
81
85
  actions: [
82
86
  'generateID',
@@ -86,6 +90,7 @@ export const machine = createMachine(
86
90
  target: '/construction',
87
91
  },
88
92
 
93
+ RESIZE: { actions: ['resize', 'buildUI'] },
89
94
  MOVE: { actions: ['moveNode', 'buildUI'], target: '/construction' },
90
95
  ADD_EDGE: { actions: ['addEdge'], target: '/construction' },
91
96
  START_NEW_EDGE: { actions: ['startNewEdge'] },
@@ -97,15 +102,19 @@ export const machine = createMachine(
97
102
  ZOOM: { actions: ['zoom'] },
98
103
  TOGGLE_ZOOM: { actions: ['toggleZoom'] },
99
104
  SET_BOARD: { actions: ['setBoard'] },
105
+ SET_NODE_DATA: { actions: ['setNodeData'] },
106
+ EDIT: { actions: ['edit'] },
107
+ STOP_EDIT: { actions: ['stopEdit'] },
100
108
  },
101
109
  },
102
110
  },
103
111
  },
104
112
  {
105
- eventsMap: type(({ intersection, use, array }) => ({
113
+ eventsMap: type(({ intersection, use, array, optional }) => ({
106
114
  CONFIGURE: {
107
115
  nodes: array(intersection(use(nodeJSON), { id: 'string' })),
108
116
  edges: array(intersection(use(edgeJSON), { id: 'string' })),
117
+ defaultData: optional(use(data)),
109
118
  },
110
119
 
111
120
  SET_BOARD: use(board),
@@ -118,13 +127,16 @@ export const machine = createMachine(
118
127
  DELETE: 'string',
119
128
  SELECT: 'string',
120
129
  DESELECT: 'never',
130
+ EDIT: 'string',
131
+ STOP_EDIT: 'never',
121
132
  ADD_EDGE: use(extremities),
122
133
  START_NEW_EDGE: 'string',
123
134
  MOVE_NEW_EDGE: use(point),
124
135
  CLEAR_NEW_EDGE: 'never',
125
136
  ZOOM: 'number',
126
137
  TOGGLE_ZOOM: 'never',
127
- ADD_DIMENSION: { id: 'string', dimension: use(dimension) },
138
+ RESIZE: { id: 'string', size: { width: 'number', height: 'number' } },
139
+ SET_NODE_DATA: { id: 'string', data: use(data) },
128
140
  })),
129
141
 
130
142
  sync: true,
@@ -132,6 +144,7 @@ export const machine = createMachine(
132
144
  generatedId: union('string', 'null'),
133
145
  previousZoom: optional('number'),
134
146
  dimensions: record(use(dimension)),
147
+ defaultData: optional(use(data)),
135
148
 
136
149
  getBoardPosition:
137
150
  custom<(clientX: number, clientY: number, board: Board) => Point>(),
@@ -176,6 +189,7 @@ export const machine = createMachine(
176
189
  edgesPositions: record(use(vector)),
177
190
  newEdge: optional(use(newEdge)),
178
191
  selected: optional('string'),
192
+ editing: optional('string'),
179
193
  updatingUI: optional('boolean'),
180
194
  zoom: 'number',
181
195
  bounds: use(point),
@@ -184,26 +198,45 @@ export const machine = createMachine(
184
198
  ).provideOptions(({ assign, batch, erase, filter, action }) => ({
185
199
  actions: {
186
200
  configure: batch(
187
- assign('data', { CONFIGURE: ({ payload }) => payload }),
201
+ assign('data', {
202
+ CONFIGURE: ({ payload: { nodes, edges } }) => ({ nodes, edges }),
203
+ }),
188
204
  assign('newEdge', () => undefined),
189
205
  assign('updatingUI', () => false),
190
206
  action(({ pContext }) => {
191
207
  pContext.generatedId = null;
192
208
  }),
193
209
  action({
194
- CONFIGURE: ({ payload: { nodes }, pContext: { dimensions } }) => {
210
+ CONFIGURE: ({ payload: { nodes, defaultData }, pContext }) => {
211
+ pContext.defaultData = defaultData;
195
212
  nodes.forEach(({ id, position }) => {
196
- dimensions[id] = calculateDimensions(position);
213
+ pContext.dimensions[id] = calculateDimensions(position);
197
214
  });
198
215
  },
199
216
  }),
200
217
  ),
201
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
+
202
230
  setBoard: assign('board', { SET_BOARD: ({ payload }) => payload }),
203
231
  generateID: action(({ pContext }) => (pContext.generatedId = nanoid())),
204
232
  select: assign('selected', { SELECT: ({ payload }) => payload }),
205
233
  clearNewEdge: erase('newEdge'),
206
- deselect: erase('selected'),
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'),
207
240
 
208
241
  startNewEdge: assign('newEdge', {
209
242
  START_NEW_EDGE: ({ payload: from, pContext: { dimensions } }) => {
@@ -221,11 +254,12 @@ export const machine = createMachine(
221
254
  }) => {
222
255
  const edges = data?.edges;
223
256
  const dimension = dimensions[payload.id];
257
+ if (!dimension) return;
224
258
 
225
259
  const outputOffset =
226
- dimension?.outputOffset ?? getDefaultOutputOffset(dimension?.width);
260
+ dimension.outputOffset ?? getDefaultOutputOffset(dimension.width);
227
261
 
228
- const inputOffset = dimension?.inputOffset ?? DEFAULT_INPUT_OFFSET;
262
+ const inputOffset = dimension.inputOffset ?? DEFAULT_INPUT_OFFSET;
229
263
 
230
264
  const output = {
231
265
  x: payload.x + outputOffset.x,
@@ -237,19 +271,20 @@ export const machine = createMachine(
237
271
  y: payload.y + inputOffset.y,
238
272
  };
239
273
 
240
- if (dimension) {
241
- dimension.output = output;
242
- dimension.input = input;
243
- }
274
+ dimension.output = output;
275
+ dimension.input = input;
244
276
 
245
277
  edges?.forEach(({ from, to, id }) => {
278
+ const edgePosition = edgesPositions[id];
279
+ if (!edgePosition) return;
280
+
246
281
  if (from === payload.id) {
247
- edgesPositions[id].x0 = output.x;
248
- edgesPositions[id].y0 = output.y;
282
+ edgePosition.x0 = output.x;
283
+ edgePosition.y0 = output.y;
249
284
  }
250
285
  if (to === payload.id) {
251
- edgesPositions[id].x1 = input.x;
252
- edgesPositions[id].y1 = input.y;
286
+ edgePosition.x1 = input.x;
287
+ edgePosition.y1 = input.y;
253
288
  }
254
289
  });
255
290
 
@@ -261,9 +296,7 @@ export const machine = createMachine(
261
296
  payload,
262
297
  pContext: { dimensions },
263
298
  }) => {
264
- const edges = data?.edges;
265
-
266
- edges?.forEach(({ from, to, id }) => {
299
+ data?.edges?.forEach(({ from, to, id }) => {
267
300
  const dimension = dimensions[payload.id];
268
301
  const edgePosition = edgesPositions[id];
269
302
  if (!edgePosition || !dimension) return;
@@ -292,44 +325,71 @@ export const machine = createMachine(
292
325
  return edgesPositions;
293
326
  },
294
327
 
295
- else: ({ context: { data, edgesPositions }, pContext: { dimensions } }) => {
296
- const edges = toArray.typed(data?.edges);
297
- edgesPositions = {};
328
+ else: ({ context: { data }, pContext: { dimensions } }) => {
329
+ const nextEdgesPositions: Record<string, Vector> = {};
298
330
 
299
- edges.forEach(({ from, id, to }) => {
331
+ data?.edges?.forEach(({ from, id, to }) => {
300
332
  const output = dimensions[from]?.output;
301
333
  const input = dimensions[to]?.input;
302
334
 
303
335
  if (output && input) {
304
- edgesPositions[id] = {
336
+ nextEdgesPositions[id] = {
305
337
  x0: output.x,
306
338
  y0: output.y,
307
339
  x1: input.x,
308
340
  y1: input.y,
309
341
  };
310
- } else {
311
- delete edgesPositions[id];
312
342
  }
313
343
  });
314
344
 
315
- return edgesPositions;
345
+ return nextEdgesPositions;
316
346
  },
317
347
  }),
318
348
 
319
349
  assign('updatingUI', () => true),
320
350
  ),
321
351
 
322
- addDimension: action({
323
- ADD_DIMENSION: ({ payload: { id, dimension }, pContext: { dimensions } }) => {
324
- dimensions[id] = dimension;
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
+ }
325
386
  },
326
387
  }),
327
388
 
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);
@@ -384,6 +444,7 @@ export const machine = createMachine(
384
444
  }),
385
445
 
386
446
  filter('data.nodes', { DELETE: ({ id }, _, { payload }) => id !== payload }),
447
+ erase('editing'),
387
448
  ),
388
449
 
389
450
  addEdge: batch(
@@ -397,13 +458,14 @@ export const machine = createMachine(
397
458
  return out;
398
459
  },
399
460
  }),
461
+
400
462
  erase('newEdge'),
401
463
  ),
402
464
 
403
465
  zoom: assign('zoom', {
404
466
  ZOOM: ({ context: { zoom }, payload, pContext }) => {
405
467
  const next = zoom + payload;
406
- const clamped = Math.min(Math.max(Number(next.toFixed(2)), 0.2), 3);
468
+ const clamped = clamp(next, 0.1, 3);
407
469
  pContext.previousZoom = undefined;
408
470
  return clamped;
409
471
  },
@@ -427,6 +489,7 @@ export const machine = createMachine(
427
489
  placeChild: assign('data.nodes', {
428
490
  ADD_CHILD: ({ payload, context: { data, board }, pContext }) => {
429
491
  if (!board) return data?.nodes;
492
+
430
493
  const nodes = data?.nodes;
431
494
  if (!payload) return nodes;
432
495
 
@@ -434,12 +497,12 @@ export const machine = createMachine(
434
497
  if (!parentNode) return nodes;
435
498
 
436
499
  const parentDimension = pContext.dimensions[payload];
437
-
438
500
  const id = `node-${pContext.generatedId}`;
439
- const width = parentDimension?.width ?? 192;
440
- const height = parentDimension?.height ?? 50;
501
+ const width = parentDimension?.width ?? DEFAULT_SIZE.width;
502
+ const height = parentDimension?.height ?? DEFAULT_SIZE.height;
441
503
  const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
442
504
  const initialY = parentNode.position.y;
505
+
443
506
  const position = pContext.clampPosition(
444
507
  board,
445
508
  initialX,
@@ -447,15 +510,15 @@ export const machine = createMachine(
447
510
  width,
448
511
  height,
449
512
  );
450
- const dimension = pContext.calculateDimensions(position, parentDimension);
451
513
 
452
- nodes?.push({
453
- id,
454
- data: { content: '<Nouveau nœud>' },
455
- input: true,
514
+ const defaultData = pContext.defaultData ?? DEFAULT_DATA;
515
+
516
+ nodes?.push({ id, data: { ...defaultData }, position });
517
+
518
+ pContext.dimensions[id] = pContext.calculateDimensions(
456
519
  position,
457
- });
458
- pContext.dimensions[id] = dimension;
520
+ parentDimension ?? DEFAULT_SIZE,
521
+ );
459
522
 
460
523
  return nodes;
461
524
  },
@@ -464,6 +527,7 @@ export const machine = createMachine(
464
527
  placeParent: assign('data.nodes', {
465
528
  ADD_PARENT: ({ context: { data, zoom = 1, board }, pContext }) => {
466
529
  if (!board) return data?.nodes;
530
+
467
531
  const nodes = data?.nodes;
468
532
  const id = `node-${pContext.generatedId}`;
469
533
  const container = board.parent;
@@ -474,17 +538,22 @@ export const machine = createMachine(
474
538
  const currentZoom = zoom;
475
539
  const x = (scrollLeft + width / 2) / currentZoom;
476
540
  const y = (scrollTop + height / 2) / currentZoom;
477
- const position = pContext.clampPosition(board, x, y);
478
- const dimension = pContext.calculateDimensions(position);
541
+ const position = pContext.clampPosition(
542
+ board,
543
+ x,
544
+ y,
545
+ DEFAULT_SIZE.width,
546
+ DEFAULT_SIZE.height,
547
+ );
479
548
 
480
- nodes?.push({
481
- id,
482
- data: { content: '<Nouveau nœud>' },
483
- input: false,
484
- position,
485
- });
549
+ const defaultData = pContext.defaultData ?? DEFAULT_DATA;
550
+
551
+ nodes?.push({ id, data: { ...defaultData }, position });
486
552
 
487
- pContext.dimensions[id] = dimension;
553
+ pContext.dimensions[id] = pContext.calculateDimensions(
554
+ position,
555
+ DEFAULT_SIZE,
556
+ );
488
557
  return nodes;
489
558
  },
490
559
  }),
@@ -492,6 +561,7 @@ export const machine = createMachine(
492
561
  placeSibling: assign('data.nodes', {
493
562
  ADD_SIBLING: ({ payload, context: { data, board }, pContext }) => {
494
563
  if (!board) return data?.nodes;
564
+
495
565
  const edges = data?.edges;
496
566
  const nodes = data?.nodes;
497
567
  const parentID = edges?.find(edge => edge.to === payload)?.from;
@@ -502,10 +572,11 @@ export const machine = createMachine(
502
572
 
503
573
  const parentDimension = pContext.dimensions[parentID];
504
574
  const id = `node-${pContext.generatedId}`;
505
- const width = parentDimension?.width ?? 192;
506
- const height = parentDimension?.height ?? 50;
575
+ const width = parentDimension?.width ?? DEFAULT_SIZE.width;
576
+ const height = parentDimension?.height ?? DEFAULT_SIZE.height;
507
577
  const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
508
578
  const initialY = parentNode.position.y + PARENT_CHILD_GAP_WIDTH;
579
+
509
580
  const position = pContext.clampPosition(
510
581
  board,
511
582
  initialX,
@@ -513,16 +584,16 @@ export const machine = createMachine(
513
584
  width,
514
585
  height,
515
586
  );
516
- const dimension = pContext.calculateDimensions(position, parentDimension);
517
587
 
518
- nodes?.push({
519
- id,
520
- data: { content: '<Nouveau nœud>' },
521
- input: true,
588
+ const defaultData = pContext.defaultData ?? DEFAULT_DATA;
589
+
590
+ nodes?.push({ id, data: { ...defaultData }, position });
591
+
592
+ pContext.dimensions[id] = pContext.calculateDimensions(
522
593
  position,
523
- });
594
+ parentDimension ?? DEFAULT_SIZE,
595
+ );
524
596
 
525
- pContext.dimensions[id] = dimension;
526
597
  return nodes;
527
598
  },
528
599
  }),
@@ -12,25 +12,47 @@ export type Point = inferT<typeof point>;
12
12
  *
13
13
  * @see {@linkcode point}
14
14
  */
15
- export const nodeOffset = type(({ optional, use }) => ({
16
- input: optional(use(point)),
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(({ optional, use }) => ({
43
+ export const nodeJSON = type(({ use }) => ({
29
44
  position: use(point),
30
- data: { label: optional('string'), content: 'string' },
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 { Component } from 'solid-js';
1
+ import { createSignal, onMount, Show, type JSX } from 'solid-js';
2
+ import {} from 'solid-js/web';
2
3
 
3
- import { FlowChart, type FlowProps } from './components/FlowChart';
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: Component<FlowProps> = props => (
18
- <Provider>
19
- <FlowChart {...props} />
20
- </Provider>
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 }] =