@bemedev/mind-flow 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +95 -0
  2. package/lib/helpers/createContext.d.ts +5 -8
  3. package/lib/helpers/createContext.d.ts.map +1 -1
  4. package/lib/index.cjs.js +1 -1
  5. package/lib/index.es.js +240 -235
  6. package/lib/server.cjs.js +148 -117
  7. package/lib/server.es.js +148 -117
  8. package/lib/services/main.machine.d.ts +7 -9
  9. package/lib/services/main.machine.d.ts.map +1 -1
  10. package/lib/services/main.typings.d.ts +2 -2
  11. package/lib/ui/Flow.d.ts +2 -2
  12. package/lib/ui/Flow.d.ts.map +1 -1
  13. package/lib/ui/components/Bounds.d.ts +4 -4
  14. package/lib/ui/components/Bounds.d.ts.map +1 -1
  15. package/lib/ui/components/EdgeComponent.d.ts +2 -2
  16. package/lib/ui/components/EdgeComponent.d.ts.map +1 -1
  17. package/lib/ui/components/EdgesBoard.d.ts +2 -2
  18. package/lib/ui/components/EdgesBoard.d.ts.map +1 -1
  19. package/lib/ui/components/FlowChart.context.d.ts +10 -18
  20. package/lib/ui/components/FlowChart.context.d.ts.map +1 -1
  21. package/lib/ui/components/FlowChart.d.ts +4 -10
  22. package/lib/ui/components/FlowChart.d.ts.map +1 -1
  23. package/lib/ui/components/FlowChart.data.d.ts +6 -10
  24. package/lib/ui/components/FlowChart.data.d.ts.map +1 -1
  25. package/lib/ui/components/NodeComponent.d.ts +2 -2
  26. package/lib/ui/components/NodeComponent.d.ts.map +1 -1
  27. package/lib/ui/components/NodesBoard.d.ts +2 -2
  28. package/lib/ui/components/NodesBoard.d.ts.map +1 -1
  29. package/lib/ui/components/classes.d.ts +2 -2
  30. package/package.json +2 -2
  31. package/src/README.md +2 -2
  32. package/src/helpers/createContext.ts +5 -8
  33. package/src/services/main.machine.ts +18 -46
  34. package/src/services/main.typings.ts +2 -2
  35. package/src/ui/Flow.tsx +3 -2
  36. package/src/ui/components/Bounds.tsx +35 -44
  37. package/src/ui/components/EdgeComponent.tsx +7 -8
  38. package/src/ui/components/EdgesBoard.tsx +3 -2
  39. package/src/ui/components/FlowChart.context.ts +56 -82
  40. package/src/ui/components/FlowChart.data.ts +8 -14
  41. package/src/ui/components/FlowChart.tsx +6 -11
  42. package/src/ui/components/NodeComponent.tsx +13 -25
  43. package/src/ui/components/NodesBoard.tsx +109 -111
  44. package/src/ui/components/classes.ts +2 -2
@@ -2,6 +2,7 @@ import { interpret } from '@bemedev/app';
2
2
  import { dequal } from 'dequal/lite';
3
3
  import { createSignal } from 'solid-js';
4
4
  import { produce } from 'solid-js/store';
5
+
5
6
  import { createContext } from '../../helpers/createContext';
6
7
  import { machine } from '../../services/main.machine';
7
8
  import type { Point, Vector } from '../../services/main.typings';
@@ -29,56 +30,50 @@ type Dimensions = {
29
30
  };
30
31
 
31
32
  /** Connection edge coordinate representation between two endpoints. */
32
- export type Edge = {
33
+ type Edge = {
33
34
  /** Source node identifier. */
34
35
  from: string;
35
- /** Starting X-coordinate. */
36
- x0: number;
37
- /** Starting Y-coordinate. */
38
- y0: number;
39
- /** Ending X-coordinate. */
40
- x1: number;
41
- /** Ending Y-coordinate. */
42
- y1: number;
43
- };
44
-
45
- /**
46
- * Shared state machine interpreter service instance for flowchart state
47
- * management.
48
- */
49
- const service = interpret(machine, {
50
- context: {},
51
- pContext: { generatedId: null },
52
- });
36
+ } & Vector;
53
37
 
