flexdesk 0.5.0 → 0.6.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 (62) hide show
  1. package/README.md +621 -1
  2. package/css/base.css +2835 -0
  3. package/css/flexdesk.css +2835 -0
  4. package/css/tokens.css +72 -0
  5. package/dist/canvas.js +390 -0
  6. package/dist/canvas.js.map +7 -0
  7. package/dist/{chunk-24MS7W5Y.js → chunk-6NG7YVBG.js} +2 -470
  8. package/dist/chunk-6NG7YVBG.js.map +7 -0
  9. package/dist/chunk-H4LT43RS.js +475 -0
  10. package/dist/chunk-H4LT43RS.js.map +7 -0
  11. package/dist/chunk-SN4LSOAR.js +532 -0
  12. package/dist/chunk-SN4LSOAR.js.map +7 -0
  13. package/dist/flexdesk.css +2835 -0
  14. package/dist/flow.js +10030 -0
  15. package/dist/flow.js.map +7 -0
  16. package/dist/tiles.js +9 -9
  17. package/dist/tokens.css +72 -0
  18. package/dist/widgets.js +6 -4
  19. package/dist/widgets.js.map +1 -1
  20. package/dist/wm.js +4 -2
  21. package/dist/wm.js.map +1 -1
  22. package/package.json +9 -1
  23. package/src/canvas/canvas_adapter.js +523 -0
  24. package/src/canvas/edge_router.js +392 -0
  25. package/src/canvas/graph/connector_router.js +189 -0
  26. package/src/canvas/graph/history_service.js +170 -0
  27. package/src/canvas/graph/node_platform.js +116 -0
  28. package/src/canvas/index.js +16 -0
  29. package/src/flow/canvas_editor/editor.js +1081 -0
  30. package/src/flow/canvas_editor/index.js +16 -0
  31. package/src/flow/canvas_editor/layout.js +118 -0
  32. package/src/flow/canvas_editor/strings.js +37 -0
  33. package/src/flow/kit/always_before.js +325 -0
  34. package/src/flow/kit/catalogue.js +170 -0
  35. package/src/flow/kit/chip_input.js +461 -0
  36. package/src/flow/kit/dom.js +205 -0
  37. package/src/flow/kit/findings.js +129 -0
  38. package/src/flow/kit/history.js +173 -0
  39. package/src/flow/kit/index.js +38 -0
  40. package/src/flow/kit/keys.js +127 -0
  41. package/src/flow/kit/logic_graph.js +210 -0
  42. package/src/flow/kit/references.js +199 -0
  43. package/src/flow/kit/settings_panel.js +357 -0
  44. package/src/flow/kit/settings_schema.js +175 -0
  45. package/src/flow/kit/step_picker.js +209 -0
  46. package/src/flow/kit/strings.js +102 -0
  47. package/src/flow/kit/value_picker.js +238 -0
  48. package/src/flow/kit/widgets.js +763 -0
  49. package/src/flow/lanes/editor.js +1469 -0
  50. package/src/flow/lanes/index.js +26 -0
  51. package/src/flow/lanes/layout.js +525 -0
  52. package/src/flow/lanes/pipeline.js +327 -0
  53. package/src/flow/lanes/preview.js +109 -0
  54. package/src/flow/lanes/strings.js +98 -0
  55. package/src/flow/outline/editor.js +1511 -0
  56. package/src/flow/outline/index.js +20 -0
  57. package/src/flow/outline/mapping.js +286 -0
  58. package/src/flow/outline/menu.js +154 -0
  59. package/src/flow/outline/operations.js +738 -0
  60. package/src/flow/outline/recognise.js +755 -0
  61. package/src/flow/outline/strings.js +135 -0
  62. package/dist/chunk-24MS7W5Y.js.map +0 -7
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The canvas kernel — GRAPH LAYER: undo and redo over recorded edits, behind a
3
+ * CLOSED action list (36 §4.3).
4
+ *
5
+ * GENERIC. An entry is `{action, …payload}` — typically `before` and `after` —
6
+ * and putting one back is the embedder's `apply(entry, direction)`: the
7
+ * service knows the stack, the batch, the hydration gate and the closed list,
8
+ * never what an entry means. (A whole-snapshot history, for a graph small
9
+ * enough to copy, is `@flexdesk/flow`'s `FlowHistory`; this one records
10
+ * DELTAS, for a diagram too large to snapshot on every move.)
11
+ *
12
+ * ══ THE CLOSED LIST ════════════════════════════════════════════════════
13
+ *
14
+ * The only actions that exist are the ones the embedder declares, and
15
+ * `record()` REJECTS anything else — loudly, with `refusal(action)`'s
16
+ * sentence. An embedder whose Ctrl+Z must never reach something (a change
17
+ * already applied to a server, say) enforces that structurally: the action
18
+ * that would touch it cannot be recorded by accident; it has to be added on
19
+ * purpose, to a list the embedder's own comment says not to add it to.
20
+ *
21
+ * ══ TWO TRAPS THIS IS BUILT AGAINST ════════════════════════════════════
22
+ *
23
+ * 1. A MISSING DEPENDENCY FAILS LOUDLY, AT CONSTRUCTION. An undo engine
24
+ * constructed with the wrong dependency set does nothing and reports
25
+ * nothing — every restore path quietly returns — and the symptom, "undo
26
+ * does nothing", says nothing about the cause. So `actions` and `apply`
27
+ * are required, and `requires` lets a subclass name its own
28
+ * (`{platform, router, plan}`): each must be present, or the constructor
29
+ * throws naming it.
30
+ * 2. THE HYDRATION GATE. Loading a drawing must not itself be undoable —
31
+ * otherwise the first Ctrl+Z after opening "undoes" the opening. Nothing is
32
+ * recorded until `hydrated()`.
33
+ */
34
+
35
+ const DEFAULT_LIMIT = 200;
36
+
37
+ const missing = (name) => new Error(
38
+ `HistoryService needs ${name}. Constructing it without the full dependency set is how an undo `
39
+ + 'engine ends up silently doing nothing.');
40
+
41
+ export class HistoryService {
42
+ /**
43
+ * @param {object} o
44
+ * @param {readonly string[]} o.actions the only actions `record` accepts
45
+ * @param {(entry: object, direction: 'undo'|'redo') => void} o.apply puts one entry back (or forward)
46
+ * @param {(state: {canUndo, canRedo, depth}) => void} [o.onState]
47
+ * @param {number} [o.limit] entries kept; the oldest goes first
48
+ * @param {object} [o.requires] `{name: value}` a subclass cannot work without; each is checked
49
+ * @param {(action: string) => string} [o.refusal] the sentence a refused `record` throws
50
+ */
51
+ constructor({ actions, apply, onState, limit = DEFAULT_LIMIT, requires = null, refusal = null } = {}) {
52
+ // Loudly, at construction — see the header.
53
+ for (const [name, value] of Object.entries(requires || {})) {
54
+ if (!value) throw missing(name);
55
+ }
56
+ if (!Array.isArray(actions) || !actions.length) throw missing('actions (its closed list)');
57
+ if (typeof apply !== 'function') throw missing('apply(entry, direction)');
58
+ this.actions = Object.freeze([...actions]);
59
+ this._applyEntry = apply;
60
+ this.onState = onState;
61
+ this.limit = limit;
62
+ this.refusal = typeof refusal === 'function' ? refusal : null;
63
+ this.undoStack = [];
64
+ this.redoStack = [];
65
+ // The hydration gate. Recording starts only once a drawing is loaded.
66
+ this.recording = false;
67
+ this.batch = null;
68
+ }
69
+
70
+ /** The drawing has been loaded: the stacks start empty, and recording starts. */
71
+ hydrated() {
72
+ this.undoStack = [];
73
+ this.redoStack = [];
74
+ this.recording = true;
75
+ this._publish();
76
+ }
77
+
78
+ /** Group what is recorded until `endBatch` into one entry. */
79
+ startBatch(label) {
80
+ if (!this.recording) return;
81
+ this.batch = { action: 'batch', label, entries: [] };
82
+ }
83
+
84
+ endBatch() {
85
+ if (!this.batch) return;
86
+ const batch = this.batch;
87
+ this.batch = null;
88
+ if (batch.entries.length) this._push(batch);
89
+ }
90
+
91
+ record(action, payload) {
92
+ if (!this.recording) return;
93
+ if (!this.actions.includes(action)) {
94
+ throw new Error(this.refusal
95
+ ? this.refusal(action)
96
+ : `${action} is not an undoable action here; the ones that are: ${this.actions.join(', ')}.`);
97
+ }
98
+ const entry = { action, ...payload };
99
+ if (this.batch) { this.batch.entries.push(entry); return; }
100
+ this._push(entry);
101
+ }
102
+
103
+ undo() {
104
+ const entry = this.undoStack.pop();
105
+ if (!entry) return false;
106
+ this._apply(entry, 'undo');
107
+ this.redoStack.push(entry);
108
+ this._publish();
109
+ return true;
110
+ }
111
+
112
+ redo() {
113
+ const entry = this.redoStack.pop();
114
+ if (!entry) return false;
115
+ this._apply(entry, 'redo');
116
+ this.undoStack.push(entry);
117
+ this._publish();
118
+ return true;
119
+ }
120
+
121
+ reset() {
122
+ this.undoStack = [];
123
+ this.redoStack = [];
124
+ this._publish();
125
+ }
126
+
127
+ /**
128
+ * Take over the undo and redo stacks of a torn-down view whose state this
129
+ * one has just taken on, entry for entry and in the same order. Nothing new
130
+ * becomes undoable: every entry was recorded through a `record` whose action
131
+ * list is closed, so a stack can only carry what that list allows. Call it
132
+ * after `hydrated()`.
133
+ */
134
+ adopt({ undo = [], redo = [] } = {}) {
135
+ if (!this.recording) return;
136
+ this.undoStack = [...undo].slice(-this.limit);
137
+ this.redoStack = [...redo];
138
+ this._publish();
139
+ }
140
+
141
+ get canUndo() { return this.undoStack.length > 0; }
142
+ get canRedo() { return this.redoStack.length > 0; }
143
+
144
+ _push(entry) {
145
+ this.undoStack.push(entry);
146
+ // A new action invalidates the redo branch. Keeping it would let a user
147
+ // redo their way into a state that never existed.
148
+ this.redoStack = [];
149
+ if (this.undoStack.length > this.limit) this.undoStack.shift();
150
+ this._publish();
151
+ }
152
+
153
+ _publish() {
154
+ this.onState?.({
155
+ canUndo: this.canUndo,
156
+ canRedo: this.canRedo,
157
+ depth: this.undoStack.length,
158
+ });
159
+ }
160
+
161
+ _apply(entry, direction) {
162
+ if (entry.action === 'batch') {
163
+ const entries = direction === 'undo'
164
+ ? [...entry.entries].reverse() : entry.entries;
165
+ for (const child of entries) this._apply(child, direction);
166
+ return;
167
+ }
168
+ this._applyEntry(entry, direction);
169
+ }
170
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The canvas kernel — GRAPH LAYER: the node and port model (36 §4.3).
3
+ *
4
+ * GENERIC. It holds nodes — each `{id, position, size, collapsed, ports}` plus
5
+ * whatever else the embedder hangs on it — and answers which ports a node
6
+ * offers. What a node IS (a table, a step, a form field) and which ports a
7
+ * collapsed one keeps are the embedder's: a subclass builds the nodes and
8
+ * calls `hydrate`, and the `collapsedPorts(node)` option decides what a folded
9
+ * node shows.
10
+ *
11
+ * The genuinely reusable idea is the shape of a port descriptor — `{id, role,
12
+ * direction, type, accepts, provides, allowMultiple, maxConnections, disabled,
13
+ * disabledReason, meta}` — which is what lets one legality guard
14
+ * (`ConnectorRouter`) serve a schema diagram, a flow graph and a form
15
+ * designer. Nothing here requires any of those keys but `id`; the router reads
16
+ * the ones it knows.
17
+ *
18
+ * DOM-free on purpose. It holds the graph; the embedder renders it. That split
19
+ * is what makes the legality guard and the history service testable under
20
+ * plain node.
21
+ *
22
+ * Events (`on(name, handler)` → an unsubscribe):
23
+ * `hydrated` `{count}` after `hydrate`
24
+ * `moved` `{id, before, after}` after a `setPosition` that is not `silent`
25
+ * `collapsed` `{id, before, after}` after a `setCollapsed` that is not `silent`
26
+ */
27
+
28
+ export class NodePlatform {
29
+ /**
30
+ * @param {object} [options]
31
+ * @param {(node: object) => object[]} [options.collapsedPorts] the ports a COLLAPSED node
32
+ * still offers; by default none
33
+ */
34
+ constructor({ collapsedPorts = null } = {}) {
35
+ /** @type {Map<string, object>} node id -> snapshot */
36
+ this.nodes = new Map();
37
+ this.listeners = new Map();
38
+ this.collapsedPorts = typeof collapsedPorts === 'function' ? collapsedPorts : null;
39
+ }
40
+
41
+ on(event, handler) {
42
+ if (!this.listeners.has(event)) this.listeners.set(event, new Set());
43
+ this.listeners.get(event).add(handler);
44
+ return () => this.listeners.get(event).delete(handler);
45
+ }
46
+
47
+ emit(event, payload) {
48
+ for (const handler of this.listeners.get(event) || []) handler(payload);
49
+ }
50
+
51
+ /**
52
+ * Replace the whole graph. Hydration, not an edit — see `HistoryService`.
53
+ *
54
+ * Each node is `{id, position?, size?, collapsed?, ports?, …}`: the position
55
+ * is copied (a missing one is (0, 0)), `size` is `{w, h}` or null, `ports`
56
+ * an array (none when absent), and every other key is kept as it is.
57
+ */
58
+ hydrate(nodes) {
59
+ this.nodes.clear();
60
+ for (const raw of Array.isArray(nodes) ? nodes : []) {
61
+ if (!raw || raw.id === undefined || raw.id === null) continue;
62
+ const id = raw.id;
63
+ this.nodes.set(id, {
64
+ ...raw,
65
+ id,
66
+ // `size` and `collapsed` are in the snapshot, not derived: at
67
+ // hundreds of nodes a node showing forty ports is unreadable, and
68
+ // a collapsed node's ports fold away.
69
+ position: raw.position ? { x: raw.position.x, y: raw.position.y } : { x: 0, y: 0 },
70
+ size: raw.size ? { w: raw.size.w, h: raw.size.h } : null,
71
+ collapsed: Boolean(raw.collapsed),
72
+ ports: Array.isArray(raw.ports) ? raw.ports : [],
73
+ });
74
+ }
75
+ this.emit('hydrated', { count: this.nodes.size });
76
+ }
77
+
78
+ get(id) { return this.nodes.get(id); }
79
+ all() { return [...this.nodes.values()]; }
80
+
81
+ port(nodeId, portId) {
82
+ return this.nodes.get(nodeId)?.ports.find((p) => p.id === portId) || null;
83
+ }
84
+
85
+ setPosition(id, position, { silent = false } = {}) {
86
+ const node = this.nodes.get(id);
87
+ if (!node) return null;
88
+ const before = { ...node.position };
89
+ node.position = { ...position };
90
+ if (!silent) this.emit('moved', { id, before, after: { ...position } });
91
+ return before;
92
+ }
93
+
94
+ setCollapsed(id, collapsed, { silent = false } = {}) {
95
+ const node = this.nodes.get(id);
96
+ if (!node) return null;
97
+ const before = node.collapsed;
98
+ node.collapsed = Boolean(collapsed);
99
+ if (!silent) this.emit('collapsed', { id, before, after: node.collapsed });
100
+ return before;
101
+ }
102
+
103
+ /**
104
+ * Ports that are visible right now.
105
+ *
106
+ * A collapsed node offers only what `collapsedPorts` says it keeps (by
107
+ * default nothing). Answering from one place is what stops a renderer and
108
+ * the legality guard disagreeing about what can be dropped on.
109
+ */
110
+ visiblePorts(id) {
111
+ const node = this.nodes.get(id);
112
+ if (!node) return [];
113
+ if (node.collapsed) return this.collapsedPorts ? this.collapsedPorts(node) : [];
114
+ return node.ports;
115
+ }
116
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * What `@flexdesk/canvas` exports — the canvas kernel (36 §4).
3
+ *
4
+ * OWNED BY the canvas agent (B, 36 §2): the base layer (`CanvasAdapter`) and
5
+ * the edge router, moved with their behaviour intact, and the GENERIC graph
6
+ * layer (`NodePlatform`, `ConnectorRouter`, `HistoryService`) that a
7
+ * consumer's own rules subclass. Nothing here knows what a node is.
8
+ */
9
+
10
+ export { CanvasAdapter } from './canvas_adapter.js';
11
+ export {
12
+ OBSTACLE_ROUTING_NODE_LIMIT, routeEdge, footAnchor, crowsFoot, cardinality, selfLoop, returnEdge, arrange,
13
+ } from './edge_router.js';
14
+ export { NodePlatform } from './graph/node_platform.js';
15
+ export { ConnectorRouter, CONNECTOR_STRINGS } from './graph/connector_router.js';
16
+ export { HistoryService } from './graph/history_service.js';