@bemedev/mind-flow 0.0.1 → 0.1.1

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 (45) hide show
  1. package/README.md +95 -0
  2. package/lib/helpers/createContext.d.ts +8 -11
  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 +181 -121
  7. package/lib/server.es.js +181 -121
  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 +3 -3
  11. package/lib/services/main.typings.d.ts.map +1 -1
  12. package/lib/ui/Flow.d.ts +2 -2
  13. package/lib/ui/Flow.d.ts.map +1 -1
  14. package/lib/ui/components/Bounds.d.ts +4 -4
  15. package/lib/ui/components/Bounds.d.ts.map +1 -1
  16. package/lib/ui/components/EdgeComponent.d.ts +2 -2
  17. package/lib/ui/components/EdgeComponent.d.ts.map +1 -1
  18. package/lib/ui/components/EdgesBoard.d.ts +2 -2
  19. package/lib/ui/components/EdgesBoard.d.ts.map +1 -1
  20. package/lib/ui/components/FlowChart.context.d.ts +10 -18
  21. package/lib/ui/components/FlowChart.context.d.ts.map +1 -1
  22. package/lib/ui/components/FlowChart.d.ts +4 -10
  23. package/lib/ui/components/FlowChart.d.ts.map +1 -1
  24. package/lib/ui/components/FlowChart.data.d.ts +10 -11
  25. package/lib/ui/components/FlowChart.data.d.ts.map +1 -1
  26. package/lib/ui/components/NodeComponent.d.ts +2 -2
  27. package/lib/ui/components/NodeComponent.d.ts.map +1 -1
  28. package/lib/ui/components/NodesBoard.d.ts +2 -2
  29. package/lib/ui/components/NodesBoard.d.ts.map +1 -1
  30. package/lib/ui/components/classes.d.ts +2 -2
  31. package/package.json +3 -9
  32. package/src/README.md +2 -2
  33. package/src/helpers/createContext.ts +8 -11
  34. package/src/services/main.machine.ts +18 -46
  35. package/src/services/main.typings.ts +3 -3
  36. package/src/ui/Flow.tsx +3 -2
  37. package/src/ui/components/Bounds.tsx +35 -44
  38. package/src/ui/components/EdgeComponent.tsx +7 -8
  39. package/src/ui/components/EdgesBoard.tsx +3 -2
  40. package/src/ui/components/FlowChart.context.ts +75 -82
  41. package/src/ui/components/FlowChart.data.ts +12 -15
  42. package/src/ui/components/FlowChart.tsx +6 -11
  43. package/src/ui/components/NodeComponent.tsx +13 -25
  44. package/src/ui/components/NodesBoard.tsx +120 -111
  45. package/src/ui/components/classes.ts +2 -2
@@ -1,17 +1,15 @@
1
- import {
2
- useDragDropContext,
3
- type Transformer,
4
- } from '@thisbeyond/solid-dnd';
1
+ import { useDragDropContext, type Transformer } from '@thisbeyond/solid-dnd';
5
2
  import { type Component } from 'solid-js';
6
- import { BOUNDS_CONSTRAINTS } from './FlowChart.data';
3
+
7
4
  import { useFlow } from './FlowChart.context';
5
+ import { BOUNDS_CONSTRAINTS } from './FlowChart.data';
8
6
 
9
7
  /**
10
- * Drag boundary transformer component that clamps draggable nodes within
11
- * container scroll bounds.
8
+ * Drag boundary transformer component that clamps draggable nodes within container
9
+ * scroll bounds.
12
10
  *
13
- * @returns `null` as this component performs side-effect transformer
14
- * registrations only.
11
+ * @returns `null` as this component performs side-effect transformer registrations
12
+ * only.
15
13
  */
