@bemedev/mind-flow 0.3.2 → 1.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 (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 +2464 -373
  12. package/lib/output.css +1 -1
  13. package/lib/server.cjs.js +1002 -262
  14. package/lib/server.es.js +968 -272
  15. package/lib/services/main.machine.d.ts +1564 -667
  16. package/lib/services/main.machine.d.ts.map +1 -1
  17. package/lib/services/main.machine.data.d.ts +14 -1
  18. package/lib/services/main.machine.data.d.ts.map +1 -1
  19. package/lib/services/main.machine.helpers.d.ts +1 -1
  20. package/lib/services/main.machine.helpers.d.ts.map +1 -1
  21. package/lib/services/main.machine.test.d.ts +2 -0
  22. package/lib/services/main.machine.test.d.ts.map +1 -0
  23. package/lib/services/main.machine.typings.d.ts +26 -8
  24. package/lib/services/main.machine.typings.d.ts.map +1 -1
  25. package/lib/ui/Flow.d.ts +8 -4
  26. package/lib/ui/Flow.d.ts.map +1 -1
  27. package/lib/ui/components/Bounds.d.ts.map +1 -1
  28. package/lib/ui/components/EdgeComponent.d.ts.map +1 -1
  29. package/lib/ui/components/EdgesBoard.d.ts.map +1 -1
  30. package/lib/ui/components/EditPanel.d.ts +19 -0
  31. package/lib/ui/components/EditPanel.d.ts.map +1 -0
  32. package/lib/ui/components/EditPanel.hooks.d.ts +29 -0
  33. package/lib/ui/components/EditPanel.hooks.d.ts.map +1 -0
  34. package/lib/ui/components/EditPanel.types.d.ts +47 -0
  35. package/lib/ui/components/EditPanel.types.d.ts.map +1 -0
  36. package/lib/ui/components/FlowChart.context.d.ts +1563 -666
  37. package/lib/ui/components/FlowChart.context.d.ts.map +1 -1
  38. package/lib/ui/components/FlowChart.d.ts +8 -47
  39. package/lib/ui/components/FlowChart.d.ts.map +1 -1
  40. package/lib/ui/components/FlowChart.types.d.ts +87 -0
  41. package/lib/ui/components/FlowChart.types.d.ts.map +1 -0
  42. package/lib/ui/components/NodeComponent.d.ts +10 -12
  43. package/lib/ui/components/NodeComponent.d.ts.map +1 -1
  44. package/lib/ui/components/NodesBoard.d.ts +17 -2
  45. package/lib/ui/components/NodesBoard.d.ts.map +1 -1
  46. package/lib/ui/components/Panels.d.ts +12 -0
  47. package/lib/ui/components/Panels.d.ts.map +1 -0
  48. package/lib/ui/globals/components/atoms/TypingText.d.ts +29 -0
  49. package/lib/ui/globals/components/atoms/TypingText.d.ts.map +1 -0
  50. package/lib/ui/globals/components/molecules/FadingDots.d.ts +21 -0
  51. package/lib/ui/globals/components/molecules/FadingDots.d.ts.map +1 -0
  52. package/lib/ui/globals/components/organisms/LoadingFallback.d.ts +46 -0
  53. package/lib/ui/globals/components/organisms/LoadingFallback.d.ts.map +1 -0
  54. package/lib/ui/globals/constants/index.d.ts +3 -0
  55. package/lib/ui/globals/constants/index.d.ts.map +1 -0
  56. package/lib/ui/globals/constants/links.d.ts +4 -0
  57. package/lib/ui/globals/constants/links.d.ts.map +1 -0
  58. package/lib/ui/globals/constants/strings.d.ts +5 -0
  59. package/lib/ui/globals/constants/strings.d.ts.map +1 -0
  60. package/lib/ui/globals/directives/clickOutside.d.ts +9 -0
  61. package/lib/ui/globals/directives/clickOutside.d.ts.map +1 -0
  62. package/lib/ui/globals/directives/index.d.ts +16 -0
  63. package/lib/ui/globals/directives/index.d.ts.map +1 -0
  64. package/lib/ui/globals/directives/mouseOut.d.ts +33 -0
  65. package/lib/ui/globals/directives/mouseOut.d.ts.map +1 -0
  66. package/lib/ui/globals/directives/mouseOut.test.d.ts +2 -0
  67. package/lib/ui/globals/directives/mouseOut.test.d.ts.map +1 -0
  68. package/lib/ui/globals/directives/resize.d.ts +11 -0
  69. package/lib/ui/globals/directives/resize.d.ts.map +1 -0
  70. package/lib/ui/globals/helpers/espace.d.ts +17 -0
  71. package/lib/ui/globals/helpers/espace.d.ts.map +1 -0
  72. package/lib/ui/globals/signals/createTyping.d.ts +27 -0
  73. package/lib/ui/globals/signals/createTyping.d.ts.map +1 -0
  74. package/lib/ui/globals/types.d.ts +54 -0
  75. package/lib/ui/globals/types.d.ts.map +1 -0
  76. package/lib/ui/utils.d.ts +11 -0
  77. package/lib/ui/utils.d.ts.map +1 -0
  78. package/package.json +2 -3
  79. package/src/helpers/clamp.ts +3 -0
  80. package/src/helpers/createContext.ts +20 -4
  81. package/src/helpers/index.ts +2 -0
  82. package/src/index.ts +9 -1
  83. package/src/services/main.machine.data.ts +13 -2
  84. package/src/services/main.machine.helpers.ts +12 -4
  85. package/src/services/main.machine.test.ts +205 -0
  86. package/src/services/main.machine.ts +136 -65
  87. package/src/services/main.machine.typings.ts +31 -6
  88. package/src/ui/Flow.tsx +47 -8
  89. package/src/ui/components/Bounds.tsx +0 -2
  90. package/src/ui/components/EdgeComponent.tsx +60 -46
  91. package/src/ui/components/EdgesBoard.tsx +1 -2
  92. package/src/ui/components/EditPanel.hooks.ts +69 -0
  93. package/src/ui/components/EditPanel.tsx +89 -0
  94. package/src/ui/components/EditPanel.types.ts +49 -0
  95. package/src/ui/components/FlowChart.context.ts +21 -11
  96. package/src/ui/components/FlowChart.tsx +20 -57
  97. package/src/ui/components/FlowChart.types.ts +92 -0
  98. package/src/ui/components/NodeComponent.tsx +81 -58
  99. package/src/ui/components/NodesBoard.tsx +29 -7
  100. package/src/ui/components/Panels.tsx +31 -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,10 +1,12 @@
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
11
  } from '#services/main.machine.data';
