@bemedev/mind-flow 1.3.0 → 1.4.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.
@@ -1 +1 @@
1
- {"version":3,"file":"EditPanel.d.ts","sourceRoot":"","sources":["../../../src/ui/components/EditPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAQ,MAAM,UAAU,CAAC;AAE1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAK/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,QAAQ,GAAG,QAAQ,EACrD,OAAO,cAAc,CAAC,CAAC,CAAC,KACvB,GAAG,CAAC,OA+DN,CAAC"}
1
+ {"version":3,"file":"EditPanel.d.ts","sourceRoot":"","sources":["../../../src/ui/components/EditPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAQ,MAAM,UAAU,CAAC;AAE1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAK/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,QAAQ,GAAG,QAAQ,EACrD,OAAO,cAAc,CAAC,CAAC,CAAC,KACvB,GAAG,CAAC,OAoEN,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"FlowChart.d.ts","sourceRoot":"","sources":["../../../src/ui/components/FlowChart.tsx"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,GAAG,EAAsB,MAAM,UAAU,CAAC;AAGtE,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAG1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAIpC;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,QAAQ,GAAG,QAAQ,EACrD,OAAO,SAAS,CAAC,CAAC,CAAC,KAClB,GAAG,CAAC,OA6DN,CAAC"}
1
+ {"version":3,"file":"FlowChart.d.ts","sourceRoot":"","sources":["../../../src/ui/components/FlowChart.tsx"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,GAAG,EAAsB,MAAM,UAAU,CAAC;AAGtE,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAG1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAIpC;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,QAAQ,GAAG,QAAQ,EACrD,OAAO,SAAS,CAAC,CAAC,CAAC,KAClB,GAAG,CAAC,OAuGN,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"NodeComponent.d.ts","sourceRoot":"","sources":["../../../src/ui/components/NodeComponent.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAO,KAAK,GAAG,EAAQ,MAAM,UAAU,CAAC;AAQ/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAK/D,uEAAuE;AACvE,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI;IAC9D,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,iEAAiE;IACjE,QAAQ,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;CACzB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,QAAQ,GAAG,QAAQ,EACzD,OAAO,kBAAkB,CAAC,CAAC,CAAC,KAC3B,GAAG,CAAC,OA+NN,CAAC"}
1
+ {"version":3,"file":"NodeComponent.d.ts","sourceRoot":"","sources":["../../../src/ui/components/NodeComponent.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAO,KAAK,GAAG,EAAQ,MAAM,UAAU,CAAC;AAQ/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAK/D,uEAAuE;AACvE,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI;IAC9D,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,iEAAiE;IACjE,QAAQ,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;CACzB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,QAAQ,GAAG,QAAQ,EACzD,OAAO,kBAAkB,CAAC,CAAC,CAAC,KAC3B,GAAG,CAAC,OA4NN,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Panels.d.ts","sourceRoot":"","sources":["../../../src/ui/components/Panels.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAEhD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,EAAE,SAAS,CAAC,UAAU,CAkBxC,CAAC"}
1
+ {"version":3,"file":"Panels.d.ts","sourceRoot":"","sources":["../../../src/ui/components/Panels.tsx"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAGhD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAiCpD;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,EAAE,SAAS,CAAC,UAAU,CAQxC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bemedev/mind-flow",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Flow Chart UI library for Solid.js applications",
5
5
  "keywords": [
6
6
  "diagram",
@@ -65,13 +65,13 @@
65
65
  "@bemedev/app": "^2.5.0",
66
66
  "@bemedev/app-cli": "^2.5.0",
67
67
  "@bemedev/app-solidjs": "^2.5.1",
68
- "@bemedev/dev-utils": "^1.2.0",
68
+ "@bemedev/dev-utils": "^1.3.1",
69
69
  "@rollup/plugin-babel": "^7.1.0",
70
70
  "@rollup/plugin-node-resolve": "^16.0.3",
71
71
  "@size-limit/file": "^13.0.3",
72
72
  "@tailwindcss/cli": "^4.3.3",
73
73
  "@tailwindcss/vite": "^4.3.3",
74
- "@types/node": "^26.3.0",
74
+ "@types/node": "^26.4.0",
75
75
  "@vitest/coverage-v8": "^4.1.11",
76
76
  "glob": "^13.0.6",
77
77
  "globals": "^17.11.0",
@@ -113,7 +113,8 @@
113
113
  "node": ">=22"
114
114
  },
115
115
  "scripts": {
116
- "build": "pnpm rm:lib && pnpm run build:server && pnpm run build:client && pnpm run build:css",
116
+ "build": "pnpm rm:lib && pnpm run build:dev",
117
+ "build:dev": "pnpm run build:server && pnpm run build:client && pnpm run build:css",
117
118
  "build:client": "vite build",
118
119
  "build:server": "vite build --mode server",
119
120
  "build:css": "pnpx @tailwindcss/cli -i ./input.css -o ../lib/output.css --minify --cwd ./src",
@@ -25,6 +25,7 @@ export const EditPanel = <D extends NodeData = NodeData>(
25
25
  props: EditPanelProps<D>,
26
26
  ): JSX.Element => {
27
27
  void clickOutside;
28
+ // const editing
28
29
  const hooks = useHook<D>(props.timeout);
29
30
  const classList = () =>
30
31
  props.classList instanceof Function ? props.classList(hooks) : props.classList;
@@ -36,7 +37,11 @@ export const EditPanel = <D extends NodeData = NodeData>(
36
37
  class={cn(
37
38
  `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
  )}
39
- classList={classList()}
40
+ classList={{
41
+ ...classList(),
42
+ 'pointer-events-none! -z-10': !hooks.editing(),
43
+ 'pointer-events-all! z-50': !!hooks.editing(),
44
+ }}
40
45
  style={props.style}
41
46
  onMouseDown={e => e.stopPropagation()}
42
47
  use:clickOutside={hooks.close}
@@ -32,7 +32,9 @@ export const FlowChart = <D extends NodeData = NodeData>(
32
32
  const primaryNodes = props.config?.nodes ?? DEFAULT_NODES;
33
33
  const primaryEdges = props.config?.edges;
34
34
  const service = useFlow();
35
- const hasNewEdge = createState(service, { selector: s => !!s.context.newEdge });
35
+ const fromSignal = createState(service, {
36
+ selector: s => s.context.newEdge?.from,
37
+ });
36
38
  onCleanup(service.pause);
37
39
 
38
40
  onMount(() => {
@@ -47,33 +49,73 @@ export const FlowChart = <D extends NodeData = NodeData>(
47
49
  });
48
50
  });
49
51
 
50
- createEffect(() => {
51
- if (!hasNewEdge()) return;
52
-
53
- const handlePointerMove = (e: MouseEvent | PointerEvent) => {
54
- service.send({
55
- type: 'MOVE_NEW_EDGE',
56
- payload: { x: e.clientX, y: e.clientY },
57
- });
58
- };
59
-
60
- const handlePointerUp = (e: MouseEvent | PointerEvent) => {
61
- const from = service.state.context.newEdge?.from;
62
- if (from) {
63
- const el = document.elementFromPoint(e.clientX, e.clientY);
64
- const inputHandle = el?.closest('[data-handle-type="input"]');
65
- const targetId = inputHandle?.getAttribute('data-node-id');
66
- if (targetId && targetId !== from) {
67
- service.send({ type: 'ADD_EDGE', payload: { from, to: targetId } });
68
- }
52
+ let added = false;
53
+
54
+ // Track the currently enlarged handle during edge drag
55
+ let activeHandle: HTMLElement | null = null;
56
+
57
+ const clearActiveHandle = () => {
58
+ if (activeHandle) {
59
+ activeHandle.classList.remove('scale-150');
60
+ activeHandle = null;
61
+ added = false;
62
+ }
63
+ };
64
+
65
+ const handlePointerMove = (e: MouseEvent | PointerEvent) => {
66
+ service.send({ type: 'MOVE_NEW_EDGE', payload: { x: e.clientX, y: e.clientY } });
67
+
68
+ const elements = document.elementsFromPoint(e.clientX, e.clientY);
69
+ const inputHandle = elements
70
+ .map(el => el.closest<HTMLElement>('.rounded-full[data-handle-type="input"]'))
71
+ .find((handle): handle is HTMLElement =>
72
+ Boolean(handle && handle.id !== 'inputs'),
73
+ );
74
+
75
+ // If cursor moved away from previous handle, reset its scale
76
+ if (activeHandle && activeHandle !== inputHandle) {
77
+ clearActiveHandle();
78
+ }
79
+
80
+ // If cursor entered a valid target handle, enlarge it
81
+ if (inputHandle) {
82
+ if (activeHandle !== inputHandle) {
83
+ inputHandle.classList.add('scale-150');
84
+ activeHandle = inputHandle;
85
+ added = true;
69
86
  }
70
- service.send('CLEAR_NEW_EDGE');
71
- };
87
+ }
88
+ };
89
+
90
+ const createHandlePointerUp = (from: string) => (e: MouseEvent | PointerEvent) => {
91
+ const elements = document.elementsFromPoint(e.clientX, e.clientY);
92
+ const inputHandle = elements
93
+ .map(el => el.closest<HTMLElement>('.rounded-full[data-handle-type="input"]'))
94
+ .find((handle): handle is HTMLElement =>
95
+ Boolean(handle && handle.id !== 'inputs'),
96
+ );
97
+ const to = inputHandle?.getAttribute('data-node-id');
98
+
99
+ if (inputHandle && to) {
100
+ service.send({ type: 'ADD_EDGE', payload: { from, to } });
101
+ }
102
+
103
+ // Cleanup on release
104
+ clearActiveHandle();
105
+ service.send('CLEAR_NEW_EDGE');
106
+ };
107
+
108
+ createEffect(() => {
109
+ const from = fromSignal();
110
+ if (!from || added) return;
72
111
 
112
+ const handlePointerUp = createHandlePointerUp(from);
73
113
  window.addEventListener('pointermove', handlePointerMove);
74
114
  window.addEventListener('pointerup', handlePointerUp);
75
115
 
76
116
  onCleanup(() => {
117
+ // Cleanup on effect disposal
118
+ clearActiveHandle();
77
119
  window.removeEventListener('pointermove', handlePointerMove);
78
120
  window.removeEventListener('pointerup', handlePointerUp);
79
121
  });
@@ -83,7 +125,7 @@ export const FlowChart = <D extends NodeData = NodeData>(
83
125
  <div class='relative h-full w-full'>
84
126
  <div
85
127
  class='h-full w-full'
86
- style={{ cursor: hasNewEdge() ? 'inherit' : 'crosshair' }}
128
+ style={{ cursor: fromSignal() ? 'inherit' : 'crosshair' }}
87
129
  >
88
130
  <NodesBoard panels={props.panels} component={props.component} />
89
131
  </div>
@@ -200,8 +200,6 @@ export const NodeComponent = <D extends NodeData = NodeData>(
200
200
 
201
201
  <div
202
202
  id='inputs'
203
- data-handle-type='input'
204
- data-node-id={props.id}
205
203
  class='pointer-events-none absolute top-0 z-10 flex cursor-default flex-col'
206
204
  style={{ left: `-${HANDLE_CONTAINER_OFFSET_X}px` }}
207
205
  >
@@ -211,7 +209,7 @@ export const NodeComponent = <D extends NodeData = NodeData>(
211
209
  onMouseDown={event => event.stopPropagation()}
212
210
  onPointerDown={e => e.stopPropagation()}
213
211
 
214
- class='cursor-default rounded-full bg-[#e38b29] shadow-md transition-transform'
212
+ class='cursor-default rounded-full bg-[#e38b29] shadow-md transition-transform duration-150 ease-in-out hover:scale-150'
215
213
 
216
214
  style={{
217
215
  width: `${HANDLE_SIZE}px`,
@@ -233,8 +231,7 @@ export const NodeComponent = <D extends NodeData = NodeData>(
233
231
  </div>
234
232
  <div
235
233
  id='outputs'
236
- data-handle-type='output'
237
- data-node-id={props.id}
234
+
238
235
  style={{ right: `-${HANDLE_CONTAINER_OFFSET_X}px` }}
239
236
 
240
237
  class='pointer-events-none absolute top-0 z-10 flex flex-col'
@@ -242,7 +239,7 @@ export const NodeComponent = <D extends NodeData = NodeData>(
242
239
  <div
243
240
  data-handle-type='output'
244
241
  data-node-id={props.id}
245
- class='cursor-crosshair rounded-full bg-[#e38b29] shadow-md transition-transform'
242
+ class='cursor-crosshair rounded-full bg-[#e38b29] shadow-md transition-transform duration-150 ease-in-out hover:scale-150'
246
243
  style={{
247
244
  width: `${HANDLE_SIZE}px`,
248
245
  height: `${HANDLE_SIZE}px`,
@@ -1,7 +1,40 @@
1
+ import { isDefined } from '@bemedev/app/bemedev';
1
2
  import { Show, type Component } from 'solid-js';
2
3
 
4
+ import { cn } from '../utils';
3
5
  import type { FlowPanels } from './FlowChart.types';
4
6
 
7
+ /** Properties for the internal {@linkcode Panel} wrapper component. */
8
+ type PanelProps = {
9
+ /** Optional component to render inside the panel container. */
10
+ children?: Component;
11
+ /** CSS class names for positioning and layout. */
12
+ class: string;
13
+ };
14
+
15
+ /**
16
+ * Overlay slot component rendering an optional panel component with z-index
17
+ * positioning.
18
+ *
19
+ * @param props - Panel properties of type {@linkcode PanelProps}.
20
+ *
21
+ * @returns The rendered panel container element or fallback.
22
+ *
23
+ * @see {@linkcode cn}
24
+ */
25
+ const Panel: Component<PanelProps> = props => {
26
+ const exists = isDefined(props.children);
27
+ return (
28
+ <Show when={props.children} keyed>
29
+ {Children => (
30
+ <div class={cn(props.class)} classList={{ 'z-50': exists }}>
31
+ <Children />
32
+ </div>
33
+ )}
34
+ </Show>
35
+ );
36
+ };
37
+
5
38
  /**
6
39
  * Overlay container rendering custom panel slots positioned around the flowchart
7
40
  * canvas.
@@ -12,20 +45,10 @@ import type { FlowPanels } from './FlowChart.types';
12
45
  */
13
46
  export const Panels: Component<FlowPanels> = props => {
14
47
  return (
15
- <>
16
- <Show when={props.topLeft} keyed>
17
- {Panel => <div class='absolute top-4 left-4 z-50'>{<Panel />}</div>}
18
- </Show>
19
-
20
- {/* Top-Right Panel */}
21
- <Show when={props.topRight} keyed>
22
- {Panel => <div class='absolute top-4 right-4 z-50'>{<Panel />}</div>}
23
- </Show>
24
-
25
- {/* Bottom-Left Panel */}
26
- <Show when={props.bottomLeft} keyed>
27
- {Panel => <div class='absolute bottom-4 left-4 z-50'>{<Panel />}</div>}
28
- </Show>
29
- </>
48
+ <div class='pointer-events-none absolute inset-0'>
49
+ <Panel children={props.topLeft} class='absolute top-4 left-4' />
50
+ <Panel children={props.topRight} class='absolute top-4 right-4' />
51
+ <Panel children={props.bottomLeft} class='absolute bottom-4 left-4' />
52
+ </div>
30
53
  );
31
54
  };