16
14
  export const DragBounds: Component = () => {
17
15
  const {
@@ -19,58 +17,51 @@ export const DragBounds: Component = () => {
19
17
  zoom: [zoom],
20
18
  } = useFlow();
21
19
 
22
- const [
23
- state,
24
- { addTransformer, removeTransformer, onDragStart, onDragEnd },
25
- ] = useDragDropContext()!;
20
+ const [state, { addTransformer, removeTransformer, onDragStart, onDragEnd }] =
21
+ useDragDropContext()!;
26
22
 
27
23
  const transformer: Transformer = {
28
24
  id: 'clamp-to-container',
29
25
  order: 100,
30
26
  callback: transform => {
31
- const container = ref()?.parentElement;
27
+ const container = ref();
32
28
  const activeDraggable = state.active.draggable;
33
29
  if (!container || !activeDraggable) return transform;
34
- const draggableLayout = activeDraggable.layout;
35
30
 
36
- // #region Inner visible boundaries (excluding borders and scrollbars)
37
- const containerRect = container.getBoundingClientRect();
38
- const innerLeft = containerRect.left + container.clientLeft;
39
- const innerTop = containerRect.top + container.clientTop;
40
- const innerRight = innerLeft + container.clientWidth;
41
- const innerBottom = innerTop + container.clientHeight;
42
- // #endregion
31
+ const node = activeDraggable.node as HTMLElement;
32
+ if (!node) return transform;
43
33
 
44
- // #region Convert boundaries to board coordinate space
45
34
  const currentZoom = zoom();
46
- const minX =
47
- innerLeft -
48
- draggableLayout.left +
49
- BOUNDS_CONSTRAINTS.horizontal * currentZoom;
50
35
 
51
- const maxX = Math.max(
52
- minX,
53
- innerRight -
54
- draggableLayout.right -
55
- BOUNDS_CONSTRAINTS.horizontal * currentZoom,
36
+ // #region Board space boundaries (unscaled CSS pixels relative to board container)
37
+ const minBoardX = BOUNDS_CONSTRAINTS.horizontal;
38
+ const maxBoardX = Math.max(
39
+ minBoardX,
40
+ container.clientWidth / currentZoom -
41
+ node.offsetWidth -
42
+ BOUNDS_CONSTRAINTS.horizontal,
56
43
  );
57
44
 
58
- const minY =
59
- innerTop -
60
- draggableLayout.top +
61
- BOUNDS_CONSTRAINTS.vertical * currentZoom;
62
-
63
- const maxY = Math.max(
64
- minY,
65
- innerBottom -
66
- draggableLayout.bottom -
67
- BOUNDS_CONSTRAINTS.vertical * currentZoom,
45
+ const minBoardY = BOUNDS_CONSTRAINTS.vertical;
46
+ const maxBoardY = Math.max(
47
+ minBoardY,
48
+ container.clientHeight / currentZoom -
49
+ node.offsetHeight -
50
+ BOUNDS_CONSTRAINTS.vertical,
68
51
  );
69
52
  // #endregion
70
53
 
54
+ // #region Convert board boundaries to transformer delta space (in screen pixels)
55
+ const minTransformX = (minBoardX - node.offsetLeft) * currentZoom;
56
+ const maxTransformX = (maxBoardX - node.offsetLeft) * currentZoom;
57
+
58
+ const minTransformY = (minBoardY - node.offsetTop) * currentZoom;
59
+ const maxTransformY = (maxBoardY - node.offsetTop) * currentZoom;
60
+ // #endregion
61
+
71
62
  return {
72
- x: Math.min(Math.max(transform.x, minX), maxX),
73
- y: Math.min(Math.max(transform.y, minY), maxY),
63
+ x: Math.min(Math.max(transform.x, minTransformX), maxTransformX),
64
+ y: Math.min(Math.max(transform.y, minTransformY), maxTransformY),
74
65
  };
75
66
  },
76
67
  };
@@ -1,5 +1,6 @@
1
1
  import { useState } from '@bemedev/app-solidjs';
2
2
  import { Component, createEffect, createSignal, Show } from 'solid-js';
3
+
3
4
  import type { Vector } from '../../services/main.typings';
4
5
  import { useFlow } from './FlowChart.context';
5
6
 
@@ -21,8 +22,8 @@ type Props = {
21
22
  const calculateOffset = (value: number) => (value * 100) / 200;
22
23
 
23
24
  /**
24
- * Generates an SVG cubic bezier path `d` string between coordinates `(x0,
25
- * y0)` and `(x1, y1)`.
25
+ * Generates an SVG cubic bezier path `d` string between coordinates `(x0, y0)` and
26
+ * `(x1, y1)`.
26
27
  *
27
28
  * @param vector - Vector coordinates of type {@linkcode Vector}.
28
29
  *
@@ -33,8 +34,8 @@ const draw = ({ x0, y0, x1, y1 }: Vector) => {
33
34
  };
34
35
 
35
36
  /**
36
- * SVG edge component rendering bezier curves and delete interaction
37
- * handles for node connections.
37
+ * SVG edge component rendering bezier curves and delete interaction handles for node
38
+ * connections.
38
39
  *
39
40
  * @param props - Edge properties of type {@linkcode Props}.
40
41
  *
@@ -64,10 +65,8 @@ export const EdgeComponent: Component<Props> = props => {
64
65
  class='relative cursor-pointer fill-transparent'
65
66
  classList={{
66
67
  'stroke-[rgba(168,168,168,0.4)] stroke-3 z-200': !!props.isNew,
67
- 'stroke-[rgba(168,168,168,1)] stroke-4 z-100':
68
- selected() && !props.isNew,
69
- 'stroke-[rgba(168,168,168,0.8)] stroke-3':
70
- !selected() && !props.isNew,
68
+ 'stroke-[rgba(168,168,168,1)] stroke-4 z-100': selected() && !props.isNew,
69
+ 'stroke-[rgba(168,168,168,0.8)] stroke-3': !selected() && !props.isNew,
71
70
  }}
72
71
  style={{ 'pointer-events': props.isNew ? 'none' : 'all' }}
73
72
  d={draw(props)}
@@ -6,12 +6,13 @@ import {
6
6
  For,
7
7
  Show,
8
8
  } from 'solid-js';
9
+
9
10
  import { EdgeComponent } from './EdgeComponent';
10
11
  import { useFlow } from './FlowChart.context';
11
12
 
12
13
  /**
13
- * SVG board overlay component that renders all active connecting edges and
14
- * ongoing edge creation previews.
14
+ * SVG board overlay component that renders all active connecting edges and ongoing
15
+ * edge creation previews.
15
16
  *
16
17
  * @returns The rendered SVG JSX element.
17
18
  */
@@ -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,57 +30,70 @@ 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
 
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
+ */
68
71
  const getBoardPoint = (clientX: number, clientY: number): Point => {
69
72
  const el = boardRef();
70
73
  if (!el) return { x: clientX, y: clientY };
74
+
71
75
  const rect = el.getBoundingClientRect();
72
76
  const currentZoom = zoom[0]();
73
- return {
74
- x: (clientX - rect.left) / currentZoom,
75
- y: (clientY - rect.top) / currentZoom,
76
- };
77
+ const x = clientX - rect.left / currentZoom;
78
+ const y = clientY - rect.top / currentZoom;
79
+ return { x, y };
77
80
  };
78
81
 
79
- const [edgesPositions, setEdgesPositions] = createSignal<
80
- Record<string, Vector>
81
- >({}, { equals: false });
82
+ const [edgesPositions, setEdgesPositions] = createSignal<Record<string, Vector>>(
83
+ {},
84
+ { equals: false },
85
+ );
82
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
+ */
83
97
  const clampPosition = (
84
98
  x: number,
85
99
  y: number,
@@ -88,10 +102,12 @@ export const [Provider, useFlow] = createContext(
88
102
  ): Point => {
89
103
  const container = boardRef()?.parentElement;
90
104
  if (!container) return { x, y };
105
+
91
106
  const currentZoom = zoom[0]();
92
107
 
93
108
  const minX =
94
109
  container.scrollLeft / currentZoom + BOUNDS_CONSTRAINTS.horizontal;
110
+
95
111
  const maxX = Math.max(
96
112
  minX,
97
113
  (container.scrollLeft + container.clientWidth) / currentZoom -
@@ -99,8 +115,8 @@ export const [Provider, useFlow] = createContext(
99
115
  nodeWidth,
100
116
  );
101
117
 
102
- const minY =
103
- container.scrollTop / currentZoom + BOUNDS_CONSTRAINTS.vertical;
118
+ const minY = container.scrollTop / currentZoom + BOUNDS_CONSTRAINTS.vertical;
119
+
104
120
  const maxY = Math.max(
105
121
  minY,
106
122
  (container.scrollTop + container.clientHeight) / currentZoom -
@@ -117,11 +133,7 @@ export const [Provider, useFlow] = createContext(
117
133
  service.addOptions(({ voidAction, batch, assign }) => ({
118
134
  actions: {
119
135
  placeChild: assign('context.data.nodes', {
120
- ADD_CHILD: ({
121
- payload,
122
- context: { data },
123
- pContext: { generatedId },
124
- }) => {
136
+ ADD_CHILD: ({ payload, context: { data }, pContext: { generatedId } }) => {
125
137
  const nodes = data?.nodes ?? [];
126
138
  if (!payload) return nodes;
127
139
 
@@ -131,33 +143,19 @@ export const [Provider, useFlow] = createContext(
131
143
  const id = `node-${generatedId}`;
132
144
  const width = dimensions()[payload]?.width ?? 0;
133
145
  const height = dimensions()[payload]?.height ?? 0;
134
- const initialX =
135
- parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
146
+ const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
136
147
  const initialY = parentNode.position.y;
137
- const position = clampPosition(
138
- initialX,
139
- initialY,
140
- width,
141
- height,
142
- );
148
+ const position = clampPosition(initialX, initialY, width, height);
143
149
 
144
150
  return [
145
151
  ...nodes,
146
- {
147
- id,
148
- data: { content: '<Nouveau nœud>' },
149
- input: true,
150
- position,
151
- },
152
+ { id, data: { content: '<Nouveau nœud>' }, input: true, position },
152
153
  ];
153
154
  },
154
155
  }),
155
156
 
156
157
  placeParent: assign('context.data.nodes', {
157
- ADD_PARENT: ({
158
- context: { data },
159
- pContext: { generatedId },
160
- }) => {
158
+ ADD_PARENT: ({ context: { data }, pContext: { generatedId } }) => {
161
159
  const nodes = data?.nodes ?? [];
162
160
  const id = `node-${generatedId}`;
163
161
  const container = boardRef()?.parentElement;
@@ -189,7 +187,6 @@ export const [Provider, useFlow] = createContext(
189
187
  }) => {
190
188
  const edges = data?.edges ?? [];
191
189
  const nodes = data?.nodes ?? [];
192
-
193
190
  const parentID = edges.find(edge => edge.to === payload)?.from;
194
191
  if (!parentID) return nodes;
195
192
 
@@ -199,24 +196,13 @@ export const [Provider, useFlow] = createContext(
199
196
  const id = `node-${generatedId}`;
200
197
  const width = dimensions()[parentID]?.width ?? 0;
201
198
  const height = dimensions()[parentID]?.height ?? 0;
202
- const initialX =
203
- parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
199
+ const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
204
200
  const initialY = parentNode.position.y + 100;
205
- const position = clampPosition(
206
- initialX,
207
- initialY,
208
- width,
209
- height,
210
- );
201
+ const position = clampPosition(initialX, initialY, width, height);
211
202
 
212
203
  return [
213
204
  ...nodes,
214
- {
215
- id,
216
- data: { content: '<Nouveau nœud>' },
217
- input: true,
218
- position,
219
- },
205
+ { id, data: { content: '<Nouveau nœud>' }, input: true, position },
220
206
  ];
221
207
  },
222
208
  }),
@@ -225,9 +211,9 @@ export const [Provider, useFlow] = createContext(
225
211
  voidAction(({ context: { data } }) => {
226
212
  const edges = data?.edges ?? [];
227
213
  setEdgesPositions(data => {
228
- const array = Object.entries({ ...data }).filter(([id]) => {
229
- return edges?.some(edge => edge.id === id);
230
- });
214
+ const array = Object.entries({ ...data }).filter(([id]) =>
215
+ edges.some(edge => edge.id === id),
216
+ );
231
217
 
232
218
  return Object.fromEntries(array);
233
219
  });
@@ -240,6 +226,7 @@ export const [Provider, useFlow] = createContext(
240
226
  edges?.forEach(({ from, id, to }) => {
241
227
  const output = dimensions()[from]?.output;
242
228
  const input = dimensions()[to]?.input;
229
+
243
230
  if (output && input) {
244
231
  next[id] = {
245
232
  x0: output.x,
@@ -261,12 +248,12 @@ export const [Provider, useFlow] = createContext(
261
248
  if (from === payload.id) {
262
249
  const offset =
263
250
  dimensions()[payload.id]?.outputOffset ??
264
- getDefaultOutputOffset(
265
- dimensions()[payload.id]?.width,
266
- );
251
+ getDefaultOutputOffset(dimensions()[payload.id]?.width);
252
+
267
253
  const x0 = payload.x + offset.x;
268
254
  const y0 = payload.y + offset.y;
269
255
  next[id] = { ...next[id], x0, y0 };
256
+
270
257
  setDimensions(
271
258
  produce(data => {
272
259
  if (data[payload.id]) {
@@ -282,9 +269,11 @@ export const [Provider, useFlow] = createContext(
282
269
  const offset =
283
270
  dimensions()[payload.id]?.inputOffset ??
284
271
  DEFAULT_INPUT_OFFSET;
272
+
285
273
  const x1 = payload.x + offset.x;
286
274
  const y1 = payload.y + offset.y;
287
275
  next[id] = { ...next[id], x1, y1 };
276
+
288
277
  setDimensions(
289
278
  produce(data => {
290
279
  if (data[payload.id]) {
@@ -301,6 +290,7 @@ export const [Provider, useFlow] = createContext(
301
290
  );
302
291
  },
303
292
  }),
293
+
304
294
  assign('context.updatingUI', () => true),
305
295
  ),
306
296
 
@@ -314,12 +304,12 @@ export const [Provider, useFlow] = createContext(
314
304
  if (from === payload.id) {
315
305
  const offset =
316
306
  dimensions()[payload.id]?.outputOffset ??
317
- getDefaultOutputOffset(
318
- dimensions()[payload.id]?.width,
319
- );
307
+ getDefaultOutputOffset(dimensions()[payload.id]?.width);
308
+
320
309
  const x0 = payload.x + offset.x;
321
310
  const y0 = payload.y + offset.y;
322
311
  next[id] = { ...next[id], x0, y0 };
312
+
323
313
  setDimensions(
324
314
  produce(data => {
325
315
  if (data[payload.id]) {
@@ -331,13 +321,15 @@ export const [Provider, useFlow] = createContext(
331
321
  }),
332
322
  );
333
323
  }
324
+
334
325
  if (to === payload.id) {
335
326
  const offset =
336
- dimensions()[payload.id]?.inputOffset ??
337
- DEFAULT_INPUT_OFFSET;
327
+ dimensions()[payload.id]?.inputOffset ?? DEFAULT_INPUT_OFFSET;
328
+
338
329
  const x1 = payload.x + offset.x;
339
330
  const y1 = payload.y + offset.y;
340
331
  next[id] = { ...next[id], x1, y1 };
332
+
341
333
  setDimensions(
342
334
  produce(data => {
343
335
  if (data[payload.id]) {
@@ -367,6 +359,7 @@ export const [Provider, useFlow] = createContext(
367
359
  edgesPositions: [edgesPositions, setEdgesPositions] as const,
368
360
  service,
369
361
  zoom,
362
+ clampPosition,
370
363
  };
371
364
  },
372
365
  { 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,19 +60,22 @@ 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
- /** Default node items provided when no initial configuration is given. */
75
+ /**
76
+ * Default node items of type {@linkcode NodeProps} provided when no initial
77
+ * configuration is given.
78
+ */
82
79
  export const DEFAULT_NODES: (NodeProps & { id: string })[] = [
83
80
  {
84
81
  id: 'node-0',
@@ -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();