@zeyos/zx 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/CHANGELOG.md +266 -1
  2. package/README.md +7 -3
  3. package/dist/types/components/activity-item/activity-item.d.ts +351 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +195 -0
  5. package/dist/types/components/app-sidebar/app-sidebar.d.ts +28 -0
  6. package/dist/types/components/button/button.d.ts +8 -0
  7. package/dist/types/components/calendar/calendar-model.d.ts +22 -0
  8. package/dist/types/components/calendar/calendar.d.ts +19 -0
  9. package/dist/types/components/card/card.d.ts +7 -0
  10. package/dist/types/components/card-view/card-view.d.ts +12 -4
  11. package/dist/types/components/card-view/record-card.d.ts +53 -5
  12. package/dist/types/components/code-editor/code-editor.d.ts +160 -0
  13. package/dist/types/components/code-editor/field-adapter.d.ts +5 -0
  14. package/dist/types/components/context-menu/context-menu.d.ts +18 -2
  15. package/dist/types/components/date-picker/date-picker.d.ts +8 -0
  16. package/dist/types/components/date-picker/time-picker.d.ts +7 -0
  17. package/dist/types/components/dialog/dialog.d.ts +29 -0
  18. package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
  19. package/dist/types/components/file-item/file-item.d.ts +234 -0
  20. package/dist/types/components/file-list/file-list.d.ts +171 -0
  21. package/dist/types/components/filter/filter.d.ts +91 -36
  22. package/dist/types/components/filter-panel/filter-panel.d.ts +449 -0
  23. package/dist/types/components/kanban-view/kanban-card.d.ts +138 -0
  24. package/dist/types/components/kanban-view/kanban-policy.d.ts +268 -0
  25. package/dist/types/components/kanban-view/kanban-view.d.ts +837 -26
  26. package/dist/types/components/launcher/launcher.d.ts +19 -12
  27. package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
  28. package/dist/types/components/menu-button/menu-button.d.ts +4 -37
  29. package/dist/types/components/message/message.d.ts +7 -0
  30. package/dist/types/components/modal/modal.d.ts +48 -0
  31. package/dist/types/components/navigation-bar/navigation-bar.d.ts +105 -21
  32. package/dist/types/components/panel/panel.d.ts +7 -0
  33. package/dist/types/components/search/search.d.ts +7 -0
  34. package/dist/types/components/select/select.d.ts +4 -0
  35. package/dist/types/components/sort-control/sort-control.d.ts +105 -0
  36. package/dist/types/components/stat-tile/stat-tile.d.ts +277 -0
  37. package/dist/types/components/tabbox/tabbox.d.ts +7 -0
  38. package/dist/types/components/table/table.d.ts +144 -0
  39. package/dist/types/components/table-view/table-view.d.ts +11 -5
  40. package/dist/types/components/timebox/timebox.d.ts +7 -0
  41. package/dist/types/components/value-list/value-list.d.ts +8 -0
  42. package/dist/types/components/view/record-view.d.ts +96 -5
  43. package/dist/types/core/component.d.ts +6 -1
  44. package/dist/types/core/icons.d.ts +17 -0
  45. package/dist/types/index.d.ts +13 -1
  46. package/dist/types/internal/app-rail.d.ts +10 -0
  47. package/dist/types/internal/menu-items.d.ts +134 -0
  48. package/dist/zx-zeyos.esm.js +651 -88
  49. package/dist/zx-zeyos.esm.min.js +8 -1
  50. package/dist/zx.css +6266 -3900
  51. package/dist/zx.esm.js +8487 -1805
  52. package/dist/zx.esm.min.js +10 -3
  53. package/dist/zx.global.js +8487 -1805
  54. package/dist/zx.global.min.js +10 -3
  55. package/dist/zx.min.css +1 -1
  56. package/docs/api.json +1811 -247
  57. package/docs/llms.md +345 -40
  58. package/docs/llms.txt +11 -7
  59. package/package.json +1 -2
  60. package/src/components/activity-item/activity-item.css +137 -0
  61. package/src/components/activity-item/activity-item.js +599 -0
  62. package/src/components/activity-list/activity-list.css +64 -0
  63. package/src/components/activity-list/activity-list.js +375 -0
  64. package/src/components/app-rail/app-rail.css +227 -19
  65. package/src/components/app-sidebar/app-sidebar.css +414 -24
  66. package/src/components/app-sidebar/app-sidebar.js +65 -5
  67. package/src/components/button/button.css +8 -0
  68. package/src/components/button/button.js +6 -1
  69. package/src/components/calendar/calendar-model.js +45 -0
  70. package/src/components/calendar/calendar.css +30 -10
  71. package/src/components/calendar/calendar.js +41 -13
  72. package/src/components/card/card.js +12 -1
  73. package/src/components/card-view/card-view.css +212 -9
  74. package/src/components/card-view/card-view.js +32 -16
  75. package/src/components/card-view/record-card.js +98 -21
  76. package/src/components/code-editor/code-editor.css +67 -0
  77. package/src/components/code-editor/code-editor.js +288 -0
  78. package/src/components/code-editor/field-adapter.js +34 -0
  79. package/src/components/context-menu/context-menu.css +80 -3
  80. package/src/components/context-menu/context-menu.js +22 -30
  81. package/src/components/date-picker/date-picker.js +22 -8
  82. package/src/components/date-picker/month-picker.js +3 -3
  83. package/src/components/date-picker/time-picker.js +20 -8
  84. package/src/components/dialog/dialog.js +88 -66
  85. package/src/components/dropdown/dropdown.js +2 -2
  86. package/src/components/entity-ref/entity-ref.css +182 -0
  87. package/src/components/entity-ref/entity-ref.js +496 -0
  88. package/src/components/file-item/file-item.css +88 -0
  89. package/src/components/file-item/file-item.js +371 -0
  90. package/src/components/file-list/file-list.css +51 -0
  91. package/src/components/file-list/file-list.js +320 -0
  92. package/src/components/filter/filter.css +27 -1
  93. package/src/components/filter/filter.js +199 -45
  94. package/src/components/filter-panel/filter-panel.css +104 -0
  95. package/src/components/filter-panel/filter-panel.js +964 -0
  96. package/src/components/kanban-view/kanban-card.js +239 -0
  97. package/src/components/kanban-view/kanban-policy.js +412 -0
  98. package/src/components/kanban-view/kanban-view.css +674 -36
  99. package/src/components/kanban-view/kanban-view.js +1762 -240
  100. package/src/components/launcher/launcher.js +59 -27
  101. package/src/components/list-toolbar/list-toolbar.css +189 -0
  102. package/src/components/list-toolbar/list-toolbar.js +851 -0
  103. package/src/components/menu-button/menu-button.css +80 -3
  104. package/src/components/menu-button/menu-button.js +21 -37
  105. package/src/components/message/message.css +14 -12
  106. package/src/components/message/message.js +13 -2
  107. package/src/components/modal/modal.css +15 -0
  108. package/src/components/modal/modal.js +141 -0
  109. package/src/components/navigation-bar/navigation-bar.css +77 -2
  110. package/src/components/navigation-bar/navigation-bar.js +171 -4
  111. package/src/components/panel/panel.js +13 -2
  112. package/src/components/search/search.js +17 -5
  113. package/src/components/select/field-adapter.js +4 -1
  114. package/src/components/select/select.js +3 -0
  115. package/src/components/sort-control/sort-control.css +17 -0
  116. package/src/components/sort-control/sort-control.js +140 -0
  117. package/src/components/stat-tile/stat-tile.css +213 -0
  118. package/src/components/stat-tile/stat-tile.js +590 -0
  119. package/src/components/tabbox/tabbox.js +12 -1
  120. package/src/components/table/table.css +51 -2
  121. package/src/components/table/table.js +298 -19
  122. package/src/components/table-view/table-view.css +8 -2
  123. package/src/components/table-view/table-view.js +21 -6
  124. package/src/components/tag-picker/tag-picker.css +3 -1
  125. package/src/components/timebox/timebox.js +18 -5
  126. package/src/components/value-list/value-list.js +22 -6
  127. package/src/components/view/record-view.css +5 -0
  128. package/src/components/view/record-view.js +100 -7
  129. package/src/core/component.js +8 -3
  130. package/src/core/icons.js +74 -5
  131. package/src/index.js +33 -3
  132. package/src/internal/app-rail.js +19 -1
  133. package/src/internal/menu-items.js +142 -0
  134. package/styles/print.css +192 -0
  135. package/styles/zx.css +11 -0
  136. package/DESIGN-SYSTEM.md +0 -182
@@ -870,21 +870,26 @@ var Component = class extends EventTarget {
870
870
  * Dispatches a component event and a bubbling, composed `zx-*` DOM event.
871
871
  * @param {string} type Lowercase event name.
872
872
  * @param {Record<string, unknown>} [detail={}] Single event detail object.
873
+ * @param {{honorDomCancellation?:boolean}} [options={}] Mirrored DOM-event behavior. Components
874
+ * with a cancelable mutation boundary may opt into treating `preventDefault()` on the bubbling
875
+ * `zx-*` event exactly like cancellation of the component event.
873
876
  * @returns {Event & {detail: Record<string, unknown>}}
874
877
  * @fires Component#event
875
878
  */
876
- emit(type, detail = {}) {
879
+ emit(type, detail = {}, options = {}) {
877
880
  if (detail === null || typeof detail !== "object" || Array.isArray(detail)) {
878
881
  throw new TypeError("Event detail must be an object");
879
882
  }
880
883
  const componentEvent = createCustomEvent(type, detail, { cancelable: true });
881
884
  this.dispatchEvent(componentEvent);
882
885
  if (this.el) {
883
- this.el.dispatchEvent(createCustomEvent(`zx-${type}`, detail, {
886
+ const domEvent = createCustomEvent(`zx-${type}`, detail, {
884
887
  bubbles: true,
885
888
  composed: true,
886
889
  cancelable: true
887
- }));
890
+ });
891
+ this.el.dispatchEvent(domEvent);
892
+ if (options.honorDomCancellation && domEvent.defaultPrevented) componentEvent.preventDefault();
888
893
  }
889
894
  return componentEvent;
890
895
  }
@@ -1795,6 +1800,271 @@ function registerChecklistFieldAdapter() {
1795
1800
  });
1796
1801
  }