10
12
  import type { Dimension, Point } from '#services/main.machine.typings';
@@ -48,14 +50,21 @@ export const [Provider, useFlow] = createContext(
48
50
  * {@linkcode Board}.
49
51
  * @param x - Desired X position in pixels.
50
52
  * @param y - Desired Y position in pixels.
51
- * @param nodeWidth - Node width in pixels, defaults to `192`.
52
- * @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`.
53
56
  *
54
57
  * @returns Clamped coordinate of type {@linkcode Point}.
55
58
  *
56
- * @see {@linkcode BOUNDS_CONSTRAINTS}
59
+ * @see {@linkcode BOUNDS_CONSTRAINTS}, {@linkcode DEFAULT_SIZE}
57
60
  */
58
- 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 => {
59
68
  const container = board?.parent;
60
69
  if (!container) return { x, y };
61
70
 
@@ -81,10 +90,7 @@ export const [Provider, useFlow] = createContext(
81
90
  nodeHeight,
82
91
  );
83
92
 
84
- return {
85
- x: Math.min(Math.max(x, minX), maxX),
86
- y: Math.min(Math.max(y, minY), maxY),
87
- };
93
+ return { x: clamp(x, minX, maxX), y: clamp(y, minY, maxY) };
88
94
  },
89
95
 
90
96
  /**
@@ -95,20 +101,24 @@ export const [Provider, useFlow] = createContext(
95
101
  *
96
102
  * @returns Calculated dimension object of type {@linkcode Dimension}.
97
103
  *
98
- * @see {@linkcode DEFAULT_INPUT_OFFSET}, {@linkcode getDefaultOutputOffset}
104
+ * @see {@linkcode DEFAULT_INPUT_OFFSET}, {@linkcode getDefaultOutputOffset}, {@linkcode DEFAULT_SIZE}
99
105
  */
100
106
  calculateDimensions: (
101
107
  position: Point,
102
108
  parentDimension: Pick<
103
109
  Dimension,
104
110
  'width' | 'height' | 'outputOffset' | 'inputOffset'
105
- > = { 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
+ },
106
116
  ): Dimension => {
107
117
  const width = parentDimension.width;
108
118
  const height = parentDimension.height;
109
119
  const outputOffset =
110
120
  parentDimension.outputOffset ?? getDefaultOutputOffset(width);
111
- const inputOffset = parentDimension.inputOffset!;
121
+ const inputOffset = parentDimension.inputOffset ?? DEFAULT_INPUT_OFFSET;
112
122
 
113
123
  const output = {
114
124
  x: position.x + outputOffset.x,
@@ -1,53 +1,14 @@
1
1
  import { createState } from '@bemedev/app-solidjs';
2
- import type { inferT } from '@bemedev/app/typings';
3
- import { Component, onCleanup, onMount } from 'solid-js';
2
+ import { type JSX, onCleanup, onMount } from 'solid-js';
3
+
4
+ import { DEFAULT_NODES } from '#services/main.machine.data';
5
+ import type { NodeData, NodeProps } from '#services/main.machine.typings';
4
6
 
5
- import { DEFAULT_NODES } from '../../services/main.machine.data';
6
- import type { edgeJSON, nodeJSON } from '../../services/main.machine.typings';
7
7
  import { useFlow } from './FlowChart.context';
8
+ import type { FlowProps } from './FlowChart.types';
8
9
  import { NodesBoard } from './NodesBoard';
9
10
 
10
- /** Serialized node properties type inferred from schema {@linkcode nodeJSON}. */
11
- export type NodeProps = inferT<typeof nodeJSON>;
12
-
13
- /** Serialized edge properties type inferred from schema {@linkcode edgeJSON}. */
14
- export type EdgeProps = inferT<typeof edgeJSON>;
15
-
16
- /**
17
- * Configuration options and callback handlers for the {@linkcode FlowChart}
18
- * component.
19
- */
20
- export type FlowProps = {
21
- /** Initial flowchart state configuration with nodes and edges. */
22
- config?: {
23
- nodes?: (NodeProps & { id: string })[];
24
- edges?: (EdgeProps & { id: string })[];
25
- };
26
- /**
27
- * Callback triggered when a new node is created.
28
- *
29
- * @param node - The created node object of type {@linkcode NodeProps}.
30
- */
31
- onNodeAdded?: (node: NodeProps) => void;
32
- /**
33
- * Callback triggered when a node is deleted.
34
- *
35
- * @param nodeId - The identifier of the deleted node.
36
- */
37
- onNodeDeleted?: (nodeId: string) => void;
38
- /**
39
- * Callback triggered when an edge is created.
40
- *
41
- * @param edge - The created edge object of type {@linkcode EdgeProps}.
42
- */
43
- onEdgeAdded?: (edge: EdgeProps) => void;
44
- /**
45
- * Callback triggered when an edge is deleted.
46
- *
47
- * @param edgeId - The identifier of the deleted edge.
48
- */
49
- onEdgeDeleted?: (edgeId: string) => void;
50
- };
11
+ export type { NodeData, NodeProps };
51
12
 
52
13
  // const PARENT_CHILD_GAP_WIDTH = 75;
53
14
 
@@ -55,6 +16,9 @@ export type FlowProps = {
55
16
  * Flowchart board canvas component that renders interactive nodes, edges, pan/zoom,
56
17
  * and toolbar controls.
57
18
  *
19
+ * @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
20
+ * {@linkcode NodeData}.
21
+ *
58
22
  * @param props - Flowchart configuration and event handlers of type
59
23
  * {@linkcode FlowProps}.
60
24
  *
@@ -62,7 +26,9 @@ export type FlowProps = {
62
26
  *
63
27
  * @see {@linkcode NodesBoard}, {@linkcode useFlow}, {@linkcode DEFAULT_NODES}
64
28
  */
65
- export const FlowChart: Component<FlowProps> = props => {
29
+ export const FlowChart = <D extends NodeData = NodeData>(
30
+ props: FlowProps<D>,
31
+ ): JSX.Element => {
66
32
  const primaryNodes = props.config?.nodes ?? DEFAULT_NODES;
67
33
  const primaryEdges = props.config?.edges;
68
34
  const service = useFlow();
@@ -71,10 +37,13 @@ export const FlowChart: Component<FlowProps> = props => {
71
37
 
72
38
  onMount(() => {
73
39
  service.resume();
74
-
75
40
  service.send({
76
41
  type: 'CONFIGURE',
77
- payload: { nodes: primaryNodes, edges: primaryEdges ?? [] },
42
+ payload: {
43
+ nodes: primaryNodes,
44
+ edges: primaryEdges ?? [],
45
+ defaultData: props.defaultData,
46
+ },
78
47
  });
79
48
  });
80
49
 
@@ -82,7 +51,6 @@ export const FlowChart: Component<FlowProps> = props => {
82
51
  <div
83
52
  class='relative h-full w-full'
84
53
  onMouseUp={() => service.send('CLEAR_NEW_EDGE')}
85
-
86
54
  onMouseMove={({ clientX, clientY }) => {
87
55
  if (hasNewEdge())
88
56
  service.send({
@@ -92,15 +60,10 @@ export const FlowChart: Component<FlowProps> = props => {
92
60
  }}
93
61
  >
94
62
  <div
95
- class='relative h-full w-full bg-white bg-size-[30px_30px]'
96
-
97
- style={{
98
- cursor: hasNewEdge() ? 'inherit' : 'crosshair',
99
- 'background-image':
100
- 'radial-gradient(circle, #b8b8b8bf 1px, rgba(0, 0, 0, 0) 1px)',
101
- }}
63
+ class='h-full w-full'
64
+ style={{ cursor: hasNewEdge() ? 'inherit' : 'crosshair' }}
102
65
  >
103
- <NodesBoard />
66
+ <NodesBoard panels={props.panels} component={props.component} />
104
67
  </div>
105
68
  </div>
106
69
  );
@@ -0,0 +1,92 @@
1
+ import type { NotUndefined } from '@bemedev/app/bemedev';
2
+ import type { Component } from 'solid-js';
3
+
4
+ import type { EdgeProps } from '#services/main.machine.typings';
5
+
6
+ import type { NodeData, NodeProps } from './FlowChart';
7
+
8
+ /** Overlay panel slots positioned around the flowchart canvas. */
9
+ export type FlowPanels = {
10
+ /** Top-left corner overlay panel slot component of type {@linkcode Component}. */
11
+ topLeft?: Component;
12
+ /** Top-right corner overlay panel slot component of type {@linkcode Component}. */
13
+ topRight?: Component;
14
+ /** Bottom-left corner overlay panel slot component of type {@linkcode Component}. */
15
+ bottomLeft?: Component;
16
+ };
17
+
18
+ /**
19
+ * Configuration options and callback handlers for the {@linkcode FlowChart}
20
+ * component.
21
+ *
22
+ * @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
23
+ * {@linkcode NodeData}.
24
+ */
25
+ export type FlowProps<D extends NodeData = NodeData> = {
26
+ /** Optional delay in milliseconds before mounting the flowchart canvas. */
27
+ delay?: number;
28
+ /** Initial flowchart state configuration with nodes and edges. */
29
+ config?: {
30
+ nodes?: (NodeProps<D> & { id: string })[];
31
+ edges?: (EdgeProps & { id: string })[];
32
+ };
33
+ /** Custom node component to render inside each flowchart node. */
34
+ component?: Component<D>;
35
+
36
+ /** Default data for new nodes created in the flowchart. */
37
+ defaultData?: D;
38
+ /** Custom overlay panels positioned around the canvas. */
39
+ panels?: FlowPanels;
40
+ /**
41
+ * Callback triggered when a new node is created.
42
+ *
43
+ * @param node - The created node object of type {@linkcode NodeProps}.
44
+ */
45
+ onNodeAdded?: (node: NodeProps<D>) => void;
46
+ /**
47
+ * Callback triggered when a node is deleted.
48
+ *
49
+ * @param nodeId - The identifier of the deleted node.
50
+ */
51
+ onNodeDeleted?: (nodeId: string) => void;
52
+ /**
53
+ * Callback triggered when an edge is created.
54
+ *
55
+ * @param edge - The created edge object of type {@linkcode EdgeProps}.
56
+ */
57
+ onEdgeAdded?: (edge: EdgeProps) => void;
58
+ /**
59
+ * Callback triggered when an edge is deleted.
60
+ *
61
+ * @param edgeId - The identifier of the deleted edge.
62
+ */
63
+ onEdgeDeleted?: (edgeId: string) => void;
64
+ };
65
+
66
+ /**
67
+ * Type alias extracting the non-undefined flowchart configuration object.
68
+ *
69
+ * @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
70
+ * {@linkcode NodeData}.
71
+ */
72
+ export type ConfigFrom<D extends NodeData = NodeData> = NotUndefined<
73
+ FlowProps<D>['config']
74
+ >;
75
+
76
+ /**
77
+ * Type alias extracting the non-undefined list of nodes from flowchart
78
+ * configuration.
79
+ *
80
+ * @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
81
+ * {@linkcode NodeData}.
82
+ */
83
+ export type NodesFrom<D extends NodeData> = NotUndefined<ConfigFrom<D>['nodes']>;
84
+
85
+ /**
86
+ * Type alias extracting the non-undefined list of edges from flowchart
87
+ * configuration.
88
+ *
89
+ * @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
90
+ * {@linkcode NodeData}.
91
+ */
92
+ export type EdgesFrom<D extends NodeData> = NotUndefined<ConfigFrom<D>['edges']>;