@zeyos/zx 4.3.4 → 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 (128) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/README.md +7 -0
  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.d.ts +19 -0
  8. package/dist/types/components/card/card.d.ts +7 -0
  9. package/dist/types/components/card-view/card-view.d.ts +11 -3
  10. package/dist/types/components/card-view/record-card.d.ts +7 -1
  11. package/dist/types/components/code-editor/code-editor.d.ts +160 -0
  12. package/dist/types/components/code-editor/field-adapter.d.ts +5 -0
  13. package/dist/types/components/context-menu/context-menu.d.ts +18 -2
  14. package/dist/types/components/date-picker/date-picker.d.ts +8 -0
  15. package/dist/types/components/date-picker/time-picker.d.ts +7 -0
  16. package/dist/types/components/dialog/dialog.d.ts +29 -0
  17. package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
  18. package/dist/types/components/file-item/file-item.d.ts +234 -0
  19. package/dist/types/components/file-list/file-list.d.ts +171 -0
  20. package/dist/types/components/filter/filter.d.ts +91 -36
  21. package/dist/types/components/filter-panel/filter-panel.d.ts +449 -0
  22. package/dist/types/components/kanban-view/kanban-card.d.ts +138 -0
  23. package/dist/types/components/kanban-view/kanban-view.d.ts +170 -4
  24. package/dist/types/components/launcher/launcher.d.ts +19 -12
  25. package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
  26. package/dist/types/components/menu-button/menu-button.d.ts +4 -37
  27. package/dist/types/components/message/message.d.ts +7 -0
  28. package/dist/types/components/modal/modal.d.ts +48 -0
  29. package/dist/types/components/navigation-bar/navigation-bar.d.ts +105 -21
  30. package/dist/types/components/panel/panel.d.ts +7 -0
  31. package/dist/types/components/search/search.d.ts +7 -0
  32. package/dist/types/components/select/select.d.ts +4 -0
  33. package/dist/types/components/sort-control/sort-control.d.ts +105 -0
  34. package/dist/types/components/stat-tile/stat-tile.d.ts +277 -0
  35. package/dist/types/components/tabbox/tabbox.d.ts +7 -0
  36. package/dist/types/components/table/table.d.ts +144 -0
  37. package/dist/types/components/table-view/table-view.d.ts +11 -5
  38. package/dist/types/components/timebox/timebox.d.ts +7 -0
  39. package/dist/types/components/value-list/value-list.d.ts +8 -0
  40. package/dist/types/components/view/record-view.d.ts +96 -5
  41. package/dist/types/core/icons.d.ts +17 -0
  42. package/dist/types/index.d.ts +11 -0
  43. package/dist/types/internal/app-rail.d.ts +10 -0
  44. package/dist/types/internal/menu-items.d.ts +134 -0
  45. package/dist/zx-zeyos.esm.js +616 -77
  46. package/dist/zx-zeyos.esm.min.js +8 -1
  47. package/dist/zx.css +2085 -238
  48. package/dist/zx.esm.js +6704 -1542
  49. package/dist/zx.esm.min.js +10 -3
  50. package/dist/zx.global.js +6704 -1542
  51. package/dist/zx.global.min.js +10 -3
  52. package/dist/zx.min.css +1 -1
  53. package/docs/api.json +1479 -127
  54. package/docs/llms.md +315 -10
  55. package/docs/llms.txt +11 -5
  56. package/package.json +1 -1
  57. package/src/components/activity-item/activity-item.css +137 -0
  58. package/src/components/activity-item/activity-item.js +599 -0
  59. package/src/components/activity-list/activity-list.css +64 -0
  60. package/src/components/activity-list/activity-list.js +375 -0
  61. package/src/components/app-rail/app-rail.css +227 -19
  62. package/src/components/app-sidebar/app-sidebar.css +414 -24
  63. package/src/components/app-sidebar/app-sidebar.js +65 -5
  64. package/src/components/button/button.css +8 -0
  65. package/src/components/button/button.js +6 -1
  66. package/src/components/calendar/calendar.js +33 -3
  67. package/src/components/card/card.js +12 -1
  68. package/src/components/card-view/card-view.css +1 -0
  69. package/src/components/card-view/card-view.js +25 -9
  70. package/src/components/card-view/record-card.js +7 -3
  71. package/src/components/code-editor/code-editor.css +67 -0
  72. package/src/components/code-editor/code-editor.js +288 -0
  73. package/src/components/code-editor/field-adapter.js +34 -0
  74. package/src/components/context-menu/context-menu.css +80 -3
  75. package/src/components/context-menu/context-menu.js +22 -30
  76. package/src/components/date-picker/date-picker.js +22 -8
  77. package/src/components/date-picker/month-picker.js +3 -3
  78. package/src/components/date-picker/time-picker.js +20 -8
  79. package/src/components/dialog/dialog.js +88 -66
  80. package/src/components/dropdown/dropdown.js +2 -2
  81. package/src/components/entity-ref/entity-ref.css +182 -0
  82. package/src/components/entity-ref/entity-ref.js +496 -0
  83. package/src/components/file-item/file-item.css +88 -0
  84. package/src/components/file-item/file-item.js +371 -0
  85. package/src/components/file-list/file-list.css +51 -0
  86. package/src/components/file-list/file-list.js +320 -0
  87. package/src/components/filter/filter.css +27 -1
  88. package/src/components/filter/filter.js +199 -45
  89. package/src/components/filter-panel/filter-panel.css +104 -0
  90. package/src/components/filter-panel/filter-panel.js +964 -0
  91. package/src/components/kanban-view/kanban-card.js +239 -0
  92. package/src/components/kanban-view/kanban-view.css +194 -0
  93. package/src/components/kanban-view/kanban-view.js +251 -8
  94. package/src/components/launcher/launcher.js +59 -27
  95. package/src/components/list-toolbar/list-toolbar.css +189 -0
  96. package/src/components/list-toolbar/list-toolbar.js +851 -0
  97. package/src/components/menu-button/menu-button.css +80 -3
  98. package/src/components/menu-button/menu-button.js +21 -37
  99. package/src/components/message/message.css +5 -2
  100. package/src/components/message/message.js +13 -2
  101. package/src/components/modal/modal.css +15 -0
  102. package/src/components/modal/modal.js +141 -0
  103. package/src/components/navigation-bar/navigation-bar.css +77 -2
  104. package/src/components/navigation-bar/navigation-bar.js +171 -4
  105. package/src/components/panel/panel.js +13 -2
  106. package/src/components/search/search.js +17 -5
  107. package/src/components/select/field-adapter.js +4 -1
  108. package/src/components/select/select.js +3 -0
  109. package/src/components/sort-control/sort-control.css +17 -0
  110. package/src/components/sort-control/sort-control.js +140 -0
  111. package/src/components/stat-tile/stat-tile.css +213 -0
  112. package/src/components/stat-tile/stat-tile.js +590 -0
  113. package/src/components/tabbox/tabbox.js +12 -1
  114. package/src/components/table/table.css +51 -2
  115. package/src/components/table/table.js +298 -19
  116. package/src/components/table-view/table-view.css +8 -2
  117. package/src/components/table-view/table-view.js +21 -6
  118. package/src/components/tag-picker/tag-picker.css +3 -1
  119. package/src/components/timebox/timebox.js +18 -5
  120. package/src/components/value-list/value-list.js +22 -6
  121. package/src/components/view/record-view.css +5 -0
  122. package/src/components/view/record-view.js +100 -7
  123. package/src/core/icons.js +74 -5
  124. package/src/index.js +25 -0
  125. package/src/internal/app-rail.js +19 -1
  126. package/src/internal/menu-items.js +142 -0
  127. package/styles/print.css +192 -0
  128. package/styles/zx.css +11 -0
