@stonecrop/node-editor 0.31.0 → 0.32.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 (32) hide show
  1. package/dist/assets/index.css +430 -0
  2. package/dist/node-editor.js +821 -644
  3. package/dist/node-editor.js.map +1 -1
  4. package/dist/src/components/EditableEdge.vue.d.ts +11 -0
  5. package/dist/src/components/EditableEdge.vue.d.ts.map +1 -0
  6. package/dist/src/components/EditableNode.vue.d.ts +10 -0
  7. package/dist/src/components/EditableNode.vue.d.ts.map +1 -0
  8. package/dist/src/components/NodeEditor.vue.d.ts +14 -0
  9. package/dist/src/components/NodeEditor.vue.d.ts.map +1 -0
  10. package/dist/src/components/SelfLoopEdge.vue.d.ts +11 -0
  11. package/dist/src/components/SelfLoopEdge.vue.d.ts.map +1 -0
  12. package/dist/src/components/StateEditor.vue.d.ts +69 -0
  13. package/dist/src/components/StateEditor.vue.d.ts.map +1 -0
  14. package/dist/{tsdoc-metadata.json → src/tsdoc-metadata.json} +1 -1
  15. package/package.json +35 -28
  16. package/dist/node-editor.css +0 -1
  17. package/dist/node-editor.d.ts +0 -60
  18. package/dist/node_editor.tsbuildinfo +0 -1
  19. package/dist/src/index.js +0 -16
  20. package/dist/src/types/index.js +0 -0
  21. package/dist/src/utils/autoLayout.js +0 -50
  22. package/dist/src/utils/stateTransforms.js +0 -148
  23. package/src/components/EditableEdge.vue +0 -92
  24. package/src/components/EditableNode.vue +0 -65
  25. package/src/components/NodeEditor.vue +0 -397
  26. package/src/components/SelfLoopEdge.vue +0 -88
  27. package/src/components/StateEditor.vue +0 -46
  28. package/src/index.ts +0 -22
  29. package/src/shims-vue.d.ts +0 -5
  30. package/src/types/index.ts +0 -33
  31. package/src/utils/autoLayout.ts +0 -67
  32. package/src/utils/stateTransforms.ts +0 -164
