flexdesk 0.17.15 → 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.
- package/CHANGELOG.md +23 -0
- package/css/base.css +224 -4
- package/css/flexdesk.css +314 -12
- package/dist/charts.js +104 -34
- package/dist/charts.js.map +3 -3
- package/dist/{chunk-2PM6ZMNS.js → chunk-2Z2ENHR3.js} +2 -2
- package/dist/{chunk-AD7FIA3Z.js → chunk-7JI7HHSY.js} +2 -2
- package/dist/{chunk-TLIV6QSU.js → chunk-7OL6F2AM.js} +3 -3
- package/dist/{chunk-MADMGRX2.js → chunk-PGZ4EHSE.js} +156 -9
- package/dist/chunk-PGZ4EHSE.js.map +7 -0
- package/dist/{chunk-OEG6NPAN.js → chunk-XJNADRGR.js} +2 -2
- package/dist/flexdesk.css +314 -12
- package/dist/flow.js +3 -3
- package/dist/tiles.js +84 -9
- package/dist/tiles.js.map +2 -2
- package/dist/widgets.js +448 -5
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +102 -14
- package/dist/wm.js.map +2 -2
- package/docs/README.md +1 -1
- package/docs/components/charts.md +70 -3
- package/docs/components/forms-and-dialogs.md +61 -0
- package/docs/components/menus.md +85 -1
- package/docs/components/shell.md +37 -0
- package/docs/components/tiles.md +37 -0
- package/package.json +1 -1
- package/src/charts/plot_config_editor.js +125 -29
- package/src/tiles/tile_base.js +48 -2
- package/src/tiles/tile_grid.js +42 -1
- package/src/tiling/kind_taxonomy.js +45 -0
- package/src/tiling/wm.js +86 -11
- package/src/ui/components/category_picker.js +556 -0
- package/src/ui/components/computing_status_window.js +3 -0
- package/src/ui/components/confirm_dialog.js +3 -0
- package/src/ui/components/managed_window.js +204 -9
- package/src/ui/components/wizard.js +4 -1
- package/widgets.js +2 -1
- package/dist/chunk-MADMGRX2.js.map +0 -7
- /package/dist/{chunk-2PM6ZMNS.js.map → chunk-2Z2ENHR3.js.map} +0 -0
- /package/dist/{chunk-AD7FIA3Z.js.map → chunk-7JI7HHSY.js.map} +0 -0
- /package/dist/{chunk-TLIV6QSU.js.map → chunk-7OL6F2AM.js.map} +0 -0
- /package/dist/{chunk-OEG6NPAN.js.map → chunk-XJNADRGR.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-
|
|
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-
|
|
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-
|
|
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-
|
|
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,
|