flexdesk 0.17.18 → 0.18.1
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 +91 -0
- package/README.md +1 -0
- package/css/flexdesk.css +7547 -5891
- package/css/reset.css +1 -1
- package/css/tokens.css +30 -1
- package/dist/charts.js +83 -13
- package/dist/charts.js.map +2 -2
- package/dist/chunk-3BLIVOFD.js +778 -0
- package/dist/chunk-3BLIVOFD.js.map +7 -0
- package/dist/{chunk-7OL6F2AM.js → chunk-4X4SY3CP.js} +7 -5
- package/dist/chunk-4X4SY3CP.js.map +7 -0
- package/dist/chunk-DNJ3N4T4.js +82 -0
- package/dist/chunk-DNJ3N4T4.js.map +7 -0
- package/dist/{chunk-PGZ4EHSE.js → chunk-HL3IIHU3.js} +501 -359
- package/dist/chunk-HL3IIHU3.js.map +7 -0
- package/dist/{chunk-REJ5HLIM.js → chunk-HYJYYEPK.js} +2 -2
- package/dist/{chunk-7U4IB65H.js → chunk-LLBJ2SJH.js} +5 -3
- package/dist/{chunk-7U4IB65H.js.map → chunk-LLBJ2SJH.js.map} +1 -1
- package/dist/{chunk-XJNADRGR.js → chunk-NFZJ6P42.js} +188 -96
- package/dist/chunk-NFZJ6P42.js.map +7 -0
- package/dist/chunk-PAI2ZUWV.js +260 -0
- package/dist/chunk-PAI2ZUWV.js.map +7 -0
- package/dist/chunk-Q7BALEPX.js +527 -0
- package/dist/chunk-Q7BALEPX.js.map +7 -0
- package/dist/{chunk-2Z2ENHR3.js → chunk-QRBOIUU3.js} +7 -8
- package/dist/chunk-QRBOIUU3.js.map +7 -0
- package/dist/chunk-QXAOTGCC.js +40 -0
- package/dist/chunk-QXAOTGCC.js.map +7 -0
- package/dist/chunk-UKP22RUG.js +219 -0
- package/dist/chunk-UKP22RUG.js.map +7 -0
- package/dist/chunk-VLOAI3HS.js +43 -0
- package/dist/chunk-VLOAI3HS.js.map +7 -0
- package/dist/{chunk-WUFLGETN.js → chunk-WVFGV5FT.js} +2 -39
- package/dist/{chunk-WUFLGETN.js.map → chunk-WVFGV5FT.js.map} +3 -3
- package/dist/editor.js +3 -2
- package/dist/editor.js.map +1 -1
- package/dist/flexdesk.css +7547 -5891
- package/dist/flow.js +29 -97
- package/dist/flow.js.map +3 -3
- package/dist/git.js +1356 -0
- package/dist/git.js.map +7 -0
- package/dist/reset.css +1 -1
- package/dist/tiles.js +20 -15
- package/dist/tiles.js.map +2 -2
- package/dist/tokens.css +30 -1
- package/dist/widgets.js +4146 -1199
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +120 -32
- package/dist/wm.js.map +2 -2
- package/docs/README.md +9 -4
- package/docs/best-practices.md +2 -2
- package/docs/components/charts.md +40 -0
- package/docs/components/chat-shell.md +204 -0
- package/docs/components/conversation.md +10 -0
- package/docs/components/filter-builder.md +292 -0
- package/docs/components/forms-and-dialogs.md +30 -0
- package/docs/components/git.md +355 -0
- package/docs/components/input-fields.md +191 -2
- package/docs/components/keymap.md +8 -0
- package/docs/components/menus.md +86 -2
- package/docs/components/panels-and-desktops.md +16 -5
- package/docs/components/shell.md +108 -1
- package/docs/components/workspace-picker.md +193 -0
- package/docs/concepts/building-and-packaging.md +2 -2
- package/docs/concepts/packages-and-entry-points.md +14 -13
- package/docs/concepts/state-guard.md +3 -3
- package/docs/quickstart.md +1 -1
- package/git.js +13 -0
- package/package.json +6 -1
- package/src/charts/plot_config_editor.js +92 -6
- package/src/charts/plot_grid.js +6 -1
- package/src/flow/kit/dom.js +28 -119
- package/src/flow/kit/step_picker.js +1 -1
- package/src/flow/kit/value_picker.js +1 -1
- package/src/flow/outline/menu.js +1 -1
- package/src/git/diff_viewer.js +212 -0
- package/src/git/diff_window.js +51 -0
- package/src/git/git_graph.js +401 -0
- package/src/git/git_ops.js +61 -0
- package/src/git/git_panel.js +755 -0
- package/src/git/index.js +9 -0
- package/src/git/line_diff.js +202 -0
- package/src/tiles/tile_base.js +5 -4
- package/src/tiling/desktops.js +10 -8
- package/src/tiling/keymap.js +6 -5
- package/src/tiling/kind_taxonomy.js +15 -2
- package/src/tiling/shell.js +124 -17
- package/src/tiling/tile_renderer.js +2 -3
- package/src/ui/components/action_dropdown.js +4 -78
- package/src/ui/components/anchored_layer.js +255 -0
- package/src/ui/components/chat_shell.js +926 -0
- package/src/ui/components/chat_state_machine.js +223 -0
- package/src/ui/components/choice_controls.js +45 -2
- package/src/ui/components/color_field.js +3 -2
- package/src/ui/components/color_list_field.js +3 -2
- package/src/ui/components/combobox.js +422 -0
- package/src/ui/components/context_menu.js +8 -2
- package/src/ui/components/conversation_view.js +17 -2
- package/src/ui/components/data_table.js +2 -2
- package/src/ui/components/filter_builder.js +791 -0
- package/src/ui/components/filter_model.js +682 -0
- package/src/ui/components/inline_renamer.js +10 -5
- package/src/ui/components/managed_window.js +24 -47
- package/src/ui/components/modal.js +104 -117
- package/src/ui/components/option_list.js +160 -0
- package/src/ui/components/popover.js +94 -0
- package/src/ui/components/recent_items.js +169 -0
- package/src/ui/components/select.js +322 -0
- package/src/ui/components/tag_input.js +77 -8
- package/src/ui/components/workspace_picker.js +1253 -0
- package/src/ui/utils/fire_event.js +17 -0
- package/src/ui/utils/focusable.js +25 -0
- package/src/ui/utils/modal_host.js +27 -0
- package/src/ui/utils/tooltip_service.js +81 -14
- package/widgets.js +12 -2
- package/css/base.css +0 -10547
- package/css/overrides.css +0 -2596
- package/dist/chunk-2Z2ENHR3.js.map +0 -7
- package/dist/chunk-7JI7HHSY.js +0 -107
- package/dist/chunk-7JI7HHSY.js.map +0 -7
- package/dist/chunk-7OL6F2AM.js.map +0 -7
- package/dist/chunk-MIW7D6WC.js +0 -526
- package/dist/chunk-MIW7D6WC.js.map +0 -7
- package/dist/chunk-PGZ4EHSE.js.map +0 -7
- package/dist/chunk-XJNADRGR.js.map +0 -7
- /package/dist/{chunk-REJ5HLIM.js.map → chunk-HYJYYEPK.js.map} +0 -0
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
choiceSections,
|
|
3
|
+
collectFocusable,
|
|
4
|
+
filterSections,
|
|
5
|
+
isInAnchoredLayer,
|
|
6
|
+
openAnchoredLayer,
|
|
7
|
+
shownValue
|
|
8
|
+
} from "./chunk-Q7BALEPX.js";
|
|
1
9
|
import {
|
|
2
10
|
getSetting
|
|
3
11
|
} from "./chunk-HNU6MM2B.js";
|
|
@@ -21,23 +29,6 @@ var DEFAULT_ICON = "web_asset";
|
|
|
21
29
|
var _zIndexCounter = 0;
|
|
22
30
|
var _activeWindows = /* @__PURE__ */ new Map();
|
|
23
31
|
var _escAnswered = /* @__PURE__ */ new WeakSet();
|
|
24
|
-
function _collectFocusable(root) {
|
|
25
|
-
if (!root) return [];
|
|
26
|
-
const sel = [
|
|
27
|
-
"a[href]",
|
|
28
|
-
"button:not([disabled])",
|
|
29
|
-
'input:not([disabled]):not([type="hidden"])',
|
|
30
|
-
"select:not([disabled])",
|
|
31
|
-
"textarea:not([disabled])",
|
|
32
|
-
'[tabindex]:not([tabindex="-1"])'
|
|
33
|
-
].join(",");
|
|
34
|
-
return Array.from(root.querySelectorAll(sel)).filter((el2) => {
|
|
35
|
-
if (el2.hidden) return false;
|
|
36
|
-
if (el2.closest("[hidden]")) return false;
|
|
37
|
-
const r = el2.getBoundingClientRect();
|
|
38
|
-
return r.width > 0 || r.height > 0;
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
32
|
var _stateCache = null;
|
|
42
33
|
function _loadState() {
|
|
43
34
|
if (_stateCache) return _stateCache;
|
|
@@ -93,14 +84,14 @@ function _focusFirstIn(root) {
|
|
|
93
84
|
'[contenteditable="true"]',
|
|
94
85
|
'[tabindex]:not([tabindex="-1"])'
|
|
95
86
|
].join(",");
|
|
96
|
-
for (const
|
|
97
|
-
if (
|
|
87
|
+
for (const el of root.querySelectorAll(sel)) {
|
|
88
|
+
if (el.hidden || el.closest("[hidden]")) continue;
|
|
98
89
|
try {
|
|
99
|
-
|
|
90
|
+
el.focus({ preventScroll: true });
|
|
100
91
|
} catch {
|
|
101
92
|
continue;
|
|
102
93
|
}
|
|
103
|
-
if (
|
|
94
|
+
if (el.ownerDocument.activeElement === el) return true;
|
|
104
95
|
}
|
|
105
96
|
return false;
|
|
106
97
|
}
|
|
@@ -327,11 +318,15 @@ var ManagedWindow = class {
|
|
|
327
318
|
this._clearTargetProperties();
|
|
328
319
|
document.removeEventListener("keydown", this._boundOnKeyDown);
|
|
329
320
|
this._teardownContainerResizeObserver();
|
|
330
|
-
|
|
331
|
-
detail: { id: this.id, container: this.container }
|
|
332
|
-
}));
|
|
321
|
+
this._announce("managed-window-closed", { id: this.id, container: this.container });
|
|
333
322
|
this._release();
|
|
334
323
|
}
|
|
324
|
+
/** Every window event goes out on the page's window, and a window takes only
|
|
325
|
+
* an event built by its own `CustomEvent`: Node's global one is refused
|
|
326
|
+
* under jsdom, in the middle of the close that fired it. */
|
|
327
|
+
_announce(name, detail) {
|
|
328
|
+
window.dispatchEvent(new window.CustomEvent(name, { detail }));
|
|
329
|
+
}
|
|
335
330
|
/** BUG-0053. A closed window leaves the registry and lets go of what it
|
|
336
331
|
* held: its content, its DOM and every consumer callback (an `onClose`
|
|
337
332
|
* typically captures its owner). Until now the registry kept all of it
|
|
@@ -376,14 +371,12 @@ var ManagedWindow = class {
|
|
|
376
371
|
if (this.backdropElement) {
|
|
377
372
|
this.backdropElement.style.display = "none";
|
|
378
373
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
}));
|
|
374
|
+
this._announce("managed-window-minimized", {
|
|
375
|
+
id: this.id,
|
|
376
|
+
title: this.title,
|
|
377
|
+
icon: this.icon,
|
|
378
|
+
container: this.container
|
|
379
|
+
});
|
|
387
380
|
if (!this.element) return;
|
|
388
381
|
this._cancelRestoreAnimation();
|
|
389
382
|
if (!getSetting("window.animateMinimize", true)) {
|
|
@@ -484,9 +477,7 @@ var ManagedWindow = class {
|
|
|
484
477
|
}
|
|
485
478
|
this.isMinimized = false;
|
|
486
479
|
this.bringToFront();
|
|
487
|
-
|
|
488
|
-
detail: { id: this.id, container: this.container }
|
|
489
|
-
}));
|
|
480
|
+
this._announce("managed-window-restored", { id: this.id, container: this.container });
|
|
490
481
|
}
|
|
491
482
|
/**
|
|
492
483
|
* Toggle maximize state.
|
|
@@ -547,9 +538,7 @@ var ManagedWindow = class {
|
|
|
547
538
|
this.element?.classList.toggle("twm-managed-window--maximized", this.isMaximized);
|
|
548
539
|
this._applyPosition();
|
|
549
540
|
this._saveCurrentState();
|
|
550
|
-
|
|
551
|
-
detail: { id: this.id, maximized: this.isMaximized, container: this.container }
|
|
552
|
-
}));
|
|
541
|
+
this._announce("managed-window-maximized", { id: this.id, maximized: this.isMaximized, container: this.container });
|
|
553
542
|
}
|
|
554
543
|
/**
|
|
555
544
|
* Find the taskbar button for this window.
|
|
@@ -864,9 +853,7 @@ var ManagedWindow = class {
|
|
|
864
853
|
this.height = bounds.height;
|
|
865
854
|
}
|
|
866
855
|
this._applyPosition();
|
|
867
|
-
|
|
868
|
-
detail: { id: this.id, container }
|
|
869
|
-
}));
|
|
856
|
+
this._announce("managed-window-moved", { id: this.id, container });
|
|
870
857
|
return true;
|
|
871
858
|
}
|
|
872
859
|
/** R1/R3. The container this drag started in, or null when the drag did
|
|
@@ -1243,9 +1230,7 @@ var ManagedWindow = class {
|
|
|
1243
1230
|
this.height = rect.height;
|
|
1244
1231
|
this._applyPosition();
|
|
1245
1232
|
this.element?.classList.add("twm-managed-window--snapped");
|
|
1246
|
-
|
|
1247
|
-
detail: { id: this.id, zone, container: this.container }
|
|
1248
|
-
}));
|
|
1233
|
+
this._announce("managed-window-snapped", { id: this.id, zone, container: this.container });
|
|
1249
1234
|
}
|
|
1250
1235
|
/** Restore the geometry a snap replaced. Called when a snapped window is
|
|
1251
1236
|
* picked up again, which is the gesture that means "un-snap". */
|
|
@@ -1329,7 +1314,8 @@ var ManagedWindow = class {
|
|
|
1329
1314
|
}
|
|
1330
1315
|
if (topWindow !== this) return;
|
|
1331
1316
|
if (e.key === "Tab" && this.modal && this.element) {
|
|
1332
|
-
|
|
1317
|
+
if (isInAnchoredLayer(document.activeElement)) return;
|
|
1318
|
+
const focusables = collectFocusable(this.element);
|
|
1333
1319
|
if (focusables.length === 0) {
|
|
1334
1320
|
e.preventDefault();
|
|
1335
1321
|
return;
|
|
@@ -1551,6 +1537,12 @@ var ToggleField = class {
|
|
|
1551
1537
|
}
|
|
1552
1538
|
};
|
|
1553
1539
|
|
|
1540
|
+
// src/ui/utils/fire_event.js
|
|
1541
|
+
function fireEvent(target, type, init) {
|
|
1542
|
+
const view = target.ownerDocument.defaultView;
|
|
1543
|
+
target.dispatchEvent(new view.CustomEvent(type, init));
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1554
1546
|
// src/ui/components/color_field.js
|
|
1555
1547
|
var HEX_RE = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
1556
1548
|
var RGB_RE = /^rgba?\(\s*([^)]*)\)$/i;
|
|
@@ -1691,15 +1683,15 @@ var ColorField = class {
|
|
|
1691
1683
|
this.swatch = null;
|
|
1692
1684
|
this.hex = null;
|
|
1693
1685
|
}
|
|
1694
|
-
#listen(
|
|
1695
|
-
|
|
1696
|
-
this._disposers.push(() =>
|
|
1686
|
+
#listen(el, type, fn) {
|
|
1687
|
+
el.addEventListener(type, fn);
|
|
1688
|
+
this._disposers.push(() => el.removeEventListener(type, fn));
|
|
1697
1689
|
}
|
|
1698
1690
|
/** Put `text` in the box and fire the box's own events, as typing would. */
|
|
1699
1691
|
#writeBox(text, commit) {
|
|
1700
1692
|
this.hex.value = text;
|
|
1701
|
-
this.hex
|
|
1702
|
-
if (commit) this.hex
|
|
1693
|
+
fireEvent(this.hex, "input", { bubbles: true });
|
|
1694
|
+
if (commit) fireEvent(this.hex, "change", { bubbles: true });
|
|
1703
1695
|
}
|
|
1704
1696
|
/** Swatch and invalid mark follow the box; `commit === null` is silent. */
|
|
1705
1697
|
#sync(commit) {
|
|
@@ -1710,332 +1702,488 @@ var ColorField = class {
|
|
|
1710
1702
|
}
|
|
1711
1703
|
};
|
|
1712
1704
|
|
|
1713
|
-
// src/ui/components/
|
|
1714
|
-
var
|
|
1715
|
-
function
|
|
1705
|
+
// src/ui/components/option_list.js
|
|
1706
|
+
var SEQ = 0;
|
|
1707
|
+
function node(tag, className, text) {
|
|
1716
1708
|
const n = document.createElement(tag);
|
|
1717
1709
|
if (className) n.className = className;
|
|
1718
1710
|
if (text !== void 0 && text !== null) n.textContent = String(text);
|
|
1719
1711
|
return n;
|
|
1720
1712
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
if (
|
|
1724
|
-
|
|
1725
|
-
|
|
1713
|
+
function marked(target, text, needle) {
|
|
1714
|
+
const at = needle ? text.toLowerCase().indexOf(needle.toLowerCase()) : -1;
|
|
1715
|
+
if (at < 0) {
|
|
1716
|
+
target.textContent = text;
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
target.append(
|
|
1720
|
+
text.slice(0, at),
|
|
1721
|
+
node("mark", "twm-option-list__match", text.slice(at, at + needle.length)),
|
|
1722
|
+
text.slice(at + needle.length)
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
function createOptionList({ onPick, onActive = null }) {
|
|
1726
|
+
const id = `twm-option-list-${++SEQ}`;
|
|
1727
|
+
const el = node("div", "twm-option-list");
|
|
1728
|
+
el.id = id;
|
|
1729
|
+
el.setAttribute("role", "listbox");
|
|
1730
|
+
let rows = [];
|
|
1731
|
+
let active = -1;
|
|
1732
|
+
const paintActive = () => {
|
|
1733
|
+
rows.forEach(({ row: row2 }, i) => row2.classList.toggle("twm-option-list__row--active", i === active));
|
|
1734
|
+
const row = rows[active]?.row;
|
|
1735
|
+
row?.scrollIntoView?.({ block: "nearest" });
|
|
1736
|
+
onActive?.(row ? row.id : null);
|
|
1737
|
+
};
|
|
1738
|
+
const rowFor = (option, { glyphs, mark, isCurrent }) => {
|
|
1739
|
+
const row = node("div", "twm-option-list__row");
|
|
1740
|
+
row.id = `${id}-${rows.length}`;
|
|
1741
|
+
row.setAttribute("role", "option");
|
|
1742
|
+
row.setAttribute("aria-selected", String(!!isCurrent?.(option)));
|
|
1743
|
+
if (option.disabled) row.setAttribute("aria-disabled", "true");
|
|
1744
|
+
row.title = option.description ? `${option.label} \u2014 ${option.description}` : option.label;
|
|
1745
|
+
if (glyphs) {
|
|
1746
|
+
const glyph = node("span", "twm-option-list__glyph material-symbols-outlined", option.icon || "");
|
|
1747
|
+
glyph.setAttribute("aria-hidden", "true");
|
|
1748
|
+
row.appendChild(glyph);
|
|
1749
|
+
}
|
|
1750
|
+
const words = node("span", "twm-option-list__words");
|
|
1751
|
+
const label = node("span", "twm-option-list__label");
|
|
1752
|
+
marked(label, option.label, mark);
|
|
1753
|
+
words.appendChild(label);
|
|
1754
|
+
if (option.description) words.appendChild(node("span", "twm-option-list__description", option.description));
|
|
1755
|
+
if (option.disabled && option.reason) words.appendChild(node("span", "twm-choice__reason", option.reason));
|
|
1756
|
+
row.appendChild(words);
|
|
1757
|
+
if (option.hint) row.appendChild(node("span", "twm-option-list__hint", option.hint));
|
|
1758
|
+
return row;
|
|
1759
|
+
};
|
|
1760
|
+
const enabled = () => rows.map((r, i) => r.option.disabled ? -1 : i).filter((i) => i >= 0);
|
|
1761
|
+
el.addEventListener("mousedown", (ev) => ev.preventDefault());
|
|
1762
|
+
el.addEventListener("click", (ev) => {
|
|
1763
|
+
const row = ev.target.closest?.('[role="option"]');
|
|
1764
|
+
const hit = rows.find((r) => r.row === row);
|
|
1765
|
+
if (hit && !hit.option.disabled) onPick(hit.option);
|
|
1766
|
+
});
|
|
1726
1767
|
return {
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1768
|
+
el,
|
|
1769
|
+
id,
|
|
1770
|
+
/**
|
|
1771
|
+
* Draw `sections` (`[{ heading, items }]`, as `choiceSections` gives them).
|
|
1772
|
+
* @param {object} [opts]
|
|
1773
|
+
* @param {string} [opts.mark] The text to mark in each label.
|
|
1774
|
+
* @param {(option: object) => boolean} [opts.isCurrent] Is this the current value?
|
|
1775
|
+
*/
|
|
1776
|
+
setRows(sections, { mark = "", isCurrent = null } = {}) {
|
|
1777
|
+
rows = [];
|
|
1778
|
+
active = -1;
|
|
1779
|
+
el.replaceChildren();
|
|
1780
|
+
const glyphs = sections.some((s) => s.items.some((o) => o.icon));
|
|
1781
|
+
for (const { heading, items } of sections) {
|
|
1782
|
+
let parent = el;
|
|
1783
|
+
if (heading != null) {
|
|
1784
|
+
parent = node("div", "twm-option-list__section");
|
|
1785
|
+
parent.setAttribute("role", "group");
|
|
1786
|
+
parent.setAttribute("aria-label", heading);
|
|
1787
|
+
const title = node("div", "twm-choice__group", heading);
|
|
1788
|
+
title.setAttribute("aria-hidden", "true");
|
|
1789
|
+
parent.appendChild(title);
|
|
1790
|
+
el.appendChild(parent);
|
|
1791
|
+
}
|
|
1792
|
+
for (const option of items) {
|
|
1793
|
+
const row = rowFor(option, { glyphs, mark, isCurrent });
|
|
1794
|
+
rows.push({ option, row });
|
|
1795
|
+
parent.appendChild(row);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
paintActive();
|
|
1799
|
+
},
|
|
1800
|
+
/** One line of text in place of the rows: nothing matched, still loading, a failure. */
|
|
1801
|
+
setMessage(text) {
|
|
1802
|
+
rows = [];
|
|
1803
|
+
active = -1;
|
|
1804
|
+
el.replaceChildren(node("div", "twm-option-list__message", text));
|
|
1805
|
+
paintActive();
|
|
1806
|
+
},
|
|
1807
|
+
/** How many rows are drawn. */
|
|
1808
|
+
get size() {
|
|
1809
|
+
return rows.length;
|
|
1810
|
+
},
|
|
1811
|
+
/** The drawn options, in order. */
|
|
1812
|
+
options: () => rows.map((r) => r.option),
|
|
1813
|
+
/** The active row's option, or null. */
|
|
1814
|
+
activeOption: () => rows[active]?.option ?? null,
|
|
1815
|
+
/** Make row `index` active; -1, or a row that is disabled, makes none active. */
|
|
1816
|
+
setActive(index) {
|
|
1817
|
+
active = rows[index] && !rows[index].option.disabled ? index : -1;
|
|
1818
|
+
paintActive();
|
|
1819
|
+
},
|
|
1820
|
+
/** The next row that may be chosen, by `step`, wrapping. From none: the first or the last. */
|
|
1821
|
+
move(step) {
|
|
1822
|
+
const stops = enabled();
|
|
1823
|
+
if (!stops.length) return;
|
|
1824
|
+
const at = stops.indexOf(active);
|
|
1825
|
+
const next = at < 0 ? step > 0 ? 0 : stops.length - 1 : (at + step + stops.length) % stops.length;
|
|
1826
|
+
active = stops[next];
|
|
1827
|
+
paintActive();
|
|
1828
|
+
},
|
|
1829
|
+
/** The first (`step` 1) or last (`step` -1) row that may be chosen. */
|
|
1830
|
+
moveToEnd(step) {
|
|
1831
|
+
const stops = enabled();
|
|
1832
|
+
active = stops.length ? stops[step > 0 ? 0 : stops.length - 1] : -1;
|
|
1833
|
+
paintActive();
|
|
1834
|
+
}
|
|
1732
1835
|
};
|
|
1733
1836
|
}
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1837
|
+
|
|
1838
|
+
// src/ui/components/combobox.js
|
|
1839
|
+
var DEBOUNCE_MS = 150;
|
|
1840
|
+
var SEQ2 = 0;
|
|
1841
|
+
function attachInputOptions(input, {
|
|
1842
|
+
options = [],
|
|
1843
|
+
onPick,
|
|
1844
|
+
anchor = null,
|
|
1845
|
+
isCurrent = null,
|
|
1846
|
+
autoHighlight = false,
|
|
1847
|
+
isCandidate = null,
|
|
1848
|
+
normalise = (text) => text,
|
|
1849
|
+
openOnFocus = false,
|
|
1850
|
+
passEnter = false,
|
|
1851
|
+
loadingText = "Loading\u2026",
|
|
1852
|
+
errorText = "Could not load the options.",
|
|
1853
|
+
debounce = DEBOUNCE_MS,
|
|
1854
|
+
panelClassName = "",
|
|
1855
|
+
onSettle = null
|
|
1856
|
+
} = {}) {
|
|
1857
|
+
let source = options;
|
|
1858
|
+
let layer = null;
|
|
1859
|
+
let wanted = false;
|
|
1860
|
+
let query = null;
|
|
1861
|
+
let asked = 0;
|
|
1862
|
+
let timer = null;
|
|
1863
|
+
let controller = null;
|
|
1864
|
+
let picking = false;
|
|
1865
|
+
const list = createOptionList({
|
|
1866
|
+
onPick: (option) => take(option),
|
|
1867
|
+
onActive: (id) => {
|
|
1868
|
+
if (id) input.setAttribute("aria-activedescendant", id);
|
|
1869
|
+
else input.removeAttribute("aria-activedescendant");
|
|
1870
|
+
}
|
|
1871
|
+
});
|
|
1872
|
+
const panel = document.createElement("div");
|
|
1873
|
+
panel.className = `twm-option-panel${panelClassName ? ` ${panelClassName}` : ""}`;
|
|
1874
|
+
panel.appendChild(list.el);
|
|
1875
|
+
input.setAttribute("autocomplete", "off");
|
|
1876
|
+
input.setAttribute("role", "combobox");
|
|
1877
|
+
input.setAttribute("aria-autocomplete", "list");
|
|
1878
|
+
input.setAttribute("aria-expanded", "false");
|
|
1879
|
+
input.setAttribute("aria-controls", list.id);
|
|
1880
|
+
const show = () => {
|
|
1881
|
+
if (layer) {
|
|
1882
|
+
layer.place();
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
layer = openAnchoredLayer({
|
|
1886
|
+
anchor: anchor || input,
|
|
1887
|
+
el: panel,
|
|
1888
|
+
gap: 4,
|
|
1889
|
+
matchWidth: true,
|
|
1890
|
+
returnFocus: false,
|
|
1891
|
+
onClose() {
|
|
1892
|
+
layer = null;
|
|
1893
|
+
wanted = false;
|
|
1894
|
+
input.setAttribute("aria-expanded", "false");
|
|
1895
|
+
input.removeAttribute("aria-activedescendant");
|
|
1896
|
+
}
|
|
1897
|
+
});
|
|
1898
|
+
if (!layer) {
|
|
1899
|
+
wanted = false;
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
input.setAttribute("aria-expanded", "true");
|
|
1903
|
+
};
|
|
1904
|
+
function forget() {
|
|
1905
|
+
clearTimeout(timer);
|
|
1906
|
+
asked += 1;
|
|
1907
|
+
controller?.abort();
|
|
1908
|
+
controller = null;
|
|
1909
|
+
}
|
|
1910
|
+
function close(reason = "closed") {
|
|
1911
|
+
wanted = false;
|
|
1912
|
+
forget();
|
|
1913
|
+
layer?.close(reason);
|
|
1914
|
+
}
|
|
1915
|
+
function highlight(typed) {
|
|
1916
|
+
const text = normalise(typed.trim());
|
|
1917
|
+
if (!autoHighlight || !text) {
|
|
1918
|
+
list.setActive(-1);
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
const exact = list.options().findIndex((o) => normalise(shownValue(o.value)) === text);
|
|
1922
|
+
if (exact >= 0) list.setActive(exact);
|
|
1923
|
+
else if (isCandidate?.(text)) list.setActive(-1);
|
|
1924
|
+
else list.moveToEnd(1);
|
|
1739
1925
|
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1926
|
+
function drawRows(sections) {
|
|
1927
|
+
if (!wanted) return;
|
|
1928
|
+
if (!sections.length) {
|
|
1929
|
+
layer?.close("empty");
|
|
1930
|
+
wanted = true;
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
const text = query ?? "";
|
|
1934
|
+
list.setRows(sections, { mark: normalise(text.trim()), isCurrent });
|
|
1935
|
+
show();
|
|
1936
|
+
highlight(text);
|
|
1937
|
+
}
|
|
1938
|
+
function ask(text) {
|
|
1939
|
+
controller = new AbortController();
|
|
1940
|
+
const token = asked;
|
|
1941
|
+
const failed = (err) => {
|
|
1942
|
+
if (token !== asked) return;
|
|
1943
|
+
console.warn("[createCombobox] options failed", err);
|
|
1944
|
+
if (!wanted) return;
|
|
1945
|
+
list.setMessage(errorText);
|
|
1946
|
+
show();
|
|
1947
|
+
};
|
|
1948
|
+
let answer;
|
|
1949
|
+
try {
|
|
1950
|
+
answer = source(text, { signal: controller.signal });
|
|
1951
|
+
} catch (err) {
|
|
1952
|
+
failed(err);
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
if (typeof answer?.then !== "function") {
|
|
1956
|
+
drawRows(choiceSections(answer));
|
|
1957
|
+
return;
|
|
1749
1958
|
}
|
|
1750
|
-
if (
|
|
1751
|
-
|
|
1959
|
+
if (!list.size && wanted) {
|
|
1960
|
+
list.setMessage(loadingText);
|
|
1961
|
+
show();
|
|
1962
|
+
}
|
|
1963
|
+
answer.then((rows) => {
|
|
1964
|
+
if (token === asked) drawRows(choiceSections(rows));
|
|
1965
|
+
}, failed);
|
|
1752
1966
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
} = {}) {
|
|
1781
|
-
const box = el("select", className);
|
|
1782
|
-
if (label) box.setAttribute("aria-label", label);
|
|
1783
|
-
let entries = [];
|
|
1784
|
-
const add = (parent, o) => {
|
|
1785
|
-
const attr = valueAttr === "index" ? String(entries.length) : o.value === void 0 ? "" : String(o.value);
|
|
1786
|
-
const opt = el("option", "", optionText(o));
|
|
1787
|
-
opt.value = attr;
|
|
1788
|
-
if (o.disabled) {
|
|
1789
|
-
opt.disabled = true;
|
|
1790
|
-
if (o.reason) opt.title = o.reason;
|
|
1791
|
-
}
|
|
1792
|
-
parent.appendChild(opt);
|
|
1793
|
-
entries.push(o);
|
|
1794
|
-
};
|
|
1795
|
-
const firstEnabled = () => {
|
|
1796
|
-
const i = entries.findIndex((e) => !e.disabled);
|
|
1797
|
-
return i >= 0 ? i : 0;
|
|
1798
|
-
};
|
|
1799
|
-
const select = (v) => {
|
|
1800
|
-
const start = empty !== null ? 1 : 0;
|
|
1801
|
-
const at = choiceIndex(entries, v, loose, start);
|
|
1802
|
-
if (at < 0 || entries[at].disabled || entries[at].stray) return false;
|
|
1803
|
-
box.selectedIndex = at;
|
|
1967
|
+
function refresh() {
|
|
1968
|
+
forget();
|
|
1969
|
+
if (typeof source !== "function") {
|
|
1970
|
+
const all = choiceSections(source);
|
|
1971
|
+
drawRows(query === null ? all : filterSections(all, query));
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
const text = query ?? "";
|
|
1975
|
+
if (query === null || debounce <= 0) {
|
|
1976
|
+
ask(text);
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
const token = asked;
|
|
1980
|
+
timer = setTimeout(() => {
|
|
1981
|
+
if (token === asked) ask(text);
|
|
1982
|
+
}, debounce);
|
|
1983
|
+
}
|
|
1984
|
+
function take(option) {
|
|
1985
|
+
close("pick");
|
|
1986
|
+
picking = true;
|
|
1987
|
+
onPick?.(option);
|
|
1988
|
+
picking = false;
|
|
1989
|
+
}
|
|
1990
|
+
function takeActive() {
|
|
1991
|
+
const option = layer && list.activeOption();
|
|
1992
|
+
if (!option) return false;
|
|
1993
|
+
take(option);
|
|
1804
1994
|
return true;
|
|
1995
|
+
}
|
|
1996
|
+
const openFor = (text) => {
|
|
1997
|
+
query = text;
|
|
1998
|
+
wanted = true;
|
|
1999
|
+
refresh();
|
|
1805
2000
|
};
|
|
1806
|
-
const
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
2001
|
+
const onInput = () => openFor(input.value);
|
|
2002
|
+
const onClick = () => openFor(null);
|
|
2003
|
+
const onFocus = () => {
|
|
2004
|
+
if (openOnFocus && !layer && !picking) openFor(input.value || null);
|
|
2005
|
+
};
|
|
2006
|
+
const onBlur = () => close("blur");
|
|
2007
|
+
const onKey = (ev) => {
|
|
2008
|
+
if (ev.isComposing) return;
|
|
2009
|
+
if (ev.key === "ArrowDown" || ev.key === "ArrowUp") {
|
|
2010
|
+
ev.preventDefault();
|
|
2011
|
+
ev.stopPropagation();
|
|
2012
|
+
if (!layer) openFor(null);
|
|
2013
|
+
list.move(ev.key === "ArrowDown" ? 1 : -1);
|
|
1818
2014
|
return;
|
|
1819
2015
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
value: void 0,
|
|
1827
|
-
label: String(empty),
|
|
1828
|
-
description: null,
|
|
1829
|
-
disabled: false,
|
|
1830
|
-
reason: null
|
|
1831
|
-
});
|
|
1832
|
-
for (const o of opts || []) {
|
|
1833
|
-
if (isGroup(o)) {
|
|
1834
|
-
const g = el("optgroup");
|
|
1835
|
-
g.label = String(o.group ?? "");
|
|
1836
|
-
for (const inner of o.options) add(g, choiceOption(inner));
|
|
1837
|
-
box.appendChild(g);
|
|
1838
|
-
} else {
|
|
1839
|
-
add(box, choiceOption(o));
|
|
2016
|
+
if (ev.key === "Enter") {
|
|
2017
|
+
if (!layer) return;
|
|
2018
|
+
if (takeActive()) {
|
|
2019
|
+
ev.preventDefault();
|
|
2020
|
+
ev.stopImmediatePropagation();
|
|
2021
|
+
return;
|
|
1840
2022
|
}
|
|
2023
|
+
close();
|
|
2024
|
+
if (passEnter) return;
|
|
2025
|
+
ev.preventDefault();
|
|
2026
|
+
ev.stopPropagation();
|
|
2027
|
+
onSettle?.();
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
if (ev.key === "Tab") {
|
|
2031
|
+
close("blur");
|
|
2032
|
+
onSettle?.();
|
|
1841
2033
|
}
|
|
1842
|
-
place(v);
|
|
1843
2034
|
};
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
2035
|
+
input.addEventListener("input", onInput);
|
|
2036
|
+
input.addEventListener("click", onClick);
|
|
2037
|
+
input.addEventListener("focus", onFocus);
|
|
2038
|
+
input.addEventListener("blur", onBlur);
|
|
2039
|
+
input.addEventListener("keydown", onKey);
|
|
1847
2040
|
return {
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
2041
|
+
get isOpen() {
|
|
2042
|
+
return !!layer;
|
|
2043
|
+
},
|
|
2044
|
+
/** Open it showing everything, or for `text`. */
|
|
2045
|
+
open(text = null) {
|
|
2046
|
+
openFor(text);
|
|
2047
|
+
},
|
|
2048
|
+
close,
|
|
2049
|
+
takeActive,
|
|
2050
|
+
/** Replace the options; an open list redraws. */
|
|
2051
|
+
setOptions(next) {
|
|
2052
|
+
source = next;
|
|
2053
|
+
if (wanted) refresh();
|
|
2054
|
+
},
|
|
2055
|
+
destroy() {
|
|
2056
|
+
close();
|
|
2057
|
+
input.removeEventListener("input", onInput);
|
|
2058
|
+
input.removeEventListener("click", onClick);
|
|
2059
|
+
input.removeEventListener("focus", onFocus);
|
|
2060
|
+
input.removeEventListener("blur", onBlur);
|
|
2061
|
+
input.removeEventListener("keydown", onKey);
|
|
1854
2062
|
}
|
|
1855
2063
|
};
|
|
1856
2064
|
}
|
|
1857
|
-
function
|
|
1858
|
-
options,
|
|
1859
|
-
value,
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
const
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
2065
|
+
function createCombobox({
|
|
2066
|
+
options = [],
|
|
2067
|
+
value = "",
|
|
2068
|
+
onChange = null,
|
|
2069
|
+
className = "",
|
|
2070
|
+
panelClassName = "",
|
|
2071
|
+
placeholder = "",
|
|
2072
|
+
title = "",
|
|
2073
|
+
ariaLabel = "",
|
|
2074
|
+
name = null,
|
|
2075
|
+
disabled = false,
|
|
2076
|
+
normalise = (text) => text,
|
|
2077
|
+
autoHighlight = true,
|
|
2078
|
+
isCandidate = null,
|
|
2079
|
+
validate = null,
|
|
2080
|
+
loadingText,
|
|
2081
|
+
errorText,
|
|
2082
|
+
debounce
|
|
2083
|
+
} = {}) {
|
|
2084
|
+
const handed = String(value ?? "");
|
|
2085
|
+
let current = normalise(handed);
|
|
2086
|
+
const el = document.createElement("div");
|
|
2087
|
+
el.className = `twm-combobox${className ? ` ${className}` : ""}`;
|
|
2088
|
+
const input = document.createElement("input");
|
|
2089
|
+
input.type = "text";
|
|
2090
|
+
input.className = "twm-combobox__input";
|
|
2091
|
+
input.spellcheck = false;
|
|
2092
|
+
input.value = current;
|
|
2093
|
+
input.disabled = !!disabled;
|
|
2094
|
+
if (name) input.name = name;
|
|
2095
|
+
if (placeholder) input.placeholder = placeholder;
|
|
2096
|
+
if (title) input.title = title;
|
|
2097
|
+
if (ariaLabel) input.setAttribute("aria-label", ariaLabel);
|
|
2098
|
+
const error = document.createElement("p");
|
|
2099
|
+
error.className = "twm-combobox__error";
|
|
2100
|
+
error.hidden = true;
|
|
2101
|
+
error.id = `twm-combobox-error-${++SEQ2}`;
|
|
2102
|
+
el.append(input, error);
|
|
2103
|
+
function report(next, option) {
|
|
2104
|
+
const settled = normalise(String(next ?? ""));
|
|
2105
|
+
if (settled === current) return;
|
|
2106
|
+
current = settled;
|
|
2107
|
+
onChange?.(current, option ?? null);
|
|
2108
|
+
}
|
|
2109
|
+
const clearValidity = () => {
|
|
2110
|
+
error.hidden = true;
|
|
2111
|
+
error.textContent = "";
|
|
2112
|
+
input.removeAttribute("aria-invalid");
|
|
2113
|
+
input.removeAttribute("aria-describedby");
|
|
1893
2114
|
};
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
const offered = (v) => {
|
|
1900
|
-
const o = findChoice(list, v, loose);
|
|
1901
|
-
return o && !o.disabled ? o : void 0;
|
|
1902
|
-
};
|
|
1903
|
-
chosen = new Set(wanted.map(offered).filter(Boolean).map((o) => o.value));
|
|
1904
|
-
strays = keepMissing ? wanted.filter((v) => !offered(v)) : [];
|
|
1905
|
-
root.replaceChildren();
|
|
1906
|
-
list.forEach((o, i) => {
|
|
1907
|
-
if (heads.has(i)) root.appendChild(groupHeading(heads.get(i)));
|
|
1908
|
-
root.appendChild(chipFor(o.label, chosen.has(o.value), () => {
|
|
1909
|
-
if (o.disabled) return false;
|
|
1910
|
-
if (chosen.has(o.value)) chosen.delete(o.value);
|
|
1911
|
-
else chosen.add(o.value);
|
|
1912
|
-
return chosen.has(o.value);
|
|
1913
|
-
}, { disabled: o.disabled, reason: o.disabled ? o.reason : null }));
|
|
1914
|
-
});
|
|
1915
|
-
for (const v of [...strays]) {
|
|
1916
|
-
root.appendChild(chipFor(notFound(shownValue(v)), true, () => {
|
|
1917
|
-
const i = strays.indexOf(v);
|
|
1918
|
-
if (i >= 0) {
|
|
1919
|
-
strays.splice(i, 1);
|
|
1920
|
-
return false;
|
|
1921
|
-
}
|
|
1922
|
-
strays.push(v);
|
|
1923
|
-
return true;
|
|
1924
|
-
}, { stray: true }));
|
|
2115
|
+
function paintValidity() {
|
|
2116
|
+
const message = validate && current ? validate(current) || null : null;
|
|
2117
|
+
if (!message) {
|
|
2118
|
+
clearValidity();
|
|
2119
|
+
return;
|
|
1925
2120
|
}
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
const named = choiceIndex(list, current, loose);
|
|
1958
|
-
const at = named >= 0 && !list[named].disabled ? named : -1;
|
|
1959
|
-
list.forEach((o, i) => {
|
|
1960
|
-
if (heads.has(i)) root.appendChild(groupHeading(heads.get(i)));
|
|
1961
|
-
const row = el("label", `twm-flow-choice${o.disabled ? " twm-choice--disabled" : ""}`);
|
|
1962
|
-
const radio = el("input", "twm-flow-choice__radio");
|
|
1963
|
-
radio.type = "radio";
|
|
1964
|
-
radio.name = name;
|
|
1965
|
-
radio.checked = i === at;
|
|
1966
|
-
radio.disabled = Boolean(readOnly) || o.disabled;
|
|
1967
|
-
radio.addEventListener("change", () => {
|
|
1968
|
-
if (radio.checked) onChange?.(o.value);
|
|
1969
|
-
});
|
|
1970
|
-
radios.push(radio);
|
|
1971
|
-
const words = el("span", "twm-flow-choice__words");
|
|
1972
|
-
words.appendChild(el("span", "twm-flow-choice__label", o.label));
|
|
1973
|
-
if (o.description !== null) words.appendChild(el("span", "twm-flow-choice__sub", o.description));
|
|
1974
|
-
if (o.disabled && o.reason) words.appendChild(el("span", "twm-choice__reason", o.reason));
|
|
1975
|
-
row.append(radio, words);
|
|
1976
|
-
root.appendChild(row);
|
|
1977
|
-
});
|
|
1978
|
-
if (keepMissing && current !== void 0 && named < 0) {
|
|
1979
|
-
root.appendChild(el("p", "twm-flow-field__note", notFound(shownValue(current))));
|
|
2121
|
+
error.textContent = message;
|
|
2122
|
+
error.hidden = false;
|
|
2123
|
+
input.setAttribute("aria-invalid", "true");
|
|
2124
|
+
input.setAttribute("aria-describedby", error.id);
|
|
2125
|
+
}
|
|
2126
|
+
function settle() {
|
|
2127
|
+
report(input.value, null);
|
|
2128
|
+
if (input.value !== current) input.value = current;
|
|
2129
|
+
paintValidity();
|
|
2130
|
+
}
|
|
2131
|
+
input.addEventListener("input", () => {
|
|
2132
|
+
report(input.value, null);
|
|
2133
|
+
clearValidity();
|
|
2134
|
+
});
|
|
2135
|
+
const menu = attachInputOptions(input, {
|
|
2136
|
+
options,
|
|
2137
|
+
autoHighlight,
|
|
2138
|
+
isCandidate,
|
|
2139
|
+
normalise,
|
|
2140
|
+
panelClassName,
|
|
2141
|
+
loadingText,
|
|
2142
|
+
errorText,
|
|
2143
|
+
debounce,
|
|
2144
|
+
isCurrent: (o) => normalise(shownValue(o.value)) === current,
|
|
2145
|
+
onSettle: settle,
|
|
2146
|
+
onPick(option) {
|
|
2147
|
+
input.value = shownValue(option.value);
|
|
2148
|
+
report(option.value, option.source);
|
|
2149
|
+
settle();
|
|
2150
|
+
input.focus();
|
|
2151
|
+
fireEvent(input, "change", { bubbles: true });
|
|
1980
2152
|
}
|
|
1981
|
-
};
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
2153
|
+
});
|
|
2154
|
+
input.addEventListener("blur", settle);
|
|
2155
|
+
paintValidity();
|
|
2156
|
+
if (onChange && current !== handed) onChange(current, null);
|
|
2157
|
+
return {
|
|
2158
|
+
el,
|
|
2159
|
+
input,
|
|
2160
|
+
get value() {
|
|
2161
|
+
return current;
|
|
2162
|
+
},
|
|
2163
|
+
set value(next) {
|
|
2164
|
+
current = normalise(String(next ?? ""));
|
|
2165
|
+
input.value = current;
|
|
2166
|
+
paintValidity();
|
|
2167
|
+
},
|
|
2168
|
+
/** The complaint on screen, or `null`. A caller that wants to refuse Save has this. */
|
|
2169
|
+
get error() {
|
|
2170
|
+
return error.hidden ? null : error.textContent;
|
|
1996
2171
|
},
|
|
1997
|
-
|
|
1998
|
-
|
|
2172
|
+
get isOpen() {
|
|
2173
|
+
return menu.isOpen;
|
|
2174
|
+
},
|
|
2175
|
+
setOptions: menu.setOptions,
|
|
2176
|
+
/** Open the list showing everything, or for `text`. */
|
|
2177
|
+
open: menu.open,
|
|
2178
|
+
close: () => menu.close(),
|
|
2179
|
+
focus() {
|
|
2180
|
+
input.focus();
|
|
2181
|
+
},
|
|
2182
|
+
destroy() {
|
|
2183
|
+
menu.destroy();
|
|
2184
|
+
el.remove();
|
|
1999
2185
|
}
|
|
2000
2186
|
};
|
|
2001
|
-
draw(options, value);
|
|
2002
|
-
return api;
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
// src/ui/components/row_activation.js
|
|
2006
|
-
var ROW_CONTROL_SELECTOR = [
|
|
2007
|
-
"button",
|
|
2008
|
-
"a",
|
|
2009
|
-
"input",
|
|
2010
|
-
"select",
|
|
2011
|
-
"textarea",
|
|
2012
|
-
"label",
|
|
2013
|
-
"summary",
|
|
2014
|
-
'[contenteditable=""]',
|
|
2015
|
-
'[contenteditable="true"]',
|
|
2016
|
-
"[data-twm-action]"
|
|
2017
|
-
].join(", ");
|
|
2018
|
-
function isRowControl(target, scope = null) {
|
|
2019
|
-
const hit = target?.closest?.(ROW_CONTROL_SELECTOR);
|
|
2020
|
-
return !!hit && (!scope || scope.contains(hit));
|
|
2021
|
-
}
|
|
2022
|
-
function endsTextSelection(target, scope = null) {
|
|
2023
|
-
const doc = target?.ownerDocument || (typeof document !== "undefined" ? document : null);
|
|
2024
|
-
const sel = doc?.getSelection?.();
|
|
2025
|
-
if (!sel || sel.isCollapsed || !String(sel)) return false;
|
|
2026
|
-
if (!scope) return true;
|
|
2027
|
-
const inside = (node) => !!node && scope.contains(node);
|
|
2028
|
-
return inside(sel.anchorNode) || inside(sel.focusNode);
|
|
2029
|
-
}
|
|
2030
|
-
function isRowActivation(ev, gesture = "click", { scope = null } = {}) {
|
|
2031
|
-
const target = ev?.target;
|
|
2032
|
-
if (!target?.closest) return false;
|
|
2033
|
-
if (isRowControl(target, scope)) return false;
|
|
2034
|
-
if (gesture === "click") {
|
|
2035
|
-
if ((ev.detail ?? 0) > 1) return false;
|
|
2036
|
-
if (endsTextSelection(target, scope)) return false;
|
|
2037
|
-
}
|
|
2038
|
-
return true;
|
|
2039
2187
|
}
|
|
2040
2188
|
|
|
2041
2189
|
export {
|
|
@@ -2043,18 +2191,12 @@ export {
|
|
|
2043
2191
|
DEFAULT_MAX_SAVED_WINDOWS,
|
|
2044
2192
|
ManagedWindow,
|
|
2045
2193
|
ToggleField,
|
|
2194
|
+
fireEvent,
|
|
2046
2195
|
isHexColor,
|
|
2047
2196
|
normalizeHex,
|
|
2048
2197
|
ColorField,
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
selectBox,
|
|
2053
|
-
toggleChips,
|
|
2054
|
-
choiceList,
|
|
2055
|
-
ROW_CONTROL_SELECTOR,
|
|
2056
|
-
isRowControl,
|
|
2057
|
-
endsTextSelection,
|
|
2058
|
-
isRowActivation
|
|
2198
|
+
createOptionList,
|
|
2199
|
+
attachInputOptions,
|
|
2200
|
+
createCombobox
|
|
2059
2201
|
};
|
|
2060
|
-
//# sourceMappingURL=chunk-
|
|
2202
|
+
//# sourceMappingURL=chunk-HL3IIHU3.js.map
|