flexdesk 0.18.0 → 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 +38 -0
- package/css/flexdesk.css +33 -5
- package/css/tokens.css +7 -0
- package/dist/charts.js +78 -12
- package/dist/charts.js.map +2 -2
- package/dist/{chunk-5W4EEKSK.js → chunk-3BLIVOFD.js} +3 -3
- package/dist/{chunk-P74F6I6P.js → chunk-4X4SY3CP.js} +4 -4
- package/dist/{chunk-P74F6I6P.js.map → chunk-4X4SY3CP.js.map} +2 -2
- package/dist/{chunk-ZL3VKINP.js → chunk-HL3IIHU3.js} +494 -3
- package/dist/{chunk-ZL3VKINP.js.map → chunk-HL3IIHU3.js.map} +4 -4
- package/dist/{chunk-RKVYIXDC.js → chunk-NFZJ6P42.js} +19 -500
- package/dist/chunk-NFZJ6P42.js.map +7 -0
- package/dist/{chunk-U5GKJUOC.js → chunk-QRBOIUU3.js} +5 -6
- package/dist/chunk-QRBOIUU3.js.map +7 -0
- package/dist/flexdesk.css +33 -5
- package/dist/flow.js +1 -1
- package/dist/git.js +3 -3
- package/dist/tiles.js +4 -4
- package/dist/tokens.css +7 -0
- package/dist/widgets.js +226 -58
- package/dist/widgets.js.map +2 -2
- package/dist/wm.js +2 -2
- package/docs/components/charts.md +40 -0
- package/docs/components/chat-shell.md +8 -2
- package/docs/components/filter-builder.md +52 -6
- package/docs/components/forms-and-dialogs.md +8 -0
- package/docs/components/input-fields.md +24 -0
- package/docs/components/menus.md +4 -0
- package/docs/components/shell.md +4 -0
- package/docs/components/workspace-picker.md +46 -2
- package/package.json +1 -1
- package/src/charts/plot_config_editor.js +92 -6
- package/src/charts/plot_grid.js +6 -1
- package/src/ui/components/chat_shell.js +7 -1
- package/src/ui/components/context_menu.js +8 -2
- package/src/ui/components/filter_builder.js +53 -28
- package/src/ui/components/filter_model.js +86 -18
- package/src/ui/components/inline_renamer.js +10 -5
- package/src/ui/components/modal.js +25 -8
- package/src/ui/components/workspace_picker.js +125 -27
- package/src/ui/utils/tooltip_service.js +81 -14
- package/dist/chunk-RKVYIXDC.js.map +0 -7
- package/dist/chunk-U5GKJUOC.js.map +0 -7
- /package/dist/{chunk-5W4EEKSK.js.map → chunk-3BLIVOFD.js.map} +0 -0
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
choiceSections,
|
|
2
3
|
collectFocusable,
|
|
3
|
-
|
|
4
|
+
filterSections,
|
|
5
|
+
isInAnchoredLayer,
|
|
6
|
+
openAnchoredLayer,
|
|
7
|
+
shownValue
|
|
4
8
|
} from "./chunk-Q7BALEPX.js";
|
|
5
9
|
import {
|
|
6
10
|
getSetting
|
|
@@ -1698,6 +1702,490 @@ var ColorField = class {
|
|
|
1698
1702
|
}
|
|
1699
1703
|
};
|
|
1700
1704
|
|
|
1705
|
+
// src/ui/components/option_list.js
|
|
1706
|
+
var SEQ = 0;
|
|
1707
|
+
function node(tag, className, text) {
|
|
1708
|
+
const n = document.createElement(tag);
|
|
1709
|
+
if (className) n.className = className;
|
|
1710
|
+
if (text !== void 0 && text !== null) n.textContent = String(text);
|
|
1711
|
+
return n;
|
|
1712
|
+
}
|
|
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
|
+
});
|
|
1767
|
+
return {
|
|
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
|
+
}
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
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);
|
|
1925
|
+
}
|
|
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;
|
|
1958
|
+
}
|
|
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);
|
|
1966
|
+
}
|
|
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);
|
|
1994
|
+
return true;
|
|
1995
|
+
}
|
|
1996
|
+
const openFor = (text) => {
|
|
1997
|
+
query = text;
|
|
1998
|
+
wanted = true;
|
|
1999
|
+
refresh();
|
|
2000
|
+
};
|
|
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);
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
if (ev.key === "Enter") {
|
|
2017
|
+
if (!layer) return;
|
|
2018
|
+
if (takeActive()) {
|
|
2019
|
+
ev.preventDefault();
|
|
2020
|
+
ev.stopImmediatePropagation();
|
|
2021
|
+
return;
|
|
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?.();
|
|
2033
|
+
}
|
|
2034
|
+
};
|
|
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);
|
|
2040
|
+
return {
|
|
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);
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
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");
|
|
2114
|
+
};
|
|
2115
|
+
function paintValidity() {
|
|
2116
|
+
const message = validate && current ? validate(current) || null : null;
|
|
2117
|
+
if (!message) {
|
|
2118
|
+
clearValidity();
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
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 });
|
|
2152
|
+
}
|
|
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;
|
|
2171
|
+
},
|
|
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();
|
|
2185
|
+
}
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
|
|
1701
2189
|
export {
|
|
1702
2190
|
DEFAULT_WINDOW_STORAGE_KEY,
|
|
1703
2191
|
DEFAULT_MAX_SAVED_WINDOWS,
|
|
@@ -1706,6 +2194,9 @@ export {
|
|
|
1706
2194
|
fireEvent,
|
|
1707
2195
|
isHexColor,
|
|
1708
2196
|
normalizeHex,
|
|
1709
|
-
ColorField
|
|
2197
|
+
ColorField,
|
|
2198
|
+
createOptionList,
|
|
2199
|
+
attachInputOptions,
|
|
2200
|
+
createCombobox
|
|
1710
2201
|
};
|
|
1711
|
-
//# sourceMappingURL=chunk-
|
|
2202
|
+
//# sourceMappingURL=chunk-HL3IIHU3.js.map
|