@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.
- package/README.md +55 -12
- package/lib/helpers/clamp.d.ts +2 -0
- package/lib/helpers/clamp.d.ts.map +1 -0
- package/lib/helpers/createContext.d.ts +1 -1
- package/lib/helpers/createContext.d.ts.map +1 -1
- package/lib/helpers/index.d.ts +3 -0
- package/lib/helpers/index.d.ts.map +1 -0
- package/lib/index.cjs.js +1 -1
- package/lib/index.d.ts +9 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.es.js +2543 -448
- package/lib/output.css +1 -1
- package/lib/server.cjs.js +1129 -397
- package/lib/server.es.js +1095 -407
- package/lib/services/main.machine.d.ts +2453 -576
- package/lib/services/main.machine.d.ts.map +1 -1
- package/lib/services/main.machine.data.d.ts +14 -1
- package/lib/services/main.machine.data.d.ts.map +1 -1
- package/lib/services/main.machine.helpers.d.ts +1 -1
- package/lib/services/main.machine.helpers.d.ts.map +1 -1
- package/lib/services/main.machine.test.d.ts +2 -0
- package/lib/services/main.machine.test.d.ts.map +1 -0
- package/lib/services/main.machine.typings.d.ts +26 -8
- package/lib/services/main.machine.typings.d.ts.map +1 -1
- package/lib/ui/Flow.d.ts +8 -4
- package/lib/ui/Flow.d.ts.map +1 -1
- package/lib/ui/components/Bounds.d.ts.map +1 -1
- package/lib/ui/components/EdgeComponent.d.ts.map +1 -1
- package/lib/ui/components/EdgesBoard.d.ts.map +1 -1
- package/lib/ui/components/EditPanel.d.ts +19 -0
- package/lib/ui/components/EditPanel.d.ts.map +1 -0
- package/lib/ui/components/EditPanel.hooks.d.ts +29 -0
- package/lib/ui/components/EditPanel.hooks.d.ts.map +1 -0
- package/lib/ui/components/EditPanel.types.d.ts +47 -0
- package/lib/ui/components/EditPanel.types.d.ts.map +1 -0
- package/lib/ui/components/FlowChart.context.d.ts +2452 -575
- package/lib/ui/components/FlowChart.context.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.d.ts +8 -47
- package/lib/ui/components/FlowChart.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.types.d.ts +87 -0
- package/lib/ui/components/FlowChart.types.d.ts.map +1 -0
- package/lib/ui/components/NodeComponent.d.ts +10 -12
- package/lib/ui/components/NodeComponent.d.ts.map +1 -1
- package/lib/ui/components/NodesBoard.d.ts +17 -2
- package/lib/ui/components/NodesBoard.d.ts.map +1 -1
- package/lib/ui/components/Panels.d.ts +12 -0
- package/lib/ui/components/Panels.d.ts.map +1 -0
- package/lib/ui/globals/components/atoms/TypingText.d.ts +29 -0
- package/lib/ui/globals/components/atoms/TypingText.d.ts.map +1 -0
- package/lib/ui/globals/components/molecules/FadingDots.d.ts +21 -0
- package/lib/ui/globals/components/molecules/FadingDots.d.ts.map +1 -0
- package/lib/ui/globals/components/organisms/LoadingFallback.d.ts +46 -0
- package/lib/ui/globals/components/organisms/LoadingFallback.d.ts.map +1 -0
- package/lib/ui/globals/constants/index.d.ts +3 -0
- package/lib/ui/globals/constants/index.d.ts.map +1 -0
- package/lib/ui/globals/constants/links.d.ts +4 -0
- package/lib/ui/globals/constants/links.d.ts.map +1 -0
- package/lib/ui/globals/constants/strings.d.ts +5 -0
- package/lib/ui/globals/constants/strings.d.ts.map +1 -0
- package/lib/ui/globals/directives/clickOutside.d.ts +9 -0
- package/lib/ui/globals/directives/clickOutside.d.ts.map +1 -0
- package/lib/ui/globals/directives/index.d.ts +16 -0
- package/lib/ui/globals/directives/index.d.ts.map +1 -0
- package/lib/ui/globals/directives/mouseOut.d.ts +33 -0
- package/lib/ui/globals/directives/mouseOut.d.ts.map +1 -0
- package/lib/ui/globals/directives/mouseOut.test.d.ts +2 -0
- package/lib/ui/globals/directives/mouseOut.test.d.ts.map +1 -0
- package/lib/ui/globals/directives/resize.d.ts +11 -0
- package/lib/ui/globals/directives/resize.d.ts.map +1 -0
- package/lib/ui/globals/helpers/espace.d.ts +17 -0
- package/lib/ui/globals/helpers/espace.d.ts.map +1 -0
- package/lib/ui/globals/signals/createTyping.d.ts +27 -0
- package/lib/ui/globals/signals/createTyping.d.ts.map +1 -0
- package/lib/ui/globals/types.d.ts +54 -0
- package/lib/ui/globals/types.d.ts.map +1 -0
- package/lib/ui/utils.d.ts +11 -0
- package/lib/ui/utils.d.ts.map +1 -0
- package/package.json +2 -3
- package/src/helpers/clamp.ts +3 -0
- package/src/helpers/createContext.ts +20 -4
- package/src/helpers/index.ts +2 -0
- package/src/index.ts +9 -1
- package/src/services/main.machine.data.ts +13 -2
- package/src/services/main.machine.helpers.ts +12 -4
- package/src/services/main.machine.test.ts +205 -0
- package/src/services/main.machine.ts +253 -75
- package/src/services/main.machine.typings.ts +31 -6
- package/src/ui/Flow.tsx +47 -8
- package/src/ui/components/Bounds.tsx +0 -2
- package/src/ui/components/EdgeComponent.tsx +60 -46
- package/src/ui/components/EdgesBoard.tsx +1 -2
- package/src/ui/components/EditPanel.hooks.ts +69 -0
- package/src/ui/components/EditPanel.tsx +89 -0
- package/src/ui/components/EditPanel.types.ts +49 -0
- package/src/ui/components/FlowChart.context.ts +22 -141
- package/src/ui/components/FlowChart.tsx +27 -64
- package/src/ui/components/FlowChart.types.ts +92 -0
- package/src/ui/components/NodeComponent.tsx +88 -65
- package/src/ui/components/NodesBoard.tsx +61 -56
- package/src/ui/components/Panels.tsx +40 -0
- package/src/ui/globals/components/atoms/TypingText.tsx +63 -0
- package/src/ui/globals/components/molecules/FadingDots.tsx +45 -0
- package/src/ui/globals/components/organisms/LoadingFallback.tsx +116 -0
- package/src/ui/globals/constants/index.ts +2 -0
- package/src/ui/globals/constants/links.ts +15 -0
- package/src/ui/globals/constants/strings.ts +5 -0
- package/src/ui/globals/directives/clickOutside.ts +18 -0
- package/src/ui/globals/directives/index.ts +16 -0
- package/src/ui/globals/directives/mouseOut.test.ts +115 -0
- package/src/ui/globals/directives/mouseOut.ts +98 -0
- package/src/ui/globals/directives/resize.ts +33 -0
- package/src/ui/globals/helpers/espace.ts +26 -0
- package/src/ui/globals/signals/createTyping.ts +82 -0
- package/src/ui/globals/types.ts +83 -0
- package/src/ui/utils.ts +14 -0
|
@@ -1,53 +1,14 @@
|
|
|
1
1
|
import { createState } from '@bemedev/app-solidjs';
|
|
2
|
-
import type
|
|
3
|
-
|
|
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
|
-
|
|
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,45 +26,44 @@ export type FlowProps = {
|
|
|
62
26
|
*
|
|
63
27
|
* @see {@linkcode NodesBoard}, {@linkcode useFlow}, {@linkcode DEFAULT_NODES}
|
|
64
28
|
*/
|
|
65
|
-
export const FlowChart
|
|
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
|
-
|
|
69
34
|
const service = useFlow();
|
|
70
|
-
|
|
71
35
|
const hasNewEdge = createState(service, { selector: s => !!s.context.newEdge });
|
|
36
|
+
onCleanup(service.pause);
|
|
72
37
|
|
|
73
38
|
onMount(() => {
|
|
74
39
|
service.resume();
|
|
75
40
|
service.send({
|
|
76
41
|
type: 'CONFIGURE',
|
|
77
|
-
payload: {
|
|
42
|
+
payload: {
|
|
43
|
+
nodes: primaryNodes,
|
|
44
|
+
edges: primaryEdges ?? [],
|
|
45
|
+
defaultData: props.defaultData,
|
|
46
|
+
},
|
|
78
47
|
});
|
|
79
48
|
});
|
|
80
49
|
|
|
81
|
-
onCleanup(service.pause);
|
|
82
|
-
|
|
83
50
|
return (
|
|
84
51
|
<div
|
|
85
52
|
class='relative h-full w-full'
|
|
86
53
|
onMouseUp={() => service.send('CLEAR_NEW_EDGE')}
|
|
87
|
-
onMouseMove={
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
54
|
+
onMouseMove={({ clientX, clientY }) => {
|
|
55
|
+
if (hasNewEdge())
|
|
56
|
+
service.send({
|
|
57
|
+
type: 'MOVE_NEW_EDGE',
|
|
58
|
+
payload: { x: clientX, y: clientY },
|
|
59
|
+
});
|
|
92
60
|
}}
|
|
93
|
-
style={{}}
|
|
94
61
|
>
|
|
95
62
|
<div
|
|
96
|
-
class='
|
|
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, JSX } 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. */
|
|
11
|
+
topLeft?: JSX.Element | Component;
|
|
12
|
+
/** Top-right corner overlay panel slot. */
|
|
13
|
+
topRight?: JSX.Element | Component;
|
|
14
|
+
/** Bottom-left corner overlay panel slot. */
|
|
15
|
+
bottomLeft?: JSX.Element | 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']>;
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-namespace */
|
|
2
1
|
import { toArray } from '@bemedev/app';
|
|
3
2
|
import { createState } from '@bemedev/app-solidjs';
|
|
4
3
|
import { createDraggable } from '@thisbeyond/solid-dnd';
|
|
5
4
|
import { dequal } from 'dequal';
|
|
6
|
-
import { Component, Show } from 'solid-js';
|
|
5
|
+
import { type Component, For, type JSX, Show } from 'solid-js';
|
|
6
|
+
import { Dynamic } from 'solid-js/web';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
HANDLE_CONTAINER_OFFSET_X,
|
|
10
10
|
HANDLE_MARGIN_TOP,
|
|
11
11
|
HANDLE_SIZE,
|
|
12
12
|
} from '#services/main.machine.data';
|
|
13
|
+
import type { NodeData } from '#services/main.machine.typings';
|
|
13
14
|
|
|
15
|
+
import { resize } from '../globals/directives';
|
|
14
16
|
import { useFlow } from './FlowChart.context';
|
|
15
17
|
|
|
16
|
-
declare module 'solid-js' {
|
|
17
|
-
namespace JSX {
|
|
18
|
-
interface Directives {
|
|
19
|
-
draggable: any;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
18
|
/** Properties for rendering an individual flowchart node component. */
|
|
25
|
-
type
|
|
19
|
+
export type NodeComponentProps<D extends NodeData = NodeData> = {
|
|
26
20
|
/** Unique identifier of the node. */
|
|
27
21
|
id: string;
|
|
22
|
+
/** Custom node component to render inside the node container. */
|
|
23
|
+
children?: Component<D>;
|
|
28
24
|
};
|
|
29
25
|
|
|
30
26
|
/**
|
|
31
27
|
* Interactive flowchart node component supporting dragging, selection, handle
|
|
32
28
|
* connections, and child/sibling creation.
|
|
33
29
|
*
|
|
34
|
-
* @
|
|
30
|
+
* @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
|
|
31
|
+
* {@linkcode NodeData}.
|
|
32
|
+
*
|
|
33
|
+
* @param props - Node rendering properties of type {@linkcode NodeComponentProps}.
|
|
35
34
|
*
|
|
36
35
|
* @returns The rendered Solid component.
|
|
37
36
|
*
|
|
38
37
|
* @see {@linkcode useFlow}, {@linkcode HANDLE_CONTAINER_OFFSET_X}, {@linkcode HANDLE_MARGIN_TOP}, {@linkcode HANDLE_SIZE}
|
|
39
38
|
*/
|
|
40
|
-
export const NodeComponent
|
|
39
|
+
export const NodeComponent = <D extends NodeData = NodeData>(
|
|
40
|
+
props: NodeComponentProps<D>,
|
|
41
|
+
): JSX.Element => {
|
|
41
42
|
const service = useFlow();
|
|
43
|
+
const newEdge = createState(service, { selector: s => s.context.newEdge });
|
|
44
|
+
const draggable = createDraggable(props.id);
|
|
45
|
+
void draggable;
|
|
42
46
|
|
|
43
47
|
const node = createState(service, {
|
|
44
48
|
selector: ({ context }) => {
|
|
45
49
|
const list = toArray.typed(context.data?.nodes);
|
|
46
|
-
const item = list.find(n => n.id === props.id)
|
|
50
|
+
const item = list.find(n => n.id === props.id);
|
|
47
51
|
return {
|
|
48
|
-
x: item
|
|
49
|
-
y: item
|
|
50
|
-
|
|
51
|
-
content: item.data.content ?? '',
|
|
52
|
-
input: item.input,
|
|
52
|
+
x: item?.position.x ?? 0,
|
|
53
|
+
y: item?.position.y ?? 0,
|
|
54
|
+
data: (item?.data ?? {}) as D,
|
|
53
55
|
};
|
|
54
56
|
},
|
|
55
57
|
equals: dequal,
|
|
56
58
|
});
|
|
57
59
|
|
|
58
|
-
const newEdge = createState(service, { selector: s => s.context.newEdge });
|
|
59
|
-
|
|
60
60
|
const selected = createState(service, {
|
|
61
|
-
selector:
|
|
61
|
+
selector: ({ context }) => context.selected === props.id,
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
const hasParent = createState(service, {
|
|
@@ -69,15 +69,12 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
69
69
|
},
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
const draggable = createDraggable(props.id);
|
|
73
|
-
void draggable;
|
|
74
|
-
|
|
75
72
|
return (
|
|
76
73
|
<div
|
|
77
74
|
classList={{
|
|
78
75
|
'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,
|
|
79
|
-
'border border-[#e38c29] z-
|
|
80
|
-
'border border-[#e6d4be] z-
|
|
76
|
+
'border border-[#e38c29] z-100': selected(),
|
|
77
|
+
'border border-[#e6d4be] z-1': !selected(),
|
|
81
78
|
}}
|
|
82
79
|
|
|
83
80
|
style={{ top: `${node().y}px`, left: `${node().x}px` }}
|
|
@@ -89,6 +86,10 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
89
86
|
e.stopPropagation();
|
|
90
87
|
service.send({ type: 'SELECT', payload: props.id });
|
|
91
88
|
}}
|
|
89
|
+
onDblClick={e => {
|
|
90
|
+
e.stopPropagation();
|
|
91
|
+
service.send({ type: 'EDIT', payload: props.id });
|
|
92
|
+
}}
|
|
92
93
|
>
|
|
93
94
|
<div
|
|
94
95
|
classList={{
|
|
@@ -121,6 +122,7 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
121
122
|
<Show when={hasParent()}>
|
|
122
123
|
<svg
|
|
123
124
|
class='cursor-pointer overflow-visible rounded-full bg-green-500 p-0.5 text-center font-bold hover:bg-green-600'
|
|
125
|
+
|
|
124
126
|
style={{
|
|
125
127
|
'pointer-events': 'all',
|
|
126
128
|
'fill-rule': 'evenodd',
|
|
@@ -144,6 +146,7 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
144
146
|
</g>
|
|
145
147
|
</svg>
|
|
146
148
|
</Show>
|
|
149
|
+
|
|
147
150
|
<svg
|
|
148
151
|
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'
|
|
149
152
|
onClick={() => service.send({ type: 'ADD_CHILD', payload: props.id })}
|
|
@@ -161,51 +164,71 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
161
164
|
</svg>
|
|
162
165
|
</div>
|
|
163
166
|
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
167
|
+
<div ref={resize(props.id)}>
|
|
168
|
+
<Show
|
|
169
|
+
when={props.children}
|
|
170
|
+
fallback={
|
|
171
|
+
<div class='p-3 select-none'>
|
|
172
|
+
<Show when={node().data.label} keyed>
|
|
173
|
+
{label => (
|
|
174
|
+
<span class='mb-2 block min-w-max border-b border-[#f0f0f0] pb-2 font-semibold whitespace-nowrap text-red-600 select-none'>
|
|
175
|
+
{String(label)}
|
|
176
|
+
</span>
|
|
177
|
+
)}
|
|
178
|
+
</Show>
|
|
179
|
+
|
|
180
|
+
<Show when={node().data.content} keyed>
|
|
181
|
+
{content => <div class='select-none'>{String(content)}</div>}
|
|
182
|
+
</Show>
|
|
183
|
+
|
|
184
|
+
<Show when={!node().data.label && !node().data.content}>
|
|
185
|
+
<For each={Object.entries(node().data)}>
|
|
186
|
+
{([key, val]) => (
|
|
187
|
+
<div>
|
|
188
|
+
<span class='font-semibold'>{key}: </span>
|
|
189
|
+
<span>{String(val)}</span>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</For>
|
|
193
|
+
</Show>
|
|
194
|
+
</div>
|
|
195
|
+
}
|
|
196
|
+
>
|
|
197
|
+
<Dynamic component={props.children} {...node().data} />
|
|
198
|
+
</Show>
|
|
199
|
+
</div>
|
|
175
200
|
|
|
176
|
-
<
|
|
201
|
+
<div
|
|
202
|
+
id='inputs'
|
|
203
|
+
class='pointer-events-none absolute top-0 z-10 flex cursor-default flex-col'
|
|
204
|
+
style={{ left: `-${HANDLE_CONTAINER_OFFSET_X}px` }}
|
|
205
|
+
>
|
|
177
206
|
<div
|
|
178
|
-
|
|
179
|
-
class='pointer-events-none absolute top-0 z-10 flex cursor-default flex-col'
|
|
180
|
-
style={{ left: `-${HANDLE_CONTAINER_OFFSET_X}px` }}
|
|
181
|
-
>
|
|
182
|
-
<div
|
|
183
|
-
class='cursor-default rounded-full bg-[#e38b29] shadow-md'
|
|
207
|
+
class='cursor-default rounded-full bg-[#e38b29] shadow-md'
|
|
184
208
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
209
|
+
style={{
|
|
210
|
+
width: `${HANDLE_SIZE}px`,
|
|
211
|
+
height: `${HANDLE_SIZE}px`,
|
|
212
|
+
'margin-top': `${HANDLE_MARGIN_TOP}px`,
|
|
213
|
+
'pointer-events': 'all',
|
|
214
|
+
}}
|
|
191
215
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
216
|
+
onMouseDown={event => {
|
|
217
|
+
event.stopPropagation();
|
|
218
|
+
}}
|
|
195
219
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
220
|
+
onMouseUp={event => {
|
|
221
|
+
event.stopPropagation();
|
|
222
|
+
const from = newEdge()?.from;
|
|
199
223
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
</Show>
|
|
224
|
+
if (from) {
|
|
225
|
+
service.send({ type: 'ADD_EDGE', payload: { from, to: props.id } });
|
|
226
|
+
} else service.send('CLEAR_NEW_EDGE');
|
|
227
|
+
}}
|
|
228
|
+
></div>
|
|
229
|
+
</div>
|
|
207
230
|
<div
|
|
208
|
-
id='
|
|
231
|
+
id='outputs'
|
|
209
232
|
class='pointer-events-none absolute top-0 z-10 flex flex-col'
|
|
210
233
|
style={{ right: `-${HANDLE_CONTAINER_OFFSET_X}px` }}
|
|
211
234
|
>
|
|
@@ -7,38 +7,55 @@ import {
|
|
|
7
7
|
} from '@thisbeyond/solid-dnd';
|
|
8
8
|
import { dequal } from 'dequal';
|
|
9
9
|
import {
|
|
10
|
-
Component,
|
|
10
|
+
type Component,
|
|
11
11
|
createEffect,
|
|
12
12
|
createSignal,
|
|
13
13
|
For,
|
|
14
|
+
type JSX,
|
|
14
15
|
on,
|
|
15
16
|
onMount,
|
|
16
17
|
Show,
|
|
17
18
|
} from 'solid-js';
|
|
18
19
|
|
|
19
20
|
import { CANVAS_FACTOR, SCROLL_MULTIPLIER } from '../../services/main.machine.data';
|
|
21
|
+
import type { NodeData } from '../../services/main.machine.typings';
|
|
20
22
|
import { DragBounds } from './Bounds';
|
|
21
23
|
import { EdgesBoard } from './EdgesBoard';
|
|
22
24
|
import { useFlow } from './FlowChart.context';
|
|
25
|
+
import type { FlowPanels } from './FlowChart.types';
|
|
23
26
|
import { NodeComponent } from './NodeComponent';
|
|
27
|
+
import { Panels } from './Panels';
|
|
28
|
+
|
|
29
|
+
/** Properties for the {@linkcode NodesBoard} component. */
|
|
30
|
+
type Props<D extends NodeData = NodeData> = {
|
|
31
|
+
/** Optional custom node component. */
|
|
32
|
+
component?: Component<D>;
|
|
33
|
+
/** Optional custom overlay panels. */
|
|
34
|
+
panels?: FlowPanels;
|
|
35
|
+
};
|
|
24
36
|
|
|
25
37
|
/**
|
|
26
38
|
* Interactive board component containing the drag-drop viewport, zoom controls,
|
|
27
39
|
* panning gestures, and rendered nodes/edges.
|
|
28
40
|
*
|
|
41
|
+
* @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
|
|
42
|
+
* {@linkcode NodeData}.
|
|
43
|
+
*
|
|
44
|
+
* @param props - Board component properties of type {@linkcode Props}.
|
|
45
|
+
*
|
|
29
46
|
* @returns The rendered Solid component.
|
|
30
47
|
*
|
|
31
48
|
* @see {@linkcode DragBounds}, {@linkcode EdgesBoard}, {@linkcode NodeComponent}, {@linkcode useFlow}, {@linkcode CANVAS_FACTOR}, {@linkcode SCROLL_MULTIPLIER}
|
|
32
49
|
*/
|
|
33
|
-
export const NodesBoard
|
|
50
|
+
export const NodesBoard = <D extends NodeData = NodeData>(
|
|
51
|
+
props: Props<D>,
|
|
52
|
+
): JSX.Element => {
|
|
34
53
|
let containerRef: HTMLDivElement;
|
|
35
54
|
const [isPanning, setIsPanning] = createSignal(false);
|
|
36
55
|
const [transform, setTransform] = createSignal({ x: 0, y: 0 });
|
|
37
|
-
const [id, setId] = createSignal<string | number>('');
|
|
38
56
|
const [ref, setRef] = createSignal<HTMLDivElement | undefined>();
|
|
39
57
|
let percentX = 0;
|
|
40
58
|
let percentY = 0;
|
|
41
|
-
|
|
42
59
|
const service = useFlow();
|
|
43
60
|
|
|
44
61
|
const newEdge = createState(service, {
|
|
@@ -107,15 +124,6 @@ export const NodesBoard: Component = () => {
|
|
|
107
124
|
|
|
108
125
|
const selectedId = createState(service, { selector: s => s.context?.selected });
|
|
109
126
|
|
|
110
|
-
/**
|
|
111
|
-
* Determines whether the given element ID matches the currently selected entity.
|
|
112
|
-
*
|
|
113
|
-
* @param id - The node or edge identifier to check.
|
|
114
|
-
*
|
|
115
|
-
* @returns `true` if the item is currently selected, otherwise `false`.
|
|
116
|
-
*/
|
|
117
|
-
const selected = (id: string | number) => selectedId() === id;
|
|
118
|
-
|
|
119
127
|
const nodeIds = createState(service, {
|
|
120
128
|
selector: ({ context }) => {
|
|
121
129
|
const list = toArray.typed(context.data?.nodes);
|
|
@@ -145,48 +153,42 @@ export const NodesBoard: Component = () => {
|
|
|
145
153
|
}
|
|
146
154
|
}}
|
|
147
155
|
|
|
148
|
-
class='relative mx-auto h-
|
|
156
|
+
class='relative mx-auto h-full w-full overflow-hidden'
|
|
149
157
|
>
|
|
150
158
|
<DragDropProvider
|
|
151
159
|
onDragMove={({ draggable: { transform: _transform, node, id } }) => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
`translate3d(${deltaX}px, ${deltaY}px, 0)`,
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
service.send({ type: 'MOVE_IMMEDIATE', payload: { id: `${id}`, x, y } });
|
|
184
|
-
setTransform({ x: deltaX * currentZoom, y: deltaY * currentZoom });
|
|
185
|
-
}
|
|
160
|
+
const grandParent = node.parentElement?.parentElement;
|
|
161
|
+
const currentZoom = zoom();
|
|
162
|
+
const minX = 0;
|
|
163
|
+
const maxX = grandParent
|
|
164
|
+
? Math.max(0, grandParent.clientWidth / currentZoom - node.offsetWidth)
|
|
165
|
+
: Infinity;
|
|
166
|
+
const minY = 0;
|
|
167
|
+
const maxY = grandParent
|
|
168
|
+
? Math.max(0, grandParent.clientHeight / currentZoom - node.offsetHeight)
|
|
169
|
+
: Infinity;
|
|
170
|
+
|
|
171
|
+
const targetX = node.offsetLeft + _transform.x / currentZoom;
|
|
172
|
+
const targetY = node.offsetTop + _transform.y / currentZoom;
|
|
173
|
+
|
|
174
|
+
const x = Math.min(Math.max(targetX, minX), maxX);
|
|
175
|
+
const y = Math.min(Math.max(targetY, minY), maxY);
|
|
176
|
+
|
|
177
|
+
const deltaX = x - node.offsetLeft;
|
|
178
|
+
const deltaY = y - node.offsetTop;
|
|
179
|
+
|
|
180
|
+
// Directly update the draggable node's CSS transform adjusted for zoom:
|
|
181
|
+
node.style.setProperty(
|
|
182
|
+
'transform',
|
|
183
|
+
`translate3d(${deltaX}px, ${deltaY}px, 0)`,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
service.send({ type: 'MOVE_IMMEDIATE', payload: { id: `${id}`, x, y } });
|
|
187
|
+
setTransform({ x: deltaX * currentZoom, y: deltaY * currentZoom });
|
|
186
188
|
}}
|
|
187
189
|
|
|
188
190
|
onDragEnd={({ draggable: { node, id } }) => {
|
|
189
|
-
if (!selected(id)) return;
|
|
191
|
+
// if (!selected(id)) return;
|
|
190
192
|
|
|
191
193
|
const grandParent = node.parentElement?.parentElement;
|
|
192
194
|
const currentZoom = zoom();
|
|
@@ -219,7 +221,7 @@ export const NodesBoard: Component = () => {
|
|
|
219
221
|
return (containerRef = el);
|
|
220
222
|
}}
|
|
221
223
|
onScroll={updateScrollPercentages}
|
|
222
|
-
class='relative h-full w-full overflow-auto
|
|
224
|
+
class='relative h-full w-full overflow-auto'
|
|
223
225
|
>
|
|
224
226
|
<DragDropSensors />
|
|
225
227
|
<div
|
|
@@ -227,11 +229,12 @@ export const NodesBoard: Component = () => {
|
|
|
227
229
|
class='relative cursor-crosshair overflow-hidden'
|
|
228
230
|
classList={{ 'cursor-grabbing': isPanning() }}
|
|
229
231
|
style={{ height: cHeight(), width: cWidth() }}
|
|
230
|
-
onScroll={() => {}}
|
|
232
|
+
// onScroll={() => {}}
|
|
231
233
|
|
|
232
234
|
onMouseDown={e => {
|
|
233
235
|
if (newEdge() || e.button !== 0) return;
|
|
234
236
|
service.send('DESELECT');
|
|
237
|
+
setIsPanning(true);
|
|
235
238
|
|
|
236
239
|
// #region Props
|
|
237
240
|
const startX = e.clientX;
|
|
@@ -240,8 +243,6 @@ export const NodesBoard: Component = () => {
|
|
|
240
243
|
const startScrollTop = containerRef.scrollTop;
|
|
241
244
|
// #endregion
|
|
242
245
|
|
|
243
|
-
setIsPanning(true);
|
|
244
|
-
|
|
245
246
|
const handleMouseMove = (moveEvent: MouseEvent) => {
|
|
246
247
|
const dx = moveEvent.clientX - startX;
|
|
247
248
|
const dy = moveEvent.clientY - startY;
|
|
@@ -268,12 +269,16 @@ export const NodesBoard: Component = () => {
|
|
|
268
269
|
>
|
|
269
270
|
<DragBounds />
|
|
270
271
|
<EdgesBoard />
|
|
271
|
-
<For each={nodeIds()}>
|
|
272
|
+
<For each={nodeIds()}>
|
|
273
|
+
{id => <NodeComponent id={id} children={props.component} />}
|
|
274
|
+
</For>
|
|
272
275
|
</div>
|
|
273
276
|
</div>
|
|
274
277
|
</div>
|
|
275
278
|
|
|
276
|
-
{
|
|
279
|
+
<Panels {...props.panels} />
|
|
280
|
+
|
|
281
|
+
{/* Bottom-Right Panel (Main Zoom & Creation Controls) */}
|
|
277
282
|
<div class='absolute right-4 bottom-4 z-50 flex items-center gap-2 rounded-xl border border-gray-200 bg-white/90 p-2 shadow-lg backdrop-blur-md'>
|
|
278
283
|
<button
|
|
279
284
|
type='button'
|
|
@@ -325,7 +330,7 @@ export const NodesBoard: Component = () => {
|
|
|
325
330
|
</svg>
|
|
326
331
|
</button>
|
|
327
332
|
</div>
|
|
328
|
-
<Show when={!
|
|
333
|
+
<Show when={!selectedId()}>
|
|
329
334
|
<DragOverlay children='' />
|
|
330
335
|
</Show>
|
|
331
336
|
</DragDropProvider>
|