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,238 @@
1
+ /**
2
+ * INSERT A VALUE — the picker a field's "{ }" opens (36 §3.7).
3
+ *
4
+ * openValuePicker({anchor, groups, note, onPick, strings});
5
+ * // groups: [{id, label, sub, items, layout?: 'grid'|'chips', empty?}]
6
+ * // item: {label, type, ref, children?} `ref` is the TEXT inserted
7
+ *
8
+ * The kit DRAWS; the groups are the consumer's — which values each step
9
+ * offers, in its own words — computed over the steps that always run before
10
+ * the field's step (`alwaysBefore`), so a value the run may not have is not
11
+ * offered. `note` says so under the list in the consumer's words.
12
+ *
13
+ * An item with `children` opens one level down ("body ›"): its first option
14
+ * there inserts the whole value, and Back (or Escape) climbs again. A group
15
+ * drawn as a `grid` lists its items two to a row with their type beside them
16
+ * (a loop's row, column by column); any other group draws chips. A group with
17
+ * no items shows its `empty` sentence instead, so "none yet, and why" is on
18
+ * the screen. The search box filters every level at once.
19
+ *
20
+ * Returns a promise of the picked item (or null), carrying `close()`; `onPick`
21
+ * is called with the item as well. The line under the list says what the
22
+ * active option inserts — "shown as a chip, kept as text".
23
+ */
24
+
25
+ import { el, icon, nextOption, openPopover, uid } from './dom.js';
26
+ import { createStrings, say } from './strings.js';
27
+
28
+ const words = (s) => String(s ?? '').toLowerCase();
29
+
30
+ export function openValuePicker({ anchor, groups = [], note = null, onPick = null, strings = null,
31
+ title = null } = {}) {
32
+ const S = createStrings(strings);
33
+ let settle;
34
+ const done = new Promise((resolve) => { settle = resolve; });
35
+ let result = null;
36
+ const trail = []; // the items drilled into
37
+
38
+ const root = el('div', 'twm-flow-values');
39
+ const headingId = uid('values-title');
40
+ const heading = el('span', 'twm-flow-values__title', title ?? say(S, 'insertValue'));
41
+ heading.id = headingId;
42
+ const head = el('div', 'twm-flow-values__head');
43
+ head.appendChild(heading);
44
+ const listId = uid('values-list');
45
+ const searchRow = el('div', 'twm-flow-values__search');
46
+ searchRow.appendChild(icon('search', 'twm-flow-values__search-icon'));
47
+ const search = el('input', 'twm-flow-values__input');
48
+ search.type = 'search';
49
+ search.placeholder = say(S, 'searchValues');
50
+ search.setAttribute('aria-label', say(S, 'searchValues'));
51
+ search.setAttribute('role', 'combobox');
52
+ search.setAttribute('aria-controls', listId);
53
+ search.setAttribute('aria-expanded', 'true');
54
+ search.setAttribute('aria-autocomplete', 'list');
55
+ search.autocomplete = 'off';
56
+ searchRow.appendChild(search);
57
+ head.appendChild(searchRow);
58
+ root.appendChild(head);
59
+
60
+ const crumb = el('div', 'twm-flow-values__crumb');
61
+ crumb.hidden = true;
62
+ root.appendChild(crumb);
63
+ const list = el('div', 'twm-flow-values__list');
64
+ list.id = listId;
65
+ list.setAttribute('role', 'listbox');
66
+ list.setAttribute('aria-labelledby', headingId);
67
+ root.appendChild(list);
68
+ const foot = el('div', 'twm-flow-values__foot');
69
+ const inserts = el('span', 'twm-flow-values__inserts');
70
+ inserts.setAttribute('aria-live', 'polite');
71
+ if (note) foot.appendChild(el('span', 'twm-flow-values__note', note));
72
+ foot.appendChild(inserts);
73
+ root.appendChild(foot);
74
+
75
+ let active = null;
76
+ let shown = []; // [{opt, item, whole?}]
77
+
78
+ function activate(opt) {
79
+ active?.setAttribute('aria-selected', 'false');
80
+ active?.classList.remove('twm-flow-values__option--active');
81
+ active = opt;
82
+ const hit = shown.find((s) => s.opt === opt);
83
+ if (opt) {
84
+ opt.setAttribute('aria-selected', 'true');
85
+ opt.classList.add('twm-flow-values__option--active');
86
+ search.setAttribute('aria-activedescendant', opt.id);
87
+ opt.scrollIntoView?.({ block: 'nearest' });
88
+ } else {
89
+ search.removeAttribute('aria-activedescendant');
90
+ }
91
+ inserts.textContent = hit && hit.item?.ref !== undefined && !(hit.item.children?.length && !hit.whole && !search.value)
92
+ ? say(S, 'insertsLine', hit.item.ref) : '';
93
+ }
94
+
95
+ function option(item, { grid = false, whole = false, label = null } = {}) {
96
+ const opens = !whole && Array.isArray(item.children) && item.children.length > 0;
97
+ const opt = el('div', `twm-flow-values__option${grid ? ' twm-flow-values__option--row' : ' twm-flow-values__option--chip'}`);
98
+ opt.id = uid('values-option');
99
+ opt.setAttribute('role', 'option');
100
+ opt.setAttribute('aria-selected', 'false');
101
+ const text = label ?? (whole ? say(S, 'wholeValue', item.label) : item.label);
102
+ opt.appendChild(el('span', 'twm-flow-values__label', opens && !search.value ? `${text} ›` : text));
103
+ if (grid && item.type) opt.appendChild(el('span', 'twm-flow-values__type', item.type));
104
+ if (opens) opt.setAttribute('aria-haspopup', 'listbox');
105
+ opt.addEventListener('mousedown', (ev) => ev.preventDefault());
106
+ opt.addEventListener('click', () => take(item, { whole }));
107
+ opt.addEventListener('mousemove', () => { if (active !== opt) activate(opt); });
108
+ shown.push({ opt, item, whole });
109
+ return opt;
110
+ }
111
+
112
+ function matches(item, terms) {
113
+ const hay = [item.label, item.type, item.ref].map(words).join(' ');
114
+ return terms.every((t) => hay.includes(t));
115
+ }
116
+
117
+ /** Every descendant of `items`, with its labels joined by ›, for a search. */
118
+ function flatten(items, prefix = '') {
119
+ const out = [];
120
+ for (const it of items || []) {
121
+ const label = prefix ? `${prefix} › ${it.label}` : it.label;
122
+ out.push({ item: it, label });
123
+ if (Array.isArray(it.children)) out.push(...flatten(it.children, label));
124
+ }
125
+ return out;
126
+ }
127
+
128
+ function paint() {
129
+ list.replaceChildren();
130
+ shown = [];
131
+ active = null;
132
+ const terms = words(search.value).split(/\s+/).filter(Boolean);
133
+ const level = trail[trail.length - 1] ?? null;
134
+ crumb.hidden = !level;
135
+ crumb.replaceChildren();
136
+ if (level) {
137
+ const back = el('button', 'twm-flow-values__back');
138
+ back.type = 'button';
139
+ back.appendChild(icon('chevron_left'));
140
+ back.appendChild(el('span', '', say(S, 'back')));
141
+ back.addEventListener('mousedown', (ev) => ev.preventDefault());
142
+ back.addEventListener('click', climb);
143
+ crumb.append(back, el('span', 'twm-flow-values__path', trail.map((t) => t.label).join(' › ')));
144
+ const box = el('div', 'twm-flow-values__chips');
145
+ if (!terms.length || matches(level, terms)) box.appendChild(option(level, { whole: true }));
146
+ for (const it of level.children || []) {
147
+ if (terms.length && !matches(it, terms)) continue;
148
+ box.appendChild(option(it));
149
+ }
150
+ list.appendChild(box);
151
+ } else {
152
+ for (const g of groups) {
153
+ const group = el('div', 'twm-flow-values__group');
154
+ group.setAttribute('role', 'group');
155
+ const gid = uid('values-group');
156
+ const label = el('div', 'twm-flow-values__group-label');
157
+ label.id = gid;
158
+ label.appendChild(el('span', 'twm-flow-values__group-name', g.label ?? ''));
159
+ if (g.sub) label.appendChild(el('span', 'twm-flow-values__group-sub', g.sub));
160
+ group.setAttribute('aria-labelledby', gid);
161
+ const items = Array.isArray(g.items) ? g.items : [];
162
+ const grid = g.layout === 'grid';
163
+ const box = el('div', grid ? 'twm-flow-values__grid' : 'twm-flow-values__chips');
164
+ if (terms.length) {
165
+ for (const { item, label: path } of flatten(items)) {
166
+ if (matches(item, terms) || words(path).includes(terms.join(' '))) {
167
+ box.appendChild(option(item, { grid, label: path }));
168
+ }
169
+ }
170
+ if (!box.childNodes.length) continue;
171
+ } else if (!items.length) {
172
+ if (g.empty) label.appendChild(el('span', 'twm-flow-values__group-empty', `— ${g.empty}`));
173
+ group.appendChild(label);
174
+ list.appendChild(group);
175
+ continue;
176
+ } else {
177
+ for (const it of items) box.appendChild(option(it, { grid }));
178
+ }
179
+ group.append(label, box);
180
+ list.appendChild(group);
181
+ }
182
+ }
183
+ if (!shown.length) list.appendChild(el('p', 'twm-flow-values__empty', terms.length
184
+ ? say(S, 'nothingMatches', search.value.trim()) : say(S, 'noValues')));
185
+ activate(nextOption(list, null, 1));
186
+ }
187
+
188
+ function take(item, { whole = false } = {}) {
189
+ const opens = !whole && Array.isArray(item.children) && item.children.length > 0;
190
+ if (opens && !search.value) {
191
+ trail.push(item);
192
+ paint();
193
+ return;
194
+ }
195
+ result = item;
196
+ onPick?.(item);
197
+ handle.close('chosen');
198
+ }
199
+
200
+ function climb() {
201
+ trail.pop();
202
+ paint();
203
+ search.focus({ preventScroll: true });
204
+ }
205
+
206
+ search.addEventListener('input', paint);
207
+ search.addEventListener('keydown', (ev) => {
208
+ if (ev.isComposing) return;
209
+ if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') {
210
+ ev.preventDefault();
211
+ activate(nextOption(list, active, ev.key === 'ArrowDown' ? 1 : -1) || active);
212
+ } else if (ev.key === 'Enter') {
213
+ ev.preventDefault();
214
+ const hit = shown.find((s) => s.opt === active);
215
+ if (hit) take(hit.item, { whole: hit.whole });
216
+ } else if (ev.key === 'ArrowRight' && !search.value) {
217
+ const hit = shown.find((s) => s.opt === active);
218
+ if (hit && !hit.whole && hit.item.children?.length) { ev.preventDefault(); take(hit.item); }
219
+ } else if (ev.key === 'Escape' && trail.length) {
220
+ ev.preventDefault();
221
+ ev.stopPropagation();
222
+ climb();
223
+ }
224
+ });
225
+ root.addEventListener('focusout', (ev) => {
226
+ const to = ev.relatedTarget;
227
+ if (to && !root.contains(to) && to !== anchor) handle.close('blur');
228
+ });
229
+
230
+ const handle = openPopover({
231
+ anchor, content: root, label: title ?? say(S, 'insertValue'), className: 'twm-flow-popover--values',
232
+ focus: search, onClose: () => settle(result),
233
+ });
234
+ if (handle.toggled) settle(null);
235
+ else paint();
236
+ done.close = () => handle.close('closed');
237
+ return done;
238
+ }