flexdesk 0.17.14 → 0.17.16

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 (47) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/css/base.css +237 -8
  3. package/css/flexdesk.css +344 -17
  4. package/css/overrides.css +6 -0
  5. package/dist/charts.js +104 -34
  6. package/dist/charts.js.map +3 -3
  7. package/dist/{chunk-2PM6ZMNS.js → chunk-2Z2ENHR3.js} +2 -2
  8. package/dist/{chunk-IG74S3ZH.js → chunk-7JI7HHSY.js} +2 -2
  9. package/dist/{chunk-TLIV6QSU.js → chunk-7OL6F2AM.js} +3 -3
  10. package/dist/{chunk-MADMGRX2.js → chunk-PGZ4EHSE.js} +156 -9
  11. package/dist/chunk-PGZ4EHSE.js.map +7 -0
  12. package/dist/{chunk-ZMH3OC7H.js → chunk-XJNADRGR.js} +4 -2
  13. package/dist/chunk-XJNADRGR.js.map +7 -0
  14. package/dist/flexdesk.css +344 -17
  15. package/dist/flow.js +3 -3
  16. package/dist/tiles.js +84 -9
  17. package/dist/tiles.js.map +2 -2
  18. package/dist/widgets.js +448 -5
  19. package/dist/widgets.js.map +4 -4
  20. package/dist/wm.js +110 -19
  21. package/dist/wm.js.map +2 -2
  22. package/docs/README.md +1 -1
  23. package/docs/components/charts.md +70 -3
  24. package/docs/components/forms-and-dialogs.md +80 -2
  25. package/docs/components/menus.md +85 -1
  26. package/docs/components/shell.md +52 -0
  27. package/docs/components/slide-out-panel.md +15 -0
  28. package/docs/components/tiles.md +37 -0
  29. package/package.json +1 -1
  30. package/src/charts/plot_config_editor.js +125 -29
  31. package/src/tiles/tile_base.js +48 -2
  32. package/src/tiles/tile_grid.js +42 -1
  33. package/src/tiling/kind_taxonomy.js +45 -0
  34. package/src/tiling/tile_breadcrumb.js +12 -4
  35. package/src/tiling/wm.js +86 -11
  36. package/src/ui/components/category_picker.js +556 -0
  37. package/src/ui/components/computing_status_window.js +3 -0
  38. package/src/ui/components/confirm_dialog.js +3 -0
  39. package/src/ui/components/managed_window.js +204 -9
  40. package/src/ui/components/modal.js +11 -2
  41. package/src/ui/components/wizard.js +4 -1
  42. package/widgets.js +2 -1
  43. package/dist/chunk-MADMGRX2.js.map +0 -7
  44. package/dist/chunk-ZMH3OC7H.js.map +0 -7
  45. /package/dist/{chunk-2PM6ZMNS.js.map → chunk-2Z2ENHR3.js.map} +0 -0
  46. /package/dist/{chunk-IG74S3ZH.js.map → chunk-7JI7HHSY.js.map} +0 -0
  47. /package/dist/{chunk-TLIV6QSU.js.map → chunk-7OL6F2AM.js.map} +0 -0
package/dist/widgets.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  import {
27
27
  hideContextMenu,
28
28
  showContextMenu
29
- } from "./chunk-IG74S3ZH.js";
29
+ } from "./chunk-7JI7HHSY.js";
30
30
  import {
31
31
  ColorListField,
32
32
  SortableList,
@@ -35,7 +35,7 @@ import {
35
35
  openForm,
36
36
  openModal,
37
37
  setModalHost
38
- } from "./chunk-ZMH3OC7H.js";
38
+ } from "./chunk-XJNADRGR.js";
39
39
  import {
40
40
  ACTIONS,
41
41
  AutocompleteField,
@@ -82,9 +82,10 @@ import {
82
82
  resolvePaging,
83
83
  toTSV,
84
84
  tsvCell
85
- } from "./chunk-2PM6ZMNS.js";
85
+ } from "./chunk-2Z2ENHR3.js";
86
86
  import {
87
87
  ColorField,
88
+ DEFAULT_MAX_SAVED_WINDOWS,
88
89
  DEFAULT_WINDOW_STORAGE_KEY,
89
90
  ManagedWindow,
90
91
  ROW_CONTROL_SELECTOR,
@@ -96,7 +97,7 @@ import {
96
97
  normalizeHex,
97
98
  selectBox,
98
99
  shownValue
99
- } from "./chunk-MADMGRX2.js";
100
+ } from "./chunk-PGZ4EHSE.js";
100
101
  import {
101
102
  getSetting
102
103
  } from "./chunk-HNU6MM2B.js";