1797
1802
 
1803
+ // src/components/code-editor/code-editor.js
1804
+ var CodeEditor = class extends Component {
1805
+ static cssName = "code-editor";
1806
+ /** @type {CodeEditorOptions} */
1807
+ static defaults = {
1808
+ value: "",
1809
+ name: "",
1810
+ language: "text",
1811
+ label: "Code editor",
1812
+ placeholder: "",
1813
+ rows: 12,
1814
+ indent: " ",
1815
+ wrap: "off",
1816
+ spellcheck: false,
1817
+ readOnly: false,
1818
+ disabled: false,
1819
+ status: true
1820
+ };
1821
+ /** @returns {HTMLElement} */
1822
+ render() {
1823
+ this._createdRoot = this.el === null;
1824
+ const root = (
1825
+ /** @type {HTMLElement} */
1826
+ this.el ?? h("div")
1827
+ );
1828
+ this.el = root;
1829
+ this._initialChildren = this._createdRoot ? [] : [...root.childNodes];
1830
+ this._initialAttributes = new Map(["data-language", "data-readonly", "data-disabled"].map((name) => [name, root.getAttribute(name)]));
1831
+ this._language = normalizeLanguage(this.options.language);
1832
+ this._languageLabel = h("span", { class: "zx-code-editor__language" }, this._language);
1833
+ this._position = h("span", {
1834
+ class: "zx-code-editor__position",
1835
+ role: "status",
1836
+ ariaLive: "polite"
1837
+ });
1838
+ this._header = h(
1839
+ "div",
1840
+ { class: "zx-code-editor__header" },
1841
+ this._languageLabel,
1842
+ this.options.status ? this._position : null
1843
+ );
1844
+ this._input = /** @type {HTMLTextAreaElement} */
1845
+ h("textarea", {
1846
+ class: "zx-code-editor__input",
1847
+ name: this.options.name ? String(this.options.name) : void 0,
1848
+ ariaLabel: String(this.options.label ?? "Code editor"),
1849
+ placeholder: String(this.options.placeholder ?? ""),
1850
+ rows: normalizeRows(this.options.rows),
1851
+ wrap: normalizeWrap(this.options.wrap),
1852
+ spellcheck: Boolean(this.options.spellcheck),
1853
+ readOnly: Boolean(this.options.readOnly),
1854
+ disabled: Boolean(this.options.disabled),
1855
+ autocomplete: "off",
1856
+ autocapitalize: "off"
1857
+ });
1858
+ this._input.value = String(this.options.value ?? "");
1859
+ root.dataset.language = this._language;
1860
+ root.dataset.readonly = String(Boolean(this.options.readOnly));
1861
+ root.dataset.disabled = String(Boolean(this.options.disabled));
1862
+ root.replaceChildren(this._header, this._input);
1863
+ this.listen(this._input, "keydown", (event) => this._handleKeydown(event));
1864
+ this.listen(this._input, "input", () => {
1865
+ this._updatePosition();
1866
+ this.emit("input", { value: this.getValue() });
1867
+ });
1868
+ this.listen(this._input, "change", () => this.emit("change", { value: this.getValue() }));
1869
+ for (const type of ["click", "keyup", "select", "focus"]) {
1870
+ this.listen(this._input, type, () => this._updatePosition());
1871
+ }
1872
+ this._updatePosition();
1873
+ return root;
1874
+ }
1875
+ /** @returns {string} */
1876
+ getValue() {
1877
+ return this._input.value;
1878
+ }
1879
+ /**
1880
+ * Replaces the editor text.
1881
+ * @param {unknown} value New value.
1882
+ * @param {{silent?:boolean}} [options={}] Suppress the `change` event.
1883
+ * @returns {this}
1884
+ */
1885
+ setValue(value, { silent = false } = {}) {
1886
+ const next = String(value ?? "");
1887
+ if (this._input.value === next) return this;
1888
+ this._input.value = next;
1889
+ this._updatePosition();
1890
+ if (!silent) this.emit("change", { value: next });
1891
+ return this;
1892
+ }
1893
+ /** @param {unknown} language New language identifier. @returns {this} */
1894
+ setLanguage(language2) {
1895
+ this._language = normalizeLanguage(language2);
1896
+ this.el.dataset.language = this._language;
1897
+ this._languageLabel.textContent = this._language;
1898
+ return this;
1899
+ }
1900
+ /** @returns {this} */
1901
+ focus() {
1902
+ this._input.focus();
1903
+ return this;
1904
+ }
1905
+ /** @returns {this} */
1906
+ enable() {
1907
+ this._input.disabled = false;
1908
+ this.el.dataset.disabled = "false";
1909
+ return this;
1910
+ }
1911
+ /** @returns {this} */
1912
+ disable() {
1913
+ this._input.disabled = true;
1914
+ this.el.dataset.disabled = "true";
1915
+ return this;
1916
+ }
1917
+ /** @param {boolean} value Read-only state. @returns {this} */
1918
+ setReadOnly(value) {
1919
+ this._input.readOnly = Boolean(value);
1920
+ this.el.dataset.readonly = String(Boolean(value));
1921
+ return this;
1922
+ }
1923
+ /** @param {KeyboardEvent} event @returns {void} */
1924
+ _handleKeydown(event) {
1925
+ if (event.key !== "Tab" || event.altKey || event.ctrlKey || event.metaKey || this._input.readOnly || this._input.disabled) return;
1926
+ const indent = String(this.options.indent ?? " ");
1927
+ if (!indent) return;
1928
+ event.preventDefault();
1929
+ const result = indentSelection(
1930
+ this._input.value,
1931
+ this._input.selectionStart ?? 0,
1932
+ this._input.selectionEnd ?? 0,
1933
+ indent,
1934
+ event.shiftKey
1935
+ );
1936
+ if (result.value === this._input.value) return;
1937
+ this._input.value = result.value;
1938
+ this._input.setSelectionRange(result.start, result.end);
1939
+ this._updatePosition();
1940
+ this.emit("input", { value: this.getValue() });
1941
+ }
1942
+ /** @returns {void} */
1943
+ _updatePosition() {
1944
+ if (!this.options.status) return;
1945
+ const position2 = editorPosition(
1946
+ this._input.value,
1947
+ this._input.selectionStart ?? 0,
1948
+ this._input.selectionEnd ?? 0
1949
+ );
1950
+ this._position.textContent = position2.lines > 1 ? `Ln ${position2.line}, Col ${position2.column} \xB7 ${position2.lines} lines selected` : `Ln ${position2.line}, Col ${position2.column}`;
1951
+ }
1952
+ /** @returns {void} */
1953
+ destroy() {
1954
+ if (!this._createdRoot) {
1955
+ this.el.replaceChildren(...this._initialChildren);
1956
+ for (const [name, value] of this._initialAttributes) {
1957
+ if (value === null) this.el.removeAttribute(name);
1958
+ else this.el.setAttribute(name, value);
1959
+ }
1960
+ }
1961
+ super.destroy();
1962
+ }
1963
+ };
1964
+ function indentSelection(value, start, end, indent = " ", outdent = false) {
1965
+ const source = String(value ?? "");
1966
+ const unit = String(indent ?? "");
1967
+ const from = clampIndex(start, source.length);
1968
+ const to = clampIndex(end, source.length);
1969
+ if (!unit) return { value: source, start: from, end: to };
1970
+ if (from === to && !outdent) {
1971
+ return {
1972
+ value: source.slice(0, from) + unit + source.slice(to),
1973
+ start: from + unit.length,
1974
+ end: from + unit.length
1975
+ };
1976
+ }
1977
+ const lineStart = source.lastIndexOf("\n", Math.max(0, from - 1)) + 1;
1978
+ let lineEnd = source.indexOf("\n", to);
1979
+ if (lineEnd < 0) lineEnd = source.length;
1980
+ const block = source.slice(lineStart, lineEnd);
1981
+ const lines = block.split("\n");
1982
+ let removedFirst = 0;
1983
+ let delta = 0;
1984
+ const transformed = lines.map((line, index) => {
1985
+ if (!outdent) {
1986
+ delta += unit.length;
1987
+ return unit + line;
1988
+ }
1989
+ const removed = removableIndent(line, unit);
1990
+ if (index === 0) removedFirst = removed;
1991
+ delta -= removed;
1992
+ return line.slice(removed);
1993
+ }).join("\n");
1994
+ const nextStart = outdent ? Math.max(lineStart, from - Math.min(removedFirst, from - lineStart)) : from + unit.length;
1995
+ return {
1996
+ value: source.slice(0, lineStart) + transformed + source.slice(lineEnd),
1997
+ start: nextStart,
1998
+ end: Math.max(nextStart, to + delta)
1999
+ };
2000
+ }
2001
+ function editorPosition(value, start, end = start) {
2002
+ const source = String(value ?? "");
2003
+ const from = clampIndex(start, source.length);
2004
+ const to = clampIndex(end, source.length);
2005
+ const before = source.slice(0, from);
2006
+ return {
2007
+ line: before.split("\n").length,
2008
+ column: from - before.lastIndexOf("\n"),
2009
+ lines: source.slice(from, to).split("\n").length
2010
+ };
2011
+ }
2012
+ function removableIndent(line, indent) {
2013
+ if (line.startsWith(indent)) return indent.length;
2014
+ if (indent === " ") return line.startsWith(" ") ? 1 : Math.min(/^ +/.exec(line)?.[0].length ?? 0, 2);
2015
+ return Math.min(/^\s+/.exec(line)?.[0].length ?? 0, indent.length);
2016
+ }
2017
+ function normalizeLanguage(value) {
2018
+ const language2 = String(value ?? "text").trim().toLowerCase();
2019
+ return /^[a-z0-9][a-z0-9+_.-]*$/.test(language2) ? language2 : "text";
2020
+ }
2021
+ function normalizeRows(value) {
2022
+ const rows = Number(value);
2023
+ return Number.isFinite(rows) ? Math.max(2, Math.min(100, Math.round(rows))) : 12;
2024
+ }
2025
+ function normalizeWrap(value) {
2026
+ return ["off", "soft", "hard"].includes(String(value)) ? (
2027
+ /** @type {'off'|'soft'|'hard'} */
2028
+ value
2029
+ ) : "off";
2030
+ }
2031
+ function clampIndex(value, length) {
2032
+ const number = Number(value);
2033
+ return Math.max(0, Math.min(length, Number.isFinite(number) ? Math.round(number) : 0));
2034
+ }
2035
+
2036
+ // src/components/code-editor/field-adapter.js
2037
+ function registerCodeEditorFieldAdapter() {
2038
+ Field.register("code", (field, options) => {
2039
+ const editor = field.own(new CodeEditor(null, {
2040
+ label: options.label ? String(options.label) : "Code editor",
2041
+ placeholder: options.placeholder,
2042
+ readOnly: Boolean(options.props?.readOnly),
2043
+ disabled: Boolean(options.disabled),
2044
+ ...options.props ?? {},
2045
+ value: options.value
2046
+ }));
2047
+ let setting = false;
2048
+ editor.on("input", (event) => {
2049
+ if (!setting) field.emit("change", { value: event.detail.value });
2050
+ });
2051
+ return {
2052
+ el: editor.el,
2053
+ get: () => editor.getValue(),
2054
+ set: (value) => {
2055
+ setting = true;
2056
+ try {
2057
+ editor.setValue(value, { silent: true });
2058
+ } finally {
2059
+ setting = false;
2060
+ }
2061
+ },
2062
+ focus: () => editor.focus(),
2063
+ setDisabled: (value) => value ? editor.disable() : editor.enable()
2064
+ };
2065
+ });
2066
+ }
2067
+
1798
2068
  // src/core/date.js
