@bemedev/mind-flow 0.3.1 → 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 +2543 -448
  12. package/lib/output.css +1 -1
  13. package/lib/server.cjs.js +1129 -397
  14. package/lib/server.es.js +1095 -407
  15. package/lib/services/main.machine.d.ts +2453 -576
  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 +2452 -575
  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 +253 -75
  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 +22 -141
  96. package/src/ui/components/FlowChart.tsx +27 -64
  97. package/src/ui/components/FlowChart.types.ts +92 -0
  98. package/src/ui/components/NodeComponent.tsx +88 -65
  99. package/src/ui/components/NodesBoard.tsx +61 -56
  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
@@ -14,6 +14,13 @@ type Props = {
14
14
  isNew?: boolean;
15
15
  };
16
16
 
17
+ /**
18
+ * Computes bezier curve control point offset based on horizontal distance.
19
+ *
20
+ * @param value - Absolute horizontal delta between start and end points.
21
+ *
22
+ * @returns Offset distance in pixels for bezier curvature.
23
+ */
17
24
  /**
18
25
  * Computes bezier curve control point offset based on horizontal distance.
19
26
  *
@@ -33,7 +40,9 @@ const calculateOffset = (value: number) => (value * 100) / 200;
33
40
  *
34
41
  * @see {@linkcode calculateOffset}
35
42
  */