54
38
  /**
55
- * Solid Context Provider component and hook for accessing flowchart board
56
- * state, services, and zoom.
39
+ * Solid Context Provider component and hook for accessing flowchart board state,
40
+ * services, and zoom.
57
41
  */
58
42
  export const [Provider, useFlow] = createContext(
59
43
  () => {
44
+ /**
45
+ * Shared state machine interpreter service instance for flowchart state
46
+ * management.
47
+ */
48
+ const service = interpret(machine, {
49
+ context: {},
50
+ pContext: { generatedId: null },
51
+ });
52
+
60
53
  const zoom = createSignal(1);
61
54
  const newEdge = createSignal<Edge>();
62
55
  const [boardRef, setBoardRef] = createSignal<HTMLDivElement>();
63
56
 
64
- const [dimensions, setDimensions] = createSignal<
65
- Record<string, Dimensions>
66
- >({}, { equals: dequal });
57
+ const [dimensions, setDimensions] = createSignal<Record<string, Dimensions>>(
58
+ {},
59
+ { equals: dequal },
60
+ );
67
61
 
68
62
  const getBoardPoint = (clientX: number, clientY: number): Point => {
69
63
  const el = boardRef();
70
64
  if (!el) return { x: clientX, y: clientY };
65
+
71
66
  const rect = el.getBoundingClientRect();
72
67
  const currentZoom = zoom[0]();
73
- return {
74
- x: (clientX - rect.left) / currentZoom,
75
- y: (clientY - rect.top) / currentZoom,
76
- };
68
+ const x = clientX - rect.left / currentZoom;
69
+ const y = clientY - rect.top / currentZoom;
70
+ return { x, y };
77
71
  };
78
72
 
79
- const [edgesPositions, setEdgesPositions] = createSignal<
80
- Record<string, Vector>
81
- >({}, { equals: false });
73
+ const [edgesPositions, setEdgesPositions] = createSignal<Record<string, Vector>>(
74
+ {},
75
+ { equals: false },
76
+ );
82
77
 
83
78
  const clampPosition = (
84
79
  x: number,
@@ -88,10 +83,12 @@ export const [Provider, useFlow] = createContext(
88
83
  ): Point => {
89
84
  const container = boardRef()?.parentElement;
90
85
  if (!container) return { x, y };
86
+
91
87
  const currentZoom = zoom[0]();
92
88
 
93
89
  const minX =
94
90
  container.scrollLeft / currentZoom + BOUNDS_CONSTRAINTS.horizontal;
91
+
95
92
  const maxX = Math.max(
96
93
  minX,
97
94
  (container.scrollLeft + container.clientWidth) / currentZoom -
@@ -99,8 +96,8 @@ export const [Provider, useFlow] = createContext(
99
96
  nodeWidth,
100
97
  );
101
98
 
102
- const minY =
103
- container.scrollTop / currentZoom + BOUNDS_CONSTRAINTS.vertical;
99
+ const minY = container.scrollTop / currentZoom + BOUNDS_CONSTRAINTS.vertical;
100
+
104
101
  const maxY = Math.max(
105
102
  minY,
106
103
  (container.scrollTop + container.clientHeight) / currentZoom -
@@ -117,11 +114,7 @@ export const [Provider, useFlow] = createContext(
117
114
  service.addOptions(({ voidAction, batch, assign }) => ({
118
115
  actions: {
119
116
  placeChild: assign('context.data.nodes', {
120
- ADD_CHILD: ({
121
- payload,
122
- context: { data },
123
- pContext: { generatedId },
124
- }) => {
117
+ ADD_CHILD: ({ payload, context: { data }, pContext: { generatedId } }) => {
125
118
  const nodes = data?.nodes ?? [];
126
119
  if (!payload) return nodes;
127
120
 
@@ -131,33 +124,19 @@ export const [Provider, useFlow] = createContext(
131
124
  const id = `node-${generatedId}`;
132
125
  const width = dimensions()[payload]?.width ?? 0;
133
126
  const height = dimensions()[payload]?.height ?? 0;
134
- const initialX =
135
- parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
127
+ const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
136
128
  const initialY = parentNode.position.y;
137
- const position = clampPosition(
138
- initialX,
139
- initialY,
140
- width,
141
- height,
142
- );
129
+ const position = clampPosition(initialX, initialY, width, height);
143
130
 
144
131
  return [
145
132
  ...nodes,
146
- {
147
- id,
148
- data: { content: '<Nouveau nœud>' },
149
- input: true,
150
- position,
151
- },
133
+ { id, data: { content: '<Nouveau nœud>' }, input: true, position },
152
134
  ];
153
135
  },
154
136
  }),
155
137
 
156
138
  placeParent: assign('context.data.nodes', {
157
- ADD_PARENT: ({
158
- context: { data },
159
- pContext: { generatedId },
160
- }) => {
139
+ ADD_PARENT: ({ context: { data }, pContext: { generatedId } }) => {
161
140
  const nodes = data?.nodes ?? [];
162
141
  const id = `node-${generatedId}`;
163
142
  const container = boardRef()?.parentElement;
@@ -189,7 +168,6 @@ export const [Provider, useFlow] = createContext(
189
168
  }) => {
190
169
  const edges = data?.edges ?? [];
191
170
  const nodes = data?.nodes ?? [];
192
-
193
171
  const parentID = edges.find(edge => edge.to === payload)?.from;
194
172
  if (!parentID) return nodes;
195
173
 
@@ -199,24 +177,13 @@ export const [Provider, useFlow] = createContext(
199
177
  const id = `node-${generatedId}`;
200
178
  const width = dimensions()[parentID]?.width ?? 0;
201
179
  const height = dimensions()[parentID]?.height ?? 0;
202
- const initialX =
203
- parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
180
+ const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
204
181
  const initialY = parentNode.position.y + 100;
205
- const position = clampPosition(
206
- initialX,
207
- initialY,
208
- width,
209
- height,
210
- );
182
+ const position = clampPosition(initialX, initialY, width, height);
211
183
 
212
184
  return [
213
185
  ...nodes,
214
- {
215
- id,
216
- data: { content: '<Nouveau nœud>' },
217
- input: true,
218
- position,
219
- },
186
+ { id, data: { content: '<Nouveau nœud>' }, input: true, position },
220
187
  ];
221
188
  },
222
189
  }),
@@ -225,9 +192,9 @@ export const [Provider, useFlow] = createContext(
225
192
  voidAction(({ context: { data } }) => {
226
193
  const edges = data?.edges ?? [];
227
194
  setEdgesPositions(data => {
228
- const array = Object.entries({ ...data }).filter(([id]) => {
229
- return edges?.some(edge => edge.id === id);
230
- });
195
+ const array = Object.entries({ ...data }).filter(([id]) =>
196
+ edges.some(edge => edge.id === id),
197
+ );
231
198
 
232
199
  return Object.fromEntries(array);
233
200
  });
@@ -240,6 +207,7 @@ export const [Provider, useFlow] = createContext(
240
207
  edges?.forEach(({ from, id, to }) => {
241
208
  const output = dimensions()[from]?.output;
242
209
  const input = dimensions()[to]?.input;
210
+
243
211
  if (output && input) {
244
212
  next[id] = {
245
213
  x0: output.x,
@@ -261,12 +229,12 @@ export const [Provider, useFlow] = createContext(
261
229
  if (from === payload.id) {
262
230
  const offset =
263
231
  dimensions()[payload.id]?.outputOffset ??
264
- getDefaultOutputOffset(
265
- dimensions()[payload.id]?.width,
266
- );
232
+ getDefaultOutputOffset(dimensions()[payload.id]?.width);
233
+
267
234
  const x0 = payload.x + offset.x;
268
235
  const y0 = payload.y + offset.y;
269
236
  next[id] = { ...next[id], x0, y0 };
237
+
270
238
  setDimensions(
271
239
  produce(data => {
272
240
  if (data[payload.id]) {
@@ -282,9 +250,11 @@ export const [Provider, useFlow] = createContext(
282
250
  const offset =
283
251
  dimensions()[payload.id]?.inputOffset ??
284
252
  DEFAULT_INPUT_OFFSET;
253
+
285
254
  const x1 = payload.x + offset.x;
286
255
  const y1 = payload.y + offset.y;
287
256
  next[id] = { ...next[id], x1, y1 };
257
+
288
258
  setDimensions(
289
259
  produce(data => {
290
260
  if (data[payload.id]) {
@@ -301,6 +271,7 @@ export const [Provider, useFlow] = createContext(
301
271
  );
302
272
  },
303
273
  }),
274
+
304
275
  assign('context.updatingUI', () => true),
305
276
  ),
306
277
 
@@ -314,12 +285,12 @@ export const [Provider, useFlow] = createContext(
314
285
  if (from === payload.id) {
315
286
  const offset =
316
287
  dimensions()[payload.id]?.outputOffset ??
317
- getDefaultOutputOffset(
318
- dimensions()[payload.id]?.width,
319
- );
288
+ getDefaultOutputOffset(dimensions()[payload.id]?.width);
289
+
320
290
  const x0 = payload.x + offset.x;
321
291
  const y0 = payload.y + offset.y;
322
292
  next[id] = { ...next[id], x0, y0 };
293
+
323
294
  setDimensions(
324
295
  produce(data => {
325
296
  if (data[payload.id]) {
@@ -331,13 +302,15 @@ export const [Provider, useFlow] = createContext(
331
302
  }),
332
303
  );
333
304
  }
305
+
334
306
  if (to === payload.id) {
335
307
  const offset =
336
- dimensions()[payload.id]?.inputOffset ??
337
- DEFAULT_INPUT_OFFSET;
308
+ dimensions()[payload.id]?.inputOffset ?? DEFAULT_INPUT_OFFSET;
309
+
338
310
  const x1 = payload.x + offset.x;
339
311
  const y1 = payload.y + offset.y;
340
312
  next[id] = { ...next[id], x1, y1 };
313
+
341
314
  setDimensions(
342
315
  produce(data => {
343
316
  if (data[payload.id]) {
@@ -367,6 +340,7 @@ export const [Provider, useFlow] = createContext(
367
340
  edgesPositions: [edgesPositions, setEdgesPositions] as const,
368
341
  service,
369
342
  zoom,
343
+ clampPosition,
370
344
  };
371
345
  },
372
346
  { name: 'FlowContext' },
@@ -1,9 +1,6 @@
1
1
  import type { NodeProps } from './FlowChart';
2
2
 
3
- /**
4
- * Horizontal gap in pixels between a parent node and newly created child
5
- * node.
6
- */
3
+ /** Horizontal gap in pixels between a parent node and newly created child node. */
7
4
  export const PARENT_CHILD_GAP_WIDTH = 100;
8
5
 
9
6
  // #region Node & Handle Layout Constants & Formulas
@@ -41,10 +38,7 @@ export const DEFAULT_INPUT_OFFSET = {
41
38
  y: DEFAULT_INPUT_OFFSET_Y,
42
39
  };
43
40
 
44
- /**
45
- * Dimensions representing width and height of the flow chart container
46
- * canvas.
47
- */
41
+ /** Dimensions representing width and height of the flow chart container canvas. */
48
42
  export const CONTAINER_DIMENSIONS = { WIDTH: 5000, HEIGHT: 3500 };
49
43
 
50
44
  /**
@@ -66,16 +60,16 @@ export const TOOLBAR_TOP_OFFSET = 30;
66
60
  export const TOOLBAR_BUFFER = 5;
67
61
 
68
62
  /**
69
- * Boundary padding constraints for node dragging and positioning within
70
- * the viewport.
63
+ * Boundary padding constraints for node dragging and positioning within the
64
+ * viewport.
71
65
  */
72
- export const BOUNDS_CONSTRAINTS = {
73
- horizontal: HANDLE_CONTAINER_OFFSET_X + 2,
74
- vertical: TOOLBAR_TOP_OFFSET + TOOLBAR_BUFFER,
75
- };
66
+ export const BOUNDS_CONSTRAINTS = { horizontal: 55, vertical: 40 };
76
67
 
77
68
  /** Multiplier factor used to compute canvas virtual scroll dimensions. */
78
69
  export const CANVAS_FACTOR = 5;
70
+
71
+ /** Multiplier factor used to compute canvas scroll dimensions. */
72
+ export const SCROLL_MULTIPLIER = 3;
79
73
  // #endregion
80
74
 
81
75
  /** Default node items provided when no initial configuration is given. */
@@ -1,20 +1,15 @@
1
1
  import type { inferT } from '@bemedev/app/typings';
2
2
  import { Component, onCleanup, onMount } from 'solid-js';
3
+
3
4
  import type { edgeJSON, nodeJSON } from '../../services/main.typings';
4
5
  import { useFlow } from './FlowChart.context';
5
- import { NodesBoard } from './NodesBoard';
6
6
  import { DEFAULT_NODES } from './FlowChart.data';
7
+ import { NodesBoard } from './NodesBoard';
7
8
 
8
- /**
9
- * Serialized node properties type inferred from schema
10
- * {@linkcode nodeJSON}.
11
- */
9
+ /** Serialized node properties type inferred from schema {@linkcode nodeJSON}. */
12
10
  export type NodeProps = inferT<typeof nodeJSON>;
13
11
 
14
- /**
15
- * Serialized edge properties type inferred from schema
16
- * {@linkcode edgeJSON}.
17
- */
12
+ /** Serialized edge properties type inferred from schema {@linkcode edgeJSON}. */
18
13
  export type EdgeProps = inferT<typeof edgeJSON>;
19
14
 
20
15
  /**
@@ -56,8 +51,8 @@ export type FlowProps = {
56
51
  // const PARENT_CHILD_GAP_WIDTH = 75;
57
52
 
58
53
  /**
59
- * Flowchart board canvas component that renders interactive nodes, edges,
60
- * pan/zoom, and toolbar controls.
54
+ * Flowchart board canvas component that renders interactive nodes, edges, pan/zoom,
55
+ * and toolbar controls.
61
56
  *
62
57
  * @param props - Flowchart configuration and event handlers of type
63
58
  * {@linkcode FlowProps}.
@@ -1,8 +1,9 @@
1
1
  /* eslint-disable @typescript-eslint/no-namespace */
2
2
  import { useState } from '@bemedev/app-solidjs';
3
3
  import { createDraggable } from '@thisbeyond/solid-dnd';
4
- import { Component, createEffect, createSignal, Show } from 'solid-js';
4
+ import { Component, createSignal, onMount, Show } from 'solid-js';
5
5
  import { produce } from 'solid-js/store';
6
+
6
7
  import { useFlow } from './FlowChart.context';
7
8
  import {
8
9
  DEFAULT_INPUT_OFFSET_X,
@@ -37,8 +38,8 @@ type Props = {
37
38
  };
38
39
 
39
40
  /**
40
- * Interactive flowchart node component supporting dragging, selection,
41
- * handle connections, and child/sibling creation.
41
+ * Interactive flowchart node component supporting dragging, selection, handle
42
+ * connections, and child/sibling creation.
42
43
  *
43
44
  * @param props - Node rendering properties of type {@linkcode Props}.
44
45
  *
@@ -60,7 +61,7 @@ export const NodeComponent: Component<Props> = props => {
60
61
  selector: s => s.context.selected === props.id,
61
62
  });
62
63
 
63
- createEffect(() => {
64
+ onMount(() => {
64
65
  const _inputRef = inputRef;
65
66
  const _outputRef = outputRef;
66
67
  const _rootRef = ref();
@@ -73,15 +74,12 @@ export const NodeComponent: Component<Props> = props => {
73
74
  const inputRect = _inputRef?.getBoundingClientRect();
74
75
 
75
76
  const outputOffsetX =
76
- (outputRect.left - rootRect.left + outputRect.width / 2) /
77
- currentZoom;
77
+ (outputRect.left - rootRect.left + outputRect.width / 2) / currentZoom;
78
78
  const outputOffsetY =
79
- (outputRect.top - rootRect.top + outputRect.height / 2) /
80
- currentZoom;
79
+ (outputRect.top - rootRect.top + outputRect.height / 2) / currentZoom;
81
80
 
82
81
  const inputOffsetX = inputRect
83
- ? (inputRect.left - rootRect.left + inputRect.width / 2) /
84
- currentZoom
82
+ ? (inputRect.left - rootRect.left + inputRect.width / 2) / currentZoom
85
83
  : DEFAULT_INPUT_OFFSET_X;
86
84
  const inputOffsetY = inputRect
87
85
  ? (inputRect.top - rootRect.top + inputRect.height / 2) / currentZoom
@@ -90,10 +88,7 @@ export const NodeComponent: Component<Props> = props => {
90
88
  const width = rootRect.width / currentZoom;
91
89
  const height = rootRect.height / currentZoom;
92
90
 
93
- const output = {
94
- x: props.x + outputOffsetX,
95
- y: props.y + outputOffsetY,
96
- };
91
+ const output = { x: props.x + outputOffsetX, y: props.y + outputOffsetY };
97
92
 
98
93
  const input = { x: props.x + inputOffsetX, y: props.y + inputOffsetY };
99
94
 
@@ -115,7 +110,7 @@ export const NodeComponent: Component<Props> = props => {
115
110
  selector: ({ context: { data } }) => {
116
111
  const edges = data?.edges;
117
112
  if (!edges) return false;
118
- return Object.values(edges).some(edge => edge.to === props.id);
113
+ return edges.some(({ to }) => to === props.id);
119
114
  },
120
115
  });
121
116
 
@@ -182,9 +177,7 @@ export const NodeComponent: Component<Props> = props => {
182
177
  preserveAspectRatio='xMaxYMax'
183
178
  xmlns='http://www.w3.org/2000/svg'
184
179
  fill='white'
185
- onClick={() =>
186
- service.send({ type: 'ADD_SIBLING', payload: props.id })
187
- }
180
+ onClick={() => service.send({ type: 'ADD_SIBLING', payload: props.id })}
188
181
  >
189
182
  <g id='background'>
190
183
  <path d='M467.40667,277.66696c-0.05948,-14.53055 5.75527,-22.95613 -8.62044,-20.90487c-112.55699,16.0607 -222.1609,112.14558 -245.06161,239.85765c-46.52056,259.43466 231.33083,443.06705 449.51209,316.97506c117.31668,-67.80002 160.95215,-190.43324 151.34416,-288.29849c-5.92276,-60.32819 -27.80273,-107.95668 -53.44246,-144.25469l59.39269,-42.05363c111.72214,156.309 73.11535,351.55635 -25.06953,459.45565c-184.18877,202.4124 -470.46624,145.52064 -592.95027,-32.92123c-156.18269,-227.53604 -27.15324,-543.64371 261.18883,-582.44416c5.0579,-0.68061 3.56556,-7.04079 3.56442,-8.58985c-0.05594,-76.3354 -0.11021,-76.7687 1.10909,-77.24589c2.06886,-0.80969 151.41433,118.4561 151.92482,118.95524c4.65592,4.55233 -0.99548,7.829 -29.07828,30.50907c-120.49369,97.31245 -120.4977,98.55675 -123.0691,97.87586c-0.43639,-0.11555 -0.80698,-0.31322 -0.74442,-66.91571Z' />
@@ -194,9 +187,7 @@ export const NodeComponent: Component<Props> = props => {
194
187
  </Show>
195
188
  <svg
196
189
  class='flex cursor-pointer items-center justify-center rounded-lg bg-blue-500 p-0.5 text-center font-bold text-white hover:bg-blue-600'
197
- onClick={() =>
198
- service.send({ type: 'ADD_CHILD', payload: props.id })
199
- }
190
+ onClick={() => service.send({ type: 'ADD_CHILD', payload: props.id })}
200
191
  style={{
201
192
  'pointer-events': 'all',
202
193
  width: `${HANDLE_SIZE * 2}px`,
@@ -242,10 +233,7 @@ export const NodeComponent: Component<Props> = props => {
242
233
  const from = newEdge()?.from;
243
234
 
244
235
  if (from) {
245
- service.send({
246
- type: 'ADD_EDGE',
247
- payload: { from, to: props.id },
248
- });
236
+ service.send({ type: 'ADD_EDGE', payload: { from, to: props.id } });
249
237
  }
250
238
 
251
239
  setNewEdge();