@@ -1,164 +0,0 @@
1
- import { Position, type Node } from '@vue-flow/core'
2
- import type { ActionDefinition, WorkflowMeta } from '@stonecrop/schema'
3
-
4
- import type { FlowElements, Layout } from '../types'
5
-
6
- export function statesToFlowElements(workflow: WorkflowMeta, layout?: Layout): FlowElements {
7
- const { states = [], actions = {} } = workflow
8
- const edges: FlowElements = []
9
- const nodes: Node[] = []
10
-
11
- for (const [actionKey, actionDef] of Object.entries(actions)) {
12
- // Stateless commands (print/email) have no graph presence; anything graph-owned needs states.
13
- if (actionDef.stateless || !actionDef.allowedStates?.length) continue
14
-
15
- // A self-transition (mutate-in-place `save`) renders as a self-loop on each allowed state:
16
- // `source === target`, tagged `selfloop` so NodeEditor routes it to the arc renderer.
17
- if (actionDef.selfTransition) {
18
- for (const source of actionDef.allowedStates) {
19
- edges.push({
20
- id: `${actionKey}-${source}`,
21
- source,
22
- target: source,
23
- label: actionDef.label ?? actionKey,
24
- data: { actionKey },
25
- animated: true,
26
- type: 'selfloop',
27
- interactionWidth: 40,
28
- })
29
- }
30
- continue
31
- }
32
-
33
- // A cross-state transition needs a target; a malformed action with neither is skipped.
34
- if (!actionDef.nextState) continue
35
- for (const source of actionDef.allowedStates) {
36
- edges.push({
37
- id: `${actionKey}-${source}`,
38
- source,
39
- target: actionDef.nextState,
40
- // The edge paints the human display label; its identity (the action key) rides in
41
- // `data.actionKey`, so relabeling the edge renames the action without re-keying it.
42
- label: actionDef.label ?? actionKey,
43
- data: { actionKey },
44
- animated: true,
45
- type: 'smoothstep',
46
- interactionWidth: 40,
47
- })
48
- }
49
- }
50
-
51
- for (let index = 0; index < states.length; index++) {
52
- const state = states[index]
53
- const node: Node = {
54
- id: state,
55
- label: state,
56
- position: layout?.[state]?.position ?? { x: 200 * index, y: 100 },
57
- targetPosition: layout?.[state]?.targetPosition ?? Position.Left,
58
- sourcePosition: layout?.[state]?.sourcePosition ?? Position.Right,
59
- }
60
- // Every state renders identically — both handles, no start-state styling — so any transition
61
- // (including a returning `reject`/`reopen` into the initial state) is authorable and a
62
- // self-loop can anchor both ends. Marking an entry state is deferred (YAGNI) until needed.
63
- nodes.push(node)
64
- }
65
-
66
- return [...edges, ...nodes]
67
- }
68
-
69
- export function flowElementsToStates(
70
- nextElements: FlowElements,
71
- existingWorkflow?: WorkflowMeta
72
- ): { workflow: WorkflowMeta; layout: Layout } {
73
- const idToLabel: Record<string, string> = {}
74
- const nextLayout: Layout = {}
75
- const stateNames: string[] = []
76
-
77
- for (const el of nextElements) {
78
- if ('source' in el) continue
79
- const label = typeof el.label === 'string' ? el.label : el.id
80
- idToLabel[el.id] = label
81
- stateNames.push(label)
82
- if (el.position) {
83
- nextLayout[label] = {
84
- position: el.position,
85
- ...(el.targetPosition !== undefined && { targetPosition: el.targetPosition }),
86
- ...(el.sourcePosition !== undefined && { sourcePosition: el.sourcePosition }),
87
- }
88
- }
89
- }
90
-
91
- // Group directed edges by their stable action key. A round-tripped edge carries the key in
92
- // `data.actionKey`; a freshly-drawn edge (no data yet) falls back to its label as the key — the
93
- // one moment label and key coincide, when the action is first born. The edge's label is captured
94
- // separately as the display name, so a later relabel renames the action without re-keying it.
95
- const transitionGroups: Record<
96
- string,
97
- { nextState?: string; allowedStates: string[]; label: string; selfLoop: boolean }
98
- > = {}
99
- for (const el of nextElements) {
100
- if (!('source' in el)) continue
101
- const edgeLabel = typeof el.label === 'string' ? el.label : el.id
102
- const actionKey = el.data?.actionKey ?? edgeLabel
103
- const sourceLabel = idToLabel[el.source] || el.source
104
- const targetLabel = idToLabel[el.target] || el.target
105
- // Classify by topology, not by edge `type`: a self-loop (source === target) round-trips to a
106
- // self-transition regardless of how it was authored (drawn node→itself, or re-rendered from a
107
- // `selfTransition` action). A group's kind is set by its first edge; a multi-state self-loop
108
- // (e.g. `save` on Draft AND Pending) accumulates each source into allowedStates.
109
- const isSelf = el.source === el.target
110
- if (!transitionGroups[actionKey]) {
111
- transitionGroups[actionKey] = {
112
- allowedStates: [sourceLabel],
113
- label: edgeLabel,
114
- selfLoop: isSelf,
115
- ...(isSelf ? {} : { nextState: targetLabel }),
116
- }
117
- } else {
118
- transitionGroups[actionKey].allowedStates.push(sourceLabel)
119
- }
120
- }
121
-
122
- const nextActions: Record<string, ActionDefinition> = {}
123
-
124
- // Transitions (and self-transitions) derived from graph edges
125
- for (const [actionKey, group] of Object.entries(transitionGroups)) {
126
- const existing = existingWorkflow?.actions?.[actionKey]
127
- nextActions[actionKey] = {
128
- // Spread existing first so every field the graph does NOT own — clientHandler,
129
- // requiredFields and any field added to ActionDefinition later — survives the
130
- // round-trip. The graph owns topology (allowedStates/nextState/selfTransition) and the
131
- // display label. (Enumerating named fields here previously dropped clientHandler.)
132
- ...existing,
133
- // The edge's label is the display name (decoupled from the key). Fall back to the
134
- // existing label, then the key, if an edge ever arrives label-less.
135
- label: group.label || existing?.label || actionKey,
136
- // The graph owns topology only. A self-transition (self-loop) stays in place: mark
137
- // `selfTransition`, carry NO `nextState`. A cross-state transition carries `nextState`
138
- // and clears any stale self flag (a self-loop redrawn as a normal edge). Explicit
139
- // `undefined` on the unused field is dropped by JSON.stringify, so no format churn.
140
- allowedStates: group.allowedStates,
141
- nextState: group.selfLoop ? undefined : group.nextState,
142
- selfTransition: group.selfLoop ? true : undefined,
143
- }
144
- }
145
-
146
- // Pass through Verbs (stateless: true) and global Workflow actions (no allowedStates) verbatim
147
- for (const [actionKey, actionDef] of Object.entries(existingWorkflow?.actions ?? {})) {
148
- if (actionKey in transitionGroups) continue
149
- if (actionDef.stateless || !actionDef.allowedStates?.length) {
150
- nextActions[actionKey] = actionDef
151
- }
152
- // Workflow action with allowedStates not in graph = user deleted its edges → remove
153
- }
154
-
155
- return {
156
- // Spread existingWorkflow first so every top-level key the graph does NOT own — the
157
- // sibling `triggers` map (field-validation triggers), and any WorkflowMeta key added
158
- // later — survives the round-trip. The graph owns topology only: states + actions,
159
- // overridden below. (Same principle the per-action spread applies above; enumerating
160
- // only states+actions here previously dropped `triggers` on every graph edit.)
161
- workflow: { ...existingWorkflow, states: stateNames, actions: nextActions },
162
- layout: nextLayout,
163
- }
164
- }