36
- const draw = ({ x0, y0, x1, y1 }: Vector) => {
43
+ const draw = (vector?: Vector) => {
44
+ if (!vector) return '';
45
+ const { x0, y0, x1, y1 } = vector;
37
46
  return `M ${x0} ${y0} C ${x0 + calculateOffset(Math.abs(x1 - x0))} ${y0}, ${x1 - calculateOffset(Math.abs(x1 - x0))} ${y1}, ${x1} ${y1}`;
38
47
  };
39
48
 
@@ -52,7 +61,7 @@ export const EdgeComponent: Component<Props> = props => {
52
61
 
53
62
  const vector = createState(service, {
54
63
  selector: ({ context: { newEdge, edgesPositions } }) => {
55
- if (props.isNew) return newEdge!;
64
+ if (props.isNew) return newEdge;
56
65
  return edgesPositions[props.id];
57
66
  },
58
67
  equals: dequal,
@@ -65,53 +74,58 @@ export const EdgeComponent: Component<Props> = props => {
65
74
  /** Computes the 2D midpoint coordinate of the edge curve for handle placement. */
66
75
  const middlePoint = () => {
67
76
  const v = vector();
77
+ if (!v) return { x: 0, y: 0 };
68
78
  return { x: v.x0 + (v.x1 - v.x0) / 2, y: v.y0 + (v.y1 - v.y0) / 2 };
69
79
  };
70
80
 
71
81
  return (
72
- <>
73
- <path
74
- class='relative cursor-pointer fill-transparent'
75
- classList={{
76
- 'stroke-[rgba(168,168,168,0.4)] stroke-3 z-200': !!props.isNew,
77
- 'stroke-[rgba(168,168,168,1)] stroke-4 z-100': selected() && !props.isNew,
78
- 'stroke-[rgba(168,168,168,0.8)] stroke-3': !selected() && !props.isNew,
79
- }}
80
- style={{ 'pointer-events': props.isNew ? 'none' : 'all' }}
81
- d={draw(vector())}
82
-
83
- onMouseDown={e => {
84
- e.stopPropagation();
85
- return service.send({ type: 'SELECT', payload: props.id });
86
- }}
87
- />
88
- <Show when={selected()}>
89
- <g
90
- cursor='pointer'
91
- transform={`translate(${middlePoint().x}, ${middlePoint().y})`}
92
- onMouseDown={e => {
93
- e.stopPropagation();
94
- service.send({ type: 'DELETE', payload: props.id });
95
- }}
96
- style={{ 'pointer-events': 'all', 'z-index': '30' }}
97
- >
98
- <circle cx='0' cy='0' r='12' fill='rgba(168, 168, 168, 1)' />
99
- <svg
100
- fill='currentColor'
101
- stroke-width='0'
102
- xmlns='http://www.w3.org/2000/svg'
103
- class='h-25 w-25 bg-white fill-white'
104
- width='20'
105
- height='20'
106
- viewBox='0 0 20 20'
107
- color='white'
108
- x='-10'
109
- y='-10'
110
- >
111
- <path d='M10.185,1.417c-4.741,0-8.583,3.842-8.583,8.583c0,4.74,3.842,8.582,8.583,8.582S18.768,14.74,18.768,10C18.768,5.259,14.926,1.417,10.185,1.417 M10.185,17.68c-4.235,0-7.679-3.445-7.679-7.68c0-4.235,3.444-7.679,7.679-7.679S17.864,5.765,17.864,10C17.864,14.234,14.42,17.68,10.185,17.68 M10.824,10l2.842-2.844c0.178-0.176,0.178-0.46,0-0.637c-0.177-0.178-0.461-0.178-0.637,0l-2.844,2.841L7.341,6.52c-0.176-0.178-0.46-0.178-0.637,0c-0.178,0.176-0.178,0.461,0,0.637L9.546,10l-2.841,2.844c-0.178,0.176-0.178,0.461,0,0.637c0.178,0.178,0.459,0.178,0.637,0l2.844-2.841l2.844,2.841c0.178,0.178,0.459,0.178,0.637,0c0.178-0.176,0.178-0.461,0-0.637L10.824,10z'></path>
112
- </svg>
113
- </g>
114
- </Show>
115
- </>
82
+ <Show when={vector()}>
83
+ {v => (
84
+ <>
85
+ <path
86
+ class='relative cursor-pointer fill-transparent'
87
+ classList={{
88
+ 'stroke-[rgba(168,168,168,0.4)] stroke-3 z-200': !!props.isNew,
89
+ 'stroke-[rgba(168,168,168,1)] stroke-4 z-100':
90
+ selected() && !props.isNew,
91
+ 'stroke-[rgba(168,168,168,0.8)] stroke-3': !selected() && !props.isNew,
92
+ }}
93
+ style={{ 'pointer-events': props.isNew ? 'none' : 'all' }}
94
+ d={draw(v())}
95
+ onMouseDown={e => {
96
+ e.stopPropagation();
97
+ return service.send({ type: 'SELECT', payload: props.id });
98
+ }}
99
+ />
100
+ <Show when={selected()}>
101
+ <g
102
+ cursor='pointer'
103
+ transform={`translate(${middlePoint().x}, ${middlePoint().y})`}
104
+ onMouseDown={e => {
105
+ e.stopPropagation();
106
+ service.send({ type: 'DELETE', payload: props.id });
107
+ }}
108
+ style={{ 'pointer-events': 'all', 'z-index': '30' }}
109
+ >
110
+ <circle cx='0' cy='0' r='12' fill='rgba(168, 168, 168, 1)' />
111
+ <svg
112
+ fill='currentColor'
113
+ stroke-width='0'
114
+ xmlns='http://www.w3.org/2000/svg'
115
+ class='h-25 w-25 bg-white fill-white'
116
+ width='20'
117
+ height='20'
118
+ viewBox='0 0 20 20'
119
+ color='white'
120
+ x='-10'
121
+ y='-10'
122
+ >
123
+ <path d='M10.185,1.417c-4.741,0-8.583,3.842-8.583,8.583c0,4.74,3.842,8.582,8.583,8.582S18.768,14.74,18.768,10C18.768,5.259,14.926,1.417,10.185,1.417 M10.185,17.68c-4.235,0-7.679-3.445-7.679-7.68c0-4.235,3.444-7.679,7.679-7.679S17.864,5.765,17.864,10C17.864,14.234,14.42,17.68,10.185,17.68 M10.824,10l2.842-2.844c0.178-0.176,0.178-0.46,0-0.637c-0.177-0.178-0.461-0.178-0.637,0l-2.844,2.841L7.341,6.52c-0.176-0.178-0.46-0.178-0.637,0c-0.178,0.176-0.178,0.461,0,0.637L9.546,10l-2.841,2.844c-0.178,0.176-0.178,0.461,0,0.637c0.178,0.178,0.459,0.178,0.637,0l2.844-2.841l2.844,2.841c0.178,0.178,0.459,0.178,0.637,0c0.178-0.176,0.178-0.461,0-0.637L10.824,10z'></path>
124
+ </svg>
125
+ </g>
126
+ </Show>
127
+ </>
128
+ )}
129
+ </Show>
116
130
  );
117
131
  };
@@ -14,11 +14,10 @@ import { useFlow } from './FlowChart.context';
14
14
  */
15
15
  export const EdgesBoard: Component = () => {
16
16
  const service = useFlow();
17
-
18
17
  const hasNewEdge = createState(service, { selector: s => !!s.context.newEdge });
19
18
 
20
19
  const edgeIds = createState(service, {
21
- selector: ({ context }) => Object.keys(context.edgesPositions),
20
+ selector: ({ context }) => Object.keys(context.edgesPositions ?? {}),
22
21
  equals: (prev, next) => prev.length === next.length,
23
22
  });
24
23
 
@@ -0,0 +1,69 @@
1
+ import { deepEqual } from '@bemedev/app';
2
+ import { createState } from '@bemedev/app-solidjs';
3
+ import { isDefined } from '@bemedev/app/bemedev';
4
+ import { createSignal, type Accessor } from 'solid-js';
5
+
6
+ import type { NodeData } from './FlowChart';
7
+ import { useFlow } from './FlowChart.context';
8
+
9
+ /**
10
+ * Hook providing reactive state and mutation helpers for editing flowchart node
11
+ * data.
12
+ *
13
+ * @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
14
+ * {@linkcode NodeData}.
15
+ *
16
+ * @param timeout - Transition delay in milliseconds before closing the panel.
17
+ * Defaults to `270`.
18
+ *
19
+ * @returns An object containing reactive accessors and mutation callbacks for the
20
+ * active node.
21
+ *
22
+ * @see {@linkcode useFlow}
23
+ */
24
+ export const useHook = <D extends NodeData = NodeData>(timeout = 270) => {
25
+ const service = useFlow();
26
+ const directClose = () => service.send('STOP_EDIT');
27
+ const [closing, setClosing] = createSignal(false);
28
+ const senderData = service.sender('SET_NODE_DATA');
29
+
30
+ const _editing = createState(service, {
31
+ selector: ({ context }) => {
32
+ const editingId = context.editing;
33
+ if (!isDefined(editingId)) return;
34
+
35
+ const item = context.data?.nodes?.find(n => n.id === editingId);
36
+ if (!isDefined(item)) return;
37
+
38
+ return { id: item.id, data: item.data as D };
39
+ },
40
+
41
+ equals: deepEqual<any>,
42
+ });
43
+
44
+ const close = () => {
45
+ setClosing(true);
46
+
47
+ setTimeout(() => {
48
+ directClose();
49
+ setClosing(false);
50
+ }, timeout);
51
+ };
52
+
53
+ const updateField = <K extends keyof D>(field: K, value: D[K]) => {
54
+ const current = _editing();
55
+ if (!current) return;
56
+
57
+ return senderData({ ...current, data: { ...current.data, [field]: value } });
58
+ };
59
+
60
+ const updateData = (data: Partial<D>) => {
61
+ const current = _editing();
62
+ if (!current) return;
63
+
64
+ return senderData({ ...current, data: { ...current.data, ...data } });
65
+ };
66
+
67
+ const editing = _editing as Accessor<{ id: string; data: D }>;
68
+ return { editing, updateField, updateData, close, closing, directClose };
69
+ };
@@ -0,0 +1,89 @@
1
+ import { type JSX, Show } from 'solid-js';
2
+
3
+ import type { NodeData } from '#services/main.machine.typings';
4
+
5
+ import { clickOutside } from '../globals/directives';
6
+ import { cn } from '../utils';
7
+ import { useHook } from './EditPanel.hooks';
8
+ import type { EditPanelProps } from './EditPanel.types';
9
+
10
+ /**
11
+ * Generic overlay edit panel component for modifying the data of the currently
12
+ * active/double-clicked flowchart node.
13
+ *
14
+ * @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
15
+ * {@linkcode NodeData}.
16
+ *
17
+ * @param props - Edit panel configuration properties of type
18
+ * {@linkcode EditPanelProps}.
19
+ *
20
+ * @returns The rendered Solid component or `null` when no node is being edited.
21
+ *
22
+ * @see {@linkcode useHook}, {@linkcode clickOutside}
23
+ */
24
+ export const EditPanel = <D extends NodeData = NodeData>(
25
+ props: EditPanelProps<D>,
26
+ ): JSX.Element => {
27
+ void clickOutside;
28
+ const hooks = useHook<D>(props.timeout);
29
+ const classList = () =>
30
+ props.classList instanceof Function ? props.classList(hooks) : props.classList;
31
+
32
+ return (
33
+ <Show when={hooks.editing()}>
34
+ {node => (
35
+ <div
36
+ class={cn(
37
+ `w-80 cursor-default rounded-2xl border border-gray-200 bg-white/95 p-4 shadow-xl backdrop-blur-md transition-all ${props.class ?? ''}`,
38
+ )}
39
+ classList={classList()}
40
+ style={props.style}
41
+ onMouseDown={e => e.stopPropagation()}
42
+ use:clickOutside={hooks.close}
43
+ >
44
+ <div class='flex flex-col gap-3'>
45
+ <Show
46
+ when={props.header}
47
+ keyed
48
+
49
+ fallback={
50
+ <div class='flex items-center justify-between border-b border-gray-100 pb-2'>
51
+ <div class='flex items-center gap-2'>
52
+ <h3 class='text-sm font-bold text-gray-800'>Edit</h3>
53
+
54
+ <span class='font-mono text-[11px] text-gray-400'>
55
+ {node().id}
56
+ </span>
57
+ </div>
58
+
59
+ <button
60
+ type='button'
61
+ onClick={hooks.close}
62
+ title='Close editor'
63
+ aria-label='Close editor'
64
+
65
+ class='cursor-pointer rounded-full p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-700'
66
+ >
67
+ <svg
68
+ class='size-4'
69
+ viewBox='0 0 24 24'
70
+ fill='none'
71
+ stroke='currentColor'
72
+ stroke-width='2'
73
+ >
74
+ <path d='M18 6L6 18M6 6l12 12' />
75
+ </svg>
76
+ </button>
77
+ </div>
78
+ }
79
+ >
80
+ {header => header({ id: node().id, close: hooks.close })}
81
+ </Show>
82
+
83
+ {props.children(hooks)}
84
+ </div>
85
+ </div>
86
+ )}
87
+ </Show>
88
+ );
89
+ };
@@ -0,0 +1,49 @@
1
+ import type { Component, JSX } from 'solid-js';
2
+
3
+ import type { ClassList } from '../globals/types';
4
+ import { useHook } from './EditPanel.hooks';
5
+ import type { NodeData } from './FlowChart';
6
+
7
+ /**
8
+ * Render properties and mutation callbacks provided to {@linkcode EditPanel} children
9
+ * or custom component.
10
+ *
11
+ * @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
12
+ * {@linkcode NodeData}.
13
+ */
14
+ export type EditPanelChildProps<D extends NodeData = NodeData> = ReturnType<
15
+ typeof useHook<D>
16
+ >;
17
+
18
+ /**
19
+ * Configuration properties for the generic {@linkcode EditPanel} component.
20
+ *
21
+ * @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
22
+ * {@linkcode NodeData}.
23
+ */
24
+ export type EditPanelProps<D extends NodeData = NodeData> = {
25
+ /** Optional additional CSS classes for the outer container card. */
26
+ class?: string;
27
+ /** Optional class map for conditional styling. */
28
+ classList?: ClassList | ((hooks: EditPanelChildProps<D>) => ClassList);
29
+ /** Optional inline CSS styles. */
30
+ style?: JSX.CSSProperties | string;
31
+ /**
32
+ * Custom header renderer.
33
+ *
34
+ * @param props - Header props containing the edited node `id` and `close`
35
+ * callback.
36
+ */
37
+ header?: Component<{ id: string; close: () => void }>;
38
+ /**
39
+ * Custom content renderer or static children. When provided as a function,
40
+ * receives {@linkcode EditPanelChildProps}.
41
+ */
42
+ children: Component<EditPanelChildProps<D>>;
43
+
44
+ /**
45
+ * Optional delay in milliseconds before closing the editor panel to allow exit
46
+ * transitions.
47
+ */
48
+ timeout?: number;
49
+ };
@@ -1,12 +1,13 @@
1
1
  import { interpret } from '@bemedev/app';
2
2
 
3
+ import { clamp } from '#helpers/clamp';
3
4
  import { createContext } from '#helpers/createContext';
4
5
  import { machine } from '#main-machine';
5
6
  import {
6
7
  BOUNDS_CONSTRAINTS,
7
8
  DEFAULT_INPUT_OFFSET,
9
+ DEFAULT_SIZE,
8
10
  getDefaultOutputOffset,
9
- PARENT_CHILD_GAP_WIDTH,
10
11
  } from '#services/main.machine.data';
11
12
  import type { Dimension, Point } from '#services/main.machine.typings';
12
13
 
@@ -34,7 +35,7 @@ export const [Provider, useFlow] = createContext(
34
35
  *
35
36
  * @returns Calculated 2D coordinate of type {@linkcode Point}.
36
37
  */
37
- getBoardPosition(clientX, clientY, el): Point {
38
+ getBoardPosition: (clientX, clientY, el): Point => {
38
39
  if (!el) return { x: clientX, y: clientY };
39
40
  const currentZoom = service.state.context.zoom;
40
41
  const x = (clientX - el.self.left) / currentZoom;
@@ -49,14 +50,21 @@ export const [Provider, useFlow] = createContext(
49
50
  * {@linkcode Board}.
50
51
  * @param x - Desired X position in pixels.
51
52
  * @param y - Desired Y position in pixels.
52
- * @param nodeWidth - Node width in pixels, defaults to `192`.
53
- * @param nodeHeight - Node height in pixels, defaults to `50`.
53
+ * @param nodeWidth - Node width in pixels, defaults to `DEFAULT_SIZE.width`.
54
+ * @param nodeHeight - Node height in pixels, defaults to
55
+ * `DEFAULT_SIZE.height`.
54
56
  *
55
57
  * @returns Clamped coordinate of type {@linkcode Point}.
56
58
  *
57
- * @see {@linkcode BOUNDS_CONSTRAINTS}
59
+ * @see {@linkcode BOUNDS_CONSTRAINTS}, {@linkcode DEFAULT_SIZE}
58
60
  */
59
- clampPosition(board, x, y, nodeWidth = 192, nodeHeight = 50): Point {
61
+ clampPosition: (
62
+ board,
63
+ x,
64
+ y,
65
+ nodeWidth = DEFAULT_SIZE.width,
66
+ nodeHeight = DEFAULT_SIZE.height,
67
+ ): Point => {
60
68
  const container = board?.parent;
61
69
  if (!container) return { x, y };
62
70
 
@@ -82,10 +90,7 @@ export const [Provider, useFlow] = createContext(
82
90
  nodeHeight,
83
91
  );
84
92
 
85
- return {
86
- x: Math.min(Math.max(x, minX), maxX),
87
- y: Math.min(Math.max(y, minY), maxY),
88
- };
93
+ return { x: clamp(x, minX, maxX), y: clamp(y, minY, maxY) };
89
94
  },
90
95
 
91
96
  /**
@@ -96,20 +101,24 @@ export const [Provider, useFlow] = createContext(
96
101
  *
97
102
  * @returns Calculated dimension object of type {@linkcode Dimension}.
98
103
  *
99
- * @see {@linkcode DEFAULT_INPUT_OFFSET}, {@linkcode getDefaultOutputOffset}
104
+ * @see {@linkcode DEFAULT_INPUT_OFFSET}, {@linkcode getDefaultOutputOffset}, {@linkcode DEFAULT_SIZE}
100
105
  */
101
106
  calculateDimensions: (
102
107
  position: Point,
103
108
  parentDimension: Pick<
104
109
  Dimension,
105
110
  'width' | 'height' | 'outputOffset' | 'inputOffset'
106
- > = { width: 192, height: 50, inputOffset: DEFAULT_INPUT_OFFSET },
111
+ > = {
112
+ width: DEFAULT_SIZE.width,
113
+ height: DEFAULT_SIZE.height,
114
+ inputOffset: DEFAULT_INPUT_OFFSET,
115
+ },
107
116
  ): Dimension => {
108
117
  const width = parentDimension.width;
109
118
  const height = parentDimension.height;
110
119
  const outputOffset =
111
120
  parentDimension.outputOffset ?? getDefaultOutputOffset(width);
112
- const inputOffset = parentDimension.inputOffset!;
121
+ const inputOffset = parentDimension.inputOffset ?? DEFAULT_INPUT_OFFSET;
113
122
 
114
123
  const output = {
115
124
  x: position.x + outputOffset.x,
@@ -125,134 +134,6 @@ export const [Provider, useFlow] = createContext(
125
134
  },
126
135
  });
127
136
 
128
- // #endregion
129
-
130
- service.addOptions(({ assign }) => ({
131
- actions: {
132
- placeChild: assign('data.nodes', {
133
- ADD_CHILD: ({ payload, context: { data, board }, pContext }) => {
134
- if (!board) return data?.nodes;
135
- const nodes = data?.nodes;
136
- if (!payload) return nodes;
137
-
138
- const parentNode = nodes?.find(node => node.id === payload);
139
- if (!parentNode) return nodes;
140
-
141
- const parentDimension = pContext.dimensions[payload];
142
-
143
- const id = `node-${pContext.generatedId}`;
144
- const width = parentDimension?.width ?? 192;
145
- const height = parentDimension?.height ?? 50;
146
- const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
147
- const initialY = parentNode.position.y;
148
- const position = pContext.clampPosition(
149
- board,
150
- initialX,
151
- initialY,
152
- width,
153
- height,
154
- );
155
- const dimension = pContext.calculateDimensions(
156
- position,
157
- parentDimension,
158
- );
159
-
160
- nodes?.push({
161
- id,
162
- data: { content: '<Nouveau nœud>' },
163
- input: true,
164
- position,
165
- });
166
- pContext.dimensions[id] = dimension;
167
-
168
- return nodes;
169
- },
170
- }),
171
-
172
- placeParent: assign('data.nodes', {
173
- ADD_PARENT: ({ context: { data, zoom = 1, board }, pContext }) => {
174
- if (!board) return data?.nodes;
175
- const nodes = data?.nodes;
176
- const id = `node-${pContext.generatedId}`;
177
- const container = board.parent;
178
- const scrollLeft = container?.scrollLeft ?? 0;
179
- const scrollTop = container?.scrollTop ?? 0;
180
- const width = container?.width ?? 0;
181
- const height = container?.height ?? 0;
182
- const currentZoom = zoom;
183
- const x = (scrollLeft + width / 2) / currentZoom;
184
- const y = (scrollTop + height / 2) / currentZoom;
185
- const position = pContext.clampPosition(board, x, y);
186
- const dimension = pContext.calculateDimensions(position);
187
-
188
- nodes?.push({
189
- id,
190
- data: { content: '<Nouveau nœud>' },
191
- input: false,
192
- position,
193
- });
194
-
195
- pContext.dimensions[id] = dimension;
196
- return nodes;
197
- },
198
- }),
199
-
200
- placeSibling: assign('data.nodes', {
201
- ADD_SIBLING: ({ payload, context: { data, board }, pContext }) => {
202
- if (!board) return data?.nodes;
203
- const edges = data?.edges;
204
- const nodes = data?.nodes;
205
- const parentID = edges?.find(edge => edge.to === payload)?.from;
206
- if (!parentID) return nodes;
207
-
208
- const parentNode = nodes?.find(node => node.id === parentID);
209
- if (!parentNode) return nodes;
210
-
211
- const parentDimension = pContext.dimensions[parentID];
212
- const id = `node-${pContext.generatedId}`;
213
- const width = parentDimension?.width ?? 192;
214
- const height = parentDimension?.height ?? 50;
215
- const initialX = parentNode.position.x + width + PARENT_CHILD_GAP_WIDTH;
216
- const initialY = parentNode.position.y + PARENT_CHILD_GAP_WIDTH;
217
- const position = pContext.clampPosition(
218
- board,
219
- initialX,
220
- initialY,
221
- width,
222
- height,
223
- );
224
- const dimension = pContext.calculateDimensions(
225
- position,
226
- parentDimension,
227
- );
228
-
229
- nodes?.push({
230
- id,
231
- data: { content: '<Nouveau nœud>' },
232
- input: true,
233
- position,
234
- });
235
-
236
- pContext.dimensions[id] = dimension;
237
- return nodes;
238
- },
239
- }),
240
-
241
- moveNewEdge: assign('newEdge', {
242
- MOVE_NEW_EDGE: ({ context: { newEdge, board }, payload, pContext }) => {
243
- if (!board) return undefined;
244
- if (!newEdge) return undefined;
245
- const { x: x1, y: y1 } = pContext.getBoardPosition(
246
- payload.x,
247
- payload.y,
248
- board,
249
- );
250
- return { ...newEdge, x1, y1 };
251
- },
252
- }),
253
- },
254
- }));
255
-
256
137
  service.start();
257
138
  return service;
258
139
  },