@@ -1380,6 +1381,440 @@ function mountAttributeListEditor(hostEl, options = {}) {
1380
1381
  };
1381
1382
  }
1382
1383
 
1384
+ // src/ui/components/category_picker.js
1385
+ var _seq2 = 0;
1386
+ function _terms(query) {
1387
+ return String(query || "").toLowerCase().split(/\s+/).filter(Boolean);
1388
+ }
1389
+ function _haystack(item) {
1390
+ const kw = Array.isArray(item.keywords) ? item.keywords.join(" ") : item.keywords || "";
1391
+ return `${item.label || ""} ${item.description || ""} ${kw}`.toLowerCase();
1392
+ }
1393
+ function _el(tag, className, text) {
1394
+ const n = document.createElement(tag);
1395
+ if (className) n.className = className;
1396
+ if (text != null) n.textContent = text;
1397
+ return n;
1398
+ }
1399
+ var CategoryPicker = class {
1400
+ /**
1401
+ * @param {Object} config
1402
+ * @param {HTMLElement} config.trigger - The element the picker opens from; its click toggles it
1403
+ * @param {Array} config.categories - `[{ id, label, color?, items: [{ id, label, icon?, badge?,
1404
+ * description?, preview?, keywords? }] }]`. `icon` is a Material Symbols ligature; `badge` a
1405
+ * short text drawn as a monospace badge in the category's colour; `preview` is text shown
1406
+ * monospace in the detail pane; `keywords` (array or string) are searched but not shown.
1407
+ * @param {Function} config.onSelect - `(item, category)` when an item is picked
1408
+ * @param {string} [config.className=''] - Additional CSS class for the popover
1409
+ * @param {string} [config.placeholder='Search…'] - The search field's placeholder
1410
+ * @param {string} [config.addLabel='Add'] - The detail pane's button
1411
+ * @param {string} [config.previewLabel] - A caption over the preview block (none when omitted)
1412
+ * @param {'start'|'end'} [config.align='start'] - Which trigger edge it lines up with first
1413
+ * @param {string} [config.label='Pick'] - The popover's accessible name (its `aria-label`)
1414
+ */
1415
+ constructor(config) {
1416
+ this.trigger = config.trigger;
1417
+ this.categories = config.categories || [];
1418
+ this.onSelect = config.onSelect;
1419
+ this.className = config.className || "";
1420
+ this.placeholder = config.placeholder ?? "Search\u2026";
1421
+ this.addLabel = config.addLabel ?? "Add";
1422
+ this.previewLabel = config.previewLabel || "";
1423
+ this.align = config.align === "end" ? "end" : "start";
1424
+ this.label = config.label || "Pick";
1425
+ this.isOpen = false;
1426
+ this.el = null;
1427
+ this._id = `twm-category-picker-${++_seq2}`;
1428
+ this._query = "";
1429
+ this._activeCat = 0;
1430
+ this._column = "items";
1431
+ this._hl = null;
1432
+ this._rows = [];
1433
+ this._onDocClick = this._onDocClick.bind(this);
1434
+ this._onKeydown = this._onKeydown.bind(this);
1435
+ this._onTriggerClick = this._onTriggerClick.bind(this);
1436
+ if (!this.trigger) {
1437
+ console.warn("[CategoryPicker] No trigger element provided");
1438
+ return;
1439
+ }
1440
+ this._build();
1441
+ this.trigger.setAttribute("aria-haspopup", "dialog");
1442
+ this.trigger.setAttribute("aria-expanded", "false");
1443
+ this.trigger.addEventListener("click", this._onTriggerClick);
1444
+ }
1445
+ // ── DOM ────────────────────────────────────────────────────────────────
1446
+ _build() {
1447
+ const root = _el("div", `twm-category-picker ${this.className}`.trim());
1448
+ root.setAttribute("role", "dialog");
1449
+ root.setAttribute("aria-label", this.label);
1450
+ root.hidden = true;
1451
+ root.addEventListener("mousedown", (e) => {
1452
+ if (e.target !== this._input) e.preventDefault();
1453
+ });
1454
+ const search = _el("div", "twm-category-picker__search");
1455
+ const input = _el("input", "twm-category-picker__input");
1456
+ input.type = "search";
1457
+ input.placeholder = this.placeholder;
1458
+ input.setAttribute("aria-label", this.placeholder || "Search");
1459
+ input.setAttribute("role", "combobox");
1460
+ input.setAttribute("aria-expanded", "true");
1461
+ input.setAttribute("aria-autocomplete", "list");
1462
+ input.setAttribute("aria-controls", `${this._id}-items`);
1463
+ input.autocomplete = "off";
1464
+ input.spellcheck = false;
1465
+ input.addEventListener("input", () => this._setQuery(input.value));
1466
+ search.appendChild(input);
1467
+ const body = _el("div", "twm-category-picker__body");
1468
+ const cats = _el("div", "twm-category-picker__categories twm-category-picker__column");
1469
+ cats.id = `${this._id}-cats`;
1470
+ cats.setAttribute("role", "listbox");
1471
+ cats.setAttribute("aria-label", "Categories");
1472
+ const items = _el("div", "twm-category-picker__items twm-category-picker__column");
1473
+ items.id = `${this._id}-items`;
1474
+ items.setAttribute("role", "listbox");
1475
+ items.setAttribute("aria-label", "Items");
1476
+ const detail = _el("div", "twm-category-picker__detail");
1477
+ body.append(cats, items, detail);
1478
+ root.append(search, body);
1479
+ document.body.appendChild(root);
1480
+ this.el = root;
1481
+ this._input = input;
1482
+ this._catsEl = cats;
1483
+ this._itemsEl = items;
1484
+ this._detailEl = detail;
1485
+ this._render();
1486
+ }
1487
+ /** The items column's entries: the active category's items, or every hit for the query. */
1488
+ _visible() {
1489
+ const terms = _terms(this._query);
1490
+ if (!terms.length) {
1491
+ const cat = this.categories[this._activeCat];
1492
+ return (cat?.items || []).map((_, i) => ({ c: this._activeCat, i }));
1493
+ }
1494
+ const out = [];
1495
+ this.categories.forEach((cat, c) => (cat.items || []).forEach((item, i) => {
1496
+ const hay = _haystack(item);
1497
+ if (terms.every((t) => hay.includes(t))) out.push({ c, i });
1498
+ }));
1499
+ return out;
1500
+ }
1501
+ /** Rebuild both lists and the detail pane. Hover never calls this. */
1502
+ _render() {
1503
+ if (!this.el) return;
1504
+ const searching = _terms(this._query).length > 0;
1505
+ const visible = this._visible();
1506
+ const hits = /* @__PURE__ */ new Map();
1507
+ for (const v of visible) hits.set(v.c, (hits.get(v.c) || 0) + 1);
1508
+ if (!this._hl || !visible.some((v) => v.c === this._hl.c && v.i === this._hl.i)) {
1509
+ this._hl = visible[0] ? { ...visible[0] } : null;
1510
+ }
1511
+ if (searching && this._hl) this._activeCat = this._hl.c;
1512
+ this._catsEl.replaceChildren();
1513
+ this.categories.forEach((cat, c) => {
1514
+ const btn = _el("button", "twm-category-picker__category");
1515
+ btn.type = "button";
1516
+ btn.tabIndex = -1;
1517
+ btn.id = `${this._id}-cat-${c}`;
1518
+ btn.setAttribute("role", "option");
1519
+ btn.dataset.index = String(c);
1520
+ if (cat.color) btn.style.setProperty("--twm-category-picker-color", cat.color);
1521
+ const count = searching ? hits.get(c) || 0 : (cat.items || []).length;
1522
+ if (searching && !count) btn.classList.add("twm-category-picker__category--empty");
1523
+ btn.append(
1524
+ _el("span", "twm-category-picker__dot"),
1525
+ _el("span", "twm-category-picker__category-label", cat.label),
1526
+ _el("span", "twm-category-picker__count", String(count))
1527
+ );
1528
+ btn.addEventListener("click", (e) => {
1529
+ e.preventDefault();
1530
+ this._column = "categories";
1531
+ this._chooseCategory(c);
1532
+ this._input.focus();
1533
+ });
1534
+ this._catsEl.appendChild(btn);
1535
+ });
1536
+ this._itemsEl.replaceChildren();
1537
+ this._rows = [];
1538
+ let lastCat = -1;
1539
+ let into = this._itemsEl;
1540
+ for (const v of visible) {
1541
+ const cat = this.categories[v.c];
1542
+ const item = cat.items[v.i];
1543
+ if (searching && v.c !== lastCat) {
1544
+ const header = _el("div", "twm-category-picker__group");
1545
+ header.id = `${this._id}-group-${v.c}`;
1546
+ if (cat.color) header.style.setProperty("--twm-category-picker-color", cat.color);
1547
+ header.append(_el("span", "twm-category-picker__dot"), _el("span", null, cat.label));
1548
+ into = _el("div", "twm-category-picker__hits");
1549
+ into.setAttribute("role", "group");
1550
+ into.setAttribute("aria-labelledby", header.id);
1551
+ into.appendChild(header);
1552
+ this._itemsEl.appendChild(into);
1553
+ lastCat = v.c;
1554
+ }
1555
+ const btn = _el("button", "twm-category-picker__item");
1556
+ btn.type = "button";
1557
+ btn.tabIndex = -1;
1558
+ btn.id = `${this._id}-item-${v.c}-${v.i}`;
1559
+ btn.setAttribute("role", "option");
1560
+ btn.dataset.category = String(v.c);
1561
+ btn.dataset.index = String(v.i);
1562
+ if (cat.color) btn.style.setProperty("--twm-category-picker-color", cat.color);
1563
+ if (item.icon) {
1564
+ btn.appendChild(_el("span", "material-symbols-outlined twm-category-picker__icon", item.icon));
1565
+ } else {
1566
+ btn.appendChild(_el("span", "twm-category-picker__badge", item.badge ?? ""));
1567
+ }
1568
+ btn.appendChild(_el("span", "twm-category-picker__item-label", item.label));
1569
+ btn.addEventListener("mouseenter", () => this._highlight(v));
1570
+ btn.addEventListener("click", (e) => {
1571
+ e.preventDefault();
1572
+ this._column = "items";
1573
+ this._highlight(v);
1574
+ this._input.focus();
1575
+ });
1576
+ btn.addEventListener("dblclick", (e) => {
1577
+ e.preventDefault();
1578
+ this._pick(v);
1579
+ });
1580
+ this._rows.push({ ...v, el: btn });
1581
+ into.appendChild(btn);
1582
+ }
1583
+ if (!visible.length) {
1584
+ this._itemsEl.appendChild(_el(
1585
+ "div",
1586
+ "twm-category-picker__empty",
1587
+ searching ? `Nothing matches \u201C${this._query.trim()}\u201D.` : "Nothing here."
1588
+ ));
1589
+ }
1590
+ this._paintState();
1591
+ }
1592
+ /** Classes and ARIA for the active category, the highlight and the current column; the detail pane. */
1593
+ _paintState() {
1594
+ const searching = _terms(this._query).length > 0;
1595
+ [...this._catsEl.children].forEach((btn, c) => {
1596
+ const on = c === this._activeCat;
1597
+ btn.classList.toggle("twm-category-picker__category--active", on);
1598
+ btn.setAttribute("aria-selected", on ? "true" : "false");
1599
+ });
1600
+ for (const r of this._rows) {
1601
+ const on = !!this._hl && r.c === this._hl.c && r.i === this._hl.i;
1602
+ r.el.classList.toggle("twm-category-picker__item--highlighted", on);
1603
+ r.el.setAttribute("aria-selected", on ? "true" : "false");
1604
+ }
1605
+ const column = searching ? "items" : this._column;
1606
+ this._catsEl.classList.toggle("twm-category-picker__column--current", column === "categories");
1607
+ this._itemsEl.classList.toggle("twm-category-picker__column--current", column === "items");
1608
+ const hlRow = this._rows.find((r) => this._hl && r.c === this._hl.c && r.i === this._hl.i);
1609
+ const activeCatEl = this._catsEl.children[this._activeCat];
1610
+ const current = column === "categories" ? activeCatEl : hlRow?.el;
1611
+ this._input.setAttribute("aria-controls", column === "categories" ? this._catsEl.id : this._itemsEl.id);
1612
+ if (current) this._input.setAttribute("aria-activedescendant", current.id);
1613
+ else this._input.removeAttribute("aria-activedescendant");
1614
+ hlRow?.el.scrollIntoView?.({ block: "nearest" });
1615
+ activeCatEl?.scrollIntoView?.({ block: "nearest" });
1616
+ this._renderDetail();
1617
+ }
1618
+ _renderDetail() {
1619
+ const d = this._detailEl;
1620
+ d.replaceChildren();
1621
+ const item = this._hl ? this.categories[this._hl.c]?.items?.[this._hl.i] : null;
1622
+ if (item) {
1623
+ d.appendChild(_el("div", "twm-category-picker__title", item.label));
1624
+ if (item.description) d.appendChild(_el("div", "twm-category-picker__description", item.description));
1625
+ if (item.preview != null && item.preview !== "") {
1626
+ if (this.previewLabel) d.appendChild(_el("div", "twm-category-picker__preview-label", this.previewLabel));
1627
+ d.appendChild(_el("pre", "twm-category-picker__preview", String(item.preview)));
1628
+ }
1629
+ } else {
1630
+ d.appendChild(_el("div", "twm-category-picker__detail-empty", "Nothing selected."));
1631
+ }
1632
+ const add = _el("button", "twm-btn twm-btn--primary twm-category-picker__add", this.addLabel);
1633
+ add.type = "button";
1634
+ add.disabled = !item;
1635
+ add.addEventListener("click", (e) => {
1636
+ e.preventDefault();
1637
+ if (this._hl) this._pick(this._hl);
1638
+ });
1639
+ d.appendChild(add);
1640
+ }
1641
+ // ── State changes ──────────────────────────────────────────────────────
1642
+ _setQuery(q) {
1643
+ this._query = String(q || "");
1644
+ if (_terms(this._query).length) {
1645
+ this._hl = null;
1646
+ } else if (this._hl) {
1647
+ this._activeCat = this._hl.c;
1648
+ }
1649
+ this._render();
1650
+ }
1651
+ _highlight(v) {
1652
+ if (!v) return;
1653
+ this._hl = { c: v.c, i: v.i };
1654
+ if (_terms(this._query).length) this._activeCat = v.c;
1655
+ this._paintState();
1656
+ }
1657
+ /** Make category `c` the active one. Browsing: show its items. Searching: jump to its first hit. */
1658
+ _chooseCategory(c) {
1659
+ if (c < 0 || c >= this.categories.length) return;
1660
+ if (_terms(this._query).length) {
1661
+ const first = this._rows.find((r) => r.c === c);
1662
+ if (first) this._highlight(first);
1663
+ return;
1664
+ }
1665
+ this._activeCat = c;
1666
+ this._hl = (this.categories[c].items || []).length ? { c, i: 0 } : null;
1667
+ this._render();
1668
+ }
1669
+ _moveHighlight(dir) {
1670
+ if (!this._rows.length) return;
1671
+ let at = this._rows.findIndex((r) => this._hl && r.c === this._hl.c && r.i === this._hl.i);
1672
+ at = at < 0 ? dir > 0 ? 0 : this._rows.length - 1 : (at + dir + this._rows.length) % this._rows.length;
1673
+ this._highlight(this._rows[at]);
1674
+ }
1675
+ _pick(v) {
1676
+ const category = this.categories[v.c];
1677
+ const item = category?.items?.[v.i];
1678
+ if (!item) return;
1679
+ const onSelect = this.onSelect;
1680
+ this.close();
1681
+ this.trigger?.focus();
1682
+ onSelect?.(item, category);
1683
+ }
1684
+ // ── Events ─────────────────────────────────────────────────────────────
1685
+ _onTriggerClick(e) {
1686
+ e.preventDefault();
1687
+ e.stopPropagation();
1688
+ this.toggle();
1689
+ }
1690
+ _onDocClick(e) {
1691
+ if (!this.isOpen) return;
1692
+ if (this.el?.contains(e.target) || this.trigger?.contains(e.target)) return;
1693
+ this.close();
1694
+ }
1695
+ _onKeydown(e) {
1696
+ if (!this.isOpen) return;
1697
+ if (e.isComposing || e.keyCode === 229) return;
1698
+ if (e.key === "Escape") {
1699
+ e.preventDefault();
1700
+ e.stopPropagation();
1701
+ this.close();
1702
+ this.trigger?.focus();
1703
+ return;
1704
+ }
1705
+ if (!this.el.contains(e.target)) return;
1706
+ const searching = _terms(this._query).length > 0;
1707
+ switch (e.key) {
1708
+ case "ArrowDown":
1709
+ case "ArrowUp": {
1710
+ e.preventDefault();
1711
+ const dir = e.key === "ArrowDown" ? 1 : -1;
1712
+ if (!searching && this._column === "categories") {
1713
+ const n = this.categories.length;
1714
+ if (n) this._chooseCategory((this._activeCat + dir + n) % n);
1715
+ } else {
1716
+ this._moveHighlight(dir);
1717
+ }
1718
+ break;
1719
+ }
1720
+ case "ArrowRight":
1721
+ case "ArrowLeft":
1722
+ if (searching) return;
1723
+ e.preventDefault();
1724
+ this._column = e.key === "ArrowRight" ? "items" : "categories";
1725
+ if (this._column === "items" && !this._hl) this._moveHighlight(1);
1726
+ this._paintState();
1727
+ break;
1728
+ case "Enter":
1729
+ e.preventDefault();
1730
+ if (this._hl) this._pick(this._hl);
1731
+ break;
1732
+ case "Tab": {
1733
+ e.preventDefault();
1734
+ e.stopPropagation();
1735
+ const add = this._detailEl.querySelector(".twm-category-picker__add");
1736
+ const stops = [this._input, ...add && !add.disabled ? [add] : []];
1737
+ const at = stops.indexOf(e.target);
1738
+ const next = at < 0 ? 0 : (at + (e.shiftKey ? -1 : 1) + stops.length) % stops.length;
1739
+ stops[next].focus();
1740
+ break;
1741
+ }
1742
+ default:
1743
+ if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey && e.target !== this._input) {
1744
+ this._input.focus();
1745
+ }
1746
+ }
1747
+ }
1748
+ // ── Public API ─────────────────────────────────────────────────────────
1749
+ /** Open it under (or above) the trigger, with an empty query and focus in the search field. */
1750
+ open() {
1751
+ if (this.isOpen || !this.el) return;
1752
+ this.isOpen = true;
1753
+ this._input.value = "";
1754
+ this._query = "";
1755
+ this._column = "items";
1756
+ this.el.hidden = false;
1757
+ this.position();
1758
+ this._render();
1759
+ this.trigger?.classList.add("twm-is-open");
1760
+ this.trigger?.setAttribute("aria-expanded", "true");
1761
+ document.addEventListener("click", this._onDocClick, true);
1762
+ document.addEventListener("keydown", this._onKeydown, true);
1763
+ this._input.focus();
1764
+ }
1765
+ close() {
1766
+ if (!this.isOpen) return;
1767
+ this.isOpen = false;
1768
+ if (this.el) this.el.hidden = true;
1769
+ this.trigger?.classList.remove("twm-is-open");
1770
+ this.trigger?.setAttribute("aria-expanded", "false");
1771
+ document.removeEventListener("click", this._onDocClick, true);
1772
+ document.removeEventListener("keydown", this._onKeydown, true);
1773
+ }
1774
+ /** @param {boolean} [force] */
1775
+ toggle(force) {
1776
+ const want = typeof force === "boolean" ? force : !this.isOpen;
1777
+ if (want) this.open();
1778
+ else this.close();
1779
+ }
1780
+ /** Place it against the trigger again (after the trigger moved). */
1781
+ position() {
1782
+ if (!this.el || !this.trigger) return;
1783
+ ActionDropdown.position(this.trigger, this.el, { align: this.align });
1784
+ this.el.style.overflowY = "hidden";
1785
+ }
1786
+ /**
1787
+ * Replace the categories. The active category and the highlight are
1788
+ * looked up again by `id`, so they are kept when they still exist,
1789
+ * wherever they moved to.
1790
+ */
1791
+ setCategories(categories) {
1792
+ const oldCat = this.categories[this._activeCat];
1793
+ const oldHlCat = this._hl ? this.categories[this._hl.c] : null;
1794
+ const oldHlItem = oldHlCat?.items?.[this._hl.i];
1795
+ this.categories = categories || [];
1796
+ const catAt = (cat) => cat ? this.categories.findIndex((c) => c.id === cat.id) : -1;
1797
+ const active = catAt(oldCat);
1798
+ this._activeCat = active >= 0 ? active : 0;
1799
+ this._hl = null;
1800
+ const hc = catAt(oldHlCat);
1801
+ if (hc >= 0 && oldHlItem) {
1802
+ const hi = (this.categories[hc].items || []).findIndex((it) => it.id === oldHlItem.id);
1803
+ if (hi >= 0) this._hl = { c: hc, i: hi };
1804
+ }
1805
+ if (this.el) this._render();
1806
+ }
1807
+ destroy() {
1808
+ this.close();
1809
+ this.trigger?.removeEventListener("click", this._onTriggerClick);
1810
+ this.el?.remove();
1811
+ this.el = null;
1812
+ this.trigger = null;
1813
+ this.categories = [];
1814
+ this.onSelect = null;
1815
+ }
1816
+ };
1817
+
1383
1818
  // src/ui/components/column_fit.js
