flexdesk 0.17.18 → 0.18.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 (118) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +1 -0
  3. package/css/flexdesk.css +7544 -5916
  4. package/css/reset.css +1 -1
  5. package/css/tokens.css +23 -1
  6. package/dist/charts.js +10 -6
  7. package/dist/charts.js.map +1 -1
  8. package/dist/chunk-5W4EEKSK.js +778 -0
  9. package/dist/chunk-5W4EEKSK.js.map +7 -0
  10. package/dist/chunk-DNJ3N4T4.js +82 -0
  11. package/dist/chunk-DNJ3N4T4.js.map +7 -0
  12. package/dist/{chunk-REJ5HLIM.js → chunk-HYJYYEPK.js} +2 -2
  13. package/dist/{chunk-7U4IB65H.js → chunk-LLBJ2SJH.js} +5 -3
  14. package/dist/{chunk-7U4IB65H.js.map → chunk-LLBJ2SJH.js.map} +1 -1
  15. package/dist/{chunk-7OL6F2AM.js → chunk-P74F6I6P.js} +6 -4
  16. package/dist/{chunk-7OL6F2AM.js.map → chunk-P74F6I6P.js.map} +1 -1
  17. package/dist/chunk-PAI2ZUWV.js +260 -0
  18. package/dist/chunk-PAI2ZUWV.js.map +7 -0
  19. package/dist/chunk-Q7BALEPX.js +527 -0
  20. package/dist/chunk-Q7BALEPX.js.map +7 -0
  21. package/dist/chunk-QXAOTGCC.js +40 -0
  22. package/dist/chunk-QXAOTGCC.js.map +7 -0
  23. package/dist/{chunk-XJNADRGR.js → chunk-RKVYIXDC.js} +664 -91
  24. package/dist/chunk-RKVYIXDC.js.map +7 -0
  25. package/dist/{chunk-2Z2ENHR3.js → chunk-U5GKJUOC.js} +3 -3
  26. package/dist/{chunk-2Z2ENHR3.js.map → chunk-U5GKJUOC.js.map} +1 -1
  27. package/dist/chunk-UKP22RUG.js +219 -0
  28. package/dist/chunk-UKP22RUG.js.map +7 -0
  29. package/dist/chunk-VLOAI3HS.js +43 -0
  30. package/dist/chunk-VLOAI3HS.js.map +7 -0
  31. package/dist/{chunk-WUFLGETN.js → chunk-WVFGV5FT.js} +2 -39
  32. package/dist/{chunk-WUFLGETN.js.map → chunk-WVFGV5FT.js.map} +3 -3
  33. package/dist/{chunk-PGZ4EHSE.js → chunk-ZL3VKINP.js} +41 -390
  34. package/dist/chunk-ZL3VKINP.js.map +7 -0
  35. package/dist/editor.js +3 -2
  36. package/dist/editor.js.map +1 -1
  37. package/dist/flexdesk.css +7544 -5916
  38. package/dist/flow.js +29 -97
  39. package/dist/flow.js.map +3 -3
  40. package/dist/git.js +1356 -0
  41. package/dist/git.js.map +7 -0
  42. package/dist/reset.css +1 -1
  43. package/dist/tiles.js +20 -15
  44. package/dist/tiles.js.map +2 -2
  45. package/dist/tokens.css +23 -1
  46. package/dist/widgets.js +3967 -1188
  47. package/dist/widgets.js.map +4 -4
  48. package/dist/wm.js +120 -32
  49. package/dist/wm.js.map +2 -2
  50. package/docs/README.md +9 -4
  51. package/docs/best-practices.md +2 -2
  52. package/docs/components/chat-shell.md +198 -0
  53. package/docs/components/conversation.md +10 -0
  54. package/docs/components/filter-builder.md +246 -0
  55. package/docs/components/forms-and-dialogs.md +22 -0
  56. package/docs/components/git.md +355 -0
  57. package/docs/components/input-fields.md +167 -2
  58. package/docs/components/keymap.md +8 -0
  59. package/docs/components/menus.md +82 -2
  60. package/docs/components/panels-and-desktops.md +16 -5
  61. package/docs/components/shell.md +104 -1
  62. package/docs/components/workspace-picker.md +149 -0
  63. package/docs/concepts/building-and-packaging.md +2 -2
  64. package/docs/concepts/packages-and-entry-points.md +14 -13
  65. package/docs/concepts/state-guard.md +3 -3
  66. package/docs/quickstart.md +1 -1
  67. package/git.js +13 -0
  68. package/package.json +6 -1
  69. package/src/flow/kit/dom.js +28 -119
  70. package/src/flow/kit/step_picker.js +1 -1
  71. package/src/flow/kit/value_picker.js +1 -1
  72. package/src/flow/outline/menu.js +1 -1
  73. package/src/git/diff_viewer.js +212 -0
  74. package/src/git/diff_window.js +51 -0
  75. package/src/git/git_graph.js +401 -0
  76. package/src/git/git_ops.js +61 -0
  77. package/src/git/git_panel.js +755 -0
  78. package/src/git/index.js +9 -0
  79. package/src/git/line_diff.js +202 -0
  80. package/src/tiles/tile_base.js +5 -4
  81. package/src/tiling/desktops.js +10 -8
  82. package/src/tiling/keymap.js +6 -5
  83. package/src/tiling/kind_taxonomy.js +15 -2
  84. package/src/tiling/shell.js +124 -17
  85. package/src/tiling/tile_renderer.js +2 -3
  86. package/src/ui/components/action_dropdown.js +4 -78
  87. package/src/ui/components/anchored_layer.js +255 -0
  88. package/src/ui/components/chat_shell.js +920 -0
  89. package/src/ui/components/chat_state_machine.js +223 -0
  90. package/src/ui/components/choice_controls.js +45 -2
  91. package/src/ui/components/color_field.js +3 -2
  92. package/src/ui/components/color_list_field.js +3 -2
  93. package/src/ui/components/combobox.js +422 -0
  94. package/src/ui/components/conversation_view.js +17 -2
  95. package/src/ui/components/data_table.js +2 -2
  96. package/src/ui/components/filter_builder.js +766 -0
  97. package/src/ui/components/filter_model.js +614 -0
  98. package/src/ui/components/managed_window.js +24 -47
  99. package/src/ui/components/modal.js +79 -109
  100. package/src/ui/components/option_list.js +160 -0
  101. package/src/ui/components/popover.js +94 -0
  102. package/src/ui/components/recent_items.js +169 -0
  103. package/src/ui/components/select.js +322 -0
  104. package/src/ui/components/tag_input.js +77 -8
  105. package/src/ui/components/workspace_picker.js +1155 -0
  106. package/src/ui/utils/fire_event.js +17 -0
  107. package/src/ui/utils/focusable.js +25 -0
  108. package/src/ui/utils/modal_host.js +27 -0
  109. package/widgets.js +12 -2
  110. package/css/base.css +0 -10547
  111. package/css/overrides.css +0 -2596
  112. package/dist/chunk-7JI7HHSY.js +0 -107
  113. package/dist/chunk-7JI7HHSY.js.map +0 -7
  114. package/dist/chunk-MIW7D6WC.js +0 -526
  115. package/dist/chunk-MIW7D6WC.js.map +0 -7
  116. package/dist/chunk-PGZ4EHSE.js.map +0 -7
  117. package/dist/chunk-XJNADRGR.js.map +0 -7
  118. /package/dist/{chunk-REJ5HLIM.js.map → chunk-HYJYYEPK.js.map} +0 -0