@@ -1800,6 +1800,271 @@ function registerChecklistFieldAdapter() {
1800
1800
  });
1801
1801
  }
1802
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
+
1803
2068
  // src/core/date.js
1804
2069
  var MONTHS = [
1805
2070
  "January",
@@ -2085,6 +2350,8 @@ function loadFontAwesomeKit(kit) {
2085
2350
  // src/core/icons.js
2086
2351
  var SVG_NS = "http://www.w3.org/2000/svg";
2087
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"],
2088
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"],
2089
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"],
2090
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"],
@@ -2180,11 +2447,21 @@ function icon(name, options = {}) {
2180
2447
  }
2181
2448
  function builtinIcon(name, options) {
2182
2449
  const entry = extraGlyphs[name] ?? icons[name];
2183
- 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
+ }
2184
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) {
2185
2463
  const { size = 16 } = options;
2186
2464
  const svg = document.createElementNS(SVG_NS, "svg");
2187
- const path = document.createElementNS(SVG_NS, "path");
2188
2465
  svg.setAttribute("class", classList("zx-icon", options.class));
2189
2466
  svg.setAttribute("viewBox", viewBox);
2190
2467
  svg.setAttribute("width", String(size));
@@ -2192,10 +2469,20 @@ function builtinIcon(name, options) {
2192
2469
  svg.setAttribute("fill", "currentColor");
2193
2470
  svg.setAttribute("focusable", "false");
2194
2471
  applyLabel(svg, options.label ?? null);
2195
- path.setAttribute("d", pathData);
2196
- svg.append(path);
2197
2472
  return svg;
2198
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
+ }
2199
2486
  function faIcon(classes, options) {
2200
2487
  const { size = 16 } = options;
2201
2488
  const element = document.createElement("i");
@@ -2239,15 +2526,15 @@ var TimePicker = class extends Component {
2239
2526
  const content = h("div", {
2240
2527
  class: "zx-time-picker__segments",
2241
2528
  role: "group",
2242
- ariaLabel: "Time"
2529
+ ariaLabel: this._message("timePicker.label", "Time")
2243
2530
  });
2244
2531
  this._content = content;
2245
- this._addSpin(content, "h", "Hour", 23);
2532
+ this._addSpin(content, "h", this._message("timePicker.hour", "Hour"), 23);
2246
2533
  content.append(h("span", { class: "zx-time-picker__separator", ariaHidden: "true" }, ":"));
2247
- this._addSpin(content, "m", "Minute", 59);
2534
+ this._addSpin(content, "m", this._message("timePicker.minute", "Minute"), 59);
2248
2535
  if (this.options.seconds) {
2249
2536
  content.append(h("span", { class: "zx-time-picker__separator", ariaHidden: "true" }, ":"));
2250
- this._addSpin(content, "s", "Second", 59);
2537
+ this._addSpin(content, "s", this._message("timePicker.second", "Second"), 59);
2251
2538
  }
2252
2539
  root.append(content);
2253
2540
  if (this.options.clock) {
@@ -2256,7 +2543,7 @@ var TimePicker = class extends Component {
2256
2543
  ref: "clockToggle",
2257
2544
  class: "zx-icon-btn zx-time-picker__clock-toggle",
2258
2545
  type: "button",
2259
- ariaLabel: "Pick the time on a clock",
2546
+ ariaLabel: this._message("timePicker.clock", "Pick the time on a clock"),
2260
2547
  ariaExpanded: "false",
2261
2548
  ariaControls: clockId
2262
2549
  }, icon("clock", { size: 15 })));
@@ -2403,7 +2690,7 @@ var TimePicker = class extends Component {
2403
2690
  ref: "headHour",
2404
2691
  class: "zx-time-picker__clock-unit",
2405
2692
  type: "button",
2406
- ariaLabel: "Select the hour",
2693
+ ariaLabel: this._message("timePicker.selectHour", "Select the hour"),
2407
2694
  dataset: { unit: "h" }
2408
2695
  }),
2409
2696
  h("span", { class: "zx-time-picker__clock-colon", ariaHidden: "true" }, ":"),
@@ -2411,7 +2698,7 @@ var TimePicker = class extends Component {
2411
2698
  ref: "headMinute",
2412
2699
  class: "zx-time-picker__clock-unit",
2413
2700
  type: "button",
2414
- ariaLabel: "Select the minutes",
2701
+ ariaLabel: this._message("timePicker.selectMinutes", "Select the minutes"),
2415
2702
  dataset: { unit: "m" }
2416
2703
  })
2417
2704
  ),