1384
1819
  var COLUMN_FIT = Object.freeze({
1385
1820
  /** Absolute floor, and the drag-resize minimum. */
@@ -2003,6 +2438,9 @@ function showComputingWindow({
2003
2438
  canResize: false,
2004
2439
  canDrag: true,
2005
2440
  modal: false,
2441
+ // BUG-0053. A status window opened by the program, not by the user:
2442
+ // it must not take focus from what the user is doing.
2443
+ focusOnOpen: false,
2006
2444
  onClose: () => {
2007
2445
  if (_activeWindow === win) _activeWindow = null;
2008
2446
  if (finished) return;
@@ -2241,6 +2679,9 @@ function showRunProgressDialog(options = {}) {
2241
2679
  canResize: false,
2242
2680
  canDrag: false,
2243
2681
  modal: true,
2682
+ // BUG-0053. Focus goes to the window, never to Stop or Cancel: a stray
2683
+ // Enter or Space would cancel the run.
2684
+ focusOnOpen: "window",
2244
2685
  onClose: () => {
2245
2686
  if (finished) return;
2246
2687
  finish("cancelled", "Cancelled.", "is-cancelled");
@@ -5738,9 +6179,9 @@ var Wizard = class extends ComponentBase {
5738
6179
  return false;
5739
6180
  }
5740
6181
  });
5741
- this.#window.show();
5742
6182
  this.#renderBody();
5743
6183
  this.#paint();
6184
+ this.#window.show();
5744
6185
  return this.#window;
5745
6186
  }
5746
6187
  /** Tear down (and close the window, if any) without calling any callback.
@@ -8493,6 +8934,7 @@ export {
8493
8934
  COLUMN_FIT,
8494
8935
  CONVERSATION_COMPOSER_MAX_PX,
8495
8936
  CONVERSATION_PIN_WITHIN_PX,
8937
+ CategoryPicker,
8496
8938
  CellEditorHost,
8497
8939
  CellSelection,
8498
8940
  ColorField,
@@ -8500,6 +8942,7 @@ export {
8500
8942
  ComponentBase,
8501
8943
  ControllerBase,
8502
8944
  DEFAULT_HISTORY_LIMIT,
8945
+ DEFAULT_MAX_SAVED_WINDOWS,
8503
8946
  DEFAULT_TYPES,
8504
8947
  DEFAULT_WINDOW_STORAGE_KEY,
8505
8948
  DataTable,