@@ -0,0 +1,778 @@
1
+ import {
2
+ openConfirm,
3
+ openForm,
4
+ openModal
5
+ } from "./chunk-RKVYIXDC.js";
6
+ import {
7
+ ManagedWindow
8
+ } from "./chunk-ZL3VKINP.js";
9
+
10
+ // src/ui/components/progress.js
11
+ var PROGRESS_STATES = Object.freeze(["running", "done", "error", "cancelled"]);
12
+ var PROGRESS_ITEM_STATES = Object.freeze(["pending", "running", "done", "failed"]);
13
+ var STEP_STATES = Object.freeze(["pending", "active", "done", "error"]);
14
+ var clamp01 = (x) => x < 0 ? 0 : x > 1 ? 1 : x;
15
+ var isNum = (x) => typeof x === "number" && Number.isFinite(x);
16
+ function formatElapsed(ms) {
17
+ const s = Math.max(0, Math.floor((ms || 0) / 1e3));
18
+ const h = Math.floor(s / 3600);
19
+ const m = Math.floor(s % 3600 / 60);
20
+ const ss = String(s % 60).padStart(2, "0");
21
+ return h ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
22
+ }
23
+ function el(tag, className, text) {
24
+ const n = document.createElement(tag);
25
+ if (className) n.className = className;
26
+ if (text !== void 0) n.textContent = text;
27
+ return n;
28
+ }
29
+ function setText(node, text) {
30
+ const t = text == null ? "" : String(text);
31
+ if (node.textContent !== t) node.textContent = t;
32
+ }
33
+ function setWidth(node, fraction) {
34
+ const w = `${(clamp01(fraction) * 100).toFixed(2).replace(/\.?0+$/, "")}%`;
35
+ if (node.style.width !== w) node.style.width = w;
36
+ }
37
+ var ProgressBar = class {
38
+ /**
39
+ * @param {HTMLElement|null} host appended to, if given
40
+ * @param {ProgressBarOptions} [options]
41
+ */
42
+ constructor(host, options = {}) {
43
+ const {
44
+ value = 0,
45
+ max = null,
46
+ indeterminate = false,
47
+ label = "",
48
+ showPercent = true,
49
+ showCount = null,
50
+ showElapsed = false,
51
+ onCancel = null,
52
+ cancelLabel = "Cancel",
53
+ now = Date.now,
54
+ className = ""
55
+ } = options;
56
+ this._value = isNum(value) ? value : 0;
57
+ this._max = isNum(max) && max > 0 ? max : null;
58
+ this._indeterminate = !!indeterminate;
59
+ this._label = label || "";
60
+ this._showPercent = showPercent !== false;
61
+ this._showCount = showCount;
62
+ this._showElapsed = !!showElapsed;
63
+ this._state = "running";
64
+ this._now = typeof now === "function" ? now : Date.now;
65
+ this._started = this._now();
66
+ this._stopped = null;
67
+ this._timer = null;
68
+ this._onCancel = typeof onCancel === "function" ? onCancel : null;
69
+ const root = el("div", `twm-progress${className ? ` ${className}` : ""}`);
70
+ const head = el("div", "twm-progress__head");
71
+ this._labelEl = el("span", "twm-progress__label");
72
+ this._readoutEl = el("span", "twm-progress__readout");
73
+ head.append(this._labelEl, this._readoutEl);
74
+ if (this._showElapsed) {
75
+ this._elapsedEl = el("span", "twm-progress__elapsed");
76
+ head.appendChild(this._elapsedEl);
77
+ this._startTimer();
78
+ } else {
79
+ this._elapsedEl = null;
80
+ }
81
+ if (this._onCancel) {
82
+ const btn = el("button", "twm-progress__cancel", cancelLabel);
83
+ btn.type = "button";
84
+ btn.addEventListener("click", () => {
85
+ if (btn.disabled || this._state !== "running") return;
86
+ btn.disabled = true;
87
+ this._onCancel();
88
+ });
89
+ this._cancelBtn = btn;
90
+ head.appendChild(btn);
91
+ } else {
92
+ this._cancelBtn = null;
93
+ }
94
+ this._track = el("div", "twm-progress__track");
95
+ this._track.setAttribute("role", "progressbar");
96
+ this._fill = el("div", "twm-progress__fill");
97
+ this._track.appendChild(this._fill);
98
+ root.append(head, this._track);
99
+ this.element = root;
100
+ this._draw();
101
+ if (host) host.appendChild(root);
102
+ }
103
+ /** The bar's fraction 0..1, or null while indeterminate. */
104
+ get fraction() {
105
+ if (this._indeterminate) return null;
106
+ return clamp01(this._max ? this._value / this._max : this._value);
107
+ }
108
+ /** `running`, `done`, `error` or `cancelled`. */
109
+ get state() {
110
+ return this._state;
111
+ }
112
+ /** Milliseconds since construction, frozen at the final state. */
113
+ get elapsed() {
114
+ return (this._stopped ?? this._now()) - this._started;
115
+ }
116
+ /**
117
+ * Change the bar in place. A number is the new value (a count with `max`,
118
+ * else a fraction); an object may carry `value`, `max`, `label` and
119
+ * `indeterminate`. Ignored once the bar is in a final state.
120
+ */
121
+ update(next) {
122
+ if (this._state !== "running") return this;
123
+ const o = isNum(next) ? { value: next } : next || {};
124
+ if ("max" in o) this._max = isNum(o.max) && o.max > 0 ? o.max : null;
125
+ if (isNum(o.value)) {
126
+ this._value = o.value;
127
+ if (!("indeterminate" in o)) this._indeterminate = false;
128
+ }
129
+ if ("indeterminate" in o) this._indeterminate = !!o.indeterminate;
130
+ if ("label" in o) this._label = o.label || "";
131
+ this._draw();
132
+ return this;
133
+ }
134
+ /**
135
+ * Put the bar in a final state (`done`, `error`, `cancelled`) or back to
136
+ * `running`. `done` fills it. A final state stops the elapsed clock and
137
+ * disables Cancel; `running` restarts the clock (still counting from
138
+ * construction) and enables Cancel again. `label` replaces the label when
139
+ * given.
140
+ */
141
+ setState(state, { label } = {}) {
142
+ if (!PROGRESS_STATES.includes(state)) throw new Error(`ProgressBar: unknown state "${state}"`);
143
+ this._state = state;
144
+ if (label !== void 0) this._label = label || "";
145
+ if (state === "done") {
146
+ this._indeterminate = false;
147
+ this._value = this._max ?? 1;
148
+ }
149
+ if (state === "running") {
150
+ this._stopped = null;
151
+ if (this._showElapsed) this._startTimer();
152
+ } else {
153
+ this._stopped = this._now();
154
+ if (this._timer) {
155
+ clearInterval(this._timer);
156
+ this._timer = null;
157
+ }
158
+ }
159
+ if (this._cancelBtn) this._cancelBtn.disabled = state !== "running";
160
+ this._draw();
161
+ this._drawElapsed();
162
+ return this;
163
+ }
164
+ /** Tick the elapsed readout once a second, unless it already ticks. */
165
+ _startTimer() {
166
+ if (!this._timer) this._timer = setInterval(() => this._drawElapsed(), 1e3);
167
+ }
168
+ /** Stop the elapsed clock and detach. */
169
+ dispose() {
170
+ this._showElapsed = false;
171
+ if (this._timer) {
172
+ clearInterval(this._timer);
173
+ this._timer = null;
174
+ }
175
+ this.element.remove();
176
+ }
177
+ _draw() {
178
+ const root = this.element;
179
+ for (const s of PROGRESS_STATES) {
180
+ root.classList.toggle(`twm-progress--${s}`, this._state === s);
181
+ }
182
+ root.classList.toggle("twm-progress--indeterminate", this._indeterminate);
183
+ setText(this._labelEl, this._label);
184
+ const f = this.fraction;
185
+ const parts = [];
186
+ if (f !== null) {
187
+ if (this._showPercent) parts.push(`${Math.floor(f * 100)}%`);
188
+ const count = this._showCount ?? !!this._max;
189
+ if (count && this._max) parts.push(`${fmtCount(this._value)} / ${fmtCount(this._max)}`);
190
+ }
191
+ setText(this._readoutEl, parts.join(" \xB7 "));
192
+ if (f === null) {
193
+ if (this._fill.style.width) this._fill.style.width = "";
194
+ this._track.removeAttribute("aria-valuenow");
195
+ } else {
196
+ setWidth(this._fill, f);
197
+ this._track.setAttribute("aria-valuenow", String(Math.round(f * 100)));
198
+ }
199
+ this._track.setAttribute("aria-valuemin", "0");
200
+ this._track.setAttribute("aria-valuemax", "100");
201
+ if (this._label) this._track.setAttribute("aria-label", this._label);
202
+ this._drawElapsed();
203
+ }
204
+ _drawElapsed() {
205
+ if (this._elapsedEl) setText(this._elapsedEl, formatElapsed(this.elapsed));
206
+ }
207
+ };
208
+ function fmtCount(n) {
209
+ return Number.isInteger(n) ? String(n) : String(Math.round(n * 100) / 100);
210
+ }
211
+ var ProgressList = class {
212
+ /**
213
+ * @param {HTMLElement|null} host appended to, if given
214
+ * @param {ProgressListOptions} [options]
215
+ */
216
+ constructor(host, options = {}) {
217
+ const { items = [], stats = true, pulse = true, emptyText = "", className = "" } = options;
218
+ this._stats = stats;
219
+ this._emptyText = emptyText;
220
+ this._rows = /* @__PURE__ */ new Map();
221
+ this._items = [];
222
+ const root = el("div", `twm-progress-list${pulse ? " twm-progress-list--pulse" : ""}${className ? ` ${className}` : ""}`);
223
+ this._listEl = el("div", "twm-progress-list__rows");
224
+ this._listEl.setAttribute("role", "list");
225
+ this._emptyEl = el("div", "twm-progress-list__empty");
226
+ this._statsEl = el("div", "twm-progress-list__stats");
227
+ root.append(this._listEl, this._emptyEl, this._statsEl);
228
+ this.element = root;
229
+ this.setItems(items);
230
+ if (host) host.appendChild(root);
231
+ }
232
+ /** The items last given, in order. */
233
+ get items() {
234
+ return this._items.slice();
235
+ }
236
+ /**
237
+ * Reconcile the rows with `items` by key: a known key keeps its row and
238
+ * has its text, width and state changed in place; a new key gets a row; a
239
+ * key that is gone loses its row. Rows follow the order given, and a row
240
+ * already in its place is not moved.
241
+ */
242
+ setItems(items) {
243
+ const list = Array.isArray(items) ? items : [];
244
+ const seen = /* @__PURE__ */ new Set();
245
+ this._items = [];
246
+ for (const it of list) {
247
+ if (!it || it.key == null) continue;
248
+ const k = String(it.key);
249
+ if (seen.has(k)) continue;
250
+ seen.add(k);
251
+ this._items.push(it);
252
+ }
253
+ for (const [k, r] of this._rows) {
254
+ if (!seen.has(k)) {
255
+ r.row.remove();
256
+ this._rows.delete(k);
257
+ }
258
+ }
259
+ const scale = this._items.reduce((m, it) => {
260
+ const d = isNum(it.total) && it.total > 0 ? it.total : isNum(it.completed) ? it.completed : 0;
261
+ return d > m ? d : m;
262
+ }, 0);
263
+ let prev = null;
264
+ for (const it of this._items) {
265
+ const k = String(it.key);
266
+ let r = this._rows.get(k);
267
+ if (!r) {
268
+ r = this._makeRow();
269
+ this._rows.set(k, r);
270
+ }
271
+ this._drawRow(r, it, scale);
272
+ const want = prev ? prev.nextSibling : this._listEl.firstChild;
273
+ if (want !== r.row) this._listEl.insertBefore(r.row, want);
274
+ prev = r.row;
275
+ }
276
+ this._emptyEl.hidden = this._items.length > 0 || !this._emptyText;
277
+ setText(this._emptyEl, this._emptyText);
278
+ const line = this._statsLine();
279
+ this._statsEl.hidden = !line;
280
+ setText(this._statsEl, line);
281
+ return this;
282
+ }
283
+ /** The row element for `key`, or null. */
284
+ rowFor(key) {
285
+ return this._rows.get(String(key))?.row ?? null;
286
+ }
287
+ dispose() {
288
+ this.element.remove();
289
+ this._rows.clear();
290
+ }
291
+ _makeRow() {
292
+ const row = el("div", "twm-progress-list__row");
293
+ row.setAttribute("role", "listitem");
294
+ const name = el("span", "twm-progress-list__name");
295
+ const track = el("div", "twm-progress-list__track");
296
+ const fill = el("div", "twm-progress-list__fill");
297
+ track.appendChild(fill);
298
+ const count = el("span", "twm-progress-list__count");
299
+ const state = el("span", "twm-progress-list__state");
300
+ row.append(state, name, track, count);
301
+ return { row, name, fill, count, state };
302
+ }
303
+ _drawRow(r, it, scale) {
304
+ const state = PROGRESS_ITEM_STATES.includes(it.state) ? it.state : "pending";
305
+ for (const s of PROGRESS_ITEM_STATES) {
306
+ r.row.classList.toggle(`twm-progress-list__row--${s}`, s === state);
307
+ }
308
+ const done = isNum(it.completed) ? it.completed : 0;
309
+ const hasTotal = isNum(it.total) && it.total > 0;
310
+ const denom = hasTotal ? it.total : scale;
311
+ setWidth(r.fill, denom > 0 ? done / denom : 0);
312
+ setText(r.name, it.name ?? String(it.key));
313
+ const count = hasTotal ? `${fmtCount(done)} / ${fmtCount(it.total)}` : fmtCount(done);
314
+ setText(r.count, it.detail ? `${count} \xB7 ${it.detail}` : count);
315
+ if (r.state.title !== state) r.state.title = state;
316
+ }
317
+ _statsLine() {
318
+ if (this._stats === false) return "";
319
+ if (typeof this._stats === "function") return String(this._stats(this.items) ?? "");
320
+ if (!this._items.length) return "";
321
+ const n = { done: 0, running: 0, failed: 0, pending: 0 };
322
+ let completed = 0;
323
+ for (const it of this._items) {
324
+ n[PROGRESS_ITEM_STATES.includes(it.state) ? it.state : "pending"] += 1;
325
+ if (isNum(it.completed)) completed += it.completed;
326
+ }
327
+ const parts = [`${n.done} / ${this._items.length} done`];
328
+ if (n.running) parts.push(`${n.running} running`);
329
+ if (n.failed) parts.push(`${n.failed} failed`);
330
+ if (n.pending) parts.push(`${n.pending} pending`);
331
+ parts.push(`${fmtCount(completed)} completed`);
332
+ return parts.join(" \xB7 ");
333
+ }
334
+ };
335
+ var StepProgress = class {
336
+ /**
337
+ * @param {HTMLElement|null} host appended to, if given
338
+ * @param {{ steps?: Array<StepDef|string>, className?: string }} [options]
339
+ */
340
+ constructor(host, options = {}) {
341
+ const { steps = [], className = "" } = options;
342
+ const root = el("ol", `twm-steps${className ? ` ${className}` : ""}`);
343
+ this.element = root;
344
+ this._steps = steps.map((s, i) => {
345
+ const def = typeof s === "string" ? { label: s } : s || {};
346
+ const li = el("li", "twm-steps__step");
347
+ const marker = el("span", "twm-steps__marker");
348
+ const label = el("span", "twm-steps__label", def.label ?? "");
349
+ const pass = el("span", "twm-steps__pass");
350
+ pass.hidden = true;
351
+ li.append(marker, label, pass);
352
+ root.appendChild(li);
353
+ return { key: def.key != null ? String(def.key) : String(i), label: def.label ?? "", state: "pending", pass: null, li, passEl: pass };
354
+ });
355
+ this._draw();
356
+ if (host) host.appendChild(root);
357
+ }
358
+ /** `[{ key, label, state, pass }]`, in order. */
359
+ get steps() {
360
+ return this._steps.map(({ key, label, state, pass }) => ({ key, label, state, pass }));
361
+ }
362
+ /** The index of the active phase, or -1. */
363
+ get activeIndex() {
364
+ return this._steps.findIndex((s) => s.state === "active");
365
+ }
366
+ /**
367
+ * Make one phase active: every phase before it is done, every phase after
368
+ * it pending. `pass` and `passes` read "pass n of m" on it.
369
+ * @param {string|number} which - A key, or an index.
370
+ * @param {{ pass?: number, passes?: number }} [opts]
371
+ */
372
+ setStep(which, { pass, passes } = {}) {
373
+ const i = this._index(which);
374
+ this._steps.forEach((s, j) => {
375
+ s.state = j < i ? "done" : j === i ? "active" : "pending";
376
+ s.pass = j === i && isNum(pass) ? { n: pass, of: isNum(passes) ? passes : null } : null;
377
+ });
378
+ this._draw();
379
+ return this;
380
+ }
381
+ /** Mark a phase (the active one by default) as failed; phases after it stay pending. */
382
+ fail(which) {
383
+ const i = which === void 0 ? this.activeIndex : this._index(which);
384
+ if (i < 0) return this;
385
+ this._steps.forEach((s, j) => {
386
+ if (j < i) s.state = "done";
387
+ else if (j === i) s.state = "error";
388
+ else s.state = "pending";
389
+ if (j !== i) s.pass = null;
390
+ });
391
+ this._draw();
392
+ return this;
393
+ }
394
+ /** Every phase done. */
395
+ complete() {
396
+ for (const s of this._steps) {
397
+ s.state = "done";
398
+ s.pass = null;
399
+ }
400
+ this._draw();
401
+ return this;
402
+ }
403
+ dispose() {
404
+ this.element.remove();
405
+ }
406
+ _index(which) {
407
+ const i = typeof which === "number" ? which : this._steps.findIndex((s) => s.key === String(which));
408
+ if (!Number.isInteger(i) || i < 0 || i >= this._steps.length) {
409
+ throw new Error(`StepProgress: no step "${which}"`);
410
+ }
411
+ return i;
412
+ }
413
+ _draw() {
414
+ for (const s of this._steps) {
415
+ for (const st of STEP_STATES) s.li.classList.toggle(`twm-steps__step--${st}`, s.state === st);
416
+ if (s.state === "active") s.li.setAttribute("aria-current", "step");
417
+ else s.li.removeAttribute("aria-current");
418
+ const text = s.pass ? s.pass.of ? `pass ${s.pass.n} of ${s.pass.of}` : `pass ${s.pass.n}` : "";
419
+ s.passEl.hidden = !text;
420
+ setText(s.passEl, text);
421
+ }
422
+ }
423
+ };
424
+
425
+ // src/ui/components/job_progress.js
426
+ function mountJobProgress(parent, { progress = null, steps = null, spinner = null } = {}) {
427
+ const stepper = Array.isArray(steps) && steps.length ? new StepProgress(parent, { steps, className: "twm-job-progress__steps" }) : null;
428
+ let bar = null;
429
+ if (progress) {
430
+ const { onCancel: _ignored, ...rest } = typeof progress === "object" ? progress : {};
431
+ bar = new ProgressBar(parent, { ...rest, className: "twm-job-progress__bar" });
432
+ if (spinner) spinner.hidden = true;
433
+ }
434
+ return {
435
+ get bar() {
436
+ return bar;
437
+ },
438
+ get steps() {
439
+ return stepper;
440
+ },
441
+ /** `bar.update(next)`. */
442
+ setProgress(next) {
443
+ if (!bar) throw new Error("setProgress: this dialog was opened without `progress`");
444
+ bar.update(next);
445
+ },
446
+ /** `steps.setStep(which, opts)`. */
447
+ setStep(which, opts) {
448
+ if (!stepper) throw new Error("setStep: this dialog was opened without `steps`");
449
+ stepper.setStep(which, opts);
450
+ },
451
+ /** Put the bar and the phases in their final state. */
452
+ finish(status) {
453
+ if (status === "done") {
454
+ bar?.setState("done");
455
+ stepper?.complete();
456
+ } else if (status === "error") {
457
+ bar?.setState("error");
458
+ stepper?.fail();
459
+ } else bar?.setState("cancelled");
460
+ }
461
+ };
462
+ }
463
+
464
+ // src/ui/components/confirm_dialog.js
465
+ var dialogIdCounter = 0;
466
+ async function showConfirmDialog(options = {}) {
467
+ const {
468
+ title = "Confirm",
469
+ message = "Are you sure?",
470
+ icon = "warning",
471
+ okLabel = "OK",
472
+ cancelLabel = "Cancel",
473
+ okVariant = "primary"
474
+ } = options;
475
+ return openConfirm({
476
+ title,
477
+ message,
478
+ icon,
479
+ confirmLabel: okLabel,
480
+ cancelLabel,
481
+ danger: okVariant === "danger"
482
+ });
483
+ }
484
+ async function showDeleteConfirmDialog(options = {}) {
485
+ const {
486
+ itemName = "",
487
+ itemType = "item",
488
+ additionalMessage = ""
489
+ } = options;
490
+ let message = `Delete ${itemType}`;
491
+ if (itemName) message += ` "${itemName}"`;
492
+ message += "?";
493
+ if (additionalMessage) message += `
494
+
495
+ ${additionalMessage}`;
496
+ return openConfirm({
497
+ title: `Delete ${capitalize(itemType)}`,
498
+ message,
499
+ icon: "delete",
500
+ confirmLabel: "Delete",
501
+ cancelLabel: "Cancel",
502
+ danger: true
503
+ });
504
+ }
505
+ async function showCloseConfirmDialog(options = {}) {
506
+ const {
507
+ itemName = "",
508
+ itemType = "tab"
509
+ } = options;
510
+ let message = `Close ${itemType}`;
511
+ if (itemName) message += ` "${itemName}"`;
512
+ message += "?";
513
+ return openConfirm({
514
+ title: `Close ${capitalize(itemType)}`,
515
+ message,
516
+ icon: "close",
517
+ confirmLabel: "Close",
518
+ cancelLabel: "Cancel"
519
+ });
520
+ }
521
+ async function showChoiceDialog(options = {}) {
522
+ const {
523
+ title = "Choose",
524
+ message = "",
525
+ icon = "warning",
526
+ actions = []
527
+ } = options;
528
+ const content = document.createElement("div");
529
+ content.className = "twm-confirm-dialog__body";
530
+ content.innerHTML = `
531
+ <span class="twm-confirm-dialog__icon material-symbols-outlined">${escapeHtml(icon)}</span>
532
+ <div class="twm-confirm-dialog__message">${escapeHtml(message)}</div>
533
+ `;
534
+ return openModal({
535
+ title,
536
+ icon,
537
+ content,
538
+ width: 480,
539
+ height: 220,
540
+ actions: actions.map((a) => ({
541
+ label: a.label,
542
+ value: a.value,
543
+ primary: a.variant === "primary",
544
+ danger: a.variant === "danger"
545
+ }))
546
+ });
547
+ }
548
+ async function showSelectDialog(options = {}) {
549
+ const {
550
+ title = "Select",
551
+ message = "",
552
+ items = [],
553
+ okLabel = "OK",
554
+ cancelLabel = "Cancel"
555
+ } = options;
556
+ const fields = [];
557
+ if (message) {
558
+ fields.push({ section: message });
559
+ }
560
+ fields.push({
561
+ name: "_choice",
562
+ label: "Choice",
563
+ type: "select",
564
+ default: items[0]?.value || "",
565
+ options: items.map((i) => ({ value: i.value, label: i.label }))
566
+ });
567
+ void cancelLabel;
568
+ const data = await openForm({
569
+ title,
570
+ submitLabel: okLabel,
571
+ fields
572
+ });
573
+ if (!data) return null;
574
+ return data._choice;
575
+ }
576
+ function escapeHtml(str) {
577
+ if (!str) return "";
578
+ return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
579
+ }
580
+ function capitalize(str) {
581
+ if (!str) return "";
582
+ return str.charAt(0).toUpperCase() + str.slice(1);
583
+ }
584
+ function showRunProgressDialog(options = {}) {
585
+ const {
586
+ title = "Running Pipeline",
587
+ message = "Starting\u2026",
588
+ onCancel,
589
+ progress = null,
590
+ steps = null
591
+ } = options;
592
+ const dialogId = `run-progress-${++dialogIdCounter}`;
593
+ let finished = false;
594
+ let settle;
595
+ const result = new Promise((resolve) => {
596
+ settle = resolve;
597
+ });
598
+ const contentEl = document.createElement("div");
599
+ contentEl.className = "twm-confirm-dialog__content run-progress";
600
+ const bodyEl = document.createElement("div");
601
+ bodyEl.className = "twm-run-progress__body";
602
+ const spinnerEl = document.createElement("div");
603
+ spinnerEl.className = "twm-run-progress__spinner";
604
+ const messageEl = document.createElement("div");
605
+ messageEl.className = "twm-run-progress__message";
606
+ messageEl.textContent = message;
607
+ bodyEl.append(spinnerEl, messageEl);
608
+ contentEl.appendChild(bodyEl);
609
+ const job = mountJobProgress(contentEl, { progress, steps, spinner: spinnerEl });
610
+ let stopBtn = null;
611
+ if (typeof onCancel === "function") {
612
+ const stopActionsEl = document.createElement("div");
613
+ stopActionsEl.className = "twm-confirm-dialog__actions twm-run-progress__stop-actions";
614
+ stopBtn = document.createElement("button");
615
+ stopBtn.type = "button";
616
+ stopBtn.className = "twm-confirm-dialog__btn twm-run-progress__stop-btn";
617
+ stopBtn.innerHTML = '<span class="material-symbols-outlined">stop</span> Stop';
618
+ stopBtn.addEventListener("click", () => {
619
+ stopBtn.disabled = true;
620
+ stopBtn.innerHTML = '<span class="material-symbols-outlined">hourglass_top</span> Stopping\u2026';
621
+ onCancel();
622
+ });
623
+ stopActionsEl.appendChild(stopBtn);
624
+ contentEl.appendChild(stopActionsEl);
625
+ }
626
+ const actionsEl = document.createElement("div");
627
+ actionsEl.className = "twm-confirm-dialog__actions";
628
+ actionsEl.style.display = "none";
629
+ const closeBtn = document.createElement("button");
630
+ closeBtn.type = "button";
631
+ closeBtn.className = "twm-confirm-dialog__btn twm-confirm-dialog__btn--primary";
632
+ closeBtn.textContent = "Close";
633
+ closeBtn.addEventListener("click", () => dialogWindow.close());
634
+ actionsEl.appendChild(closeBtn);
635
+ contentEl.appendChild(actionsEl);
636
+ const finish = (status, text, cls) => {
637
+ finished = true;
638
+ spinnerEl.style.display = "none";
639
+ if (stopBtn) stopBtn.parentElement.style.display = "none";
640
+ messageEl.textContent = text;
641
+ messageEl.className = `twm-run-progress__message ${cls}`;
642
+ job.finish(status);
643
+ actionsEl.style.display = "";
644
+ settle({ status, message: text });
645
+ };
646
+ const dialogWindow = new ManagedWindow({
647
+ id: dialogId,
648
+ title,
649
+ icon: "play_arrow",
650
+ content: contentEl,
651
+ minWidth: 320,
652
+ minHeight: 120,
653
+ defaultWidth: 420,
654
+ defaultHeight: 200 + (job.bar ? 40 : 0) + (job.steps ? 40 : 0),
655
+ canMinimize: false,
656
+ canMaximize: false,
657
+ canResize: false,
658
+ canDrag: false,
659
+ modal: true,
660
+ // BUG-0053. Focus goes to the window, never to Stop or Cancel: a stray
661
+ // Enter or Space would cancel the run.
662
+ focusOnOpen: "window",
663
+ onClose: () => {
664
+ if (finished) return;
665
+ finish("cancelled", "Cancelled.", "is-cancelled");
666
+ }
667
+ });
668
+ dialogWindow.show();
669
+ return {
670
+ result,
671
+ update(msg) {
672
+ if (finished) return;
673
+ messageEl.textContent = msg;
674
+ },
675
+ setProgress(next) {
676
+ if (finished) return;
677
+ job.setProgress(next);
678
+ },
679
+ setStep(which, opts) {
680
+ if (finished) return;
681
+ job.setStep(which, opts);
682
+ },
683
+ complete(res) {
684
+ if (finished) return;
685
+ finish(res?.ok ? "done" : "error", res?.message || "", res?.ok ? "is-ok" : "is-err");
686
+ },
687
+ cancelled(msg) {
688
+ if (finished) return;
689
+ finish("cancelled", msg || "Cancelled.", "is-cancelled");
690
+ },
691
+ close() {
692
+ try {
693
+ dialogWindow.close();
694
+ } catch {
695
+ }
696
+ }
697
+ };
698
+ }
699
+
700
+ // src/ui/components/toast.js
701
+ var toastBus = null;
702
+ function setToastBus(bus) {
703
+ const prev = toastBus;
704
+ toastBus = bus && typeof bus.emit === "function" ? bus : null;
705
+ return prev;
706
+ }
707
+ function getToastBus() {
708
+ return toastBus;
709
+ }
710
+ function _emit(severity, persistent, titleOrMsg, msg) {
711
+ const bus = toastBus;
712
+ const title = msg !== void 0 ? titleOrMsg : null;
713
+ const message = msg !== void 0 ? msg : titleOrMsg;
714
+ if (!bus) {
715
+ (severity === "error" || severity === "warn" ? console.warn : console.info)(
716
+ `[toast/${severity}]`,
717
+ title ? `${title}:` : "",
718
+ message
719
+ );
720
+ return;
721
+ }
722
+ bus.emit("toast:show", {
723
+ title,
724
+ message,
725
+ severity,
726
+ persistent,
727
+ durationMs: persistent ? 0 : 5e3
728
+ });
729
+ }
730
+ function toastError(titleOrMsg, msg) {
731
+ _emit("error", true, titleOrMsg, msg);
732
+ }
733
+ function toastWarn(titleOrMsg, msg) {
734
+ _emit("warn", false, titleOrMsg, msg);
735
+ }
736
+ function toastInfo(titleOrMsg, msg) {
737
+ _emit("info", false, titleOrMsg, msg);
738
+ }
739
+ function toastSuccess(titleOrMsg, msg) {
740
+ _emit("success", false, titleOrMsg, msg);
741
+ }
742
+ function reportBridgeError(opName, resOrErr) {
743
+ if (!resOrErr) return true;
744
+ if (resOrErr instanceof Error) {
745
+ toastError(opName, resOrErr.message || String(resOrErr));
746
+ return false;
747
+ }
748
+ if (resOrErr && resOrErr.ok === false) {
749
+ toastError(opName, resOrErr.error || "operation refused");
750
+ return false;
751
+ }
752
+ return true;
753
+ }
754
+
755
+ export {
756
+ PROGRESS_STATES,
757
+ PROGRESS_ITEM_STATES,
758
+ STEP_STATES,
759
+ formatElapsed,
760
+ ProgressBar,
761
+ ProgressList,
762
+ StepProgress,
763
+ mountJobProgress,
764
+ showConfirmDialog,
765
+ showDeleteConfirmDialog,
766
+ showCloseConfirmDialog,
767
+ showChoiceDialog,
768
+ showSelectDialog,
769
+ showRunProgressDialog,
770
+ setToastBus,
771
+ getToastBus,
772
+ toastError,
773
+ toastWarn,
774
+ toastInfo,
775
+ toastSuccess,
776
+ reportBridgeError
777
+ };
778
+ //# sourceMappingURL=chunk-5W4EEKSK.js.map