@@ -2469,7 +2756,7 @@ var TimePicker = class extends Component {
2469
2756
  this.refs.headMinute.textContent = pad2(this._display.m);
2470
2757
  this.refs.headHour.setAttribute("aria-pressed", String(hours));
2471
2758
  this.refs.headMinute.setAttribute("aria-pressed", String(!hours));
2472
- 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"));
2473
2760
  if (!this._clockOpen) return;
2474
2761
  const selected = hours ? this._display.h : this._display.m;
2475
2762
  const marks = hours ? hourMarks() : minuteMarks();
@@ -2504,6 +2791,16 @@ var TimePicker = class extends Component {
2504
2791
  element.setAttribute("aria-checked", String(checked));
2505
2792
  element.tabIndex = checked ? 0 : -1;
2506
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
+ }
2507
2804
  };
2508
2805
  function hourMarks() {
2509
2806
  const marks = [];
@@ -2590,7 +2887,7 @@ var DatePicker = class extends Component {
2590
2887
  ref: "previous",
2591
2888
  class: "zx-date-picker__nav",
2592
2889
  type: "button",
2593
- ariaLabel: "Previous month"
2890
+ ariaLabel: this._message("datePicker.previousMonth", "Previous month")
2594
2891
  }, icon("chevron-left")),
2595
2892
  h("button", {
2596
2893
  ref: "heading",
@@ -2603,7 +2900,7 @@ var DatePicker = class extends Component {
2603
2900
  ref: "next",
2604
2901
  class: "zx-date-picker__nav",
2605
2902
  type: "button",
2606
- ariaLabel: "Next month"
2903
+ ariaLabel: this._message("datePicker.nextMonth", "Next month")
2607
2904
  }, icon("chevron-right"))
2608
2905
  ),
2609
2906
  h(
@@ -2620,14 +2917,14 @@ var DatePicker = class extends Component {
2620
2917
  ref: "quickPrevious",
2621
2918
  class: "zx-date-picker__nav",
2622
2919
  type: "button",
2623
- ariaLabel: "Previous year"
2920
+ ariaLabel: this._message("datePicker.previousYear", "Previous year")
2624
2921
  }, icon("chevron-left")),
2625
2922
  h("span", { ref: "quickYear", class: "zx-date-picker__quick-year", ariaLive: "polite" }),
2626
2923
  h("button", {
2627
2924
  ref: "quickNext",
2628
2925
  class: "zx-date-picker__nav",
2629
2926
  type: "button",
2630
- ariaLabel: "Next year"
2927
+ ariaLabel: this._message("datePicker.nextYear", "Next year")
2631
2928
  }, icon("chevron-right"))
2632
2929
  ),
2633
2930
  h("div", { ref: "quickGrid", class: "zx-date-picker__quick-grid", role: "grid" })
@@ -2638,7 +2935,7 @@ var DatePicker = class extends Component {
2638
2935
  ref: "grid",
2639
2936
  class: "zx-date-picker__grid",
2640
2937
  role: "grid",
2641
- ariaLabel: "Calendar"
2938
+ ariaLabel: this._message("datePicker.calendar", "Calendar")
2642
2939
  },
2643
2940
  h("thead", { ref: "weekdays" }),
2644
2941
  h("tbody", { ref: "days" })
@@ -2736,7 +3033,7 @@ var DatePicker = class extends Component {
2736
3033
  row.append(h("th", {
2737
3034
  class: "zx-date-picker__week-heading",
2738
3035
  scope: "col",
2739
- ariaLabel: "Week number"
3036
+ ariaLabel: this._message("datePicker.weekNumber", "Week number")
2740
3037
  }, "#"));
2741
3038
  }
2742
3039
  const sunday = new Date(2023, 0, 1);
@@ -2756,7 +3053,10 @@ var DatePicker = class extends Component {
2756
3053
  const offset = (first.getDay() - this._weekStart + 7) % 7;
2757
3054
  const start = addDays(first, -offset);
2758
3055
  this.refs.heading.textContent = formatDate(first, "%B %Y");
2759
- 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
+ );
2760
3060
  const rows = [];
