@mindlogic-ai/logician-ui 4.0.0-alpha.36 → 4.0.0-alpha.37
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/dist/components/Form/FormSection/CollapsibleFormSection.js +1 -0
- package/dist/components/Form/FormSection/CollapsibleFormSection.js.map +1 -1
- package/dist/components/Form/FormSection/CollapsibleFormSection.mjs +1 -0
- package/dist/components/Form/FormSection/CollapsibleFormSection.mjs.map +1 -1
- package/dist/components/Workflow/Workflow.d.ts +1 -1
- package/dist/components/Workflow/Workflow.d.ts.map +1 -1
- package/dist/components/Workflow/Workflow.js +2 -2
- package/dist/components/Workflow/Workflow.js.map +1 -1
- package/dist/components/Workflow/Workflow.mjs +2 -2
- package/dist/components/Workflow/Workflow.mjs.map +1 -1
- package/dist/components/Workflow/Workflow.types.d.ts +9 -0
- package/dist/components/Workflow/Workflow.types.d.ts.map +1 -1
- package/dist/components/Workflow/Workflow.types.js.map +1 -1
- package/dist/components/Workflow/Workflow.types.mjs.map +1 -1
- package/dist/components/Workflow/canvas/Canvas/Canvas.d.ts +6 -2
- package/dist/components/Workflow/canvas/Canvas/Canvas.d.ts.map +1 -1
- package/dist/components/Workflow/canvas/Canvas/Canvas.js +29 -23
- package/dist/components/Workflow/canvas/Canvas/Canvas.js.map +1 -1
- package/dist/components/Workflow/canvas/Canvas/Canvas.mjs +31 -25
- package/dist/components/Workflow/canvas/Canvas/Canvas.mjs.map +1 -1
- package/dist/components/Workflow/createNode.d.ts +25 -1
- package/dist/components/Workflow/createNode.d.ts.map +1 -1
- package/dist/components/Workflow/createNode.js +39 -1
- package/dist/components/Workflow/createNode.js.map +1 -1
- package/dist/components/Workflow/createNode.mjs +38 -2
- package/dist/components/Workflow/createNode.mjs.map +1 -1
- package/dist/components/Workflow/index.d.ts +2 -0
- package/dist/components/Workflow/index.d.ts.map +1 -1
- package/dist/components/Workflow/layout/autoLayout.d.ts +10 -0
- package/dist/components/Workflow/layout/autoLayout.d.ts.map +1 -1
- package/dist/components/Workflow/layout/autoLayout.js +4 -0
- package/dist/components/Workflow/layout/autoLayout.js.map +1 -1
- package/dist/components/Workflow/layout/autoLayout.mjs +1 -1
- package/dist/components/Workflow/layout/autoLayout.mjs.map +1 -1
- package/dist/components/Workflow/placement.d.ts +65 -0
- package/dist/components/Workflow/placement.d.ts.map +1 -0
- package/dist/components/Workflow/placement.js +100 -0
- package/dist/components/Workflow/placement.js.map +1 -0
- package/dist/components/Workflow/placement.mjs +96 -0
- package/dist/components/Workflow/placement.mjs.map +1 -0
- package/dist/components/Workflow/useWorkflowActions.d.ts +54 -0
- package/dist/components/Workflow/useWorkflowActions.d.ts.map +1 -0
- package/dist/components/Workflow/useWorkflowActions.js +105 -0
- package/dist/components/Workflow/useWorkflowActions.js.map +1 -0
- package/dist/components/Workflow/useWorkflowActions.mjs +103 -0
- package/dist/components/Workflow/useWorkflowActions.mjs.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/Workflow/README.md +64 -0
- package/src/components/Workflow/Workflow.tsx +2 -0
- package/src/components/Workflow/Workflow.types.ts +10 -0
- package/src/components/Workflow/canvas/Canvas/Canvas.tsx +57 -53
- package/src/components/Workflow/createNode.test.ts +40 -2
- package/src/components/Workflow/createNode.ts +70 -2
- package/src/components/Workflow/index.ts +8 -0
- package/src/components/Workflow/layout/autoLayout.ts +4 -4
- package/src/components/Workflow/placement.test.ts +156 -0
- package/src/components/Workflow/placement.ts +172 -0
- package/src/components/Workflow/stories/Workflow.stories.tsx +79 -0
- package/src/components/Workflow/useWorkflowActions.test.tsx +345 -0
- package/src/components/Workflow/useWorkflowActions.ts +196 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useWorkflowActions.d.ts","sourceRoot":"","sources":["../../../src/components/Workflow/useWorkflowActions.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,aAAa,CAAC;AAGrB,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,iFAAiF;AACjF,MAAM,MAAM,aAAa,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAEtE,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;CACzC,CAAC;AAEF,qEAAqE;AACrE,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;;;OAMG;IACH,OAAO,EAAE,CACP,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,aAAa,EACrB,OAAO,CAAC,EAAE,cAAc,KACrB,aAAa,GAAG,IAAI,CAAC;IAC1B;;;;;;OAMG;IACH,iBAAiB,EAAE,CACjB,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,KAC9B,cAAc,GAAG,IAAI,CAAC;IAC3B,4CAA4C;IAC5C,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,KAAK,OAAO,CAAC;CAC7D,CAAC;AAKF;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAuHpD"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var react = require('@xyflow/react');
|
|
6
|
+
var createNode = require('./createNode.js');
|
|
7
|
+
var placement = require('./placement.js');
|
|
8
|
+
var WorkflowContext = require('./WorkflowContext/WorkflowContext.js');
|
|
9
|
+
|
|
10
|
+
/** Pan duration when `addNode` brings the new node into view. */
|
|
11
|
+
const PAN_MS = 300;
|
|
12
|
+
/**
|
|
13
|
+
* Imperative editor actions for content rendered inside `<Workflow>` (its
|
|
14
|
+
* `children` and render slots sit inside the canvas's React Flow provider).
|
|
15
|
+
* Hosts use this instead of re-implementing node creation and placement — the
|
|
16
|
+
* tap-to-add palette on touch devices being the first caller.
|
|
17
|
+
*
|
|
18
|
+
* Must be called from a component rendered inside `<Workflow>`.
|
|
19
|
+
*/
|
|
20
|
+
function useWorkflowActions() {
|
|
21
|
+
const { graph, dispatch, getNodeType, translate, hostBridge, readOnly, editor: { drawerTarget }, setSelectedNodeId, setSelectedEdgeId, setDrawerTarget, } = WorkflowContext.useWorkflow();
|
|
22
|
+
const { getNode, getViewport, setCenter } = react.useReactFlow();
|
|
23
|
+
const store = react.useStoreApi();
|
|
24
|
+
const getAddAfterSource = React.useCallback((nodeId) => {
|
|
25
|
+
const exit = placement.findFreeExit(graph, getNodeType, nodeId);
|
|
26
|
+
if (!exit)
|
|
27
|
+
return null;
|
|
28
|
+
const def = getNodeType(exit.node.kind);
|
|
29
|
+
const label = def?.getInstanceTitle?.(exit.node.config) || def?.label || exit.node.id;
|
|
30
|
+
return { nodeId: exit.node.id, sourceHandle: exit.sourceHandle, label };
|
|
31
|
+
}, [graph, getNodeType]);
|
|
32
|
+
const canAddAfter = React.useCallback((nodeId) => getAddAfterSource(nodeId) !== null, [getAddAfterSource]);
|
|
33
|
+
const addNode = React.useCallback((kind, target, { inspector = 'follow' } = {}) => {
|
|
34
|
+
if (readOnly)
|
|
35
|
+
return null;
|
|
36
|
+
const def = getNodeType(kind);
|
|
37
|
+
if (!def)
|
|
38
|
+
return null;
|
|
39
|
+
const draft = createNode.createNodeFromType(def, {
|
|
40
|
+
position: { x: 0, y: 0 },
|
|
41
|
+
nodes: graph.nodes,
|
|
42
|
+
translate: createNode.toDefaultsTranslate(translate),
|
|
43
|
+
hostBridge,
|
|
44
|
+
});
|
|
45
|
+
// `nextNodeId` returns a fixed id for singletons (`start`); a second one
|
|
46
|
+
// would be dropped by the reducer's dedupe, so report it instead.
|
|
47
|
+
if (graph.nodes.some((n) => n.id === draft.id))
|
|
48
|
+
return null;
|
|
49
|
+
// Centre of the visible pane, in flow coordinates.
|
|
50
|
+
const { width, height } = store.getState();
|
|
51
|
+
const viewport = getViewport();
|
|
52
|
+
const viewportCenter = {
|
|
53
|
+
x: (width / 2 - viewport.x) / viewport.zoom,
|
|
54
|
+
y: (height / 2 - viewport.y) / viewport.zoom,
|
|
55
|
+
};
|
|
56
|
+
const { position, edge } = placement.planNodePlacement({
|
|
57
|
+
graph,
|
|
58
|
+
getNodeType,
|
|
59
|
+
node: draft,
|
|
60
|
+
target,
|
|
61
|
+
viewportCenter,
|
|
62
|
+
edgeId: createNode.genId('e'),
|
|
63
|
+
getNodeSize: (id) => {
|
|
64
|
+
const { width: w, height: h } = getNode(id)?.measured ?? {};
|
|
65
|
+
return w && h ? { width: w, height: h } : undefined;
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
const node = { ...draft, position };
|
|
69
|
+
dispatch({
|
|
70
|
+
type: 'addNodeWithEdge',
|
|
71
|
+
node,
|
|
72
|
+
autoConnectFrom: edge ? { edge } : null,
|
|
73
|
+
});
|
|
74
|
+
// Select the new node so a follow-up `{ after: selected }` chains on.
|
|
75
|
+
setSelectedNodeId(node.id);
|
|
76
|
+
setSelectedEdgeId(null);
|
|
77
|
+
if (inspector === 'open' || (inspector === 'follow' && drawerTarget)) {
|
|
78
|
+
setDrawerTarget({ type: 'node', id: node.id });
|
|
79
|
+
}
|
|
80
|
+
else if (inspector === 'close') {
|
|
81
|
+
setDrawerTarget(null);
|
|
82
|
+
}
|
|
83
|
+
void setCenter(position.x + placement.FALLBACK_NODE_SIZE.width / 2, position.y + placement.FALLBACK_NODE_SIZE.height / 2, { zoom: viewport.zoom, duration: PAN_MS });
|
|
84
|
+
return { nodeId: node.id, edgeId: edge?.id ?? null };
|
|
85
|
+
}, [
|
|
86
|
+
readOnly,
|
|
87
|
+
getNodeType,
|
|
88
|
+
graph,
|
|
89
|
+
translate,
|
|
90
|
+
hostBridge,
|
|
91
|
+
store,
|
|
92
|
+
getViewport,
|
|
93
|
+
getNode,
|
|
94
|
+
dispatch,
|
|
95
|
+
setSelectedNodeId,
|
|
96
|
+
setSelectedEdgeId,
|
|
97
|
+
drawerTarget,
|
|
98
|
+
setDrawerTarget,
|
|
99
|
+
setCenter,
|
|
100
|
+
]);
|
|
101
|
+
return React.useMemo(() => ({ addNode, getAddAfterSource, canAddAfter }), [addNode, getAddAfterSource, canAddAfter]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
exports.useWorkflowActions = useWorkflowActions;
|
|
105
|
+
//# sourceMappingURL=useWorkflowActions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useWorkflowActions.js","sources":["../../../src/components/Workflow/useWorkflowActions.ts"],"sourcesContent":[null],"names":["useWorkflow","useReactFlow","useStoreApi","useCallback","findFreeExit","createNodeFromType","toDefaultsTranslate","planNodePlacement","genId","FALLBACK_NODE_SIZE","useMemo"],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useMemo } from 'react';
|
|
3
|
+
import { useReactFlow, useStoreApi } from '@xyflow/react';
|
|
4
|
+
import { createNodeFromType, genId, toDefaultsTranslate } from './createNode.mjs';
|
|
5
|
+
import { findFreeExit, planNodePlacement, FALLBACK_NODE_SIZE } from './placement.mjs';
|
|
6
|
+
import { useWorkflow } from './WorkflowContext/WorkflowContext.mjs';
|
|
7
|
+
|
|
8
|
+
/** Pan duration when `addNode` brings the new node into view. */
|
|
9
|
+
const PAN_MS = 300;
|
|
10
|
+
/**
|
|
11
|
+
* Imperative editor actions for content rendered inside `<Workflow>` (its
|
|
12
|
+
* `children` and render slots sit inside the canvas's React Flow provider).
|
|
13
|
+
* Hosts use this instead of re-implementing node creation and placement — the
|
|
14
|
+
* tap-to-add palette on touch devices being the first caller.
|
|
15
|
+
*
|
|
16
|
+
* Must be called from a component rendered inside `<Workflow>`.
|
|
17
|
+
*/
|
|
18
|
+
function useWorkflowActions() {
|
|
19
|
+
const { graph, dispatch, getNodeType, translate, hostBridge, readOnly, editor: { drawerTarget }, setSelectedNodeId, setSelectedEdgeId, setDrawerTarget, } = useWorkflow();
|
|
20
|
+
const { getNode, getViewport, setCenter } = useReactFlow();
|
|
21
|
+
const store = useStoreApi();
|
|
22
|
+
const getAddAfterSource = useCallback((nodeId) => {
|
|
23
|
+
const exit = findFreeExit(graph, getNodeType, nodeId);
|
|
24
|
+
if (!exit)
|
|
25
|
+
return null;
|
|
26
|
+
const def = getNodeType(exit.node.kind);
|
|
27
|
+
const label = def?.getInstanceTitle?.(exit.node.config) || def?.label || exit.node.id;
|
|
28
|
+
return { nodeId: exit.node.id, sourceHandle: exit.sourceHandle, label };
|
|
29
|
+
}, [graph, getNodeType]);
|
|
30
|
+
const canAddAfter = useCallback((nodeId) => getAddAfterSource(nodeId) !== null, [getAddAfterSource]);
|
|
31
|
+
const addNode = useCallback((kind, target, { inspector = 'follow' } = {}) => {
|
|
32
|
+
if (readOnly)
|
|
33
|
+
return null;
|
|
34
|
+
const def = getNodeType(kind);
|
|
35
|
+
if (!def)
|
|
36
|
+
return null;
|
|
37
|
+
const draft = createNodeFromType(def, {
|
|
38
|
+
position: { x: 0, y: 0 },
|
|
39
|
+
nodes: graph.nodes,
|
|
40
|
+
translate: toDefaultsTranslate(translate),
|
|
41
|
+
hostBridge,
|
|
42
|
+
});
|
|
43
|
+
// `nextNodeId` returns a fixed id for singletons (`start`); a second one
|
|
44
|
+
// would be dropped by the reducer's dedupe, so report it instead.
|
|
45
|
+
if (graph.nodes.some((n) => n.id === draft.id))
|
|
46
|
+
return null;
|
|
47
|
+
// Centre of the visible pane, in flow coordinates.
|
|
48
|
+
const { width, height } = store.getState();
|
|
49
|
+
const viewport = getViewport();
|
|
50
|
+
const viewportCenter = {
|
|
51
|
+
x: (width / 2 - viewport.x) / viewport.zoom,
|
|
52
|
+
y: (height / 2 - viewport.y) / viewport.zoom,
|
|
53
|
+
};
|
|
54
|
+
const { position, edge } = planNodePlacement({
|
|
55
|
+
graph,
|
|
56
|
+
getNodeType,
|
|
57
|
+
node: draft,
|
|
58
|
+
target,
|
|
59
|
+
viewportCenter,
|
|
60
|
+
edgeId: genId('e'),
|
|
61
|
+
getNodeSize: (id) => {
|
|
62
|
+
const { width: w, height: h } = getNode(id)?.measured ?? {};
|
|
63
|
+
return w && h ? { width: w, height: h } : undefined;
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
const node = { ...draft, position };
|
|
67
|
+
dispatch({
|
|
68
|
+
type: 'addNodeWithEdge',
|
|
69
|
+
node,
|
|
70
|
+
autoConnectFrom: edge ? { edge } : null,
|
|
71
|
+
});
|
|
72
|
+
// Select the new node so a follow-up `{ after: selected }` chains on.
|
|
73
|
+
setSelectedNodeId(node.id);
|
|
74
|
+
setSelectedEdgeId(null);
|
|
75
|
+
if (inspector === 'open' || (inspector === 'follow' && drawerTarget)) {
|
|
76
|
+
setDrawerTarget({ type: 'node', id: node.id });
|
|
77
|
+
}
|
|
78
|
+
else if (inspector === 'close') {
|
|
79
|
+
setDrawerTarget(null);
|
|
80
|
+
}
|
|
81
|
+
void setCenter(position.x + FALLBACK_NODE_SIZE.width / 2, position.y + FALLBACK_NODE_SIZE.height / 2, { zoom: viewport.zoom, duration: PAN_MS });
|
|
82
|
+
return { nodeId: node.id, edgeId: edge?.id ?? null };
|
|
83
|
+
}, [
|
|
84
|
+
readOnly,
|
|
85
|
+
getNodeType,
|
|
86
|
+
graph,
|
|
87
|
+
translate,
|
|
88
|
+
hostBridge,
|
|
89
|
+
store,
|
|
90
|
+
getViewport,
|
|
91
|
+
getNode,
|
|
92
|
+
dispatch,
|
|
93
|
+
setSelectedNodeId,
|
|
94
|
+
setSelectedEdgeId,
|
|
95
|
+
drawerTarget,
|
|
96
|
+
setDrawerTarget,
|
|
97
|
+
setCenter,
|
|
98
|
+
]);
|
|
99
|
+
return useMemo(() => ({ addNode, getAddAfterSource, canAddAfter }), [addNode, getAddAfterSource, canAddAfter]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { useWorkflowActions };
|
|
103
|
+
//# sourceMappingURL=useWorkflowActions.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useWorkflowActions.mjs","sources":["../../../src/components/Workflow/useWorkflowActions.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -173,6 +173,7 @@ var NodeShell_styles = require('./components/Workflow/canvas/NodeShell/NodeShell
|
|
|
173
173
|
var workflowLabelProps = require('./components/Workflow/canvas/workflowLabelProps.js');
|
|
174
174
|
var graphObserver = require('./components/Workflow/graphObserver.js');
|
|
175
175
|
var graphReducer = require('./components/Workflow/graphReducer.js');
|
|
176
|
+
var useWorkflowActions = require('./components/Workflow/useWorkflowActions.js');
|
|
176
177
|
var Workflow = require('./components/Workflow/Workflow.js');
|
|
177
178
|
var Workflow_types = require('./components/Workflow/Workflow.types.js');
|
|
178
179
|
var WorkflowContext = require('./components/Workflow/WorkflowContext/WorkflowContext.js');
|
|
@@ -424,6 +425,7 @@ exports.WorkflowGraphObserverContext = graphObserver.WorkflowGraphObserverContex
|
|
|
424
425
|
exports.useWorkflowGraphObserver = graphObserver.useWorkflowGraphObserver;
|
|
425
426
|
exports.emptyGraph = graphReducer.emptyGraph;
|
|
426
427
|
exports.graphReducer = graphReducer.graphReducer;
|
|
428
|
+
exports.useWorkflowActions = useWorkflowActions.useWorkflowActions;
|
|
427
429
|
exports.Workflow = Workflow.Workflow;
|
|
428
430
|
exports.defineNodeType = Workflow_types.defineNodeType;
|
|
429
431
|
exports.resolveDefaultConfig = Workflow_types.resolveDefaultConfig;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.mjs
CHANGED
|
@@ -171,6 +171,7 @@ export { CARD_WIDTH, MAX_META_CHIPS, PORT_STYLE, metaChipStyles } from './compon
|
|
|
171
171
|
export { workflowLabelProps } from './components/Workflow/canvas/workflowLabelProps.mjs';
|
|
172
172
|
export { WorkflowGraphObserverContext, useWorkflowGraphObserver } from './components/Workflow/graphObserver.mjs';
|
|
173
173
|
export { emptyGraph, graphReducer } from './components/Workflow/graphReducer.mjs';
|
|
174
|
+
export { useWorkflowActions } from './components/Workflow/useWorkflowActions.mjs';
|
|
174
175
|
export { Workflow } from './components/Workflow/Workflow.mjs';
|
|
175
176
|
export { defineNodeType, resolveDefaultConfig } from './components/Workflow/Workflow.types.mjs';
|
|
176
177
|
export { WorkflowProvider, useWorkflow, useWorkflowTranslate } from './components/Workflow/WorkflowContext/WorkflowContext.mjs';
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -182,6 +182,69 @@ to keep the built-in drawer but also highlight the selection elsewhere.
|
|
|
182
182
|
|
|
183
183
|
---
|
|
184
184
|
|
|
185
|
+
## Adding nodes from your own UI (`useWorkflowActions`)
|
|
186
|
+
|
|
187
|
+
The palette is drag-and-drop. For anything else — a tap-to-add sheet on touch
|
|
188
|
+
devices, a "+" beside a node, a command menu — call `useWorkflowActions()` from a
|
|
189
|
+
component rendered inside `<Workflow>` (its `children` or
|
|
190
|
+
`renderSelectedNodeToolbar`). It uses the same node builder as a palette drop
|
|
191
|
+
(`{kind}_{N}` id; `defaultConfig` → `localizeDefaults` → `hostDefaults`), so you
|
|
192
|
+
never rebuild ids, configs or placement, and you never import `@xyflow/react`.
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import {
|
|
196
|
+
useWorkflow,
|
|
197
|
+
useWorkflowActions,
|
|
198
|
+
Workflow,
|
|
199
|
+
} from '@mindlogic-ai/logician-ui';
|
|
200
|
+
|
|
201
|
+
function AddAgentButton() {
|
|
202
|
+
const { editor } = useWorkflow();
|
|
203
|
+
const { addNode, getAddAfterSource } = useWorkflowActions();
|
|
204
|
+
const after = getAddAfterSource(editor.selectedNodeId);
|
|
205
|
+
return (
|
|
206
|
+
<button
|
|
207
|
+
onClick={() =>
|
|
208
|
+
addNode(
|
|
209
|
+
'agent',
|
|
210
|
+
after ? { after: after.nodeId } : { at: 'viewportCenter' }
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
>
|
|
214
|
+
{after ? `Add after ${after.label}` : 'Add at centre'}
|
|
215
|
+
</button>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
<Workflow
|
|
220
|
+
nodeTypes={NODE_TYPES}
|
|
221
|
+
renderSelectedNodeToolbar={(node) => <PlusAfter nodeId={node.id} />}
|
|
222
|
+
>
|
|
223
|
+
<AddAgentButton />
|
|
224
|
+
</Workflow>;
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
- `addNode(kind, target, { inspector? })` → `{ nodeId, edgeId | null } | null`.
|
|
228
|
+
- `{ after: nodeId }` places the node to that node's right and wires it from the
|
|
229
|
+
first free exit. No edge when the new kind has no entry (Note) or a
|
|
230
|
+
`canConnect` hook / connection rule rejects it. A node with no free exit (End,
|
|
231
|
+
Note, every exit taken) falls back to the viewport centre.
|
|
232
|
+
- `{ at: 'viewportCenter' }` places it unconnected in the middle of the view.
|
|
233
|
+
- Either way the node moves down past anything already there, becomes the
|
|
234
|
+
selection, and the view pans to it. Node + edge are **one undo step**.
|
|
235
|
+
- `inspector`: `'follow'` (default — an open inspector moves to the new node),
|
|
236
|
+
`'open'`, or `'close'`.
|
|
237
|
+
- Returns `null` without changing anything when read-only, the kind is not
|
|
238
|
+
registered, or its id is taken (a second `start`).
|
|
239
|
+
- `getAddAfterSource(nodeId)` → `{ nodeId, sourceHandle, label } | null` — the
|
|
240
|
+
exit an add-after would use; `null` means the add would land at the centre.
|
|
241
|
+
`canAddAfter(nodeId)` is the boolean form.
|
|
242
|
+
- `renderSelectedNodeToolbar(node)` (a `<Workflow>` prop) renders your content
|
|
243
|
+
beside the selected node's exit side, in screen space (constant size at any
|
|
244
|
+
zoom). Return `null` to show nothing.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
185
248
|
## Controlled vs uncontrolled graph
|
|
186
249
|
|
|
187
250
|
- **Controlled:** pass `graph` and `onGraphChange`. You own the source of truth.
|
|
@@ -335,6 +398,7 @@ surfaces.
|
|
|
335
398
|
| `onArrange` | `(g: Graph) => void` | After one-click auto-arrange. |
|
|
336
399
|
| `onHistoryNavigate` | `(g: Graph) => void` | After undo/redo. |
|
|
337
400
|
| `children` | `ReactNode` | Canvas overlay slot (mount `<NodeInspector>` here). |
|
|
401
|
+
| `renderSelectedNodeToolbar` | `(node) => ReactNode` | Content beside the selected node (e.g. a "+"). |
|
|
338
402
|
| `footer` | `ReactNode` | Slot below the canvas. |
|
|
339
403
|
| `categoryTokens` | `CategoryTokenMap` | Override category → color tokens. |
|
|
340
404
|
| `hostBridge` | `unknown` | Opaque host data for inspectors. |
|
|
@@ -36,6 +36,7 @@ export function Workflow({
|
|
|
36
36
|
onEdgeClick,
|
|
37
37
|
onSelectionChange,
|
|
38
38
|
onIssuesChange,
|
|
39
|
+
renderSelectedNodeToolbar,
|
|
39
40
|
footer,
|
|
40
41
|
children,
|
|
41
42
|
categoryTokens,
|
|
@@ -169,6 +170,7 @@ export function Workflow({
|
|
|
169
170
|
showPalette={showPalette && !readOnly}
|
|
170
171
|
onNodeClick={onNodeClick}
|
|
171
172
|
onEdgeClick={onEdgeClick}
|
|
173
|
+
renderSelectedNodeToolbar={renderSelectedNodeToolbar}
|
|
172
174
|
>
|
|
173
175
|
{children}
|
|
174
176
|
</Canvas>
|
|
@@ -443,6 +443,16 @@ export type WorkflowProps = {
|
|
|
443
443
|
*/
|
|
444
444
|
children?: ReactNode;
|
|
445
445
|
|
|
446
|
+
/**
|
|
447
|
+
* Content rendered beside the selected node, on its exit (right) side — e.g.
|
|
448
|
+
* a "+" that adds a node after it via `useWorkflowActions().addNode`. Called
|
|
449
|
+
* with the selected node whenever one is selected; return `null` to show
|
|
450
|
+
* nothing. The library positions it in screen space (it keeps its size at
|
|
451
|
+
* any zoom) and renders it inside the editor, so the content may call
|
|
452
|
+
* `useWorkflow` / `useWorkflowActions`. Hosts never touch React Flow.
|
|
453
|
+
*/
|
|
454
|
+
renderSelectedNodeToolbar?: (node: GraphNode) => ReactNode;
|
|
455
|
+
|
|
446
456
|
/**
|
|
447
457
|
* Notified whenever the issues array identity changes. Mostly a Storybook
|
|
448
458
|
* convenience now that validation is owned entirely by the backend — prod
|
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
IsValidConnection,
|
|
22
22
|
MiniMap,
|
|
23
23
|
NodeChange,
|
|
24
|
+
NodeToolbar,
|
|
24
25
|
NodeTypes,
|
|
26
|
+
Position as HandleSide,
|
|
25
27
|
ReactFlow,
|
|
26
28
|
ReactFlowProvider,
|
|
27
29
|
useReactFlow,
|
|
@@ -34,7 +36,11 @@ import {
|
|
|
34
36
|
isValidConnection as isValidWorkflowConnection,
|
|
35
37
|
soleEdgeToHandle,
|
|
36
38
|
} from '../../connectionRules';
|
|
37
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
createNodeFromType,
|
|
41
|
+
genId,
|
|
42
|
+
toDefaultsTranslate,
|
|
43
|
+
} from '../../createNode';
|
|
38
44
|
import {
|
|
39
45
|
autoLayout,
|
|
40
46
|
estimateLabelOverhang,
|
|
@@ -47,7 +53,6 @@ import type {
|
|
|
47
53
|
GraphNode,
|
|
48
54
|
Position,
|
|
49
55
|
} from '../../Workflow.types';
|
|
50
|
-
import { resolveDefaultConfig } from '../../Workflow.types';
|
|
51
56
|
import { useWorkflow, useWorkflowTranslate } from '../../WorkflowContext';
|
|
52
57
|
import type { WorkflowReactFlowNode } from '../GenericNode';
|
|
53
58
|
import { GenericNode } from '../GenericNode';
|
|
@@ -107,17 +112,22 @@ function graphToReactFlow(
|
|
|
107
112
|
};
|
|
108
113
|
}
|
|
109
114
|
|
|
115
|
+
type CanvasProps = {
|
|
116
|
+
showPalette?: boolean;
|
|
117
|
+
onNodeClick?: (node: GraphNode) => void;
|
|
118
|
+
onEdgeClick?: (edge: GraphEdge) => void;
|
|
119
|
+
/** See `WorkflowProps.renderSelectedNodeToolbar`. */
|
|
120
|
+
renderSelectedNodeToolbar?: (node: GraphNode) => ReactNode;
|
|
121
|
+
children?: ReactNode;
|
|
122
|
+
};
|
|
123
|
+
|
|
110
124
|
function CanvasInner({
|
|
111
|
-
showPalette,
|
|
125
|
+
showPalette = true,
|
|
112
126
|
onNodeClick: onNodeClickHost,
|
|
113
127
|
onEdgeClick: onEdgeClickHost,
|
|
128
|
+
renderSelectedNodeToolbar,
|
|
114
129
|
children,
|
|
115
|
-
}: {
|
|
116
|
-
showPalette: boolean;
|
|
117
|
-
onNodeClick?: (node: GraphNode) => void;
|
|
118
|
-
onEdgeClick?: (edge: GraphEdge) => void;
|
|
119
|
-
children?: ReactNode;
|
|
120
|
-
}) {
|
|
130
|
+
}: CanvasProps) {
|
|
121
131
|
const {
|
|
122
132
|
graph,
|
|
123
133
|
dispatch,
|
|
@@ -144,13 +154,6 @@ function CanvasInner({
|
|
|
144
154
|
const [isPaletteOpen, setIsPaletteOpen] = useState(true);
|
|
145
155
|
|
|
146
156
|
const translate = useWorkflowTranslate();
|
|
147
|
-
// Tear off only the string lookup so `localizeDefaults` doesn't depend
|
|
148
|
-
// on the React-fragment interpolation path.
|
|
149
|
-
const t = useCallback(
|
|
150
|
-
(key: string, params?: Record<string, string>) =>
|
|
151
|
-
translate(key, params) as string,
|
|
152
|
-
[translate]
|
|
153
|
-
);
|
|
154
157
|
|
|
155
158
|
useWorkflowKeyboard();
|
|
156
159
|
|
|
@@ -606,32 +609,37 @@ function CanvasInner({
|
|
|
606
609
|
if (!kind) return;
|
|
607
610
|
const def = getNodeType(kind);
|
|
608
611
|
if (!def) return;
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
// it. Absent on a generic host, leaving the static defaults in place.
|
|
618
|
-
const bridged = def.hostDefaults?.(hostBridge) as
|
|
619
|
-
| Record<string, unknown>
|
|
620
|
-
| undefined;
|
|
621
|
-
const node = {
|
|
622
|
-
id: genId(kind),
|
|
623
|
-
kind,
|
|
624
|
-
position,
|
|
625
|
-
config: { ...base, ...overlay, ...bridged },
|
|
626
|
-
};
|
|
612
|
+
// Same builder `useWorkflowActions().addNode` uses, so a drop and a
|
|
613
|
+
// programmatic add get the same `{kind}_{N}` id and starting config.
|
|
614
|
+
const node = createNodeFromType(def, {
|
|
615
|
+
position: screenToFlowPosition({ x: e.clientX, y: e.clientY }),
|
|
616
|
+
nodes: graph.nodes,
|
|
617
|
+
translate: toDefaultsTranslate(translate),
|
|
618
|
+
hostBridge,
|
|
619
|
+
});
|
|
627
620
|
|
|
628
621
|
// Dropped nodes land unconnected — authors wire edges explicitly by
|
|
629
622
|
// dragging from an exit point.
|
|
630
623
|
dispatch({ type: 'addNode', node });
|
|
631
624
|
},
|
|
632
|
-
[
|
|
625
|
+
[
|
|
626
|
+
dispatch,
|
|
627
|
+
getNodeType,
|
|
628
|
+
screenToFlowPosition,
|
|
629
|
+
graph.nodes,
|
|
630
|
+
translate,
|
|
631
|
+
hostBridge,
|
|
632
|
+
]
|
|
633
633
|
);
|
|
634
634
|
|
|
635
|
+
const selectedNode = selectedNodeId
|
|
636
|
+
? graph.nodes.find((n) => n.id === selectedNodeId)
|
|
637
|
+
: undefined;
|
|
638
|
+
const selectedNodeToolbar =
|
|
639
|
+
selectedNode && renderSelectedNodeToolbar
|
|
640
|
+
? renderSelectedNodeToolbar(selectedNode)
|
|
641
|
+
: null;
|
|
642
|
+
|
|
635
643
|
// Does the canvas top-left corner actually hold overlay chrome the zoom
|
|
636
644
|
// controls should tuck under? The palette toggle docks there when the palette
|
|
637
645
|
// is collapsed, and the error banner when the (editable) graph has issues. If
|
|
@@ -716,6 +724,18 @@ function CanvasInner({
|
|
|
716
724
|
proOptions={{ hideAttribution: true }}
|
|
717
725
|
>
|
|
718
726
|
<Background />
|
|
727
|
+
{selectedNode && selectedNodeToolbar ? (
|
|
728
|
+
// Positioned in screen space beside the node's exit side, so the
|
|
729
|
+
// host's content keeps its size at any zoom.
|
|
730
|
+
<NodeToolbar
|
|
731
|
+
nodeId={selectedNode.id}
|
|
732
|
+
isVisible
|
|
733
|
+
position={HandleSide.Right}
|
|
734
|
+
offset={4}
|
|
735
|
+
>
|
|
736
|
+
{selectedNodeToolbar}
|
|
737
|
+
</NodeToolbar>
|
|
738
|
+
) : null}
|
|
719
739
|
<CanvasControls
|
|
720
740
|
readOnly={readOnly}
|
|
721
741
|
onAutoArrange={onAutoArrange}
|
|
@@ -748,26 +768,10 @@ function CanvasInner({
|
|
|
748
768
|
);
|
|
749
769
|
}
|
|
750
770
|
|
|
751
|
-
export function Canvas({
|
|
752
|
-
showPalette = true,
|
|
753
|
-
onNodeClick,
|
|
754
|
-
onEdgeClick,
|
|
755
|
-
children,
|
|
756
|
-
}: {
|
|
757
|
-
showPalette?: boolean;
|
|
758
|
-
onNodeClick?: (node: GraphNode) => void;
|
|
759
|
-
onEdgeClick?: (edge: GraphEdge) => void;
|
|
760
|
-
children?: ReactNode;
|
|
761
|
-
}) {
|
|
771
|
+
export function Canvas(props: CanvasProps) {
|
|
762
772
|
return (
|
|
763
773
|
<ReactFlowProvider>
|
|
764
|
-
<CanvasInner
|
|
765
|
-
showPalette={showPalette}
|
|
766
|
-
onNodeClick={onNodeClick}
|
|
767
|
-
onEdgeClick={onEdgeClick}
|
|
768
|
-
>
|
|
769
|
-
{children}
|
|
770
|
-
</CanvasInner>
|
|
774
|
+
<CanvasInner {...props} />
|
|
771
775
|
</ReactFlowProvider>
|
|
772
776
|
);
|
|
773
777
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import { nextNodeId } from './createNode';
|
|
4
|
-
import type { GraphNode } from './Workflow.types';
|
|
3
|
+
import { createNodeFromType, nextNodeId } from './createNode';
|
|
4
|
+
import type { GraphNode, NodeTypeDef } from './Workflow.types';
|
|
5
5
|
|
|
6
6
|
const node = (id: string, kind: string): GraphNode => ({
|
|
7
7
|
id,
|
|
@@ -59,3 +59,41 @@ describe('nextNodeId', () => {
|
|
|
59
59
|
expect(nextNodeId('agent', nodes)).toBe('agent_4');
|
|
60
60
|
});
|
|
61
61
|
});
|
|
62
|
+
|
|
63
|
+
describe('nextNodeId with namespaced kinds', () => {
|
|
64
|
+
it('treats the dot in a kind literally', () => {
|
|
65
|
+
// Unescaped, `math.add` would match `mathXadd_7` and jump to _8.
|
|
66
|
+
const nodes = [
|
|
67
|
+
node('mathXadd_7', 'math.add'),
|
|
68
|
+
node('math.add_2', 'math.add'),
|
|
69
|
+
];
|
|
70
|
+
expect(nextNodeId('math.add', nodes)).toBe('math.add_3');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('createNodeFromType', () => {
|
|
75
|
+
const def = {
|
|
76
|
+
kind: 'agent',
|
|
77
|
+
defaultConfig: () => ({ name: 'Agent', model: 'static', temp: 1 }),
|
|
78
|
+
localizeDefaults: (t: (k: string) => string) => ({ name: t('agent_name') }),
|
|
79
|
+
hostDefaults: (bridge: unknown) => ({
|
|
80
|
+
model: (bridge as { model: string }).model,
|
|
81
|
+
}),
|
|
82
|
+
handles: () => ({ inputs: [], outputs: [] }),
|
|
83
|
+
} as unknown as NodeTypeDef;
|
|
84
|
+
|
|
85
|
+
it('layers defaultConfig, then localizeDefaults, then hostDefaults', () => {
|
|
86
|
+
const created = createNodeFromType(def, {
|
|
87
|
+
position: { x: 1, y: 2 },
|
|
88
|
+
nodes: [node('agent_4', 'agent')],
|
|
89
|
+
translate: (key) => `t:${key}`,
|
|
90
|
+
hostBridge: { model: 'live' },
|
|
91
|
+
});
|
|
92
|
+
expect(created).toEqual({
|
|
93
|
+
id: 'agent_5',
|
|
94
|
+
kind: 'agent',
|
|
95
|
+
position: { x: 1, y: 2 },
|
|
96
|
+
config: { name: 't:agent_name', model: 'live', temp: 1 },
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|