@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,17 +1,17 @@
1
1
  /* eslint-disable @typescript-eslint/no-namespace */
2
- import { useState } from '@bemedev/app-solidjs';
2
+ import { toArray } from '@bemedev/app';
3
+ import { createState } from '@bemedev/app-solidjs';
3
4
  import { createDraggable } from '@thisbeyond/solid-dnd';
4
- import { Component, createSignal, onMount, Show } from 'solid-js';
5
- import { produce } from 'solid-js/store';
5
+ import { dequal } from 'dequal';
6
+ import { Component, Show } from 'solid-js';
6
7
 
7
- import { useFlow } from './FlowChart.context';
8
8
  import {
9
- DEFAULT_INPUT_OFFSET_X,
10
- DEFAULT_INPUT_OFFSET_Y,
11
9
  HANDLE_CONTAINER_OFFSET_X,
12
10
  HANDLE_MARGIN_TOP,
13
11
  HANDLE_SIZE,
14
- } from './FlowChart.data';
12
+ } from '#services/main.machine.data';
13
+
14
+ import { useFlow } from './FlowChart.context';
15
15
 
16
16
  declare module 'solid-js' {
17
17
  namespace JSX {
@@ -25,16 +25,6 @@ declare module 'solid-js' {
25
25
  type Props = {
26
26
  /** Unique identifier of the node. */
27
27
  id: string;
28
- /** Horizontal position of the node in board coordinates. */
29
- x: number;
30
- /** Vertical position of the node in board coordinates. */
31
- y: number;
32
- /** Optional header label for the node. */
33
- label?: string;
34
- /** Body content text of the node. */
35
- content: string;
36
- /** Whether the node has an input handle. */
37
- input: boolean;
38
28
  };
39
29
 
40
30
  /**
@@ -44,69 +34,35 @@ type Props = {
44
34
  * @param props - Node rendering properties of type {@linkcode Props}.
45
35
  *
46
36
  * @returns The rendered Solid component.
37
+ *
38
+ * @see {@linkcode useFlow}, {@linkcode HANDLE_CONTAINER_OFFSET_X}, {@linkcode HANDLE_MARGIN_TOP}, {@linkcode HANDLE_SIZE}
47
39
  */
48
40
  export const NodeComponent: Component<Props> = props => {
49
- let inputRef: HTMLDivElement | undefined;
50
- let outputRef: HTMLDivElement | undefined;
51
- const [ref, setRef] = createSignal<HTMLDivElement>();
52
- const {
53
- dimensions: [dimensions, setDimensions],
54
- newEdge: [newEdge, setNewEdge],
55
- getBoardPoint,
56
- service,
57
- zoom: [zoom],
58
- } = useFlow();
59
-
60
- const selected = useState(service, {
61
- selector: s => s.context.selected === props.id,
41
+ const service = useFlow();
42
+
43
+ const node = createState(service, {
44
+ selector: ({ context }) => {
45
+ const list = toArray.typed(context.data?.nodes);
46
+ const item = list.find(n => n.id === props.id);
47
+ if (!item) return undefined;
48
+ return {
49
+ x: item.position.x,
50
+ y: item.position.y,
51
+ label: item.data.label,
52
+ content: item.data.content ?? '',
53
+ input: item.input,
54
+ };
55
+ },
56
+ equals: dequal,
62
57
  });
63
58
 
64
- onMount(() => {
65
- const _inputRef = inputRef;
66
- const _outputRef = outputRef;
67
- const _rootRef = ref();
68
- const currentZoom = zoom();
69
-
70
- if (!_outputRef || !_rootRef) return;
71
-
72
- const rootRect = _rootRef.getBoundingClientRect();
73
- const outputRect = _outputRef.getBoundingClientRect();
74
- const inputRect = _inputRef?.getBoundingClientRect();
75
-
76
- const outputOffsetX =
77
- (outputRect.left - rootRect.left + outputRect.width / 2) / currentZoom;
78
- const outputOffsetY =
79
- (outputRect.top - rootRect.top + outputRect.height / 2) / currentZoom;
80
-
81
- const inputOffsetX = inputRect
82
- ? (inputRect.left - rootRect.left + inputRect.width / 2) / currentZoom
83
- : DEFAULT_INPUT_OFFSET_X;
84
- const inputOffsetY = inputRect
85
- ? (inputRect.top - rootRect.top + inputRect.height / 2) / currentZoom
86
- : DEFAULT_INPUT_OFFSET_Y;
87
-
88
- const width = rootRect.width / currentZoom;
89
- const height = rootRect.height / currentZoom;
90
-
91
- const output = { x: props.x + outputOffsetX, y: props.y + outputOffsetY };
59
+ const newEdge = createState(service, { selector: s => s.context.newEdge });
92
60
 
93
- const input = { x: props.x + inputOffsetX, y: props.y + inputOffsetY };
94
-
95
- setDimensions(
96
- produce(data => {
97
- data[props.id] = {
98
- width,
99
- height,
100
- output,
101
- input,
102
- outputOffset: { x: outputOffsetX, y: outputOffsetY },
103
- inputOffset: { x: inputOffsetX, y: inputOffsetY },
104
- };
105
- }),
106
- );
61
+ const selected = createState(service, {
62
+ selector: s => s.context.selected === props.id,
107
63
  });
108
64
 
109
- const hasParent = useState(service, {
65
+ const hasParent = createState(service, {
110
66
  selector: ({ context: { data } }) => {
111
67
  const edges = data?.edges;
112
68
  if (!edges) return false;
@@ -119,22 +75,21 @@ export const NodeComponent: Component<Props> = props => {
119
75
 
120
76
  return (
121
77
  <div
122
- ref={setRef}
123
- id={props.id}
124
78
  classList={{
125
79
  'flex flex-col absolute cursor-grab bg-white rounded-md shadow-md select-none transition-[border,box-shadow] duration-200 ease-in-out hover:shadow-lg draggable': true,
126
80
  'border border-[#e38c29] z-[100]': selected(),
127
81
  'border border-[#e6d4be] z-[1]': !selected(),
128
82
  }}
129
- style={{ top: `${props.y}px`, left: `${props.x}px` }}
83
+
84
+ style={{ top: `${node()?.y ?? 0}px`, left: `${node()?.x ?? 0}px` }}
85
+ class='min-w-48'
86
+ use:draggable={{ skipTransform: true }}
87
+ id={props.id}
88
+
130
89
  onMouseDown={e => {
131
90
  e.stopPropagation();
132
- e.stopImmediatePropagation();
133
91
  service.send({ type: 'SELECT', payload: props.id });
134
92
  }}
135
- class='min-w-48'
136
-
137
- use:draggable={{ skipTransform: true }}
138
93
  >
139
94
  <div
140
95
  classList={{
@@ -145,13 +100,15 @@ export const NodeComponent: Component<Props> = props => {
145
100
  >
146
101
  <svg
147
102
  class='cursor-pointer rounded-full fill-[#a11111] opacity-100 transition-all duration-200 ease-in-out'
103
+ fill='currentColor'
104
+ stroke-width='2'
105
+ viewBox='4 4 16 16'
106
+
148
107
  onClick={e => {
149
108
  e.stopPropagation();
150
109
  service.send({ type: 'DELETE', payload: props.id });
151
110
  }}
152
- fill='currentColor'
153
- stroke-width='2'
154
- viewBox='4 4 16 16'
111
+
155
112
  style={{
156
113
  overflow: 'visible',
157
114
  'pointer-events': 'all',
@@ -161,6 +118,7 @@ export const NodeComponent: Component<Props> = props => {
161
118
  >
162
119
  <path d='M12 4c-4.419 0-8 3.582-8 8s3.581 8 8 8 8-3.582 8-8-3.581-8-8-8zm3.707 10.293a.999.999 0 11-1.414 1.414L12 13.414l-2.293 2.293a.997.997 0 01-1.414 0 .999.999 0 010-1.414L10.586 12 8.293 9.707a.999.999 0 111.414-1.414L12 10.586l2.293-2.293a.999.999 0 111.414 1.414L13.414 12l2.293 2.293z' />
163
120
  </svg>
121
+
164
122
  <Show when={hasParent()}>
165
123
  <svg
166
124
  class='cursor-pointer overflow-visible rounded-full bg-green-500 p-0.5 text-center font-bold hover:bg-green-600'
@@ -173,6 +131,7 @@ export const NodeComponent: Component<Props> = props => {
173
131
  width: `${HANDLE_SIZE * 2}px`,
174
132
  height: `${HANDLE_SIZE * 2}px`,
175
133
  }}
134
+
176
135
  viewBox='0 0 1024 1024'
177
136
  preserveAspectRatio='xMaxYMax'
178
137
  xmlns='http://www.w3.org/2000/svg'
@@ -181,6 +140,7 @@ export const NodeComponent: Component<Props> = props => {
181
140
  >
182
141
  <g id='background'>
183
142
  <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' />
143
+
184
144
  <path d='M316.61562,611.03414c0.11517,-90.16257 -0.25516,-99.92912 0.64739,-101.78856c1.56486,-3.22393 131.99102,0.91032 134.6959,-1.89763c2.21336,-2.2977 -0.59362,-129.97807 1.1376,-132.37034c0.7253,-1.00225 11.10552,-0.99175 12.07474,-0.99077c104.50336,0.10564 104.90098,-0.37811 105.967,0.85765c1.56461,1.81373 0.25596,114.18436 0.67852,129.81412c0.1322,4.88975 3.22386,3.28152 99.72028,3.4563c33.0841,0.05992 36.14259,-1.40368 36.21852,4.50462c0.11713,9.11348 1.41954,110.45369 -0.40274,113.92715c-1.81106,3.45208 -130.39967,-0.42618 -134.48289,1.62075c-2.29035,1.14816 -0.36989,101.12392 -1.11542,130.51904c-0.09548,3.76459 -2.13506,3.20617 -47.84854,3.17365c-69.30253,-0.0493 -69.31099,-0.25627 -69.65762,-0.42191c-3.77927,-1.80595 0.16287,-129.33555 -2.24266,-132.58994c-1.79931,-2.43424 -124.06108,-0.29118 -132.80252,-0.97392c-3.81102,-0.29766 -2.58229,-14.8847 -2.58758,-16.8402Z' />
185
145
  </g>
186
146
  </svg>
@@ -188,55 +148,59 @@ export const NodeComponent: Component<Props> = props => {
188
148
  <svg
189
149
  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'
190
150
  onClick={() => service.send({ type: 'ADD_CHILD', payload: props.id })}
151
+ viewBox='0 0 24 24'
152
+ stroke='currentColor'
153
+ stroke-width='2'
154
+
191
155
  style={{
192
156
  'pointer-events': 'all',
193
157
  width: `${HANDLE_SIZE * 2}px`,
194
158
  height: `${HANDLE_SIZE * 2}px`,
195
159
  }}
196
- viewBox='0 0 24 24'
197
- stroke='currentColor'
198
- stroke-width='2'
199
160
  >
200
161
  <path d='M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z' />
201
162
  </svg>
202
163
  </div>
203
- <Show when={props.label} keyed>
164
+
165
+ <Show when={node()?.label} keyed>
204
166
  {label => (
205
167
  <span class='min-w-max border-b border-[#f0f0f0] p-3 whitespace-nowrap text-red-600 select-none'>
206
168
  {label}
207
169
  </span>
208
170
  )}
209
171
  </Show>
210
- <Show when={props.content} keyed>
172
+
173
+ <Show when={node()?.content} keyed>
211
174
  {content => <div class='p-3 select-none'>{content}</div>}
212
175
  </Show>
213
- <Show when={props.input || hasParent()}>
176
+
177
+ <Show when={node()?.input || hasParent()}>
214
178
  <div
215
179
  id='outputs'
216
180
  class='pointer-events-none absolute top-0 z-10 flex cursor-default flex-col'
217
181
  style={{ left: `-${HANDLE_CONTAINER_OFFSET_X}px` }}
218
182
  >
219
183
  <div
220
- ref={el => (inputRef = el)}
221
184
  class='cursor-default rounded-full bg-[#e38b29] shadow-md'
185
+
222
186
  style={{
223
187
  width: `${HANDLE_SIZE}px`,
224
188
  height: `${HANDLE_SIZE}px`,
225
189
  'margin-top': `${HANDLE_MARGIN_TOP}px`,
226
190
  'pointer-events': 'all',
227
191
  }}
192
+
228
193
  onMouseDown={event => {
229
194
  event.stopPropagation();
230
195
  }}
196
+
231
197
  onMouseUp={event => {
232
198
  event.stopPropagation();
233
199
  const from = newEdge()?.from;
234
200
 
235
201
  if (from) {
236
202
  service.send({ type: 'ADD_EDGE', payload: { from, to: props.id } });
237
- }
238
-
239
- setNewEdge();
203
+ } else service.send('CLEAR_NEW_EDGE');
240
204
  }}
241
205
  ></div>
242
206
  </div>
@@ -247,27 +211,19 @@ export const NodeComponent: Component<Props> = props => {
247
211
  style={{ right: `-${HANDLE_CONTAINER_OFFSET_X}px` }}
248
212
  >
249
213
  <div
250
- ref={el => (outputRef = el)}
251
214
  class='cursor-crosshair rounded-full bg-[#e38b29] shadow-md'
215
+
252
216
  style={{
253
217
  width: `${HANDLE_SIZE}px`,
254
218
  height: `${HANDLE_SIZE}px`,
255
219
  'margin-top': `${HANDLE_MARGIN_TOP}px`,
256
220
  'pointer-events': 'all',
257
221
  }}
222
+
258
223
  onMouseDown={event => {
259
224
  event.stopPropagation();
260
225
  service.send('DESELECT');
261
- const output = dimensions()[props.id]?.output;
262
- const boardPoint = getBoardPoint(event.clientX, event.clientY);
263
- if (output)
264
- setNewEdge({
265
- x0: output.x,
266
- y0: output.y,
267
- x1: boardPoint.x,
268
- y1: boardPoint.y,
269
- from: props.id,
270
- });
226
+ service.send({ type: 'START_NEW_EDGE', payload: props.id });
271
227
  }}
272
228
  ></div>
273
229
  </div>
@@ -1,16 +1,25 @@
1
- import { useState } from '@bemedev/app-solidjs';
1
+ import { toArray } from '@bemedev/app';
2
+ import { createState } from '@bemedev/app-solidjs';
2
3
  import {
3
4
  DragDropProvider,
4
5
  DragDropSensors,
5
6
  DragOverlay,
6
7
  } from '@thisbeyond/solid-dnd';
7
8
  import { dequal } from 'dequal';
8
- import { Component, createEffect, createSignal, For, on, Show } from 'solid-js';
9
+ import {
10
+ Component,
11
+ createEffect,
12
+ createSignal,
13
+ For,
14
+ on,
15
+ onMount,
16
+ Show,
17
+ } from 'solid-js';
9
18
 
19
+ import { CANVAS_FACTOR, SCROLL_MULTIPLIER } from '../../services/main.machine.data';
10
20
  import { DragBounds } from './Bounds';
11
21
  import { EdgesBoard } from './EdgesBoard';
12
22
  import { useFlow } from './FlowChart.context';
13
- import { CANVAS_FACTOR, SCROLL_MULTIPLIER } from './FlowChart.data';
14
23
  import { NodeComponent } from './NodeComponent';
15
24
 
16
25
  /**
@@ -18,22 +27,56 @@ import { NodeComponent } from './NodeComponent';
18
27
  * panning gestures, and rendered nodes/edges.
19
28
  *
20
29
  * @returns The rendered Solid component.
30
+ *
31
+ * @see {@linkcode DragBounds}, {@linkcode EdgesBoard}, {@linkcode NodeComponent}, {@linkcode useFlow}, {@linkcode CANVAS_FACTOR}, {@linkcode SCROLL_MULTIPLIER}
21
32
  */
22
33
  export const NodesBoard: Component = () => {
23
34
  let containerRef: HTMLDivElement;
24
35
  const [isPanning, setIsPanning] = createSignal(false);
25
36
  const [transform, setTransform] = createSignal({ x: 0, y: 0 });
26
37
  const [id, setId] = createSignal<string | number>('');
27
- const [previousZoom, setPreviousZoom] = createSignal<number>();
38
+ const [ref, setRef] = createSignal<HTMLDivElement | undefined>();
28
39
  let percentX = 0;
29
40
  let percentY = 0;
30
41
 
31
- const {
32
- board: [, setRef],
33
- service,
34
- newEdge: [newEdge],
35
- zoom: [zoom, setZoom],
36
- } = useFlow();
42
+ const service = useFlow();
43
+
44
+ const newEdge = createState(service, {
45
+ selector: s => s.context.newEdge,
46
+ equals: dequal,
47
+ });
48
+
49
+ const zoom = createState(service, { selector: s => s.context.zoom ?? 1 });
50
+
51
+ /**
52
+ * Dispatches the current board geometry and parent container dimensions to the
53
+ * state machine service.
54
+ */
55
+ const sendBoard = () => {
56
+ const el = ref();
57
+ if (!el) return;
58
+ const rect = el.getBoundingClientRect();
59
+ const parent = () => ref()?.parentElement;
60
+
61
+ const payload = {
62
+ self: {
63
+ left: rect.left,
64
+ top: rect.top,
65
+ width: el.clientWidth,
66
+ height: el.clientHeight,
67
+ },
68
+ parent: parent()
69
+ ? {
70
+ scrollLeft: parent()!.scrollLeft,
71
+ scrollTop: parent()!.scrollTop,
72
+ height: parent()!.clientHeight,
73
+ width: parent()!.clientWidth,
74
+ }
75
+ : undefined,
76
+ };
77
+
78
+ service.send({ type: 'SET_BOARD', payload });
79
+ };
37
80
 
38
81
  /**
39
82
  * Calculates and saves the normalized scroll percentages across X and Y axes for
@@ -44,6 +87,7 @@ export const NodesBoard: Component = () => {
44
87
  const maxScrollY = containerRef.scrollHeight - containerRef.clientHeight;
45
88
  percentX = maxScrollX > 0 ? containerRef.scrollLeft / maxScrollX : 0;
46
89
  percentY = maxScrollY > 0 ? containerRef.scrollTop / maxScrollY : 0;
90
+ sendBoard();
47
91
  };
48
92
 
49
93
  createEffect(
@@ -59,7 +103,9 @@ export const NodesBoard: Component = () => {
59
103
  ),
60
104
  );
61
105
 
62
- const selectedId = useState(service, { selector: s => s.context?.selected });
106
+ onMount(sendBoard);
107
+
108
+ const selectedId = createState(service, { selector: s => s.context?.selected });
63
109
 
64
110
  /**
65
111
  * Determines whether the given element ID matches the currently selected entity.
@@ -70,25 +116,22 @@ export const NodesBoard: Component = () => {
70
116
  */
71
117
  const selected = (id: string | number) => selectedId() === id;
72
118
 
73
- const nodes = useState(service, {
74
- selector: s => {
75
- const list = s.context.data?.nodes ?? [];
76
- return list.map(item => ({
77
- id: item.id,
78
- x: item.position.x,
79
- y: item.position.y,
80
- label: item.data.label,
81
- content: item.data.content ?? '',
82
- input: item.input,
83
- }));
119
+ const nodeIds = createState(service, {
120
+ selector: ({ context }) => {
121
+ const list = toArray.typed(context.data?.nodes);
122
+ return list.map(item => item.id);
84
123
  },
85
- equals: dequal,
124
+ equals: (prev, next) => prev.length === next.length,
86
125
  });
87
126
 
88
127
  const CANVAS_SIZE = CANVAS_FACTOR * 100;
89
128
  const MARGIN_X = 53 * CANVAS_FACTOR;
90
129
  const MARGIN_Y = 85 * CANVAS_FACTOR;
130
+
131
+ /** Computes the dynamic canvas width string in CSS units adjusted for zoom. */
91
132
  const cWidth = () => `calc((${CANVAS_SIZE}vw - ${MARGIN_X}px) * ${zoom()})`;
133
+
134
+ /** Computes the dynamic canvas height string in CSS units adjusted for zoom. */
92
135
  const cHeight = () => `calc((${CANVAS_SIZE}vh - ${MARGIN_Y}px) * ${zoom()})`;
93
136
 
94
137
  return (
@@ -98,9 +141,7 @@ export const NodesBoard: Component = () => {
98
141
  e.preventDefault();
99
142
  updateScrollPercentages();
100
143
  const delta = e.deltaY < 0 ? 0.1 : -0.1;
101
- setZoom(prev =>
102
- Math.min(Math.max(Number((prev + delta).toFixed(2)), 0.2), 3),
103
- );
144
+ service.send({ type: 'ZOOM', payload: delta });
104
145
  }
105
146
  }}
106
147
 
@@ -167,8 +208,8 @@ export const NodesBoard: Component = () => {
167
208
  node.style.setProperty('left', X + 'px');
168
209
  node.style.removeProperty('transform');
169
210
 
211
+ service.send({ type: 'MOVE', payload: { id: `${id}`, x: X, y: Y } });
170
212
  setTimeout(() => {
171
- service.send({ type: 'MOVE', payload: { id: `${id}`, x: X, y: Y } });
172
213
  setTransform({ x: 0, y: 0 });
173
214
  }, 0);
174
215
  }}
@@ -178,7 +219,7 @@ export const NodesBoard: Component = () => {
178
219
  return (containerRef = el);
179
220
  }}
180
221
  onScroll={updateScrollPercentages}
181
- class='relative h-full w-full overflow-scroll rounded-lg border-2 border-gray-600'
222
+ class='relative h-full w-full overflow-auto rounded-lg border-2 border-gray-600'
182
223
  >
183
224
  <DragDropSensors />
184
225
  <div
@@ -186,6 +227,7 @@ export const NodesBoard: Component = () => {
186
227
  class='relative cursor-crosshair overflow-hidden'
187
228
  classList={{ 'cursor-grabbing': isPanning() }}
188
229
  style={{ height: cHeight(), width: cWidth() }}
230
+ onScroll={() => {}}
189
231
 
190
232
  onMouseDown={e => {
191
233
  if (newEdge() || e.button !== 0) return;
@@ -226,7 +268,7 @@ export const NodesBoard: Component = () => {
226
268
  >
227
269
  <DragBounds />
228
270
  <EdgesBoard />
229
- <For each={nodes()} children={NodeComponent} />
271
+ <For each={nodeIds()}>{id => <NodeComponent id={id} />}</For>
230
272
  </div>
231
273
  </div>
232
274
  </div>
@@ -238,10 +280,7 @@ export const NodesBoard: Component = () => {
238
280
  class='flex size-9 cursor-pointer items-center justify-center rounded-lg bg-gray-100 text-lg font-bold text-gray-700 shadow-sm transition-all duration-150 hover:bg-gray-200 active:scale-95'
239
281
  onClick={() => {
240
282
  updateScrollPercentages();
241
- setPreviousZoom(undefined);
242
- setZoom(prev =>
243
- Math.max(1 / CANVAS_FACTOR, Number((prev - 0.1).toFixed(2))),
244
- );
283
+ service.send({ type: 'ZOOM', payload: -0.1 });
245
284
  }}
246
285
  title='Zoom out'
247
286
  aria-label='Zoom out'
@@ -253,13 +292,7 @@ export const NodesBoard: Component = () => {
253
292
  class='h-9 cursor-pointer rounded-lg px-2 text-xs font-semibold text-gray-700 transition-colors hover:bg-gray-100'
254
293
  onClick={() => {
255
294
  updateScrollPercentages();
256
- if (previousZoom()) {
257
- setZoom(previousZoom()!);
258
- setPreviousZoom(undefined);
259
- } else {
260
- setPreviousZoom(zoom());
261
- setZoom(1);
262
- }
295
+ service.send('TOGGLE_ZOOM');
263
296
  }}
264
297
  title='Reset zoom'
265
298
  aria-label='Reset zoom'
@@ -271,13 +304,7 @@ export const NodesBoard: Component = () => {
271
304
  class='flex size-9 cursor-pointer items-center justify-center rounded-lg bg-gray-100 text-lg font-bold text-gray-700 shadow-sm transition-all duration-150 hover:bg-gray-200 active:scale-95'
272
305
  onClick={() => {
273
306
  updateScrollPercentages();
274
- setPreviousZoom(undefined);
275
- setZoom(prev =>
276
- Math.min(
277
- Math.min(3, CANVAS_FACTOR),
278
- Number((prev + 0.1).toFixed(2)),
279
- ),
280
- );
307
+ service.send({ type: 'ZOOM', payload: 0.1 });
281
308
  }}
282
309
  title='Zoom in'
283
310
  aria-label='Zoom in'
@@ -23,7 +23,7 @@ export const CLASSES = [
23
23
  'w-full',
24
24
  'h-full',
25
25
  'overflow-hidden',
26
- 'overflow-scroll',
26
+ 'overflow-auto',
27
27
  'h-[150vh]',
28
28
  'w-[2160px]',
29
29
  'bg-size-[30px_30px]',
@@ -1,68 +0,0 @@
1
- import { inferT } from '@bemedev/app/typings';
2
- /** Schema definition for 2D coordinates `(x, y)`. */
3
- export declare const point: import('@bemedev/typings').inferSh<{
4
- x: "number";
5
- y: "number";
6
- }>;
7
- /** 2D coordinate point type inferred from schema {@linkcode point}. */
8
- export type Point = inferT<typeof point>;
9
- /** Schema definition for node handle offsets (input and output). */
10
- export declare const nodeOffset: import('@bemedev/typings').inferSh<{
11
- input: import('@bemedev/typings').Optional<{
12
- x: "number";
13
- y: "number";
14
- }>;
15
- output: {
16
- x: "number";
17
- y: "number";
18
- };
19
- }>;
20
- /** Schema definition for edge extremities. */
21
- export declare const extremities: import('@bemedev/typings').inferSh<{
22
- from: "string";
23
- to: "string";
24
- }>;
25
- /** Schema definition for a serialized flowchart node entity. */
26
- export declare const nodeJSON: import('@bemedev/typings').inferSh<{
27
- position: {
28
- x: "number";
29
- y: "number";
30
- };
31
- data: {
32
- label: import('@bemedev/typings').Optional<"string">;
33
- content: "string";
34
- };
35
- input: "boolean";
36
- }>;
37
- /** Schema definition for a serialized flowchart edge entity. */
38
- export declare const edgeJSON: import('@bemedev/typings').inferSh<{
39
- from: "string";
40
- to: "string";
41
- }>;
42
- /** Schema definition for layout dimensions and connection points of a node. */
43
- export declare const dimensions: import('@bemedev/typings').inferSh<{
44
- width: "number";
45
- height: "number";
46
- id: "string";
47
- output: {
48
- x: "number";
49
- y: "number";
50
- };
51
- input: import('@bemedev/typings').Optional<{
52
- x: "number";
53
- y: "number";
54
- }>;
55
- }>;
56
- /**
57
- * Schema definition for a 2D line vector representing edge coordinates `(x0, y0)` to
58
- * `(x1, y1)`.
59
- */
60
- export declare const vector: import('@bemedev/typings').inferSh<{
61
- x0: "number";
62
- y0: "number";
63
- x1: "number";
64
- y1: "number";
65
- }>;
66
- /** 2D vector coordinate type inferred from schema {@linkcode vector}. */
67
- export type Vector = inferT<typeof vector>;
68
- //# sourceMappingURL=main.typings.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"main.typings.d.ts","sourceRoot":"","sources":["../../src/services/main.typings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEnD,qDAAqD;AACrD,eAAO,MAAM,KAAK;;;EAAqC,CAAC;AAExD,uEAAuE;AACvE,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,KAAK,CAAC,CAAC;AAEzC,oEAAoE;AACpE,eAAO,MAAM,UAAU;;;;;;;;;EAGpB,CAAC;AAEJ,8CAA8C;AAC9C,eAAO,MAAM,WAAW;;;EAAyC,CAAC;AAElE,gEAAgE;AAChE,eAAO,MAAM,QAAQ;;;;;;;;;;EAIlB,CAAC;AAEJ,gEAAgE;AAChE,eAAO,MAAM,QAAQ;;;EAAc,CAAC;AAEpC,+EAA+E;AAC/E,eAAO,MAAM,UAAU;;;;;;;;;;;;EAMpB,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,MAAM;;;;;EAKjB,CAAC;AAEH,yEAAyE;AACzE,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"FlowChart.data.d.ts","sourceRoot":"","sources":["../../../src/ui/components/FlowChart.data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,mFAAmF;AACnF,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAG1C,mDAAmD;AACnD,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,sDAAsD;AACtD,eAAO,MAAM,WAAW,KAAK,CAAC;AAE9B,oDAAoD;AACpD,eAAO,MAAM,aAAa,QAAkB,CAAC;AAE7C,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,kEAAkE;AAClE,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,sDAAsD;AACtD,eAAO,MAAM,eAAe,QAAoC,CAAC;AAEjE,mEAAmE;AACnE,eAAO,MAAM,sBAAsB,QAC4B,CAAC;AAEhE,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,QAA0B,CAAC;AAE9D,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,QAAkB,CAAC;AAEtD,kEAAkE;AAClE,eAAO,MAAM,oBAAoB;;;CAGhC,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,oBAAoB;;;CAAgC,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,GAAI,cAAS;;;CAG9C,CAAC;AAEH,qDAAqD;AACrD,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,yDAAyD;AACzD,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;CAAmC,CAAC;AAEnE,0EAA0E;AAC1E,eAAO,MAAM,aAAa,IAAI,CAAC;AAE/B,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAGnC;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,CAAC,SAAS,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,EAOvD,CAAC"}
@@ -1,50 +0,0 @@
1
- import { type } from '@bemedev/app/bemedev';
2
- import type { inferT } from '@bemedev/app/typings';
3
-
4
- /** Schema definition for 2D coordinates `(x, y)`. */
5
- export const point = type({ x: 'number', y: 'number' });
6
-
7
- /** 2D coordinate point type inferred from schema {@linkcode point}. */
8
- export type Point = inferT<typeof point>;
9
-
10
- /** Schema definition for node handle offsets (input and output). */
11
- export const nodeOffset = type(({ optional, use }) => ({
12
- input: optional(use(point)),
13
- output: use(point),
14
- }));
15
-
16
- /** Schema definition for edge extremities. */
17
- export const extremities = type({ from: 'string', to: 'string' });
18
-
19
- /** Schema definition for a serialized flowchart node entity. */
20
- export const nodeJSON = type(({ optional, use }) => ({
21
- position: use(point),
22
- data: { label: optional('string'), content: 'string' },
23
- input: 'boolean',
24
- }));
25
-
26
- /** Schema definition for a serialized flowchart edge entity. */
27
- export const edgeJSON = extremities;
28
-
29
- /** Schema definition for layout dimensions and connection points of a node. */
30
- export const dimensions = type(({ optional, use }) => ({
31
- width: 'number',
32
- height: 'number',
33
- id: 'string',
34
- output: use(point),
35
- input: optional(use(point)),
36
- }));
37
-
38
- /**
39
- * Schema definition for a 2D line vector representing edge coordinates `(x0, y0)` to
40
- * `(x1, y1)`.
41
- */
42
- export const vector = type({
43
- x0: 'number',
44
- y0: 'number',
45
- x1: 'number',
46
- y1: 'number',
47
- });
48
-
49
- /** 2D vector coordinate type inferred from schema {@linkcode vector}. */
50
- export type Vector = inferT<typeof vector>;