2761
3061
  for (let week = 0; week < 6; week += 1) {
2762
3062
  const rowStart = addDays(start, week * 7);
@@ -2765,7 +3065,7 @@ var DatePicker = class extends Component {
2765
3065
  row.append(h("th", {
2766
3066
  class: "zx-date-picker__week",
2767
3067
  scope: "row",
2768
- ariaLabel: `Week ${isoWeekNumber(rowStart)}`
3068
+ ariaLabel: this._message("datePicker.week", "Week %1", isoWeekNumber(rowStart))
2769
3069
  }, String(isoWeekNumber(rowStart))));
2770
3070
  }
2771
3071
  for (let column = 0; column < 7; column += 1) {
@@ -2970,6 +3270,17 @@ var DatePicker = class extends Component {
2970
3270
  _buttonForDate(date) {
2971
3271
  return this.refs.days.querySelector(`[data-date="${formatDate(date, "%Y-%m-%d")}"]`);
2972
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
+ }
2973
3284
  };
2974
3285
  function validDate(value) {
2975
3286
  return value instanceof Date && !Number.isNaN(value.getTime()) ? new Date(value.getTime()) : null;
@@ -3031,21 +3342,21 @@ var MonthPicker = class extends DatePicker {
3031
3342
  ref: "previousYear",
3032
3343
  class: "zx-month-picker__nav",
3033
3344
  type: "button",
3034
- ariaLabel: "Previous year"
3345
+ ariaLabel: this._message("monthPicker.previousYear", "Previous year")
3035
3346
  }, icon("chevron-left")),
3036
3347
  h("span", { ref: "year", class: "zx-month-picker__year", ariaLive: "polite" }),
3037
3348
  h("button", {
3038
3349
  ref: "nextYear",
3039
3350
  class: "zx-month-picker__nav",
3040
3351
  type: "button",
3041
- ariaLabel: "Next year"
3352
+ ariaLabel: this._message("monthPicker.nextYear", "Next year")
3042
3353
  }, icon("chevron-right"))
3043
3354
  ),
3044
3355
  h("div", {
3045
3356
  ref: "grid",
3046
3357
  class: "zx-month-picker__grid",
3047
3358
  role: "grid",
3048
- ariaLabel: "Choose month"
3359
+ ariaLabel: this._message("monthPicker.chooseMonth", "Choose month")
3049
3360
  })
3050
3361
  );
3051
3362
  this._content = content;