1799
2069
  var MONTHS = [
1800
2070
  "January",
@@ -2080,6 +2350,8 @@ function loadFontAwesomeKit(kit) {
2080
2350
  // src/core/icons.js
2081
2351
  var SVG_NS = "http://www.w3.org/2000/svg";
2082
2352
  var icons = Object.freeze({
2353
+ "arrow-up-wide-short": ["0 0 576 512", "M151.6 42.4C145.5 35.8 137 32 128 32s-17.5 3.8-23.6 10.4l-88 96c-11.9 13-11.1 33.3 2 45.2s33.3 11.1 45.2-2L96 146.3 96 448c0 17.7 14.3 32 32 32s32-14.3 32-32l0-301.7 32.4 35.4c11.9 13 32.2 13.9 45.2 2s13.9-32.2 2-45.2l-88-96zM320 480l32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-32 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm0-128l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-96 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm0-128l160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-160 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm0-128l224 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L320 32c-17.7 0-32 14.3-32 32s14.3 32 32 32z"],
2354
+ "arrow-down-wide-short": ["0 0 576 512", "M151.6 469.6C145.5 476.2 137 480 128 480s-17.5-3.8-23.6-10.4l-88-96c-11.9-13-11.1-33.3 2-45.2s33.3-11.1 45.2 2L96 365.7 96 64c0-17.7 14.3-32 32-32s32 14.3 32 32l0 301.7 32.4-35.4c11.9-13 32.2-13.9 45.2-2s13.9 32.2 2 45.2l-88 96zM320 480c-17.7 0-32-14.3-32-32s14.3-32 32-32l32 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-32 0zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32l96 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-96 0zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32l160 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-160 0zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32l224 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L320 96z"],
2083
2355
  "chevron-down": ["0 0 512 512", "M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"],
2084
2356
  "chevron-up": ["0 0 512 512", "M233.4 105.4c12.5-12.5 32.8-12.5 45.3 0l192 192c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L256 173.3 86.6 342.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l192-192z"],
2085
2357
  "chevron-left": ["0 0 320 512", "M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"],
@@ -2175,11 +2447,21 @@ function icon(name, options = {}) {
2175
2447
  }
2176
2448
  function builtinIcon(name, options) {
2177
2449
  const entry = extraGlyphs[name] ?? icons[name];
2178
- if (!entry) throw new RangeError(`Unknown icon: ${name}`);
2450
+ if (!entry) {
2451
+ if (options.strict) throw new RangeError(`Unknown icon: ${name}`);
2452
+ warnMissingIcon(name);
2453
+ return missingIcon(name, options);
2454
+ }
2179
2455
  const [viewBox, pathData] = entry;
2456
+ const svg = iconSvg(viewBox, options);
2457
+ const path = document.createElementNS(SVG_NS, "path");
2458
+ path.setAttribute("d", pathData);
2459
+ svg.append(path);
2460
+ return svg;
2461
+ }
2462
+ function iconSvg(viewBox, options) {
2180
2463
  const { size = 16 } = options;
2181
2464
  const svg = document.createElementNS(SVG_NS, "svg");
2182
- const path = document.createElementNS(SVG_NS, "path");
2183
2465
  svg.setAttribute("class", classList("zx-icon", options.class));
2184
2466
  svg.setAttribute("viewBox", viewBox);
2185
2467
  svg.setAttribute("width", String(size));
@@ -2187,10 +2469,20 @@ function builtinIcon(name, options) {
2187
2469
  svg.setAttribute("fill", "currentColor");
2188
2470
  svg.setAttribute("focusable", "false");
2189
2471
  applyLabel(svg, options.label ?? null);
2190
- path.setAttribute("d", pathData);
2191
- svg.append(path);
2192
2472
  return svg;
2193
2473
  }
2474
+ var PLACEHOLDER_VIEW_BOX = "0 0 512 512";
2475
+ function missingIcon(name, options) {
2476
+ const svg = iconSvg(PLACEHOLDER_VIEW_BOX, options);
2477
+ svg.setAttribute("data-zx-icon-missing", name);
2478
+ return svg;
2479
+ }
2480
+ var warnedMissing = /* @__PURE__ */ new Set();
2481
+ function warnMissingIcon(name) {
2482
+ if (warnedMissing.has(name)) return;
2483
+ warnedMissing.add(name);
2484
+ console.warn(`[zx.icon] Unknown icon "${name}" \u2014 rendered an empty placeholder. Add it with registerIcons(), or render Font Awesome with useFontAwesome().`);
2485
+ }
2194
2486
  function faIcon(classes, options) {
2195
2487
  const { size = 16 } = options;
2196
2488
  const element = document.createElement("i");
@@ -2234,15 +2526,15 @@ var TimePicker = class extends Component {
2234
2526
  const content = h("div", {
2235
2527
  class: "zx-time-picker__segments",
2236
2528
  role: "group",
2237
- ariaLabel: "Time"
2529
+ ariaLabel: this._message("timePicker.label", "Time")
2238
2530
  });
2239
2531
  this._content = content;
2240
- this._addSpin(content, "h", "Hour", 23);
2532
+ this._addSpin(content, "h", this._message("timePicker.hour", "Hour"), 23);
2241
2533
  content.append(h("span", { class: "zx-time-picker__separator", ariaHidden: "true" }, ":"));
2242
- this._addSpin(content, "m", "Minute", 59);
2534
+ this._addSpin(content, "m", this._message("timePicker.minute", "Minute"), 59);
2243
2535
  if (this.options.seconds) {
2244
2536
  content.append(h("span", { class: "zx-time-picker__separator", ariaHidden: "true" }, ":"));
2245
- this._addSpin(content, "s", "Second", 59);
2537
+ this._addSpin(content, "s", this._message("timePicker.second", "Second"), 59);
2246
2538
  }
2247
2539
  root.append(content);
2248
2540
  if (this.options.clock) {
@@ -2251,7 +2543,7 @@ var TimePicker = class extends Component {
2251
2543
  ref: "clockToggle",
2252
2544
  class: "zx-icon-btn zx-time-picker__clock-toggle",
2253
2545
  type: "button",
2254
- ariaLabel: "Pick the time on a clock",
2546
+ ariaLabel: this._message("timePicker.clock", "Pick the time on a clock"),
2255
2547
  ariaExpanded: "false",
2256
2548
  ariaControls: clockId
2257
2549
  }, icon("clock", { size: 15 })));
@@ -2398,7 +2690,7 @@ var TimePicker = class extends Component {
2398
2690
  ref: "headHour",
2399
2691
  class: "zx-time-picker__clock-unit",
2400
2692
  type: "button",
2401
- ariaLabel: "Select the hour",
2693
+ ariaLabel: this._message("timePicker.selectHour", "Select the hour"),
2402
2694
  dataset: { unit: "h" }
2403
2695
  }),
2404
2696
  h("span", { class: "zx-time-picker__clock-colon", ariaHidden: "true" }, ":"),
@@ -2406,7 +2698,7 @@ var TimePicker = class extends Component {
2406
2698
  ref: "headMinute",
2407
2699
  class: "zx-time-picker__clock-unit",
2408
2700
  type: "button",
2409
- ariaLabel: "Select the minutes",
2701
+ ariaLabel: this._message("timePicker.selectMinutes", "Select the minutes"),
2410
2702
  dataset: { unit: "m" }
2411
2703
  })
2412
2704
  ),
@@ -2464,7 +2756,7 @@ var TimePicker = class extends Component {
2464
2756
  this.refs.headMinute.textContent = pad2(this._display.m);
2465
2757
  this.refs.headHour.setAttribute("aria-pressed", String(hours));
2466
2758
  this.refs.headMinute.setAttribute("aria-pressed", String(!hours));
2467
- this.refs.dial.setAttribute("aria-label", hours ? "Hour" : "Minutes");
2759
+ this.refs.dial.setAttribute("aria-label", hours ? this._message("timePicker.hour", "Hour") : this._message("timePicker.minutes", "Minutes"));
2468
2760
  if (!this._clockOpen) return;
2469
2761
  const selected = hours ? this._display.h : this._display.m;
2470
2762
  const marks = hours ? hourMarks() : minuteMarks();
@@ -2499,6 +2791,16 @@ var TimePicker = class extends Component {
2499
2791
  element.setAttribute("aria-checked", String(checked));
2500
2792
  element.tabIndex = checked ? 0 : -1;
2501
2793
  }
2794
+ /**
2795
+ * Resolves a message through the host translator, falling back to the built-in English text.
2796
+ * @param {string} key Message key.
2797
+ * @param {string} fallback English default.
2798
+ * @returns {string}
2799
+ */
2800
+ _message(key, fallback) {
2801
+ const message = this.msg(key);
2802
+ return message === key ? fallback : message;
2803
+ }
2502
2804
  };
2503
2805
  function hourMarks() {
2504
2806
  const marks = [];
@@ -2585,7 +2887,7 @@ var DatePicker = class extends Component {
2585
2887
  ref: "previous",
2586
2888
  class: "zx-date-picker__nav",
2587
2889
  type: "button",
2588
- ariaLabel: "Previous month"
2890
+ ariaLabel: this._message("datePicker.previousMonth", "Previous month")
2589
2891
  }, icon("chevron-left")),
2590
2892
  h("button", {
2591
2893
  ref: "heading",
@@ -2598,7 +2900,7 @@ var DatePicker = class extends Component {
2598
2900
  ref: "next",
2599
2901
  class: "zx-date-picker__nav",
2600
2902
  type: "button",
2601
- ariaLabel: "Next month"
2903
+ ariaLabel: this._message("datePicker.nextMonth", "Next month")
2602
2904
  }, icon("chevron-right"))
2603
2905
  ),
2604
2906
  h(
@@ -2615,14 +2917,14 @@ var DatePicker = class extends Component {
2615
2917
  ref: "quickPrevious",
2616
2918
  class: "zx-date-picker__nav",
2617
2919
  type: "button",
2618
- ariaLabel: "Previous year"
2920
+ ariaLabel: this._message("datePicker.previousYear", "Previous year")
2619
2921
  }, icon("chevron-left")),
2620
2922
  h("span", { ref: "quickYear", class: "zx-date-picker__quick-year", ariaLive: "polite" }),
2621
2923
  h("button", {
2622
2924
  ref: "quickNext",
2623
2925
  class: "zx-date-picker__nav",
2624
2926
  type: "button",
2625
- ariaLabel: "Next year"
2927
+ ariaLabel: this._message("datePicker.nextYear", "Next year")
2626
2928
  }, icon("chevron-right"))
2627
2929
  ),
2628
2930
  h("div", { ref: "quickGrid", class: "zx-date-picker__quick-grid", role: "grid" })
@@ -2633,7 +2935,7 @@ var DatePicker = class extends Component {
2633
2935
  ref: "grid",
2634
2936
  class: "zx-date-picker__grid",
2635
2937
  role: "grid",
2636
- ariaLabel: "Calendar"
2938
+ ariaLabel: this._message("datePicker.calendar", "Calendar")
2637
2939
  },
2638
2940
  h("thead", { ref: "weekdays" }),
2639
2941
  h("tbody", { ref: "days" })
@@ -2731,7 +3033,7 @@ var DatePicker = class extends Component {
2731
3033
  row.append(h("th", {
2732
3034
  class: "zx-date-picker__week-heading",
2733
3035
  scope: "col",
2734
- ariaLabel: "Week number"
3036
+ ariaLabel: this._message("datePicker.weekNumber", "Week number")
2735
3037
  }, "#"));
2736
3038
  }
2737
3039
  const sunday = new Date(2023, 0, 1);
@@ -2751,7 +3053,10 @@ var DatePicker = class extends Component {
2751
3053
  const offset = (first.getDay() - this._weekStart + 7) % 7;
2752
3054
  const start = addDays(first, -offset);
2753
3055
  this.refs.heading.textContent = formatDate(first, "%B %Y");
2754
- this.refs.heading.setAttribute("aria-label", `Choose month and year, ${formatDate(first, "%B %Y")}`);
3056
+ this.refs.heading.setAttribute(
3057
+ "aria-label",
3058
+ this._message("datePicker.chooseMonthYear", "Choose month and year, %1", formatDate(first, "%B %Y"))
3059
+ );
2755
3060
  const rows = [];
2756
3061
  for (let week = 0; week < 6; week += 1) {
2757
3062
  const rowStart = addDays(start, week * 7);
@@ -2760,7 +3065,7 @@ var DatePicker = class extends Component {
2760
3065
  row.append(h("th", {
2761
3066
  class: "zx-date-picker__week",
2762
3067
  scope: "row",
2763
- ariaLabel: `Week ${isoWeekNumber(rowStart)}`
3068
+ ariaLabel: this._message("datePicker.week", "Week %1", isoWeekNumber(rowStart))
2764
3069
  }, String(isoWeekNumber(rowStart))));
2765
3070
  }
2766
3071
  for (let column = 0; column < 7; column += 1) {
@@ -2965,6 +3270,17 @@ var DatePicker = class extends Component {
2965
3270
  _buttonForDate(date) {
2966
3271
  return this.refs.days.querySelector(`[data-date="${formatDate(date, "%Y-%m-%d")}"]`);
2967
3272
  }
3273
+ /**
3274
+ * Resolves a message through the host translator, falling back to the built-in English text.
3275
+ * @param {string} key Message key.
3276
+ * @param {string} fallback Built-in text, with `%1`-style placeholders.
3277
+ * @param {...unknown} args Interpolation values.
3278
+ * @returns {string}
3279
+ */
3280
+ _message(key, fallback, ...args) {
3281
+ const message = this.msg(key, ...args);
3282
+ return message === key ? printf(fallback, args) : message;
3283
+ }
2968
3284
  };
2969
3285
  function validDate(value) {
2970
3286
  return value instanceof Date && !Number.isNaN(value.getTime()) ? new Date(value.getTime()) : null;
@@ -3026,21 +3342,21 @@ var MonthPicker = class extends DatePicker {
3026
3342
  ref: "previousYear",
3027
3343
  class: "zx-month-picker__nav",
3028
3344
  type: "button",
3029
- ariaLabel: "Previous year"
3345
+ ariaLabel: this._message("monthPicker.previousYear", "Previous year")
3030
3346
  }, icon("chevron-left")),
3031
3347
  h("span", { ref: "year", class: "zx-month-picker__year", ariaLive: "polite" }),
3032
3348
  h("button", {
3033
3349
  ref: "nextYear",
3034
3350
  class: "zx-month-picker__nav",
3035
3351
  type: "button",
3036
- ariaLabel: "Next year"
3352
+ ariaLabel: this._message("monthPicker.nextYear", "Next year")
3037
3353
  }, icon("chevron-right"))
3038
3354
  ),
3039
3355
  h("div", {
3040
3356
  ref: "grid",
3041
3357
  class: "zx-month-picker__grid",
3042
3358
  role: "grid",
3043
- ariaLabel: "Choose month"
3359
+ ariaLabel: this._message("monthPicker.chooseMonth", "Choose month")
3044
3360
  })
3045
3361
  );
3046
3362
  this._content = content;
@@ -6589,6 +6905,7 @@ var Select = class _Select extends Component {
6589
6905
  value: null,
6590
6906
  disabled: false,
6591
6907
  placeholder: "",
6908
+ label: "",
6592
6909
  clearable: false,
6593
6910
  filter: false,
6594
6911
  searchKeys: null,
@@ -6630,6 +6947,7 @@ var Select = class _Select extends Component {
6630
6947
  class: "zx-select__input",
6631
6948
  type: "text",
6632
6949
  role: "combobox",
6950
+ ariaLabel: String(this.options.label || this.options.placeholder || "Select"),
6633
6951
  autocomplete: "off",
6634
6952
  spellcheck: false,
6635
6953
  ariaExpanded: "false",
@@ -7548,7 +7866,10 @@ function registerSelectFieldAdapter() {
7548
7866
  Field.register("zxselect", (field, options) => {
7549
7867
  const props = options.props ?? {};
7550
7868
  const preset = props.preset;
7551
- const selectOptions = { ...props, value: options.value };
7869
+ const selectOptions = { ...props, value: options.value, label: options.label || props.label, placeholder: props.placeholder ?? options.placeholder };
7870
+ field.listen(field.el, "click", (event) => {
7871
+ if (event.target.closest?.(".zx-field__label")) field.adapter.focus();
7872
+ });
7552
7873
  delete selectOptions.preset;
7553
7874
  const select = field.own(preset === "priority" ? Select.priority(null, selectOptions) : preset === "status" ? Select.status(null, selectOptions) : new Select(null, selectOptions));
7554
7875
  field.listen(select.el, "change", (event) => event.stopImmediatePropagation());
@@ -8788,7 +9109,7 @@ var Timebox = class extends Component {
8788
9109
  const content = h("div", {
8789
9110
  class: "zx-timebox__content",
8790
9111
  role: "group",
8791
- ariaLabel: this.options.seconds ? "Duration in hours, minutes, and seconds" : "Duration in hours and minutes"
9112
+ ariaLabel: this.options.seconds ? this._message("timebox.durationWithSeconds", "Duration in hours, minutes, and seconds") : this._message("timebox.duration", "Duration in hours and minutes")
8792
9113
  });
8793
9114
  this._content = content;
8794
9115
  if (this.options.signed) {
@@ -8796,7 +9117,7 @@ var Timebox = class extends Component {
8796
9117
  ref: "sign",
8797
9118
  class: "zx-timebox__sign",
8798
9119
  type: "button",
8799
- ariaLabel: "Toggle duration sign",
9120
+ ariaLabel: this._message("timebox.toggleSign", "Toggle duration sign"),
8800
9121
  ariaPressed: "false"
8801
9122
  }, "+"));
8802
9123
  this.listen(this.refs.sign, "click", () => {
@@ -8806,12 +9127,12 @@ var Timebox = class extends Component {
8806
9127
  this._emitChange();
8807
9128
  });
8808
9129
  }
8809
- this._addSegment(content, "hours", "Hours", false);
9130
+ this._addSegment(content, "hours", this._message("timebox.hours", "Hours"), false);
8810
9131
  content.append(h("span", { class: "zx-timebox__separator", ariaHidden: "true" }, ":"));
8811
- this._addSegment(content, "minutes", "Minutes", true);
9132
+ this._addSegment(content, "minutes", this._message("timebox.minutes", "Minutes"), true);
8812
9133
  if (this.options.seconds) {
8813
9134
  content.append(h("span", { class: "zx-timebox__separator", ariaHidden: "true" }, ":"));
8814
- this._addSegment(content, "seconds", "Seconds", true);
9135
+ this._addSegment(content, "seconds", this._message("timebox.seconds", "Seconds"), true);
8815
9136
  }
8816
9137
  root.append(content);
8817
9138
  this.set(this.options.value, this.options.unit, { silent: true });
@@ -8937,6 +9258,16 @@ var Timebox = class extends Component {
8937
9258
  _emitChange() {
8938
9259
  this.emit("change", { value: this.get(this.options.unit) });
8939
9260
  }
9261
+ /**
9262
+ * Resolves a message through the host translator, falling back to the built-in English text.
9263
+ * @param {string} key Message key.
9264
+ * @param {string} fallback English default.
9265
+ * @returns {string}
9266
+ */
9267
+ _message(key, fallback) {
9268
+ const message = this.msg(key);
9269
+ return message === key ? fallback : message;
9270
+ }
8940
9271
  };
8941
9272
  function finiteNumber2(value) {
8942
9273
  const number = Number(value);
@@ -9149,14 +9480,14 @@ var ValueList = class extends Component {
9149
9480
  this._list = h("ul", {
9150
9481
  class: "zx-value-list__list",
9151
9482
  role: "listbox",
9152
- ariaLabel: "Values",
9483
+ ariaLabel: this._message("valueList.values", "Values"),
9153
9484
  ariaOrientation: "horizontal"
9154
9485
  });
9155
9486
  this._input = h("input", {
9156
9487
  class: "zx-value-list__input",
9157
9488
  type: "text",
9158
9489
  placeholder: String(this.options.placeholder ?? ""),
9159
- ariaLabel: "Add value",
9490
+ ariaLabel: this._message("valueList.add", "Add value"),
9160
9491
  ariaDescribedby: instructionsId
9161
9492
  });
9162
9493
  this._status = h("div", {
@@ -9167,7 +9498,10 @@ var ValueList = class extends Component {
9167
9498
  this._instructions = h("span", {
9168
9499
  class: "zx-value-list__instructions",
9169
9500
  id: instructionsId
9170
- }, "Press Enter to add. Focus a value and press Ctrl plus Left or Right to reorder.");
9501
+ }, this._message(
9502
+ "valueList.instructions",
9503
+ "Press Enter to add. Focus a value and press Ctrl plus Left or Right to reorder."
9504
+ ));
9171
9505
  this._ownedNodes.push(this._list, this._input, this._status, this._instructions);
9172
9506
  root.append(this._list, this._input, this._status, this._instructions);
9173
9507
  this.listen(this._input, "keydown", (event) => this._handleInputKeydown(event));
@@ -9216,13 +9550,13 @@ var ValueList = class extends Component {
9216
9550
  const normalized = String(value ?? "").trim();
9217
9551
  if (!normalized) return false;
9218
9552
  if (this.options.unique && this._values.includes(normalized)) {
9219
- this._showError("This value already exists.");
9553
+ this._showError(this._message("valueList.duplicate", "This value already exists."));
9220
9554
  return false;
9221
9555
  }
9222
9556
  if (typeof this.options.validate === "function") {
9223
9557
  const result = this.options.validate(normalized);
9224
9558
  if (result !== true) {
9225
- this._showError(typeof result === "string" ? result : "This value is not valid.");
9559
+ this._showError(typeof result === "string" ? result : this._message("valueList.invalid", "This value is not valid."));
9226
9560
  return false;
9227
9561
  }
9228
9562
  }
@@ -9287,7 +9621,7 @@ var ValueList = class extends Component {
9287
9621
  draggable: Boolean(this.options.sortable) && !this._disabled,
9288
9622
  ariaSelected: "false",
9289
9623
  ariaDisabled: String(this._disabled),
9290
- ariaLabel: this.options.deletable ? `${value}. Press Delete to remove.` : value,
9624
+ ariaLabel: this.options.deletable ? this._message("valueList.removable", "%1. Press Delete to remove.", value) : value,
9291
9625
  dataset: { index }
9292
9626
  }, h("span", { class: "zx-value-list__value" }, value), this.options.deletable ? h("span", {
9293
9627
  class: "zx-value-list__remove",
@@ -9383,6 +9717,17 @@ var ValueList = class extends Component {
9383
9717
  this._status.textContent = message;
9384
9718
  this._input.setAttribute("aria-invalid", "true");
9385
9719
  }
9720
+ /**
9721
+ * Resolves a message through the host translator, falling back to the built-in English text.
9722
+ * @param {string} key Message key.
9723
+ * @param {string} fallback Built-in text, with `%1`-style placeholders.
9724
+ * @param {...unknown} args Interpolation values.
9725
+ * @returns {string}
9726
+ */
9727
+ _message(key, fallback, ...args) {
9728
+ const message = this.msg(key, ...args);
9729
+ return message === key ? printf(fallback, args) : message;
9730
+ }
9386
9731
  /** @returns {void} */
9387
9732
  destroy() {
9388
9733
  for (const node of this._ownedNodes ?? []) node.remove();
@@ -9626,6 +9971,8 @@ var Table = class extends Component {
9626
9971
  height: null,
9627
9972
  emptyText: null,
9628
9973
  rowClass: null,
9974
+ rowKind: null,
9975
+ rowKinds: null,
9629
9976
  zebra: true,
9630
9977
  editMode: false,
9631
9978
  editTrigger: "double",
@@ -9655,6 +10002,10 @@ var Table = class extends Component {
9655
10002
  this._original = created ? null : snapshotTarget(root);
9656
10003
  this._columns = normalizeTableColumns(this.options.columns);
9657
10004
  this._data = Array.isArray(this.options.data) ? [...this.options.data] : [];
10005
+ this._rowKind = normalizeRowKind(this.options.rowKind);
10006
+ this._rowKinds = normalizeRowKinds(this.options.rowKinds);
10007
+ this._spanning = this._hasSpanningRow();
10008
+ this._sortWarned = false;
9658
10009
  this._selected = /* @__PURE__ */ new Set();
9659
10010
  this._selectionAnchorId = null;
9660
10011
  this._sort = normalizeSort(this.options.sort, this._columns);
@@ -10030,6 +10381,7 @@ var Table = class extends Component {
10030
10381
  const row = this.getRow(id);
10031
10382
  const target = this.getRow(targetId);
10032
10383
  if (!row || !target || Object.is(id, targetId)) return this;
10384
+ if (this._isSpanningRow(row)) return this;
10033
10385
  const next = reorderTableRows(this._data, this.options.rowId, id, targetId, position2, this._hierarchy?.parentId);
10034
10386
  if (!next) return this;
10035
10387
  const event = this.emit("rowmove", { row, id, target, targetId, position: position2 });
@@ -10066,6 +10418,10 @@ var Table = class extends Component {
10066
10418
  /**
10067
10419
  * Activates a column sort. Local mode stably reorders data; server mode only updates the
10068
10420
  * header state and emits the request event.
10421
+ *
10422
+ * Inert while the data holds a spanning row: a sort would scatter the positions out of the
10423
+ * sections that group them, in either sort mode. The header withholds its controls for the same
10424
+ * reason, so nothing on screen invites the call.
10069
10425
  * @param {string} id Column id.
10070
10426
  * @param {TableSortDirection} [dir] Direction; omitted toggles the current column.
10071
10427
  * @param {{silent?: boolean}} [options={}] Event options.
@@ -10074,6 +10430,13 @@ var Table = class extends Component {
10074
10430
  * @fires Table#editcancel
10075
10431
  */
10076
10432
  setSort(id, dir, options = {}) {
10433
+ if (this._spanning) {
10434
+ if (!this._sortWarned) {
10435
+ this._sortWarned = true;
10436
+ console.warn("[zx.Table] setSort() is inert while the data contains spanning rowKinds rows: sorting would scatter the rows out of the sections that group them.");
10437
+ }
10438
+ return this;
10439
+ }
10077
10440
  this._abortEdit({ focus: false });
10078
10441
  const column = this._columns.find((candidate) => candidate.id === id);
10079
10442
  if (!column) throw new RangeError(`Unknown table column: ${id}`);
@@ -10125,7 +10488,7 @@ var Table = class extends Component {
10125
10488
  */
10126
10489
  setSelection(ids) {
10127
10490
  if (!Array.isArray(ids)) throw new TypeError("Table selection must be an array of row ids");
10128
- const valid = new Set(this._data.map((row) => this._idFor(row)));
10491
+ const valid = this._selectableIds();
10129
10492
  const next = /* @__PURE__ */ new Set();
10130
10493
  if (this.options.selectable !== false) {
10131
10494
  for (const id of ids) {
@@ -10237,6 +10600,7 @@ var Table = class extends Component {
10237
10600
  if (!requested) return this;
10238
10601
  const current = this._data.find((candidate) => Object.is(this._idFor(candidate), rowId));
10239
10602
  if (current === void 0) return this;
10603
+ if (this._isSpanningRow(current)) return this;
10240
10604
  const column = this._editMode === "row" && this._hiddenColumns.has(requested.id) ? this._visibleColumns().find((candidate) => resolveEditable(candidate, current) !== false) ?? null : requested;
10241
10605
  if (!column || resolveEditable(column, current) === false) return this;
10242
10606
  const activeColumnId = column.id;
@@ -10525,6 +10889,41 @@ var Table = class extends Component {
10525
10889
  _visibleColumns() {
10526
10890
  return this._columns.filter((column) => !this._hiddenColumns.has(column.id));
10527
10891
  }
10892
+ /**
10893
+ * Whether a row draws as one cell across the table instead of as a record.
10894
+ * @param {TableRow} row Row to classify.
10895
+ * @returns {boolean}
10896
+ */
10897
+ _isSpanningRow(row) {
10898
+ if (this._rowKind === null || this._rowKinds === null) return false;
10899
+ return isSpanningRow(row, this._rowKind, this._rowKinds);
10900
+ }
10901
+ /**
10902
+ * Whether any row in the current data — not only the rendered prefix — spans. This is what
10903
+ * withholds the sort controls, and the reader must not be offered a control that the next
10904
+ * `growBy()` would have to take away again.
10905
+ * @returns {boolean}
10906
+ */
10907
+ _hasSpanningRow() {
10908
+ return hasSpanningRow(this._data, this._rowKind, this._rowKinds);
10909
+ }
10910
+ /**
10911
+ * How many cells one row occupies: every visible column plus the structural columns the table
10912
+ * renders beside them. Shared by the spanning cell and the empty-state cell so the two can never
10913
+ * disagree about the table's width.
10914
+ * @returns {number}
10915
+ */
10916
+ _spanColspan() {
10917
+ return tableSpanColspan(this._visibleColumns().length, {
10918
+ selectable: this.options.selectable,
10919
+ rowReorder: this.options.rowReorder
10920
+ });
10921
+ }
10922
+ /** @returns {TableRow[]} Rendered rows that can carry a selection; spanning rows cannot. */
10923
+ _selectableData() {
10924
+ const visible = this._visibleData();
10925
+ return this._rowKind === null ? visible : visible.filter((row) => !this._isSpanningRow(row));
10926
+ }
10528
10927
  /** @returns {HTMLElement} */
10529
10928
  _createColumnControls() {
10530
10929
  const list = h("div", { class: "zx-table__column-list" });
@@ -10537,7 +10936,12 @@ var Table = class extends Component {
10537
10936
  const details = h(
10538
10937
  "details",
10539
10938
  { class: "zx-table__column-controls" },
10540
- h("summary", { class: "zx-button zx-button--sm" }, icon("fields", { size: 13 }), "Columns"),
10939
+ h(
10940
+ "summary",
10941
+ { class: "zx-button zx-button--sm" },
10942
+ icon("fields", { size: 13 }),
10943
+ this._message("table.columns", "Columns", [])
10944
+ ),
10541
10945
  list
10542
10946
  );
10543
10947
  this._renderColumnControls();
@@ -10572,7 +10976,7 @@ var Table = class extends Component {
10572
10976
  {
10573
10977
  class: "zx-table__column-order",
10574
10978
  role: "group",
10575
- ariaLabel: `Move ${column.label}`
10979
+ ariaLabel: this._message("table.moveColumn", "Move %1", [column.label])
10576
10980
  },
10577
10981
  h("button", {
10578
10982
  class: "zx-table__column-move",
@@ -10845,9 +11249,9 @@ var Table = class extends Component {
10845
11249
  row.append(selectionHeader);
10846
11250
  this.listen(this._selectAll, "click", () => {
10847
11251
  if (this._selectAll.checked) {
10848
- for (const dataRow of this._visibleData()) this._selected.add(this._idFor(dataRow));
11252
+ for (const dataRow of this._selectableData()) this._selected.add(this._idFor(dataRow));
10849
11253
  } else {
10850
- for (const dataRow of this._visibleData()) this._selected.delete(this._idFor(dataRow));
11254
+ for (const dataRow of this._selectableData()) this._selected.delete(this._idFor(dataRow));
10851
11255
  }
10852
11256
  this._selectionAnchorId = null;
10853
11257
  this._syncSelection();
@@ -10857,12 +11261,13 @@ var Table = class extends Component {
10857
11261
  this._selectAll = null;
10858
11262
  }
10859
11263
  this._headers = /* @__PURE__ */ new Map();
11264
+ this._headerSpanning = this._spanning;
10860
11265
  for (const column of this._visibleColumns()) {
10861
11266
  const th = h("th", { scope: "col" });
10862
11267
  const align = column.align ?? (isNumericColumn(column) ? "end" : null);
10863
11268
  if (align) th.dataset.align = align;
10864
11269
  if (column.headerTitle) th.title = column.headerTitle;
10865
- if (column.sortable) {
11270
+ if (column.sortable && !this._spanning) {
10866
11271
  const button = h("button", {
10867
11272
  class: "zx-table__sort-button",
10868
11273
  type: "button"
@@ -10884,6 +11289,8 @@ var Table = class extends Component {
10884
11289
  /** @returns {void} @fires Table#editcancel */
10885
11290
  _renderBody() {
10886
11291
  this._abortEdit({ focus: false });
11292
+ this._spanning = this._hasSpanningRow();
11293
+ if (this._headers && this._spanning !== this._headerSpanning) this._renderHeader();
10887
11294
  const fragment2 = document.createDocumentFragment();
10888
11295
  this._rowMeta = /* @__PURE__ */ new WeakMap();
10889
11296
  this._rowElements = /* @__PURE__ */ new Map();
@@ -10895,10 +11302,7 @@ var Table = class extends Component {
10895
11302
  fragment2.append(h(
10896
11303
  "tr",
10897
11304
  { class: "zx-table__empty-row" },
10898
- h("td", {
10899
- class: "zx-table__empty",
10900
- colspan: this._visibleColumns().length + (this.options.selectable === "multi" ? 1 : 0) + (this.options.rowReorder ? 1 : 0)
10901
- }, this._emptyContent())
11305
+ h("td", { class: "zx-table__empty", colspan: this._spanColspan() }, this._emptyContent())
10902
11306
  ));
10903
11307
  } else {
10904
11308
  this._visibleEntries().forEach((entry, visibleIndex) => {
@@ -10941,7 +11345,14 @@ var Table = class extends Component {
10941
11345
  if (this._growStep) tr.setAttribute("aria-rowindex", String(tree.visibleIndex + 2));
10942
11346
  const rowClass = typeof this.options.rowClass === "function" ? this.options.rowClass(row) : "";
10943
11347
  if (rowClass) tr.classList.add(...String(rowClass).split(/\s+/).filter(Boolean));
10944
- if (this.options.selectable !== false) tr.setAttribute("aria-selected", String(this._selected.has(id)));
11348
+ const kind = this._rowKind === null ? null : resolveRowKind(row, this._rowKind);
11349
+ const kindSpec = kind === null ? null : resolveRowKindSpec(this._rowKinds, kind);
11350
+ const spanning = Boolean(kindSpec?.span);
11351
+ if (kind !== null) tr.dataset.rowKind = kind;
11352
+ if (kindSpec?.class) tr.classList.add(...String(kindSpec.class).split(/\s+/).filter(Boolean));
11353
+ if (this.options.selectable !== false && !spanning) {
11354
+ tr.setAttribute("aria-selected", String(this._selected.has(id)));
11355
+ }
10945
11356
  if (this._hierarchy) {
10946
11357
  tr.setAttribute("aria-level", String(tree.depth + 1));
10947
11358
  if (tree.hasChildren) tr.setAttribute("aria-expanded", String(this._expanded.has(id)));
@@ -10950,6 +11361,23 @@ var Table = class extends Component {
10950
11361
  const elements = this._rowElements.get(id) ?? [];
10951
11362
  elements.push(tr);
10952
11363
  this._rowElements.set(id, elements);
11364
+ if (spanning) {
11365
+ tr.dataset.span = "true";
11366
+ const cell = (
11367
+ /** @type {HTMLTableCellElement} */
11368
+ h("th", {
11369
+ class: "zx-table__section-cell",
11370
+ scope: "colgroup",
11371
+ colspan: this._spanColspan()
11372
+ })
11373
+ );
11374
+ const content = spanRowContent(row, kindSpec, this._visibleColumns(), index);
11375
+ if (content && typeof content === "object" && typeof content.nodeType === "number") {
11376
+ cell.append(content);
11377
+ } else if (content != null) cell.append(document.createTextNode(String(content)));
11378
+ tr.append(cell);
11379
+ return tr;
11380
+ }
10953
11381
  if (this.options.rowReorder) {
10954
11382
  const handle = h("button", {
10955
11383
  class: "zx-table__row-handle",
@@ -11068,7 +11496,7 @@ var Table = class extends Component {
11068
11496
  }
11069
11497
  }
11070
11498
  this.emit("rowclick", { ...meta, event });
11071
- if (this.options.selectable === "single" && !this._selected.has(meta.id)) {
11499
+ if (this.options.selectable === "single" && !this._selected.has(meta.id) && !this._isSpanningRow(meta.row)) {
11072
11500
  this._selected = /* @__PURE__ */ new Set([meta.id]);
11073
11501
  this._selectionAnchorId = meta.id;
11074
11502
  this._syncSelection();
@@ -11411,6 +11839,7 @@ var Table = class extends Component {
11411
11839
  if (row < 0 || row >= visible.length) return null;
11412
11840
  const candidateRow = visible[row];
11413
11841
  const candidateColumn = columns[column];
11842
+ if (this._isSpanningRow(candidateRow)) continue;
11414
11843
  if (resolveEditable(candidateColumn, candidateRow) !== false) {
11415
11844
  return {
11416
11845
  id: this._idFor(candidateRow),
@@ -11492,6 +11921,7 @@ var Table = class extends Component {
11492
11921
  const start = Math.min(anchorIndex, currentIndex);
11493
11922
  const end = Math.max(anchorIndex, currentIndex);
11494
11923
  for (let index = start; index <= end; index += 1) {
11924
+ if (this._isSpanningRow(visible[index])) continue;
11495
11925
  const id = this._idFor(visible[index]);
11496
11926
  if (checked) this._selected.add(id);
11497
11927
  else this._selected.delete(id);
@@ -11510,6 +11940,7 @@ var Table = class extends Component {
11510
11940
  for (const [id, rows] of this._rowElements) {
11511
11941
  const selected = this._selected.has(id);
11512
11942
  for (const row of rows) {
11943
+ if (row.hasAttribute("data-span")) continue;
11513
11944
  row.setAttribute("aria-selected", String(selected));
11514
11945
  const checkbox = row.querySelector(".zx-table__row-checkbox");
11515
11946
  if (checkbox) checkbox.checked = selected;
@@ -11520,7 +11951,7 @@ var Table = class extends Component {
11520
11951
  /** @returns {void} */
11521
11952
  _syncSelectAll() {
11522
11953
  if (!this._selectAll) return;
11523
- const visible = this._visibleData();
11954
+ const visible = this._selectableData();
11524
11955
  const selectedRows = visible.reduce(
11525
11956
  (count, row) => count + Number(this._selected.has(this._idFor(row))),
11526
11957
  0
@@ -11533,13 +11964,15 @@ var Table = class extends Component {
11533
11964
  _syncSortHeader() {
11534
11965
  if (!this._headers) return;
11535
11966
  for (const [id, header] of this._headers) {
11536
- if (this._sort?.id === id) header.setAttribute("aria-sort", this._sort.dir === "asc" ? "ascending" : "descending");
11537
- else header.removeAttribute("aria-sort");
11967
+ if (!this._spanning && this._sort?.id === id) {
11968
+ header.setAttribute("aria-sort", this._sort.dir === "asc" ? "ascending" : "descending");
11969
+ } else header.removeAttribute("aria-sort");
11538
11970
  }
11539
11971
  }
11540
11972
  /** @returns {void} */
11541
11973
  _sortData() {
11542
- if (!this._sort) return;
11974
+ this._spanning = this._hasSpanningRow();
11975
+ if (!this._sort || this._spanning) return;
11543
11976
  const column = this._columns.find((candidate) => candidate.id === this._sort.id);
11544
11977
  if (!column) return;
11545
11978
  const getValue = typeof column.sortValue === "function" ? column.sortValue : (row) => row?.[column.id];
@@ -11572,9 +12005,17 @@ var Table = class extends Component {
11572
12005
  const valid = new Set(branches);
11573
12006
  for (const id of this._expanded) if (!valid.has(id)) this._expanded.delete(id);
11574
12007
  }
12008
+ /** @returns {Set<unknown>} Ids of every row in the data that may hold a selection. */
12009
+ _selectableIds() {
12010
+ const ids = /* @__PURE__ */ new Set();
12011
+ for (const row of this._data) {
12012
+ if (!this._isSpanningRow(row)) ids.add(this._idFor(row));
12013
+ }
12014
+ return ids;
12015
+ }
11575
12016
  /** @returns {boolean} Whether selection changed. */
11576
12017
  _pruneSelection() {
11577
- const valid = new Set(this._data.map((row) => this._idFor(row)));
12018
+ const valid = this._selectableIds();
11578
12019
  const previousSize = this._selected.size;
11579
12020
  for (const id of this._selected) if (!valid.has(id)) this._selected.delete(id);
11580
12021
  if (!valid.has(this._selectionAnchorId)) this._selectionAnchorId = null;
@@ -11594,6 +12035,34 @@ var Table = class extends Component {
11594
12035
  this.emit("datachange", { rows: this.getData() });
11595
12036
  }
11596
12037
  };
12038
+ function resolveRowKind(row, rowKind) {
12039
+ if (rowKind == null || rowKind === "") return null;
12040
+ const raw2 = typeof rowKind === "function" ? rowKind(row) : row?.[rowKind];
12041
+ return raw2 == null ? null : String(raw2);
12042
+ }
12043
+ function resolveRowKindSpec(rowKinds, kind) {
12044
+ if (kind === null || rowKinds == null || typeof rowKinds !== "object") return null;
12045
+ if (!Object.prototype.hasOwnProperty.call(rowKinds, kind)) return null;
12046
+ const spec = rowKinds[kind];
12047
+ return spec && typeof spec === "object" ? spec : null;
12048
+ }
12049
+ function isSpanningRow(row, rowKind, rowKinds) {
12050
+ return Boolean(resolveRowKindSpec(rowKinds, resolveRowKind(row, rowKind))?.span);
12051
+ }
12052
+ function hasSpanningRow(rows, rowKind, rowKinds) {
12053
+ if (!Array.isArray(rows) || rowKind == null || rowKinds == null) return false;
12054
+ return rows.some((row) => isSpanningRow(row, rowKind, rowKinds));
12055
+ }
12056
+ function tableSpanColspan(columnCount, structure = {}) {
12057
+ const columns = Number.isFinite(Number(columnCount)) ? Math.max(0, Math.trunc(Number(columnCount))) : 0;
12058
+ const extras = (structure.selectable === "multi" ? 1 : 0) + (structure.rowReorder ? 1 : 0);
12059
+ return Math.max(1, columns + extras);
12060
+ }
12061
+ function spanRowContent(row, spec, visibleColumns, index = 0) {
12062
+ if (typeof spec?.render === "function") return spec.render(row, index);
12063
+ const first = visibleColumns?.[0];
12064
+ return first ? row?.[first.id] : null;
12065
+ }
11597
12066
  function formatTableCell(column, row, index = 0) {
11598
12067
  if (typeof column?.render === "function") return column.render(row, index);
11599
12068
  const value = row?.[column?.id];
@@ -11822,6 +12291,20 @@ function normalizeHierarchy(option, columns) {
11822
12291
  if (!column) return null;
11823
12292
  return { ...option, parentId, column };
11824
12293
  }
12294
+ function normalizeRowKind(option) {
12295
+ if (typeof option === "function") return (
12296
+ /** @type {(row: TableRow) => unknown} */
12297
+ option
12298
+ );
12299
+ return typeof option === "string" && option !== "" ? option : null;
12300
+ }
12301
+ function normalizeRowKinds(option) {
12302
+ if (!option || typeof option !== "object" || Array.isArray(option)) return null;
12303
+ return (
12304
+ /** @type {Record<string, TableRowKind>} */
12305
+ option
12306
+ );
12307
+ }
11825
12308
  function initialExpanded(option) {
11826
12309
  return new Set(Array.isArray(option) ? option : []);
11827
12310
  }
@@ -12058,14 +12541,20 @@ var Launcher = class extends Component {
12058
12541
  debounce: 250,
12059
12542
  minQuery: 0,
12060
12543
  maxResults: 100,
12061
- placeholder: "Search applications and records",
12062
- label: "Launcher",
12063
- closeLabel: "Close launcher",
12064
- resultsLabel: "Launcher results",
12065
- emptyText: "No results",
12066
- loadingText: "Searching\u2026",
12544
+ /*
12545
+ * The six labels default to null rather than to their English text so that an application
12546
+ * which installs a translator and passes no labels still gets a translated launcher. The
12547
+ * documented default is what `_message()` falls back to, so an untranslated application
12548
+ * renders exactly the same words as before.
12549
+ */
12550
+ placeholder: null,
12551
+ label: null,
12552
+ closeLabel: null,
12553
+ resultsLabel: null,
12554
+ emptyText: null,
12555
+ loadingText: null,
12067
12556
  shortcut: "mod+k",
12068
- hints: { move: "Move", open: "Open", close: "Close" },
12557
+ hints: null,
12069
12558
  scope: null
12070
12559
  };
12071
12560
  /**
@@ -12104,6 +12593,7 @@ var Launcher = class extends Component {
12104
12593
  this._busy = false;
12105
12594
  this._destroyed = false;
12106
12595
  const listId = uid("zx-launcher-results");
12596
+ const label = String(this.options.label ?? this._message("launcher.label", "Launcher"));
12107
12597
  const input = h("input", {
12108
12598
  ref: "input",
12109
12599
  class: "zx-launcher__input",
@@ -12114,15 +12604,15 @@ var Launcher = class extends Component {
12114
12604
  ariaAutocomplete: "list",
12115
12605
  ariaControls: listId,
12116
12606
  ariaExpanded: "false",
12117
- ariaLabel: this.options.label,
12118
- placeholder: this.options.placeholder,
12607
+ ariaLabel: label,
12608
+ placeholder: this.options.placeholder ?? this._message("launcher.placeholder", "Search applications and records"),
12119
12609
  value: this._query
12120
12610
  });
12121
12611
  const close = h("button", {
12122
12612
  ref: "close",
12123
12613
  class: "zx-icon-btn zx-launcher__close",
12124
12614
  type: "button",
12125
- ariaLabel: this.options.closeLabel
12615
+ ariaLabel: this.options.closeLabel ?? this._message("launcher.close", "Close launcher")
12126
12616
  }, icon("x"));
12127
12617
  const status = h("div", {
12128
12618
  ref: "status",
@@ -12135,14 +12625,18 @@ var Launcher = class extends Component {
12135
12625
  class: "zx-launcher__results",
12136
12626
  id: listId,
12137
12627
  role: "listbox",
12138
- ariaLabel: this.options.resultsLabel
12628
+ ariaLabel: this.options.resultsLabel ?? this._message("launcher.results", "Launcher results")
12139
12629
  });
12140
12630
  const shortcut = this.options.shortcut === "mod+k" ? h("kbd", {
12141
12631
  class: "zx-launcher__shortcut",
12142
12632
  ariaHidden: "true"
12143
12633
  }, launcherShortcutLabel()) : null;
12144
- const footer = launcherHints(this.options.hints);
12145
- dialog.setAttribute("aria-label", String(this.options.label));
12634
+ const footer = launcherHints(this.options.hints, {
12635
+ move: this._message("launcher.hintMove", "Move"),
12636
+ open: this._message("launcher.hintOpen", "Open"),
12637
+ close: this._message("launcher.hintClose", "Close")
12638
+ });
12639
+ dialog.setAttribute("aria-label", label);
12146
12640
  dialog.replaceChildren(
12147
12641
  h(
12148
12642
  "div",
@@ -12434,9 +12928,13 @@ var Launcher = class extends Component {
12434
12928
  }
12435
12929
  /** @returns {void} */
12436
12930
  _renderStatus() {
12437
- if (this._busy) this.refs.status.textContent = String(this.options.loadingText);
12438
- else if (this._results.length === 0) this.refs.status.textContent = String(this.options.emptyText);
12439
- else this.refs.status.textContent = "";
12931
+ if (this._busy) {
12932
+ this.refs.status.textContent = String(this.options.loadingText ?? this._message("launcher.loading", "Searching\u2026"));
12933
+ } else if (this._results.length === 0) {
12934
+ this.refs.status.textContent = String(this.options.emptyText ?? this._message("launcher.empty", "No results"));
12935
+ } else {
12936
+ this.refs.status.textContent = "";
12937
+ }
12440
12938
  this.refs.status.hidden = !this.refs.status.textContent;
12441
12939
  }
12442
12940
  /** @returns {void} */
@@ -12529,6 +13027,16 @@ var Launcher = class extends Component {
12529
13027
  event.preventDefault();
12530
13028
  this.open();
12531
13029
  }
13030
+ /**
13031
+ * Resolves a message through the host translator, falling back to the built-in English text.
13032
+ * @param {string} key Message key.
13033
+ * @param {string} fallback English default.
13034
+ * @returns {string}
13035
+ */
13036
+ _message(key, fallback) {
13037
+ const message = this.msg(key);
13038
+ return message === key ? fallback : message;
13039
+ }
12532
13040
  };
12533
13041
  function launcherShortcutLabel() {
12534
13042
  const platform = String(
@@ -12537,15 +13045,15 @@ function launcherShortcutLabel() {
12537
13045
  );
12538
13046
  return /mac|iphone|ipad/i.test(platform) ? "\u2318 K" : "Ctrl K";
12539
13047
  }
12540
- function launcherHints(hints) {
13048
+ function launcherHints(hints, defaults) {
12541
13049
  if (hints === false) return null;
12542
13050
  const labels = hints && typeof hints === "object" ? hints : {};
12543
13051
  return h(
12544
13052
  "footer",
12545
13053
  { class: "zx-launcher__hints", ariaHidden: "true" },
12546
- h("span", {}, h("kbd", {}, "\u2191\u2193\u2190\u2192"), labels.move ?? "Move"),
12547
- h("span", {}, h("kbd", {}, "\u21B5"), labels.open ?? "Open"),
12548
- h("span", {}, h("kbd", {}, "Esc"), labels.close ?? "Close")
13054
+ h("span", {}, h("kbd", {}, "\u2191\u2193\u2190\u2192"), labels.move ?? defaults.move),
13055
+ h("span", {}, h("kbd", {}, "\u21B5"), labels.open ?? defaults.open),
13056
+ h("span", {}, h("kbd", {}, "Esc"), labels.close ?? defaults.close)
12549
13057
  );
12550
13058
  }
12551
13059
  function launcherItemIcon(item) {
@@ -12965,8 +13473,8 @@ var Message = class _Message extends Component {
12965
13473
  const close = h("button", {
12966
13474
  class: "zx-icon-btn zx-message__close",
12967
13475
  type: "button",
12968
- ariaLabel: "Close message",
12969
- title: "Close"
13476
+ ariaLabel: this._message("message.close", "Close message"),
13477
+ title: this._message("message.closeTitle", "Close")
12970
13478
  }, icon("x", { size: 14 }));
12971
13479
  node.append(close);
12972
13480
  this.listen(close, "click", () => this._close(entry));
@@ -13034,6 +13542,16 @@ var Message = class _Message extends Component {
13034
13542
  this._entries.delete(entry);
13035
13543
  this._queue.remove(entry);
13036
13544
  }
13545
+ /**
13546
+ * Resolves a message through the host translator, falling back to the built-in English text.
13547
+ * @param {string} key Message key.
13548
+ * @param {string} fallback English default.
13549
+ * @returns {string}
13550
+ */
13551
+ _message(key, fallback) {
13552
+ const message = this.msg(key);
13553
+ return message === key ? fallback : message;
13554
+ }
13037
13555
  /** @param {Element|string|null|undefined} scope @returns {Message} */
13038
13556
  static #floatingRegion(scope2) {
13039
13557
  const host = overlayHost(scope2 ?? null);
@@ -13132,6 +13650,29 @@ function calendarRange(view, anchor, options = {}) {
13132
13650
  }
13133
13651
  return { start, end };
13134
13652
  }
13653
+ function calendarPageDate(view, anchor, direction, options = {}) {
13654
+ const date = validDate5(anchor, "Calendar date");
13655
+ const step = integer(direction, "direction");
13656
+ if (view === "day") date.setDate(date.getDate() + step);
13657
+ else if (view === "agenda") {
13658
+ date.setDate(date.getDate() + step * positiveInteger(options.agendaDays ?? 14, "agendaDays"));
13659
+ } else if (view === "week") date.setDate(date.getDate() + step * 7);
13660
+ else if (view === "month") return addCalendarMonths(date, step);
13661
+ else if (view === "year") return addCalendarMonths(date, step * 12);
13662
+ else throw new RangeError(`Unknown calendar view: ${view}`);
13663
+ return date;
13664
+ }
13665
+ function addCalendarMonths(value, months) {
13666
+ const date = validDate5(value, "Calendar date");
13667
+ const day = date.getDate();
13668
+ date.setDate(1);
13669
+ date.setMonth(date.getMonth() + integer(months, "month amount"));
13670
+ date.setDate(Math.min(day, daysInCalendarMonth(date.getFullYear(), date.getMonth())));
13671
+ return date;
13672
+ }
13673
+ function daysInCalendarMonth(year, month) {
13674
+ return new Date(year, month + 1, 0).getDate();
13675
+ }
13135
13676
  function normalizeCalendarEvents(records, options = {}) {
13136
13677
  if (!Array.isArray(records)) throw new TypeError("Calendar events must be an array");
13137
13678
  const readers = {
@@ -13478,7 +14019,7 @@ var Calendar = class extends Component {
13478
14019
  ref: "views",
13479
14020
  class: "zx-calendar__views",
13480
14021
  role: "group",
13481
- ariaLabel: "Calendar view"
14022
+ ariaLabel: this._message("calendar.viewSwitch", "Calendar view")
13482
14023
  }, this._views.map((view) => h("button", {
13483
14024
  type: "button",
13484
14025
  class: "zx-calendar__view-button",
@@ -13575,7 +14116,7 @@ var Calendar = class extends Component {
13575
14116
  durationEditable: Object.prototype.hasOwnProperty.call(changes, "durationEditable") ? changes.durationEditable : current.durationEditable,
13576
14117
  data: changes.data ?? current.data
13577
14118
  };
13578
- const [next] = normalizeCalendarEvents([source]);
14119
+ const [next] = normalizeCalendarEvents([source], { dateUnit: this.options.dateUnit });
13579
14120
  next.data = source.data;
13580
14121
  this._events = this._events.map((event, eventIndex) => eventIndex === index ? next : event);
13581
14122
  this._mutationVersions.delete(String(id));
@@ -13926,7 +14467,7 @@ var Calendar = class extends Component {
13926
14467
  eventCount: String(count)
13927
14468
  },
13928
14469
  ariaCurrent: sameCalendarDay(day, this._now()) ? "date" : null,
13929
- ariaLabel: `${this._format(day, { dateStyle: "full" })}, ${count} ${count === 1 ? "event" : "events"}`,
14470
+ ariaLabel: this._dayCountLabel(day, count),
13930
14471
  disabled: this.options.disabled
13931
14472
  }, this._format(day, { day: "numeric" })));
13932
14473
  }
@@ -13996,7 +14537,7 @@ var Calendar = class extends Component {
13996
14537
  activity: String(Math.min(3, count))
13997
14538
  },
13998
14539
  ariaCurrent: sameCalendarDay(day, this._now()) ? "date" : null,
13999
- ariaLabel: `${this._format(day, { dateStyle: "full" })}, ${count} ${count === 1 ? "event" : "events"}`,
14540
+ ariaLabel: this._dayCountLabel(day, count),
14000
14541
  disabled: this.options.disabled
14001
14542
  }, String(day.getDate())));
14002
14543
  }
@@ -14601,13 +15142,9 @@ var Calendar = class extends Component {
14601
15142
  }
14602
15143
  /** @param {number} direction @returns {this} */
14603
15144
  _page(direction) {
14604
- const date = new Date(this._date.getTime());
14605
- if (this._view === "day") date.setDate(date.getDate() + direction);
14606
- else if (this._view === "agenda") date.setDate(date.getDate() + direction * this.options.agendaDays);
14607
- else if (this._view === "week") date.setDate(date.getDate() + direction * 7);
14608
- else if (this._view === "month") date.setMonth(date.getMonth() + direction);
14609
- else date.setFullYear(date.getFullYear() + direction);
14610
- return this.setDate(date);
15145
+ return this.setDate(calendarPageDate(this._view, this._date, direction, {
15146
+ agendaDays: this.options.agendaDays
15147
+ }));
14611
15148
  }
14612
15149
  /** @param {{start: Date, end: Date}} range @returns {string} */
14613
15150
  _rangeTitle(range) {
@@ -14731,6 +15268,31 @@ var Calendar = class extends Component {
14731
15268
  if (!this._destroyed) this.refs.status.textContent = message;
14732
15269
  });
14733
15270
  }
15271
+ /**
15272
+ * Names a month or year cell by its date and how many events it holds. Singular and plural are
15273
+ * separate keys because a language that inflects the noun cannot reach it from one template.
15274
+ * @param {Date} day Day the cell stands for.
15275
+ * @param {number} count Events on that day.
15276
+ * @returns {string}
15277
+ */
15278
+ _dayCountLabel(day, count) {
15279
+ const date = this._format(day, { dateStyle: "full" });
15280
+ return count === 1 ? this._message("calendar.dayEvent", "%1, %2 event", date, count) : this._message("calendar.dayEvents", "%1, %2 events", date, count);
15281
+ }
15282
+ /**
15283
+ * Resolves a message through the host translator, falling back to the built-in English text.
15284
+ *
15285
+ * Distinct from `msg()`: the built-in `msg` option answers the calendar's own vocabulary, so a
15286
+ * dotted key falls past it to the host translator, which is what these strings never had.
15287
+ * @param {string} key Message key.
15288
+ * @param {string} fallback Built-in text, with `%1`-style placeholders.
15289
+ * @param {...unknown} args Interpolation values.
15290
+ * @returns {string}
15291
+ */
15292
+ _message(key, fallback, ...args) {
15293
+ const message = this.msg(key, ...args);
15294
+ return message === key ? printf(fallback, args) : message;
15295
+ }
14734
15296
  };
14735
15297
  function normalizeViews(value) {
14736
15298
  if (!Array.isArray(value) || value.length === 0) throw new TypeError("Calendar views must be a non-empty array");
@@ -15349,6 +15911,7 @@ function isEmpty(value) {
15349
15911
  function registerFieldAdapters() {
15350
15912
  registerSelectFieldAdapter();
15351
15913
  registerChecklistFieldAdapter();
15914
+ registerCodeEditorFieldAdapter();
15352
15915
  registerDateboxFieldAdapters();
15353
15916
  registerDatePickerFieldAdapters();
15354
15917
  registerDateRangeFieldAdapter();