@@ -6594,6 +6905,7 @@ var Select = class _Select extends Component {
6594
6905
  value: null,
6595
6906
  disabled: false,
6596
6907
  placeholder: "",
6908
+ label: "",
6597
6909
  clearable: false,
6598
6910
  filter: false,
6599
6911
  searchKeys: null,
@@ -6635,6 +6947,7 @@ var Select = class _Select extends Component {
6635
6947
  class: "zx-select__input",
6636
6948
  type: "text",
6637
6949
  role: "combobox",
6950
+ ariaLabel: String(this.options.label || this.options.placeholder || "Select"),
6638
6951
  autocomplete: "off",
6639
6952
  spellcheck: false,
6640
6953
  ariaExpanded: "false",
@@ -7553,7 +7866,10 @@ function registerSelectFieldAdapter() {
7553
7866
  Field.register("zxselect", (field, options) => {
7554
7867
  const props = options.props ?? {};
7555
7868
  const preset = props.preset;
7556
- 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
+ });
7557
7873
  delete selectOptions.preset;
7558
7874
  const select = field.own(preset === "priority" ? Select.priority(null, selectOptions) : preset === "status" ? Select.status(null, selectOptions) : new Select(null, selectOptions));
7559
7875
  field.listen(select.el, "change", (event) => event.stopImmediatePropagation());
@@ -8793,7 +9109,7 @@ var Timebox = class extends Component {
8793
9109
  const content = h("div", {
8794
9110
  class: "zx-timebox__content",
8795
9111
  role: "group",
8796
- 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")
8797
9113
  });
8798
9114
  this._content = content;
8799
9115
  if (this.options.signed) {
@@ -8801,7 +9117,7 @@ var Timebox = class extends Component {
8801
9117
  ref: "sign",
8802
9118
  class: "zx-timebox__sign",
8803
9119
  type: "button",
8804
- ariaLabel: "Toggle duration sign",
9120
+ ariaLabel: this._message("timebox.toggleSign", "Toggle duration sign"),
8805
9121
  ariaPressed: "false"
8806
9122
  }, "+"));
8807
9123
  this.listen(this.refs.sign, "click", () => {
@@ -8811,12 +9127,12 @@ var Timebox = class extends Component {
8811
9127
  this._emitChange();
8812
9128
  });
8813
9129
  }
8814
- this._addSegment(content, "hours", "Hours", false);
9130
+ this._addSegment(content, "hours", this._message("timebox.hours", "Hours"), false);
8815
9131
  content.append(h("span", { class: "zx-timebox__separator", ariaHidden: "true" }, ":"));
8816
- this._addSegment(content, "minutes", "Minutes", true);
9132
+ this._addSegment(content, "minutes", this._message("timebox.minutes", "Minutes"), true);
8817
9133
  if (this.options.seconds) {
8818
9134
  content.append(h("span", { class: "zx-timebox__separator", ariaHidden: "true" }, ":"));
8819
- this._addSegment(content, "seconds", "Seconds", true);
9135
+ this._addSegment(content, "seconds", this._message("timebox.seconds", "Seconds"), true);
8820
9136
  }
8821
9137
  root.append(content);
8822
9138
  this.set(this.options.value, this.options.unit, { silent: true });
@@ -8942,6 +9258,16 @@ var Timebox = class extends Component {
8942
9258
  _emitChange() {
8943
9259
  this.emit("change", { value: this.get(this.options.unit) });
8944
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
+ }
8945
9271
  };
8946
9272
  function finiteNumber2(value) {
8947
9273
  const number = Number(value);
@@ -9154,14 +9480,14 @@ var ValueList = class extends Component {
9154
9480
  this._list = h("ul", {
9155
9481
  class: "zx-value-list__list",
9156
9482
  role: "listbox",
9157
- ariaLabel: "Values",
9483
+ ariaLabel: this._message("valueList.values", "Values"),
9158
9484
  ariaOrientation: "horizontal"
9159
9485
  });
9160
9486
  this._input = h("input", {
9161
9487
  class: "zx-value-list__input",
9162
9488
  type: "text",
9163
9489
  placeholder: String(this.options.placeholder ?? ""),
9164
- ariaLabel: "Add value",
9490
+ ariaLabel: this._message("valueList.add", "Add value"),
9165
9491
  ariaDescribedby: instructionsId
9166
9492
  });
9167
9493
  this._status = h("div", {
@@ -9172,7 +9498,10 @@ var ValueList = class extends Component {
9172
9498
  this._instructions = h("span", {
9173
9499
  class: "zx-value-list__instructions",
9174
9500
  id: instructionsId
9175
- }, "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
+ ));
9176
9505
  this._ownedNodes.push(this._list, this._input, this._status, this._instructions);
9177
9506
  root.append(this._list, this._input, this._status, this._instructions);
9178
9507
  this.listen(this._input, "keydown", (event) => this._handleInputKeydown(event));
@@ -9221,13 +9550,13 @@ var ValueList = class extends Component {
9221
9550
  const normalized = String(value ?? "").trim();
9222
9551
  if (!normalized) return false;
9223
9552
  if (this.options.unique && this._values.includes(normalized)) {
9224
- this._showError("This value already exists.");
9553
+ this._showError(this._message("valueList.duplicate", "This value already exists."));
9225
9554
  return false;
9226
9555
  }
9227
9556
  if (typeof this.options.validate === "function") {
9228
9557
  const result = this.options.validate(normalized);
9229
9558
  if (result !== true) {
9230
- 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."));
9231
9560
  return false;
9232
9561
  }
9233
9562
  }
@@ -9292,7 +9621,7 @@ var ValueList = class extends Component {
9292
9621
  draggable: Boolean(this.options.sortable) && !this._disabled,
9293
9622
  ariaSelected: "false",
9294
9623
  ariaDisabled: String(this._disabled),
9295
- 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,
9296
9625
  dataset: { index }
9297
9626
  }, h("span", { class: "zx-value-list__value" }, value), this.options.deletable ? h("span", {
9298
9627
  class: "zx-value-list__remove",
@@ -9388,6 +9717,17 @@ var ValueList = class extends Component {
9388
9717
  this._status.textContent = message;
9389
9718
  this._input.setAttribute("aria-invalid", "true");
9390
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
+ }
9391
9731
  /** @returns {void} */
9392
9732
  destroy() {
9393
9733
  for (const node of this._ownedNodes ?? []) node.remove();
@@ -9631,6 +9971,8 @@ var Table = class extends Component {
9631
9971
  height: null,
9632
9972
  emptyText: null,
9633
9973
  rowClass: null,
9974
+ rowKind: null,
9975
+ rowKinds: null,
9634
9976
  zebra: true,
9635
9977
  editMode: false,
9636
9978
  editTrigger: "double",
@@ -9660,6 +10002,10 @@ var Table = class extends Component {
9660
10002
  this._original = created ? null : snapshotTarget(root);
9661
10003
  this._columns = normalizeTableColumns(this.options.columns);
9662
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;
9663
10009
  this._selected = /* @__PURE__ */ new Set();
9664
10010
  this._selectionAnchorId = null;
9665
10011
  this._sort = normalizeSort(this.options.sort, this._columns);
@@ -10035,6 +10381,7 @@ var Table = class extends Component {
10035
10381
  const row = this.getRow(id);
10036
10382
  const target = this.getRow(targetId);
10037
10383
  if (!row || !target || Object.is(id, targetId)) return this;
10384
+ if (this._isSpanningRow(row)) return this;
10038
10385
  const next = reorderTableRows(this._data, this.options.rowId, id, targetId, position2, this._hierarchy?.parentId);
10039
10386
  if (!next) return this;
10040
10387
  const event = this.emit("rowmove", { row, id, target, targetId, position: position2 });
@@ -10071,6 +10418,10 @@ var Table = class extends Component {
10071
10418
  /**
10072
10419
  * Activates a column sort. Local mode stably reorders data; server mode only updates the
10073
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.
10074
10425
  * @param {string} id Column id.
10075
10426
  * @param {TableSortDirection} [dir] Direction; omitted toggles the current column.
10076
10427
  * @param {{silent?: boolean}} [options={}] Event options.
@@ -10079,6 +10430,13 @@ var Table = class extends Component {
10079
10430
  * @fires Table#editcancel
10080
10431
  */
10081
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
+ }
10082
10440
  this._abortEdit({ focus: false });
10083
10441
  const column = this._columns.find((candidate) => candidate.id === id);
10084
10442
  if (!column) throw new RangeError(`Unknown table column: ${id}`);
@@ -10130,7 +10488,7 @@ var Table = class extends Component {
10130
10488
  */
10131
10489
  setSelection(ids) {
10132
10490
  if (!Array.isArray(ids)) throw new TypeError("Table selection must be an array of row ids");
10133
- const valid = new Set(this._data.map((row) => this._idFor(row)));
10491
+ const valid = this._selectableIds();
10134
10492
  const next = /* @__PURE__ */ new Set();
10135
10493
  if (this.options.selectable !== false) {
10136
10494
  for (const id of ids) {
@@ -10242,6 +10600,7 @@ var Table = class extends Component {
10242
10600
  if (!requested) return this;
10243
10601
  const current = this._data.find((candidate) => Object.is(this._idFor(candidate), rowId));
10244
10602
  if (current === void 0) return this;
10603
+ if (this._isSpanningRow(current)) return this;
10245
10604
  const column = this._editMode === "row" && this._hiddenColumns.has(requested.id) ? this._visibleColumns().find((candidate) => resolveEditable(candidate, current) !== false) ?? null : requested;
10246
10605
  if (!column || resolveEditable(column, current) === false) return this;
10247
10606
  const activeColumnId = column.id;
@@ -10530,6 +10889,41 @@ var Table = class extends Component {
10530
10889
  _visibleColumns() {
10531
10890
  return this._columns.filter((column) => !this._hiddenColumns.has(column.id));
10532
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
+ }
10533
10927
  /** @returns {HTMLElement} */
10534
10928
  _createColumnControls() {
10535
10929
  const list = h("div", { class: "zx-table__column-list" });
@@ -10542,7 +10936,12 @@ var Table = class extends Component {
10542
10936
  const details = h(
10543
10937
  "details",
10544
10938
  { class: "zx-table__column-controls" },
10545
- 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
+ ),
10546
10945
  list
10547
10946
  );
10548
10947
  this._renderColumnControls();
@@ -10577,7 +10976,7 @@ var Table = class extends Component {
10577
10976
  {
10578
10977
  class: "zx-table__column-order",
10579
10978
  role: "group",
10580
- ariaLabel: `Move ${column.label}`
10979
+ ariaLabel: this._message("table.moveColumn", "Move %1", [column.label])
10581
10980
  },
10582
10981
  h("button", {
10583
10982
  class: "zx-table__column-move",
@@ -10850,9 +11249,9 @@ var Table = class extends Component {
10850
11249
  row.append(selectionHeader);
10851
11250
  this.listen(this._selectAll, "click", () => {
10852
11251
  if (this._selectAll.checked) {
10853
- 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));
10854
11253
  } else {
10855
- 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));
10856
11255
  }
10857
11256
  this._selectionAnchorId = null;
10858
11257
  this._syncSelection();
@@ -10862,12 +11261,13 @@ var Table = class extends Component {
10862
11261
  this._selectAll = null;
10863
11262
  }
10864
11263
  this._headers = /* @__PURE__ */ new Map();
11264
+ this._headerSpanning = this._spanning;
10865
11265
  for (const column of this._visibleColumns()) {
10866
11266
  const th = h("th", { scope: "col" });
10867
11267
  const align = column.align ?? (isNumericColumn(column) ? "end" : null);
10868
11268
  if (align) th.dataset.align = align;
10869
11269
  if (column.headerTitle) th.title = column.headerTitle;
10870
- if (column.sortable) {
11270
+ if (column.sortable && !this._spanning) {
10871
11271
  const button = h("button", {
10872
11272
  class: "zx-table__sort-button",
10873
11273
  type: "button"
@@ -10889,6 +11289,8 @@ var Table = class extends Component {
10889
11289
  /** @returns {void} @fires Table#editcancel */
10890
11290
  _renderBody() {
10891
11291
  this._abortEdit({ focus: false });
11292
+ this._spanning = this._hasSpanningRow();
11293
+ if (this._headers && this._spanning !== this._headerSpanning) this._renderHeader();
10892
11294
  const fragment2 = document.createDocumentFragment();
10893
11295
  this._rowMeta = /* @__PURE__ */ new WeakMap();
10894
11296
  this._rowElements = /* @__PURE__ */ new Map();
@@ -10900,10 +11302,7 @@ var Table = class extends Component {
10900
11302
  fragment2.append(h(
10901
11303
  "tr",
10902
11304
  { class: "zx-table__empty-row" },
10903
- h("td", {
10904
- class: "zx-table__empty",
10905
- colspan: this._visibleColumns().length + (this.options.selectable === "multi" ? 1 : 0) + (this.options.rowReorder ? 1 : 0)
10906
- }, this._emptyContent())
11305
+ h("td", { class: "zx-table__empty", colspan: this._spanColspan() }, this._emptyContent())
10907
11306
  ));
10908
11307
  } else {
10909
11308
  this._visibleEntries().forEach((entry, visibleIndex) => {
@@ -10946,7 +11345,14 @@ var Table = class extends Component {
10946
11345
  if (this._growStep) tr.setAttribute("aria-rowindex", String(tree.visibleIndex + 2));
10947
11346
  const rowClass = typeof this.options.rowClass === "function" ? this.options.rowClass(row) : "";
10948
11347
  if (rowClass) tr.classList.add(...String(rowClass).split(/\s+/).filter(Boolean));
10949
- 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
+ }
10950
11356
  if (this._hierarchy) {
10951
11357
  tr.setAttribute("aria-level", String(tree.depth + 1));
10952
11358
  if (tree.hasChildren) tr.setAttribute("aria-expanded", String(this._expanded.has(id)));
@@ -10955,6 +11361,23 @@ var Table = class extends Component {
10955
11361
  const elements = this._rowElements.get(id) ?? [];
10956
11362
  elements.push(tr);
10957
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
+ }
10958
11381
  if (this.options.rowReorder) {
10959
11382
  const handle = h("button", {
10960
11383
  class: "zx-table__row-handle",
@@ -11073,7 +11496,7 @@ var Table = class extends Component {
11073
11496
  }
11074
11497
  }
11075
11498
  this.emit("rowclick", { ...meta, event });
11076
- 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)) {
11077
11500
  this._selected = /* @__PURE__ */ new Set([meta.id]);
11078
11501
  this._selectionAnchorId = meta.id;
11079
11502
  this._syncSelection();
@@ -11416,6 +11839,7 @@ var Table = class extends Component {
11416
11839
  if (row < 0 || row >= visible.length) return null;
11417
11840
  const candidateRow = visible[row];
11418
11841
  const candidateColumn = columns[column];
11842
+ if (this._isSpanningRow(candidateRow)) continue;
11419
11843
  if (resolveEditable(candidateColumn, candidateRow) !== false) {
11420
11844
  return {
11421
11845
  id: this._idFor(candidateRow),
@@ -11497,6 +11921,7 @@ var Table = class extends Component {
11497
11921
  const start = Math.min(anchorIndex, currentIndex);
11498
11922
  const end = Math.max(anchorIndex, currentIndex);
11499
11923
  for (let index = start; index <= end; index += 1) {
11924
+ if (this._isSpanningRow(visible[index])) continue;
11500
11925
  const id = this._idFor(visible[index]);
11501
11926
  if (checked) this._selected.add(id);
11502
11927
  else this._selected.delete(id);
@@ -11515,6 +11940,7 @@ var Table = class extends Component {
11515
11940
  for (const [id, rows] of this._rowElements) {
11516
11941
  const selected = this._selected.has(id);
11517
11942
  for (const row of rows) {
11943
+ if (row.hasAttribute("data-span")) continue;
11518
11944
  row.setAttribute("aria-selected", String(selected));
11519
11945
  const checkbox = row.querySelector(".zx-table__row-checkbox");
11520
11946
  if (checkbox) checkbox.checked = selected;
@@ -11525,7 +11951,7 @@ var Table = class extends Component {
11525
11951
  /** @returns {void} */
11526
11952
  _syncSelectAll() {
11527
11953
  if (!this._selectAll) return;
11528
- const visible = this._visibleData();
11954
+ const visible = this._selectableData();
11529
11955
  const selectedRows = visible.reduce(
11530
11956
  (count, row) => count + Number(this._selected.has(this._idFor(row))),
11531
11957
  0
@@ -11538,13 +11964,15 @@ var Table = class extends Component {
11538
11964
  _syncSortHeader() {
11539
11965
  if (!this._headers) return;
11540
11966
  for (const [id, header] of this._headers) {
11541
- if (this._sort?.id === id) header.setAttribute("aria-sort", this._sort.dir === "asc" ? "ascending" : "descending");
11542
- 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");
11543
11970
  }
11544
11971
  }
11545
11972
  /** @returns {void} */
11546
11973
  _sortData() {
11547
- if (!this._sort) return;
11974
+ this._spanning = this._hasSpanningRow();
11975
+ if (!this._sort || this._spanning) return;
11548
11976
  const column = this._columns.find((candidate) => candidate.id === this._sort.id);
11549
11977
  if (!column) return;
11550
11978
  const getValue = typeof column.sortValue === "function" ? column.sortValue : (row) => row?.[column.id];
@@ -11577,9 +12005,17 @@ var Table = class extends Component {
11577
12005
  const valid = new Set(branches);
11578
12006
  for (const id of this._expanded) if (!valid.has(id)) this._expanded.delete(id);
11579
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
+ }
11580
12016
  /** @returns {boolean} Whether selection changed. */
11581
12017
  _pruneSelection() {
11582
- const valid = new Set(this._data.map((row) => this._idFor(row)));
12018
+ const valid = this._selectableIds();
11583
12019
  const previousSize = this._selected.size;
11584
12020
  for (const id of this._selected) if (!valid.has(id)) this._selected.delete(id);
11585
12021
  if (!valid.has(this._selectionAnchorId)) this._selectionAnchorId = null;
@@ -11599,6 +12035,34 @@ var Table = class extends Component {
11599
12035
  this.emit("datachange", { rows: this.getData() });
11600
12036
  }
11601
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
+ }
11602
12066
  function formatTableCell(column, row, index = 0) {
11603
12067
  if (typeof column?.render === "function") return column.render(row, index);
11604
12068
  const value = row?.[column?.id];
@@ -11827,6 +12291,20 @@ function normalizeHierarchy(option, columns) {
11827
12291
  if (!column) return null;
11828
12292
  return { ...option, parentId, column };
11829
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
+ }
11830
12308
  function initialExpanded(option) {
11831
12309
  return new Set(Array.isArray(option) ? option : []);
11832
12310
  }
@@ -12063,14 +12541,20 @@ var Launcher = class extends Component {
12063
12541
  debounce: 250,
12064
12542
  minQuery: 0,
12065
12543
  maxResults: 100,
12066
- placeholder: "Search applications and records",
12067
- label: "Launcher",
12068
- closeLabel: "Close launcher",
12069
- resultsLabel: "Launcher results",
12070
- emptyText: "No results",
12071
- 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,
12072
12556
  shortcut: "mod+k",
12073
- hints: { move: "Move", open: "Open", close: "Close" },
12557
+ hints: null,
12074
12558
  scope: null
12075
12559
  };
12076
12560
  /**
@@ -12109,6 +12593,7 @@ var Launcher = class extends Component {
12109
12593
  this._busy = false;
12110
12594
  this._destroyed = false;
12111
12595
  const listId = uid("zx-launcher-results");
12596
+ const label = String(this.options.label ?? this._message("launcher.label", "Launcher"));
12112
12597
  const input = h("input", {
12113
12598
  ref: "input",
12114
12599
  class: "zx-launcher__input",
@@ -12119,15 +12604,15 @@ var Launcher = class extends Component {
12119
12604
  ariaAutocomplete: "list",
12120
12605
  ariaControls: listId,
12121
12606
  ariaExpanded: "false",
12122
- ariaLabel: this.options.label,
12123
- placeholder: this.options.placeholder,
12607
+ ariaLabel: label,
12608
+ placeholder: this.options.placeholder ?? this._message("launcher.placeholder", "Search applications and records"),
12124
12609
  value: this._query
12125
12610
  });
12126
12611
  const close = h("button", {
12127
12612
  ref: "close",
12128
12613
  class: "zx-icon-btn zx-launcher__close",
12129
12614
  type: "button",
12130
- ariaLabel: this.options.closeLabel
12615
+ ariaLabel: this.options.closeLabel ?? this._message("launcher.close", "Close launcher")
12131
12616
  }, icon("x"));
12132
12617
  const status = h("div", {
12133
12618
  ref: "status",
@@ -12140,14 +12625,18 @@ var Launcher = class extends Component {
12140
12625
  class: "zx-launcher__results",
12141
12626
  id: listId,
12142
12627
  role: "listbox",
12143
- ariaLabel: this.options.resultsLabel
12628
+ ariaLabel: this.options.resultsLabel ?? this._message("launcher.results", "Launcher results")
12144
12629
  });
12145
12630
  const shortcut = this.options.shortcut === "mod+k" ? h("kbd", {
12146
12631
  class: "zx-launcher__shortcut",
12147
12632
  ariaHidden: "true"
12148
12633
  }, launcherShortcutLabel()) : null;
12149
- const footer = launcherHints(this.options.hints);
12150
- 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);
12151
12640
  dialog.replaceChildren(
12152
12641
  h(
12153
12642
  "div",
@@ -12439,9 +12928,13 @@ var Launcher = class extends Component {
12439
12928
  }
12440
12929
  /** @returns {void} */
12441
12930
  _renderStatus() {
12442
- if (this._busy) this.refs.status.textContent = String(this.options.loadingText);
12443
- else if (this._results.length === 0) this.refs.status.textContent = String(this.options.emptyText);
12444
- 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
+ }
12445
12938
  this.refs.status.hidden = !this.refs.status.textContent;
12446
12939
  }
12447
12940
  /** @returns {void} */
@@ -12534,6 +13027,16 @@ var Launcher = class extends Component {
12534
13027
  event.preventDefault();
12535
13028
  this.open();
12536
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
+ }
12537
13040
  };
12538
13041
  function launcherShortcutLabel() {
12539
13042
  const platform = String(
@@ -12542,15 +13045,15 @@ function launcherShortcutLabel() {
12542
13045
  );
12543
13046
  return /mac|iphone|ipad/i.test(platform) ? "\u2318 K" : "Ctrl K";
12544
13047
  }
12545
- function launcherHints(hints) {
13048
+ function launcherHints(hints, defaults) {
12546
13049
  if (hints === false) return null;
12547
13050
  const labels = hints && typeof hints === "object" ? hints : {};
12548
13051
  return h(
12549
13052
  "footer",
12550
13053
  { class: "zx-launcher__hints", ariaHidden: "true" },
12551
- h("span", {}, h("kbd", {}, "\u2191\u2193\u2190\u2192"), labels.move ?? "Move"),
12552
- h("span", {}, h("kbd", {}, "\u21B5"), labels.open ?? "Open"),
12553
- 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)
12554
13057
  );
12555
13058
  }
12556
13059
  function launcherItemIcon(item) {
@@ -12970,8 +13473,8 @@ var Message = class _Message extends Component {
12970
13473
  const close = h("button", {
12971
13474
  class: "zx-icon-btn zx-message__close",
12972
13475
  type: "button",
12973
- ariaLabel: "Close message",
12974
- title: "Close"
13476
+ ariaLabel: this._message("message.close", "Close message"),
13477
+ title: this._message("message.closeTitle", "Close")
12975
13478
  }, icon("x", { size: 14 }));
12976
13479
  node.append(close);
12977
13480
  this.listen(close, "click", () => this._close(entry));
@@ -13039,6 +13542,16 @@ var Message = class _Message extends Component {
13039
13542
  this._entries.delete(entry);
13040
13543
  this._queue.remove(entry);
13041
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
+ }
13042
13555
  /** @param {Element|string|null|undefined} scope @returns {Message} */
13043
13556
  static #floatingRegion(scope2) {
13044
13557
  const host = overlayHost(scope2 ?? null);
@@ -13506,7 +14019,7 @@ var Calendar = class extends Component {
13506
14019
  ref: "views",
13507
14020
  class: "zx-calendar__views",
13508
14021
  role: "group",
13509
- ariaLabel: "Calendar view"
14022
+ ariaLabel: this._message("calendar.viewSwitch", "Calendar view")
13510
14023
  }, this._views.map((view) => h("button", {
13511
14024
  type: "button",
13512
14025
  class: "zx-calendar__view-button",
@@ -13954,7 +14467,7 @@ var Calendar = class extends Component {
13954
14467
  eventCount: String(count)
13955
14468
  },
13956
14469
  ariaCurrent: sameCalendarDay(day, this._now()) ? "date" : null,
13957
- ariaLabel: `${this._format(day, { dateStyle: "full" })}, ${count} ${count === 1 ? "event" : "events"}`,
14470
+ ariaLabel: this._dayCountLabel(day, count),
13958
14471
  disabled: this.options.disabled
13959
14472
  }, this._format(day, { day: "numeric" })));
13960
14473
  }
@@ -14024,7 +14537,7 @@ var Calendar = class extends Component {
14024
14537
  activity: String(Math.min(3, count))
14025
14538
  },
14026
14539
  ariaCurrent: sameCalendarDay(day, this._now()) ? "date" : null,
14027
- ariaLabel: `${this._format(day, { dateStyle: "full" })}, ${count} ${count === 1 ? "event" : "events"}`,
14540
+ ariaLabel: this._dayCountLabel(day, count),
14028
14541
  disabled: this.options.disabled
14029
14542
  }, String(day.getDate())));
14030
14543
  }
@@ -14755,6 +15268,31 @@ var Calendar = class extends Component {
14755
15268
  if (!this._destroyed) this.refs.status.textContent = message;
14756
15269
  });
14757
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
+ }
14758
15296
  };
14759
15297
  function normalizeViews(value) {
14760
15298
  if (!Array.isArray(value) || value.length === 0) throw new TypeError("Calendar views must be a non-empty array");
@@ -15373,6 +15911,7 @@ function isEmpty(value) {
15373
15911
  function registerFieldAdapters() {
15374
15912
  registerSelectFieldAdapter();
15375
15913
  registerChecklistFieldAdapter();
15914
+ registerCodeEditorFieldAdapter();
15376
15915
  registerDateboxFieldAdapters();
15377
15916
  registerDatePickerFieldAdapters();
15378
15917
  registerDateRangeFieldAdapter();