@zeyos/zx 4.3.4 → 5.0.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 (231) hide show
  1. package/CHANGELOG.md +438 -0
  2. package/README.md +49 -10
  3. package/dist/types/components/activity-item/activity-item.d.ts +351 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +217 -0
  5. package/dist/types/components/app-sidebar/app-sidebar.d.ts +33 -3
  6. package/dist/types/components/avatar/avatar.d.ts +46 -7
  7. package/dist/types/components/badge/badge.d.ts +21 -1
  8. package/dist/types/components/button/button.d.ts +23 -0
  9. package/dist/types/components/calendar/calendar.d.ts +19 -0
  10. package/dist/types/components/card/card.d.ts +7 -0
  11. package/dist/types/components/card-view/card-view.d.ts +11 -3
  12. package/dist/types/components/card-view/record-card.d.ts +7 -1
  13. package/dist/types/components/chart/chart.d.ts +10 -0
  14. package/dist/types/components/checklist/checklist.d.ts +9 -0
  15. package/dist/types/components/code-editor/code-editor.d.ts +149 -0
  16. package/dist/types/components/code-editor/field-adapter.d.ts +5 -0
  17. package/dist/types/components/context-menu/context-menu.d.ts +18 -2
  18. package/dist/types/components/date-picker/date-picker.d.ts +8 -0
  19. package/dist/types/components/date-picker/time-picker.d.ts +7 -0
  20. package/dist/types/components/datebox/date-range-box.d.ts +5 -1
  21. package/dist/types/components/datebox/datebox.d.ts +7 -2
  22. package/dist/types/components/dialog/dialog.d.ts +75 -6
  23. package/dist/types/components/dropdown/dropdown.d.ts +2 -2
  24. package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
  25. package/dist/types/components/field/field.d.ts +24 -2
  26. package/dist/types/components/fieldset/fieldset.d.ts +19 -1
  27. package/dist/types/components/file-item/file-item.d.ts +262 -0
  28. package/dist/types/components/file-list/file-list.d.ts +171 -0
  29. package/dist/types/components/filter/filter.d.ts +91 -36
  30. package/dist/types/components/filter-panel/filter-panel.d.ts +449 -0
  31. package/dist/types/components/form/form.d.ts +12 -1
  32. package/dist/types/components/kanban-view/kanban-card.d.ts +146 -0
  33. package/dist/types/components/kanban-view/kanban-view.d.ts +170 -4
  34. package/dist/types/components/launcher/launcher.d.ts +278 -26
  35. package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
  36. package/dist/types/components/menu-button/menu-button.d.ts +37 -42
  37. package/dist/types/components/message/message.d.ts +126 -17
  38. package/dist/types/components/modal/modal.d.ts +61 -2
  39. package/dist/types/components/navigation-bar/navigation-bar.d.ts +105 -21
  40. package/dist/types/components/panel/panel.d.ts +7 -0
  41. package/dist/types/components/search/search.d.ts +7 -0
  42. package/dist/types/components/select/color.d.ts +12 -0
  43. package/dist/types/components/select/glyphs.d.ts +124 -0
  44. package/dist/types/components/select/select.d.ts +15 -3
  45. package/dist/types/components/sort-control/sort-control.d.ts +105 -0
  46. package/dist/types/components/stat-tile/stat-tile.d.ts +288 -0
  47. package/dist/types/components/tabbox/tabbox.d.ts +16 -0
  48. package/dist/types/components/table/table.d.ts +190 -1
  49. package/dist/types/components/table-view/table-view.d.ts +11 -5
  50. package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
  51. package/dist/types/components/timebox/timebox.d.ts +7 -0
  52. package/dist/types/components/tree/tree.d.ts +13 -0
  53. package/dist/types/components/value-list/value-list.d.ts +8 -0
  54. package/dist/types/components/view/record-view.d.ts +96 -5
  55. package/dist/types/core/breakpoint.d.ts +1 -1
  56. package/dist/types/core/icons.d.ts +37 -22
  57. package/dist/types/core/motion.d.ts +59 -0
  58. package/dist/types/core/overlay-host.d.ts +15 -1
  59. package/dist/types/index.d.ts +16 -3
  60. package/dist/types/internal/app-rail.d.ts +10 -0
  61. package/dist/types/internal/menu-items.d.ts +134 -0
  62. package/dist/types/zeyos/connect.d.ts +6 -0
  63. package/dist/types/zeyos/form.d.ts +50 -9
  64. package/dist/types/zeyos/index.d.ts +1 -1
  65. package/dist/types/zeyos/query.d.ts +79 -0
  66. package/dist/types/zeyos/schema.d.ts +24 -1
  67. package/dist/types/zeyos/table.d.ts +100 -14
  68. package/dist/types/zeyos/view.d.ts +10 -0
  69. package/dist/zx-zeyos.esm.js +2206 -489
  70. package/dist/zx-zeyos.esm.min.js +56 -1
  71. package/dist/zx.css +3322 -724
  72. package/dist/zx.esm.js +12244 -5809
  73. package/dist/zx.esm.min.js +58 -3
  74. package/dist/zx.global.js +12246 -5811
  75. package/dist/zx.global.min.js +58 -3
  76. package/dist/zx.min.css +1 -1
  77. package/docs/api.json +1683 -146
  78. package/docs/llms.md +777 -110
  79. package/docs/llms.txt +14 -8
  80. package/package.json +1 -1
  81. package/src/components/account-menu/account-menu.css +35 -10
  82. package/src/components/activity-item/activity-item.css +137 -0
  83. package/src/components/activity-item/activity-item.js +602 -0
  84. package/src/components/activity-list/activity-list.css +99 -0
  85. package/src/components/activity-list/activity-list.js +409 -0
  86. package/src/components/app-icon/app-icon.css +1 -1
  87. package/src/components/app-rail/app-rail.css +191 -22
  88. package/src/components/app-sidebar/app-sidebar.css +349 -24
  89. package/src/components/app-sidebar/app-sidebar.js +86 -11
  90. package/src/components/aurora/aurora.css +5 -3
  91. package/src/components/avatar/avatar.css +32 -7
  92. package/src/components/avatar/avatar.js +72 -8
  93. package/src/components/badge/badge.css +54 -2
  94. package/src/components/badge/badge.js +29 -2
  95. package/src/components/button/button.css +74 -8
  96. package/src/components/button/button.js +44 -1
  97. package/src/components/calendar/calendar.css +46 -46
  98. package/src/components/calendar/calendar.js +33 -3
  99. package/src/components/card/card.css +2 -2
  100. package/src/components/card/card.js +12 -1
  101. package/src/components/card-view/card-view.css +10 -7
  102. package/src/components/card-view/card-view.js +25 -9
  103. package/src/components/card-view/record-card.js +7 -3
  104. package/src/components/chart/chart.css +4 -1
  105. package/src/components/chart/chart.js +12 -0
  106. package/src/components/check-button/check-button.css +10 -2
  107. package/src/components/checklist/checklist.css +9 -2
  108. package/src/components/checklist/checklist.js +11 -5
  109. package/src/components/checklist/field-adapter.js +3 -1
  110. package/src/components/code-editor/code-editor.css +68 -0
  111. package/src/components/code-editor/code-editor.js +306 -0
  112. package/src/components/code-editor/field-adapter.js +34 -0
  113. package/src/components/context-menu/context-menu.css +87 -5
  114. package/src/components/context-menu/context-menu.js +22 -30
  115. package/src/components/copy/copy-button.css +6 -0
  116. package/src/components/copy/copy-input.css +8 -3
  117. package/src/components/data-filter/data-filter.css +5 -4
  118. package/src/components/date-picker/date-picker.css +6 -6
  119. package/src/components/date-picker/date-picker.js +22 -8
  120. package/src/components/date-picker/date-range-picker.css +3 -3
  121. package/src/components/date-picker/month-picker.js +3 -3
  122. package/src/components/date-picker/time-picker.css +1 -1
  123. package/src/components/date-picker/time-picker.js +20 -8
  124. package/src/components/datebox/date-range-box.css +79 -2
  125. package/src/components/datebox/date-range-box.js +13 -2
  126. package/src/components/datebox/datebox.css +79 -2
  127. package/src/components/datebox/datebox.js +13 -2
  128. package/src/components/dialog/dialog.css +26 -0
  129. package/src/components/dialog/dialog.js +174 -70
  130. package/src/components/dropdown/dropdown.css +1 -1
  131. package/src/components/dropdown/dropdown.js +7 -5
  132. package/src/components/empty-state/empty-state.css +2 -2
  133. package/src/components/entity-ref/entity-ref.css +182 -0
  134. package/src/components/entity-ref/entity-ref.js +496 -0
  135. package/src/components/field/field.css +28 -9
  136. package/src/components/field/field.js +68 -26
  137. package/src/components/field-upload/field-upload.css +1 -1
  138. package/src/components/fieldset/fieldset.css +5 -3
  139. package/src/components/fieldset/fieldset.js +28 -5
  140. package/src/components/file-item/file-item.css +88 -0
  141. package/src/components/file-item/file-item.js +505 -0
  142. package/src/components/file-list/file-list.css +51 -0
  143. package/src/components/file-list/file-list.js +320 -0
  144. package/src/components/filter/filter.css +27 -1
  145. package/src/components/filter/filter.js +205 -47
  146. package/src/components/filter-panel/filter-panel.css +104 -0
  147. package/src/components/filter-panel/filter-panel.js +964 -0
  148. package/src/components/finder/finder.css +15 -13
  149. package/src/components/form/form.css +5 -4
  150. package/src/components/form/form.js +11 -2
  151. package/src/components/grid/grid.css +1 -1
  152. package/src/components/groupbox/groupbox.css +1 -1
  153. package/src/components/kanban-view/kanban-card.js +247 -0
  154. package/src/components/kanban-view/kanban-view.css +226 -8
  155. package/src/components/kanban-view/kanban-view.js +255 -12
  156. package/src/components/launcher/launcher.css +107 -25
  157. package/src/components/launcher/launcher.js +557 -63
  158. package/src/components/list-toolbar/list-toolbar.css +194 -0
  159. package/src/components/list-toolbar/list-toolbar.js +851 -0
  160. package/src/components/master-panel/master-panel.css +2 -0
  161. package/src/components/menu-button/menu-button.css +87 -5
  162. package/src/components/menu-button/menu-button.js +86 -46
  163. package/src/components/message/message.css +38 -5
  164. package/src/components/message/message.js +230 -17
  165. package/src/components/modal/modal.css +15 -0
  166. package/src/components/modal/modal.js +159 -3
  167. package/src/components/navigation-bar/navigation-bar.css +95 -11
  168. package/src/components/navigation-bar/navigation-bar.js +171 -4
  169. package/src/components/number-field/field-adapter.js +1 -0
  170. package/src/components/number-field/number-field.css +70 -0
  171. package/src/components/number-field/number-field.js +2 -0
  172. package/src/components/pagination/pagination.css +8 -6
  173. package/src/components/panel/panel.css +1 -1
  174. package/src/components/panel/panel.js +13 -2
  175. package/src/components/rating/rating.css +7 -0
  176. package/src/components/search/search.js +17 -5
  177. package/src/components/select/color.js +22 -0
  178. package/src/components/select/field-adapter.js +6 -1
  179. package/src/components/select/glyphs.js +165 -0
  180. package/src/components/select/select.css +138 -45
  181. package/src/components/select/select.js +86 -47
  182. package/src/components/sheet/sheet.css +11 -8
  183. package/src/components/slider/slider.css +16 -2
  184. package/src/components/slider/slider.js +1 -1
  185. package/src/components/sort-control/sort-control.css +20 -0
  186. package/src/components/sort-control/sort-control.js +140 -0
  187. package/src/components/stat-tile/stat-tile.css +208 -0
  188. package/src/components/stat-tile/stat-tile.js +601 -0
  189. package/src/components/tabbox/tabbox.css +32 -27
  190. package/src/components/tabbox/tabbox.js +17 -2
  191. package/src/components/table/table.css +155 -31
  192. package/src/components/table/table.js +386 -33
  193. package/src/components/table-view/table-view.css +8 -2
  194. package/src/components/table-view/table-view.js +21 -6
  195. package/src/components/tag-picker/tag-picker.css +86 -6
  196. package/src/components/tag-picker/tag-picker.js +25 -8
  197. package/src/components/timebox/timebox.css +22 -2
  198. package/src/components/timebox/timebox.js +18 -5
  199. package/src/components/toggle/toggle.css +12 -1
  200. package/src/components/toolbar/toolbar.js +2 -2
  201. package/src/components/tooltip/tooltip.css +4 -0
  202. package/src/components/tooltip/tooltip.js +10 -3
  203. package/src/components/tree/tree.css +25 -16
  204. package/src/components/tree/tree.js +31 -0
  205. package/src/components/value-list/value-list.css +3 -3
  206. package/src/components/value-list/value-list.js +22 -6
  207. package/src/components/view/record-view.css +5 -0
  208. package/src/components/view/record-view.js +100 -7
  209. package/src/core/breakpoint.js +1 -1
  210. package/src/core/icons.js +259 -57
  211. package/src/core/motion.js +147 -0
  212. package/src/core/overlay-host.js +21 -2
  213. package/src/index.js +30 -3
  214. package/src/internal/app-rail.js +18 -1
  215. package/src/internal/menu-items.js +142 -0
  216. package/src/zeyos/connect.js +6 -2
  217. package/src/zeyos/form.js +67 -27
  218. package/src/zeyos/index.js +1 -1
  219. package/src/zeyos/query.js +153 -0
  220. package/src/zeyos/schema.js +144 -27
  221. package/src/zeyos/table.js +68 -5
  222. package/src/zeyos/view.js +6 -0
  223. package/styles/base.css +56 -0
  224. package/styles/icons.css +11 -1
  225. package/styles/print.css +192 -0
  226. package/styles/tokens/dark.css +113 -37
  227. package/styles/tokens/global.css +121 -27
  228. package/styles/tokens/semantic.css +100 -31
  229. package/styles/tokens/themes.css +7 -6
  230. package/styles/zx.css +11 -1
  231. package/styles/tokens/density.css +0 -9
@@ -12,6 +12,95 @@ function unixSecondsToDate(seconds) {
12
12
  if (!Number.isFinite(value)) throw new TypeError("Expected finite Unix seconds");
13
13
  return new Date(value * 1e3);
14
14
  }
15
+ function unixSecondsToCalendarDay(seconds, timeZone) {
16
+ if (seconds == null || seconds === "") return null;
17
+ const value = Number(seconds);
18
+ if (!Number.isFinite(value)) throw new TypeError("Expected finite Unix seconds");
19
+ const { year, month, day } = calendarZone(timeZone).dayOf(value * 1e3);
20
+ return localMidnight(year, month, day);
21
+ }
22
+ function calendarDayToUnixSeconds(date, timeZone) {
23
+ if (date == null) return null;
24
+ if (!(date instanceof Date)) throw new TypeError("Expected a Date");
25
+ if (!Number.isFinite(date.getTime())) throw new RangeError("Invalid Date");
26
+ const midnight = calendarZone(timeZone).midnightOf(date.getFullYear(), date.getMonth(), date.getDate());
27
+ return Math.floor(midnight / 1e3);
28
+ }
29
+ function normalizeTimeZone(timeZone) {
30
+ if (timeZone == null || timeZone === "") return void 0;
31
+ try {
32
+ return new Intl.DateTimeFormat("en-US", { timeZone: String(timeZone) }).resolvedOptions().timeZone;
33
+ } catch {
34
+ throw new RangeError(`Unknown time zone "${String(timeZone)}"`);
35
+ }
36
+ }
37
+ function calendarZone(timeZone) {
38
+ const zone = normalizeTimeZone(timeZone);
39
+ if (!zone) {
40
+ return {
41
+ timeZone: void 0,
42
+ dayOf: (milliseconds) => {
43
+ const date = new Date(milliseconds);
44
+ return { year: date.getFullYear(), month: date.getMonth(), day: date.getDate() };
45
+ },
46
+ midnightOf: (year, month, day) => localMidnight(year, month, day).getTime()
47
+ };
48
+ }
49
+ const formatter = new Intl.DateTimeFormat("en-US", {
50
+ timeZone: zone,
51
+ hourCycle: "h23",
52
+ year: "numeric",
53
+ month: "numeric",
54
+ day: "numeric",
55
+ hour: "numeric",
56
+ minute: "numeric",
57
+ second: "numeric"
58
+ });
59
+ const wallClock = (milliseconds) => {
60
+ const parts = {};
61
+ for (const part of formatter.formatToParts(new Date(milliseconds))) parts[part.type] = Number(part.value);
62
+ return {
63
+ year: parts.year,
64
+ month: parts.month - 1,
65
+ day: parts.day,
66
+ seconds: parts.hour % 24 * 3600 + parts.minute * 60 + parts.second
67
+ };
68
+ };
69
+ const offsetAt = (milliseconds) => {
70
+ const wall = wallClock(milliseconds);
71
+ return utcMidnight(wall.year, wall.month, wall.day) + wall.seconds * 1e3 - Math.floor(milliseconds / 1e3) * 1e3;
72
+ };
73
+ return {
74
+ timeZone: zone,
75
+ dayOf: (milliseconds) => {
76
+ const { year, month, day } = wallClock(milliseconds);
77
+ return { year, month, day };
78
+ },
79
+ midnightOf: (year, month, day) => {
80
+ const target = utcMidnight(year, month, day);
81
+ const first = target - offsetAt(target);
82
+ const second = target - offsetAt(first);
83
+ let best = null;
84
+ for (const candidate of [second, first]) {
85
+ const wall = wallClock(candidate);
86
+ if (wall.year !== year || wall.month !== month || wall.day !== day) continue;
87
+ if (!best || wall.seconds < best.seconds) best = { candidate, seconds: wall.seconds };
88
+ }
89
+ return best ? best.candidate : second;
90
+ }
91
+ };
92
+ }
93
+ function localMidnight(year, month, day) {
94
+ const date = new Date(2e3, 0, 1);
95
+ date.setFullYear(year, month, day);
96
+ date.setHours(0, 0, 0, 0);
97
+ return date;
98
+ }
99
+ function utcMidnight(year, month, day) {
100
+ const date = /* @__PURE__ */ new Date(0);
101
+ date.setUTCFullYear(year, month, day);
102
+ return date.getTime();
103
+ }
15
104
  function tableSortToQuery(sort) {
16
105
  const entries = Array.isArray(sort) ? sort : sort ? [sort] : [];
17
106
  return entries.flatMap((entry) => {
@@ -119,6 +208,37 @@ function mapSortAliases(sort, fields) {
119
208
 
120
209
  // src/zeyos/schema.js
121
210
  var TYPE_MAP = /* @__PURE__ */ Object.create(null);
211
+ var DATE_ONLY_FIELDS = /* @__PURE__ */ new Set(["date", "duedate", "birthdate", "datefrom", "dateto", "datecancel", "lastbilling"]);
212
+ var INSTANT_FIELDS = /* @__PURE__ */ new Set([
213
+ "appointments.datefrom",
214
+ "appointments.dateto",
215
+ "events.datefrom",
216
+ "events.dateto",
217
+ "comments.date",
218
+ "documentversions.date",
219
+ "records.date"
220
+ ]);
221
+ var ENUM_WORDS = Object.freeze({
222
+ AWAITINGACCEPTANCE: "awaiting acceptance",
223
+ AWAITINGAPPROVAL: "awaiting approval",
224
+ BILLINGONLY: "billing only",
225
+ CUSTOMERANDSUPPLIER: "customer and supplier",
226
+ FEEDBACKREQUIRED: "feedback required",
227
+ INEVALUATION: "in evaluation",
228
+ INNEGOTIATION: "in negotiation",
229
+ INREVISION: "in revision",
230
+ NOSTOCK: "no stock",
231
+ NOTSTARTED: "not started",
232
+ PARTLYDELIVERED: "partly delivered",
233
+ PARTLYINVOICED: "partly invoiced",
234
+ PARTLYORDERED: "partly ordered",
235
+ PARTLYPAID: "partly paid",
236
+ PROCUREMENTONLY: "procurement only",
237
+ REMOTECALL: "remote call",
238
+ SERIALSANDCHARGES: "serials and charges"
239
+ });
240
+ var PRIORITY_LABELS = Object.freeze({ 0: "Lowest", 1: "Low", 2: "Medium", 3: "High", 4: "Highest" });
241
+ var ENUM_CONSTANT = /^[A-Z][A-Z\d]*(?:_[A-Z\d]+)*$/;
122
242
  var warnedUnknownTypes = /* @__PURE__ */ new Set();
123
243
  function registerFieldType(typeOrFormat, strategy) {
124
244
  const key = normalizeType(typeOrFormat);
@@ -180,6 +300,8 @@ function resolveFields(client, resource, opts = {}) {
180
300
  const requested = Array.isArray(opts.fields) ? opts.fields.map(String) : [...available.keys()];
181
301
  const excluded = new Set(Array.isArray(opts.exclude) ? opts.exclude.map(String) : []);
182
302
  const labels = opts.labels && typeof opts.labels === "object" ? opts.labels : {};
303
+ const formats = opts.formats && typeof opts.formats === "object" ? opts.formats : {};
304
+ const timeZone = normalizeTimeZone(opts.timeZone);
183
305
  const result = [];
184
306
  for (const id of requested) {
185
307
  if (excluded.has(id) || !available.has(id)) continue;
@@ -191,6 +313,8 @@ function resolveFields(client, resource, opts = {}) {
191
313
  if (meta.enum == null) meta.enum = enumValues;
192
314
  }
193
315
  if (Object.prototype.hasOwnProperty.call(labels, id)) meta.label = String(labels[id]);
316
+ if (Object.prototype.hasOwnProperty.call(formats, id) && formats[id]) meta.format = String(formats[id]);
317
+ if (timeZone) meta.timeZone = timeZone;
194
318
  result.push(meta);
195
319
  }
196
320
  return result;
@@ -223,7 +347,7 @@ var enumColumn = {
223
347
  };
224
348
  var priorityColumn = {
225
349
  field: { type: "zxselect", props: { preset: "priority" } },
226
- column: (meta) => ({ render: enumRenderer(meta, priorityOptions()) })
350
+ column: (meta) => ({ render: enumRenderer(meta, PRIORITY_LABELS) })
227
351
  };
228
352
  var percentColumn = {
229
353
  field: { type: "float", props: { min: 0, max: 100 } },
@@ -266,10 +390,13 @@ for (const type of ["email", "tel", "url", "uri"]) {
266
390
  function dateStrategy(type) {
267
391
  return {
268
392
  field: { type },
269
- column: (meta) => ({
270
- render: (row) => formatUnixDate(rowValue(row, meta), type === "datetime"),
271
- sortValue: (row) => unixSortValue(rowValue(row, meta))
272
- })
393
+ column: (meta) => {
394
+ const format2 = type === "datetime" ? instantFormatter() : calendarDayFormatter(meta.timeZone);
395
+ return {
396
+ render: (row) => format2(rowValue(row, meta)),
397
+ sortValue: (row) => unixSortValue(rowValue(row, meta))
398
+ };
399
+ }
273
400
  };
274
401
  }
275
402
  function strategyFor(meta) {
@@ -290,9 +417,8 @@ function strategyKey(meta) {
290
417
  if (meta.enums != null || meta.enum != null) return "enum";
291
418
  const raw2 = normalizeType(meta.type);
292
419
  if (raw2 === "bigint" || raw2 === "int64") {
293
- const id = String(meta.id).toLowerCase();
294
- if (id === "date" && meta.indexed) return "date";
295
- if (/(?:date|time)$/.test(id)) return "datetime";
420
+ const temporal = timestampKind(meta);
421
+ if (temporal) return temporal;
296
422
  }
297
423
  if (isLongText(meta, raw2)) return "longtext";
298
424
  if (raw2 && TYPE_MAP[raw2]) return raw2;
@@ -301,6 +427,15 @@ function strategyKey(meta) {
301
427
  if (/^character varying(?:\(\d+\))?$/.test(raw2)) return "character varying";
302
428
  return raw2 || semantic;
303
429
  }
430
+ function timestampKind(meta) {
431
+ const id = String(meta.id).toLowerCase();
432
+ const resource = String(meta.resource ?? "").toLowerCase();
433
+ if (DATE_ONLY_FIELDS.has(id) && !INSTANT_FIELDS.has(`${resource}.${id}`)) {
434
+ if (id !== "date" || meta.indexed) return "date";
435
+ }
436
+ if (INSTANT_FIELDS.has(`${resource}.${id}`) || /(?:date|time)$|^lastmodified$/.test(id)) return "datetime";
437
+ return null;
438
+ }
304
439
  function normalizeMeta(meta) {
305
440
  if (!meta || typeof meta !== "object" || Array.isArray(meta)) throw new TypeError("Field metadata must be an object");
306
441
  const id = meta.id ?? meta.name ?? meta.field ?? meta.identifier ?? meta.reference;
@@ -382,26 +517,29 @@ function normalizeEnumOptions(values) {
382
517
  return Object.fromEntries(values.map((entry, index) => {
383
518
  if (Array.isArray(entry)) {
384
519
  const [value = index, label = value] = entry;
385
- return [String(value), String(label)];
520
+ return [String(value), enumLabel(label)];
386
521
  }
387
522
  if (entry && typeof entry === "object") {
388
523
  const value = entry.value ?? entry.id ?? index;
389
- return [String(value), String(entry.label ?? entry.name ?? value)];
524
+ return [String(value), entry.label != null ? String(entry.label) : enumLabel(entry.name ?? value)];
390
525
  }
391
- return [String(index), String(entry)];
526
+ return [String(index), enumLabel(entry)];
392
527
  }));
393
528
  }
394
529
  if (values && typeof values === "object") {
395
- return Object.fromEntries(Object.entries(values).map(([value, label]) => [value, String(label)]));
530
+ return Object.fromEntries(Object.entries(values).map(([value, label]) => [value, enumLabel(label)]));
396
531
  }
397
532
  return {};
398
533
  }
534
+ function enumLabel(name2) {
535
+ const text3 = String(name2 ?? "");
536
+ if (!ENUM_CONSTANT.test(text3)) return text3;
537
+ const words = text3.split("_").map((part) => ENUM_WORDS[part] ?? part.toLowerCase()).join(" ");
538
+ return words.charAt(0).toUpperCase() + words.slice(1);
539
+ }
399
540
  function enumFieldType(meta) {
400
541
  return Object.keys(enumOptions(meta)).length <= 5 ? "optionlist" : "select";
401
542
  }
402
- function priorityOptions() {
403
- return { "0": "LOWEST", "1": "LOW", "2": "MEDIUM", "3": "HIGH", "4": "HIGHEST" };
404
- }
405
543
  function enumRenderer(meta, fallback = {}) {
406
544
  const labels = { ...fallback, ...enumOptions(meta) };
407
545
  return (row) => {
@@ -431,17 +569,38 @@ function formatPercent(value) {
431
569
  const number = finiteNumber(value);
432
570
  return number == null ? "" : `${new Intl.NumberFormat().format(number)}%`;
433
571
  }
434
- function formatUnixDate(value, includeTime) {
435
- if (value == null || value === "") return "";
572
+ function instantFormatter() {
573
+ const formatter = new Intl.DateTimeFormat(void 0, { dateStyle: "medium", timeStyle: "short" });
574
+ return (value) => {
575
+ const date = timestampDate(value);
576
+ return date ? formatter.format(date) : "";
577
+ };
578
+ }
579
+ function calendarDayFormatter(timeZone) {
580
+ const zone = calendarZone(
581
+ /** @type {string|undefined} */
582
+ timeZone ?? void 0
583
+ );
584
+ const formatter = new Intl.DateTimeFormat(void 0, { dateStyle: "medium", timeZone: "UTC" });
585
+ return (value) => {
586
+ const date = timestampDate(value);
587
+ if (!date) return "";
588
+ const { year, month, day } = value instanceof Date ? { year: date.getFullYear(), month: date.getMonth(), day: date.getDate() } : zone.dayOf(date.getTime());
589
+ const utc = /* @__PURE__ */ new Date(0);
590
+ utc.setUTCFullYear(year, month, day);
591
+ return formatter.format(utc);
592
+ };
593
+ }
594
+ function timestampDate(value) {
595
+ if (value == null || value === "") return null;
436
596
  try {
437
597
  const date = value instanceof Date ? value : unixSecondsToDate(
438
598
  /** @type {number|string} */
439
599
  value
440
600
  );
441
- if (!date || !Number.isFinite(date.getTime())) return "";
442
- return new Intl.DateTimeFormat(void 0, includeTime ? { dateStyle: "medium", timeStyle: "short" } : { dateStyle: "medium" }).format(date);
601
+ return date && Number.isFinite(date.getTime()) ? date : null;
443
602
  } catch {
444
- return "";
603
+ return null;
445
604
  }
446
605
  }
447
606
  function unixSortValue(value) {
@@ -589,7 +748,7 @@ function snapshotTarget(element) {
589
748
  function restoreTarget(element, snapshot3) {
590
749
  if (!snapshot3) return;
591
750
  for (const attribute of Array.from(element.attributes)) element.removeAttribute(attribute.name);
592
- for (const [name, value] of snapshot3.attributes) element.setAttribute(name, value);
751
+ for (const [name2, value] of snapshot3.attributes) element.setAttribute(name2, value);
593
752
  element.replaceChildren(...snapshot3.children);
594
753
  }
595
754
  function resolveElement(target) {
@@ -634,9 +793,9 @@ function applyProperty(element, key, value) {
634
793
  return;
635
794
  }
636
795
  if (key === "dataset" && value && typeof value === "object") {
637
- for (const [name, datasetValue] of Object.entries(value)) {
638
- if (datasetValue == null) delete element.dataset[name];
639
- else element.dataset[name] = String(datasetValue);
796
+ for (const [name2, datasetValue] of Object.entries(value)) {
797
+ if (datasetValue == null) delete element.dataset[name2];
798
+ else element.dataset[name2] = String(datasetValue);
640
799
  }
641
800
  return;
642
801
  }
@@ -966,12 +1125,12 @@ function mergeOptions(constructor, options) {
966
1125
  const result = {};
967
1126
  let messages = {};
968
1127
  let hasMessages = false;
969
- for (const defaults of chain) {
970
- if (!defaults || typeof defaults !== "object") continue;
971
- Object.assign(result, defaults);
972
- if (defaults.msg && typeof defaults.msg === "object") {
1128
+ for (const defaults4 of chain) {
1129
+ if (!defaults4 || typeof defaults4 !== "object") continue;
1130
+ Object.assign(result, defaults4);
1131
+ if (defaults4.msg && typeof defaults4.msg === "object") {
973
1132
  hasMessages = true;
974
- messages = deepMerge(messages, defaults.msg);
1133
+ messages = deepMerge(messages, defaults4.msg);
975
1134
  }
976
1135
  }
977
1136
  Object.assign(result, options && typeof options === "object" ? options : {});
@@ -1000,6 +1159,7 @@ var Field = class _Field extends Component {
1000
1159
  /** @type {FieldOptions & {msg: Record<string, string>}} */
1001
1160
  static defaults = {
1002
1161
  id: null,
1162
+ name: null,
1003
1163
  type: "text",
1004
1164
  label: "",
1005
1165
  description: "",
@@ -1025,10 +1185,10 @@ var Field = class _Field extends Component {
1025
1185
  * @returns {typeof Field}
1026
1186
  */
1027
1187
  static register(type, adapter) {
1028
- const name = String(type).trim().toLowerCase();
1029
- if (!name) throw new TypeError("Field adapter type must not be empty");
1188
+ const name2 = String(type).trim().toLowerCase();
1189
+ if (!name2) throw new TypeError("Field adapter type must not be empty");
1030
1190
  if (typeof adapter !== "function") throw new TypeError("Field adapter must be a function");
1031
- _Field.#adapters.set(name, adapter);
1191
+ _Field.#adapters.set(name2, adapter);
1032
1192
  return this;
1033
1193
  }
1034
1194
  /**
@@ -1039,9 +1199,10 @@ var Field = class _Field extends Component {
1039
1199
  static has(type) {
1040
1200
  return _Field.#adapters.has(String(type).trim().toLowerCase());
1041
1201
  }
1042
- // `adapter` (FieldAdapter), `initialValue`, and `controlId` are assigned in render();
1043
- // declaring them as class fields would re-initialize them to undefined after the base
1044
- // constructor (which calls render()) returns — see AGENTS.md.
1202
+ // `adapter` (FieldAdapter), `initialValue`, `controlId` (the control's DOM id), and `name` (the
1203
+ // field name, '' when there is none) are assigned in render(); declaring them as class fields
1204
+ // would re-initialize them to undefined after the base constructor (which calls render())
1205
+ // returns — see AGENTS.md.
1045
1206
  /** @returns {HTMLElement} */
1046
1207
  render() {
1047
1208
  const root = (
@@ -1057,6 +1218,7 @@ var Field = class _Field extends Component {
1057
1218
  root.dataset.layout = layout;
1058
1219
  root.dataset.fieldType = type;
1059
1220
  this.controlId = this.options.id ? String(this.options.id) : uid("zx-field");
1221
+ this.name = String(this.options.name ?? this.options.id ?? "");
1060
1222
  const labelId = `${this.controlId}-label`;
1061
1223
  const descriptionId = `${this.controlId}-description`;
1062
1224
  const highlightId = `${this.controlId}-highlight`;
@@ -1102,9 +1264,12 @@ var Field = class _Field extends Component {
1102
1264
  }
1103
1265
  const control = this.adapter.el;
1104
1266
  if (!control.id) control.id = this.controlId;
1105
- label.htmlFor = control.id;
1106
- if (!isLabelable(control)) control.setAttribute("aria-labelledby", labelId);
1107
- this._baseDescribedBy = control.getAttribute("aria-describedby") ?? "";
1267
+ const labelled = this.adapter.labelTarget ?? (isLabelable(control) ? control : unnamedControlWithin(control)) ?? control;
1268
+ this._labelled = labelled;
1269
+ if (labelled !== control && isLabelable(labelled) && !labelled.id) labelled.id = `${this.controlId}-input`;
1270
+ label.htmlFor = isLabelable(labelled) ? labelled.id : control.id;
1271
+ if (!isLabelable(labelled)) labelled.setAttribute("aria-labelledby", labelId);
1272
+ this._baseDescribedBy = labelled.getAttribute("aria-describedby") ?? "";
1108
1273
  controlSlot.append(control);
1109
1274
  this._descriptionId = String(this.options.description ?? "") ? descriptionId : "";
1110
1275
  this._highlightId = highlightId;
@@ -1179,11 +1344,11 @@ var Field = class _Field extends Component {
1179
1344
  this._highlight.hidden = !message;
1180
1345
  if (normalizedKind === "danger" && message) {
1181
1346
  this._highlight.setAttribute("role", "alert");
1182
- this.adapter.el.setAttribute("aria-invalid", "true");
1347
+ this._labelled.setAttribute("aria-invalid", "true");
1183
1348
  this.emit("invalid", { message: String(message) });
1184
1349
  } else {
1185
1350
  this._highlight.removeAttribute("role");
1186
- this.adapter.el.removeAttribute("aria-invalid");
1351
+ this._labelled.removeAttribute("aria-invalid");
1187
1352
  }
1188
1353
  this._syncDescription(Boolean(message));
1189
1354
  return this;
@@ -1197,7 +1362,7 @@ var Field = class _Field extends Component {
1197
1362
  this._highlight.textContent = "";
1198
1363
  this._highlight.hidden = true;
1199
1364
  this._highlight.removeAttribute("role");
1200
- this.adapter.el.removeAttribute("aria-invalid");
1365
+ this._labelled.removeAttribute("aria-invalid");
1201
1366
  this._syncDescription(false);
1202
1367
  return this;
1203
1368
  }
@@ -1222,8 +1387,8 @@ var Field = class _Field extends Component {
1222
1387
  /** @param {boolean} [highlighted=false] @returns {void} */
1223
1388
  _syncDescription(highlighted = false) {
1224
1389
  const ids = [this._baseDescribedBy, this._descriptionId, highlighted ? this._highlightId : ""].flatMap((value) => value.split(/\s+/)).filter(Boolean);
1225
- if (ids.length) this.adapter.el.setAttribute("aria-describedby", [...new Set(ids)].join(" "));
1226
- else this.adapter.el.removeAttribute("aria-describedby");
1390
+ if (ids.length) this._labelled.setAttribute("aria-describedby", [...new Set(ids)].join(" "));
1391
+ else this._labelled.removeAttribute("aria-describedby");
1227
1392
  }
1228
1393
  /** @returns {void} */
1229
1394
  destroy() {
@@ -1319,25 +1484,35 @@ function errorAdapter(message, id) {
1319
1484
  function isLabelable(element) {
1320
1485
  return ["BUTTON", "INPUT", "METER", "OUTPUT", "PROGRESS", "SELECT", "TEXTAREA"].includes(element.tagName);
1321
1486
  }
1487
+ function unnamedControlWithin(root) {
1488
+ for (const candidate of root.querySelectorAll('input:not([type="hidden"]), select, textarea')) {
1489
+ if (candidate.hasAttribute("aria-label") || candidate.hasAttribute("aria-labelledby")) continue;
1490
+ const labels = [.../** @type {HTMLInputElement} */
1491
+ candidate.labels ?? []];
1492
+ if (labels.some((label) => label.textContent?.trim())) continue;
1493
+ return candidate;
1494
+ }
1495
+ return null;
1496
+ }
1322
1497
  function rememberAttributes(element, names) {
1323
- return new Map(names.map((name) => [name, element.getAttribute(name)]));
1498
+ return new Map(names.map((name2) => [name2, element.getAttribute(name2)]));
1324
1499
  }
1325
1500
  function restoreAttributes(element, attributes) {
1326
- for (const [name, value] of attributes) {
1327
- if (value === null) element.removeAttribute(name);
1328
- else element.setAttribute(name, value);
1501
+ for (const [name2, value] of attributes) {
1502
+ if (value === null) element.removeAttribute(name2);
1503
+ else element.setAttribute(name2, value);
1329
1504
  }
1330
1505
  }
1331
- function controlProps(options, base) {
1506
+ function controlProps(field, options, base) {
1332
1507
  const props = { ...options.props ?? {} };
1333
1508
  for (const key of Object.keys(props)) {
1334
1509
  if (/^on[a-z]/i.test(key) || key === "content") delete props[key];
1335
1510
  }
1336
- return { ...props, ...base };
1511
+ return { name: field.name || void 0, ...props, ...base };
1337
1512
  }
1338
1513
  function textAdapter(tag, type) {
1339
- return (_field, options) => {
1340
- const el = h(tag, controlProps(options, {
1514
+ return (field, options) => {
1515
+ const el = h(tag, controlProps(field, options, {
1341
1516
  type: tag === "input" ? type : void 0,
1342
1517
  placeholder: options.placeholder,
1343
1518
  required: options.required,
@@ -1362,7 +1537,7 @@ function elementValueAdapter(el) {
1362
1537
  function numericAdapter(kind) {
1363
1538
  return (field, options) => {
1364
1539
  const coerce = kind === "int" ? coerceInteger : coerceFloat;
1365
- const input = h("input", controlProps(options, {
1540
+ const input = h("input", controlProps(field, options, {
1366
1541
  type: "text",
1367
1542
  inputMode: kind === "int" ? "numeric" : "decimal",
1368
1543
  placeholder: options.placeholder,
@@ -1384,8 +1559,8 @@ function numericAdapter(kind) {
1384
1559
  };
1385
1560
  };
1386
1561
  }
1387
- var checkboxAdapter = (_field, options) => {
1388
- const input = h("input", controlProps(options, {
1562
+ var checkboxAdapter = (field, options) => {
1563
+ const input = h("input", controlProps(field, options, {
1389
1564
  type: "checkbox",
1390
1565
  required: options.required,
1391
1566
  disabled: options.disabled
@@ -1402,8 +1577,8 @@ var checkboxAdapter = (_field, options) => {
1402
1577
  }
1403
1578
  };
1404
1579
  };
1405
- var selectAdapter = (_field, options) => {
1406
- const select = h("select", controlProps(options, {
1580
+ var selectAdapter = (field, options) => {
1581
+ const select = h("select", controlProps(field, options, {
1407
1582
  required: options.required,
1408
1583
  disabled: options.disabled
1409
1584
  }));
@@ -1412,16 +1587,16 @@ var selectAdapter = (_field, options) => {
1412
1587
  }
1413
1588
  return elementValueAdapter(select);
1414
1589
  };
1415
- var optionListAdapter = (_field, options) => {
1590
+ var optionListAdapter = (field, options) => {
1416
1591
  const root = h("div", {
1417
1592
  class: "zx-field__option-list",
1418
1593
  role: "radiogroup"
1419
1594
  });
1420
- const name = options.id ? String(options.id) : uid("zx-option-list");
1595
+ const name2 = field.controlId;
1421
1596
  const radios = normalizeOptions(options.options).map(([value, label], index) => {
1422
1597
  const input = h("input", {
1423
1598
  type: "radio",
1424
- name,
1599
+ name: name2,
1425
1600
  value,
1426
1601
  required: Boolean(options.required) && index === 0,
1427
1602
  disabled: options.disabled
@@ -1442,8 +1617,8 @@ var optionListAdapter = (_field, options) => {
1442
1617
  }
1443
1618
  };
1444
1619
  };
1445
- var htmlAdapter = (_field, options) => {
1446
- const el = h("div", controlProps(options, { class: "zx-field__static" }));
1620
+ var htmlAdapter = (field, options) => {
1621
+ const el = h("div", controlProps(field, options, { class: "zx-field__static", name: void 0 }));
1447
1622
  let adopted = null;
1448
1623
  const set = (value) => {
1449
1624
  restoreAdopted(adopted);
@@ -1528,6 +1703,7 @@ var Checklist = class extends Component {
1528
1703
  search: true,
1529
1704
  height: 280,
1530
1705
  defaultChecked: false,
1706
+ hideLabel: false,
1531
1707
  load: null
1532
1708
  };
1533
1709
  /** @returns {HTMLElement} */
@@ -1548,14 +1724,15 @@ var Checklist = class extends Component {
1548
1724
  );
1549
1725
  this.el = root;
1550
1726
  if (!this._createdRoot) this._originalChildren = Array.from(root.childNodes);
1551
- for (const name of ["role", "aria-labelledby", "data-state"]) {
1552
- this._originalAttributes.set(name, root.getAttribute(name));
1727
+ for (const name2 of ["role", "aria-label", "aria-labelledby", "data-state"]) {
1728
+ this._originalAttributes.set(name2, root.getAttribute(name2));
1553
1729
  }
1730
+ const labelText = this._message("checklist.label", "Checklist");
1554
1731
  const labelId = uid("zx-checklist-label");
1555
- const title = h("div", {
1732
+ const title = this.options.hideLabel ? null : h("div", {
1556
1733
  class: "zx-checklist__label",
1557
1734
  id: labelId
1558
- }, this._message("checklist.label", "Checklist"));
1735
+ }, labelText);
1559
1736
  const search = h("input", {
1560
1737
  ref: "search",
1561
1738
  class: "zx-checklist__search",
@@ -1570,9 +1747,10 @@ var Checklist = class extends Component {
1570
1747
  class: "zx-checklist__list"
1571
1748
  });
1572
1749
  list.style.maxBlockSize = `${Math.max(0, Number(this.options.height) || 0)}px`;
1573
- root.replaceChildren(title, search, list);
1750
+ root.replaceChildren(...[title, search, list].filter(Boolean));
1574
1751
  root.setAttribute("role", "group");
1575
- root.setAttribute("aria-labelledby", labelId);
1752
+ if (title) root.setAttribute("aria-labelledby", labelId);
1753
+ else root.setAttribute("aria-label", labelText);
1576
1754
  this.listen(search, "input", () => this.search(
1577
1755
  /** @type {HTMLInputElement} */
1578
1756
  search.value
@@ -1683,9 +1861,9 @@ var Checklist = class extends Component {
1683
1861
  );
1684
1862
  if (!this._createdRoot && root) {
1685
1863
  root.replaceChildren(...this._originalChildren);
1686
- for (const [name, value] of this._originalAttributes) {
1687
- if (value === null) root.removeAttribute(name);
1688
- else root.setAttribute(name, value);
1864
+ for (const [name2, value] of this._originalAttributes) {
1865
+ if (value === null) root.removeAttribute(name2);
1866
+ else root.setAttribute(name2, value);
1689
1867
  }
1690
1868
  }
1691
1869
  super.destroy();
@@ -1762,7 +1940,7 @@ function setsEqual(left, right) {
1762
1940
  // src/components/checklist/field-adapter.js
1763
1941
  function registerChecklistFieldAdapter() {
1764
1942
  Field.register("checklist", (field, options) => {
1765
- const checklist = field.own(new Checklist(null, { ...options.props ?? {} }));
1943
+ const checklist = field.own(new Checklist(null, { hideLabel: true, ...options.props ?? {} }));
1766
1944
  let disabled = false;
1767
1945
  let setting = false;
1768
1946
  const syncDisabled = () => {
@@ -1800,6 +1978,283 @@ function registerChecklistFieldAdapter() {
1800
1978
  });
1801
1979
  }
1802
1980
 
1981
+ // src/components/code-editor/code-editor.js
1982
+ var MODIFIER_KEYS = /* @__PURE__ */ new Set(["Shift", "Control", "Alt", "Meta", "AltGraph", "CapsLock", "Fn", "OS"]);
1983
+ var CodeEditor = class extends Component {
1984
+ static cssName = "code-editor";
1985
+ /** @type {CodeEditorOptions} */
1986
+ static defaults = {
1987
+ value: "",
1988
+ name: "",
1989
+ language: "text",
1990
+ label: "Code editor",
1991
+ placeholder: "",
1992
+ rows: 12,
1993
+ indent: " ",
1994
+ wrap: "off",
1995
+ spellcheck: false,
1996
+ readOnly: false,
1997
+ disabled: false,
1998
+ status: true
1999
+ };
2000
+ /** @returns {HTMLElement} */
2001
+ render() {
2002
+ this._createdRoot = this.el === null;
2003
+ const root = (
2004
+ /** @type {HTMLElement} */
2005
+ this.el ?? h("div")
2006
+ );
2007
+ this.el = root;
2008
+ this._initialChildren = this._createdRoot ? [] : [...root.childNodes];
2009
+ this._initialAttributes = new Map(["data-language", "data-readonly", "data-disabled"].map((name2) => [name2, root.getAttribute(name2)]));
2010
+ this._language = normalizeLanguage(this.options.language);
2011
+ this._languageLabel = h("span", { class: "zx-code-editor__language" }, this._language);
2012
+ this._position = h("span", {
2013
+ class: "zx-code-editor__position",
2014
+ role: "status",
2015
+ ariaLive: "polite"
2016
+ });
2017
+ this._header = h(
2018
+ "div",
2019
+ { class: "zx-code-editor__header" },
2020
+ this._languageLabel,
2021
+ this.options.status ? this._position : null
2022
+ );
2023
+ this._input = /** @type {HTMLTextAreaElement} */
2024
+ h("textarea", {
2025
+ class: "zx-code-editor__input",
2026
+ name: this.options.name ? String(this.options.name) : void 0,
2027
+ ariaLabel: String(this.options.label ?? "Code editor"),
2028
+ placeholder: String(this.options.placeholder ?? ""),
2029
+ rows: normalizeRows(this.options.rows),
2030
+ wrap: normalizeWrap(this.options.wrap),
2031
+ spellcheck: Boolean(this.options.spellcheck),
2032
+ readOnly: Boolean(this.options.readOnly),
2033
+ disabled: Boolean(this.options.disabled),
2034
+ autocomplete: "off",
2035
+ autocapitalize: "off"
2036
+ });
2037
+ this._input.value = String(this.options.value ?? "");
2038
+ root.dataset.language = this._language;
2039
+ root.dataset.readonly = String(Boolean(this.options.readOnly));
2040
+ root.dataset.disabled = String(Boolean(this.options.disabled));
2041
+ root.replaceChildren(this._header, this._input);
2042
+ this._tabReleased = false;
2043
+ this.listen(this._input, "keydown", (event) => this._handleKeydown(event));
2044
+ this.listen(this._input, "blur", () => {
2045
+ this._tabReleased = false;
2046
+ });
2047
+ this.listen(this._input, "input", () => {
2048
+ this._updatePosition();
2049
+ this.emit("input", { value: this.getValue() });
2050
+ });
2051
+ this.listen(this._input, "change", () => this.emit("change", { value: this.getValue() }));
2052
+ for (const type of ["click", "keyup", "select", "focus"]) {
2053
+ this.listen(this._input, type, () => this._updatePosition());
2054
+ }
2055
+ this._updatePosition();
2056
+ return root;
2057
+ }
2058
+ /** @returns {string} */
2059
+ getValue() {
2060
+ return this._input.value;
2061
+ }
2062
+ /**
2063
+ * Replaces the editor text.
2064
+ * @param {unknown} value New value.
2065
+ * @param {{silent?:boolean}} [options={}] Suppress the `change` event.
2066
+ * @returns {this}
2067
+ */
2068
+ setValue(value, { silent = false } = {}) {
2069
+ const next = String(value ?? "");
2070
+ if (this._input.value === next) return this;
2071
+ this._input.value = next;
2072
+ this._updatePosition();
2073
+ if (!silent) this.emit("change", { value: next });
2074
+ return this;
2075
+ }
2076
+ /** @param {unknown} language New language identifier. @returns {this} */
2077
+ setLanguage(language2) {
2078
+ this._language = normalizeLanguage(language2);
2079
+ this.el.dataset.language = this._language;
2080
+ this._languageLabel.textContent = this._language;
2081
+ return this;
2082
+ }
2083
+ /** @returns {this} */
2084
+ focus() {
2085
+ this._input.focus();
2086
+ return this;
2087
+ }
2088
+ /** @returns {this} */
2089
+ enable() {
2090
+ this._input.disabled = false;
2091
+ this.el.dataset.disabled = "false";
2092
+ return this;
2093
+ }
2094
+ /** @returns {this} */
2095
+ disable() {
2096
+ this._input.disabled = true;
2097
+ this.el.dataset.disabled = "true";
2098
+ return this;
2099
+ }
2100
+ /** @param {boolean} value Read-only state. @returns {this} */
2101
+ setReadOnly(value) {
2102
+ this._input.readOnly = Boolean(value);
2103
+ this.el.dataset.readonly = String(Boolean(value));
2104
+ return this;
2105
+ }
2106
+ /** @param {KeyboardEvent} event @returns {void} */
2107
+ _handleKeydown(event) {
2108
+ if (event.isComposing || MODIFIER_KEYS.has(event.key)) return;
2109
+ if (event.key === "Escape") {
2110
+ this._tabReleased = true;
2111
+ return;
2112
+ }
2113
+ const released = this._tabReleased;
2114
+ this._tabReleased = false;
2115
+ if (released || event.key !== "Tab" || event.altKey || event.ctrlKey || event.metaKey || this._input.readOnly || this._input.disabled) return;
2116
+ const indent = String(this.options.indent ?? " ");
2117
+ if (!indent) return;
2118
+ event.preventDefault();
2119
+ const result = indentSelection(
2120
+ this._input.value,
2121
+ this._input.selectionStart ?? 0,
2122
+ this._input.selectionEnd ?? 0,
2123
+ indent,
2124
+ event.shiftKey
2125
+ );
2126
+ if (result.value === this._input.value) return;
2127
+ this._input.value = result.value;
2128
+ this._input.setSelectionRange(result.start, result.end);
2129
+ this._updatePosition();
2130
+ this.emit("input", { value: this.getValue() });
2131
+ }
2132
+ /** @returns {void} */
2133
+ _updatePosition() {
2134
+ if (!this.options.status) return;
2135
+ const position2 = editorPosition(
2136
+ this._input.value,
2137
+ this._input.selectionStart ?? 0,
2138
+ this._input.selectionEnd ?? 0
2139
+ );
2140
+ this._position.textContent = position2.lines > 1 ? `Ln ${position2.line}, Col ${position2.column} \xB7 ${position2.lines} lines selected` : `Ln ${position2.line}, Col ${position2.column}`;
2141
+ }
2142
+ /** @returns {void} */
2143
+ destroy() {
2144
+ if (!this._createdRoot) {
2145
+ this.el.replaceChildren(...this._initialChildren);
2146
+ for (const [name2, value] of this._initialAttributes) {
2147
+ if (value === null) this.el.removeAttribute(name2);
2148
+ else this.el.setAttribute(name2, value);
2149
+ }
2150
+ }
2151
+ super.destroy();
2152
+ }
2153
+ };
2154
+ function indentSelection(value, start, end, indent = " ", outdent = false) {
2155
+ const source = String(value ?? "");
2156
+ const unit = String(indent ?? "");
2157
+ const from = clampIndex(start, source.length);
2158
+ const to = clampIndex(end, source.length);
2159
+ if (!unit) return { value: source, start: from, end: to };
2160
+ if (from === to && !outdent) {
2161
+ return {
2162
+ value: source.slice(0, from) + unit + source.slice(to),
2163
+ start: from + unit.length,
2164
+ end: from + unit.length
2165
+ };
2166
+ }
2167
+ const lineStart = source.lastIndexOf("\n", Math.max(0, from - 1)) + 1;
2168
+ let lineEnd = source.indexOf("\n", to);
2169
+ if (lineEnd < 0) lineEnd = source.length;
2170
+ const block = source.slice(lineStart, lineEnd);
2171
+ const lines = block.split("\n");
2172
+ let removedFirst = 0;
2173
+ let delta = 0;
2174
+ const transformed = lines.map((line, index) => {
2175
+ if (!outdent) {
2176
+ delta += unit.length;
2177
+ return unit + line;
2178
+ }
2179
+ const removed = removableIndent(line, unit);
2180
+ if (index === 0) removedFirst = removed;
2181
+ delta -= removed;
2182
+ return line.slice(removed);
2183
+ }).join("\n");
2184
+ const nextStart = outdent ? Math.max(lineStart, from - Math.min(removedFirst, from - lineStart)) : from + unit.length;
2185
+ return {
2186
+ value: source.slice(0, lineStart) + transformed + source.slice(lineEnd),
2187
+ start: nextStart,
2188
+ end: Math.max(nextStart, to + delta)
2189
+ };
2190
+ }
2191
+ function editorPosition(value, start, end = start) {
2192
+ const source = String(value ?? "");
2193
+ const from = clampIndex(start, source.length);
2194
+ const to = clampIndex(end, source.length);
2195
+ const before = source.slice(0, from);
2196
+ return {
2197
+ line: before.split("\n").length,
2198
+ column: from - before.lastIndexOf("\n"),
2199
+ lines: source.slice(from, to).split("\n").length
2200
+ };
2201
+ }
2202
+ function removableIndent(line, indent) {
2203
+ if (line.startsWith(indent)) return indent.length;
2204
+ if (indent === " ") return line.startsWith(" ") ? 1 : Math.min(/^ +/.exec(line)?.[0].length ?? 0, 2);
2205
+ return Math.min(/^\s+/.exec(line)?.[0].length ?? 0, indent.length);
2206
+ }
2207
+ function normalizeLanguage(value) {
2208
+ const language2 = String(value ?? "text").trim().toLowerCase();
2209
+ return /^[a-z0-9][a-z0-9+_.-]*$/.test(language2) ? language2 : "text";
2210
+ }
2211
+ function normalizeRows(value) {
2212
+ const rows = Number(value);
2213
+ return Number.isFinite(rows) ? Math.max(2, Math.min(100, Math.round(rows))) : 12;
2214
+ }
2215
+ function normalizeWrap(value) {
2216
+ return ["off", "soft", "hard"].includes(String(value)) ? (
2217
+ /** @type {'off'|'soft'|'hard'} */
2218
+ value
2219
+ ) : "off";
2220
+ }
2221
+ function clampIndex(value, length) {
2222
+ const number = Number(value);
2223
+ return Math.max(0, Math.min(length, Number.isFinite(number) ? Math.round(number) : 0));
2224
+ }
2225
+
2226
+ // src/components/code-editor/field-adapter.js
2227
+ function registerCodeEditorFieldAdapter() {
2228
+ Field.register("code", (field, options) => {
2229
+ const editor = field.own(new CodeEditor(null, {
2230
+ label: options.label ? String(options.label) : "Code editor",
2231
+ placeholder: options.placeholder,
2232
+ readOnly: Boolean(options.props?.readOnly),
2233
+ disabled: Boolean(options.disabled),
2234
+ ...options.props ?? {},
2235
+ value: options.value
2236
+ }));
2237
+ let setting = false;
2238
+ editor.on("input", (event) => {
2239
+ if (!setting) field.emit("change", { value: event.detail.value });
2240
+ });
2241
+ return {
2242
+ el: editor.el,
2243
+ get: () => editor.getValue(),
2244
+ set: (value) => {
2245
+ setting = true;
2246
+ try {
2247
+ editor.setValue(value, { silent: true });
2248
+ } finally {
2249
+ setting = false;
2250
+ }
2251
+ },
2252
+ focus: () => editor.focus(),
2253
+ setDisabled: (value) => value ? editor.disable() : editor.enable()
2254
+ };
2255
+ });
2256
+ }
2257
+
1803
2258
  // src/core/date.js
1804
2259
  var MONTHS = [
1805
2260
  "January",
@@ -2025,19 +2480,19 @@ function parseIconSpec(spec) {
2025
2480
  const separator = text3.indexOf(":");
2026
2481
  if (separator > 0) {
2027
2482
  const prefix = text3.slice(0, separator).toLowerCase();
2028
- const name = text3.slice(separator + 1);
2483
+ const name2 = text3.slice(separator + 1);
2029
2484
  if (prefix === "builtin" || prefix === "zx") {
2030
- return { provider: "builtin", name, style: null, classes: null };
2485
+ return { provider: "builtin", name: name2, style: null, classes: null };
2031
2486
  }
2032
2487
  if (Object.hasOwn(stylePrefixes, prefix)) {
2033
- return { provider: "fa", name, style: stylePrefixes[prefix], classes: null };
2488
+ return { provider: "fa", name: name2, style: stylePrefixes[prefix], classes: null };
2034
2489
  }
2035
2490
  }
2036
2491
  return { provider: null, name: text3, style: null, classes: null };
2037
2492
  }
2038
- function faIconClasses(name, options = {}) {
2493
+ function faIconClasses(name2, options = {}) {
2039
2494
  const { style = "solid", family = "classic", fixedWidth = false, translate: translate2 = true } = options;
2040
- const bare = String(name).replace(/^fa-/, "");
2495
+ const bare = String(name2).replace(/^fa-/, "");
2041
2496
  const resolved = translate2 ? faNames[bare] ?? bare : bare;
2042
2497
  const classes = [];
2043
2498
  const familyClass = faFamilies[family];
@@ -2084,42 +2539,61 @@ function loadFontAwesomeKit(kit) {
2084
2539
 
2085
2540
  // src/core/icons.js
2086
2541
  var SVG_NS = "http://www.w3.org/2000/svg";
2542
+ var VIEW_BOX = "0 0 24 24";
2543
+ var STROKE_WIDTH = 1.75;
2544
+ var MIN_STROKE_PX = 1;
2545
+ var outline = (path) => [VIEW_BOX, path, "stroke"];
2087
2546
  var icons = Object.freeze({
2088
- "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
- "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
- "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"],
2091
- "chevron-right": ["0 0 320 512", "M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"],
2092
- check: ["0 0 448 512", "M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"],
2093
- x: ["0 0 384 512", "M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"],
2094
- plus: ["0 0 448 512", "M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32V224H48c-17.7 0-32 14.3-32 32s14.3 32 32 32H192V432c0 17.7 14.3 32 32 32s32-14.3 32-32V288H400c17.7 0 32-14.3 32-32s-14.3-32-32-32H256V80z"],
2095
- minus: ["0 0 448 512", "M432 256c0 17.7-14.3 32-32 32L48 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l352 0c17.7 0 32 14.3 32 32z"],
2096
- search: ["0 0 512 512", "M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"],
2097
- calendar: ["0 0 448 512", "M128 0c17.7 0 32 14.3 32 32V64H288V32c0-17.7 14.3-32 32-32s32 14.3 32 32V64h48c26.5 0 48 21.5 48 48v48H0V112C0 85.5 21.5 64 48 64H96V32c0-17.7 14.3-32 32-32zM0 192H448V464c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V192z"],
2098
- clock: ["0 0 512 512", "M256 0a256 256 0 1 1 0 512A256 256 0 1 1 256 0zM232 120V256c0 8 4 15.5 10.7 20l96 64c11 7.4 25.9 4.4 33.3-6.7s4.4-25.9-6.7-33.3L280 243.2V120c0-13.3-10.7-24-24-24s-24 10.7-24 24z"],
2099
- trash: ["0 0 448 512", "M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"],
2100
- gear: ["0 0 512 512", "M481.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z"],
2101
- eye: ["0 0 576 512", "M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM144 256a144 144 0 1 1 288 0 144 144 0 1 1-288 0zm144-64c0 35.3-28.7 64-64 64c-7.1 0-13.9-1.2-20.3-3.3c-5.5-1.8-11.9 1.6-11.7 7.4c.3 6.9 1.3 13.8 3.2 20.7c13.7 51.2 66.4 81.6 117.6 67.9s81.6-66.4 67.9-117.6c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3z"],
2102
- lock: ["0 0 448 512", "M144 144v48H304V144c0-44.2-35.8-80-80-80s-80 35.8-80 80zM80 192V144C80 64.5 144.5 0 224 0s144 64.5 144 144v48h16c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V256c0-35.3 28.7-64 64-64H80z"],
2103
- reload: ["0 0 512 512", "M386.3 160H336c-17.7 0-32 14.3-32 32s14.3 32 32 32H464c17.7 0 32-14.3 32-32V64c0-17.7-14.3-32-32-32s-32 14.3-32 32v51.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0s-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3s163.8-62.5 226.3 0L386.3 160z"],
2104
- list: ["0 0 512 512", "M40 48C26.7 48 16 58.7 16 72v48c0 13.3 10.7 24 24 24H88c13.3 0 24-10.7 24-24V72c0-13.3-10.7-24-24-24H40zM192 64c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zM16 232v48c0 13.3 10.7 24 24 24H88c13.3 0 24-10.7 24-24V232c0-13.3-10.7-24-24-24H40c-13.3 0-24 10.7-24 24zM40 368c-13.3 0-24 10.7-24 24v48c0 13.3 10.7 24 24 24H88c13.3 0 24-10.7 24-24V392c0-13.3-10.7-24-24-24H40z"],
2105
- filter: ["0 0 512 512", "M3.9 54.9C10.5 40.9 24.5 32 40 32H472c15.5 0 29.5 8.9 36.1 22.9s4.6 30.5-5.2 42.5L320 320.9V448c0 12.1-6.8 23.2-17.7 28.6s-23.8 4.3-33.5-3l-64-48c-8.1-6-12.8-15.5-12.8-25.6V320.9L9 97.3C-.7 85.4-2.8 68.8 3.9 54.9z"],
2106
- dots: ["0 0 448 512", "M8 256a56 56 0 1 1 112 0A56 56 0 1 1 8 256zm160 0a56 56 0 1 1 112 0 56 56 0 1 1-112 0zm216-56a56 56 0 1 1 0 112 56 56 0 1 1 0-112z"],
2107
- info: ["0 0 512 512", "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"],
2108
- warning: ["0 0 512 512", "M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0-64 0 32 32 0 1 0 64 0z"],
2109
- error: ["0 0 512 512", "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1-64 0z"],
2110
- success: ["0 0 512 512", "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"],
2111
- upload: ["0 0 512 512", "M288 109.3V352c0 17.7-14.3 32-32 32s-32-14.3-32-32V109.3l-73.4 73.4c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l128-128c12.5-12.5 32.8-12.5 45.3 0l128 128c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L288 109.3zM64 352H192c0 35.3 28.7 64 64 64s64-28.7 64-64H448c35.3 0 64 28.7 64 64v32c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V416c0-35.3 28.7-64 64-64z"],
2112
- drag: ["0 0 320 512", "M40 352l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zm192 0l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zM40 176l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zm192 0l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zM40 0L88 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40L40 128C17.9 128 0 110.1 0 88L0 40C0 17.9 17.9 0 40 0zM232 0l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40z"],
2113
- star: ["0 0 576 512", "M287.9 0c9.2 0 17.6 5.2 21.6 13.5l68.6 141.3 153.2 22.6c9 1.3 16.5 7.6 19.3 16.3s.5 18.1-5.9 24.5L433.6 328.4l26.2 155.6c1.5 9-2.2 18.1-9.7 23.5s-17.3 6-25.3 1.7l-137-73.2L151 509.1c-8.1 4.3-17.9 3.7-25.3-1.7s-11.2-14.5-9.7-23.5l26.2-155.6L31.1 218.2c-6.5-6.4-8.7-15.9-5.9-24.5s10.3-14.9 19.3-16.3l153.2-22.6L266.3 13.5C270.4 5.2 278.7 0 287.9 0z"],
2114
- heart: ["0 0 512 512", "M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"],
2115
- tag: ["0 0 448 512", "M0 80V229.5c0 17 6.7 33.3 18.7 45.3l176 176c25 25 65.5 25 90.5 0L418.7 317.3c25-25 25-65.5 0-90.5l-176-176c-12-12-28.3-18.7-45.3-18.7H48C21.5 32 0 53.5 0 80zm112 32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"],
2116
- folder: ["0 0 512 512", "M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H298.5c-17 0-33.3-6.7-45.3-18.7L226.7 50.7c-12-12-28.3-18.7-45.3-18.7H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z"],
2117
- "folder-open": ["0 0 576 512", "M88.7 223.8L0 375.8V96C0 60.7 28.7 32 64 32H181.5c17 0 33.3 6.7 45.3 18.7l26.5 26.5c12 12 28.3 18.7 45.3 18.7H416c35.3 0 64 28.7 64 64v32H144c-22.8 0-43.8 12.1-55.3 31.8zm27.6 16.1C122.1 230 132.6 224 144 224H544c11.5 0 22 6.1 27.7 16.1s5.7 22.2-.1 32.1l-112 192C453.9 474 443.4 480 432 480H32c-11.5 0-22-6.1-27.7-16.1s-5.7-22.2 .1-32.1l112-192z"],
2118
- file: ["0 0 384 512", "M0 64C0 28.7 28.7 0 64 0H224V128c0 17.7 14.3 32 32 32H384V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V64zm384 64H256V0L384 128z"],
2119
- copy: ["0 0 448 512", "M208 0L332.1 0c12.7 0 24.9 5.1 33.9 14.1l67.9 67.9c9 9 14.1 21.2 14.1 33.9L448 336c0 26.5-21.5 48-48 48l-192 0c-26.5 0-48-21.5-48-48l0-288c0-26.5 21.5-48 48-48zM48 128l80 0 0 64-64 0 0 256 192 0 0-32 64 0 0 48c0 26.5-21.5 48-48 48L48 512c-26.5 0-48-21.5-48-48L0 176c0-26.5 21.5-48 48-48z"],
2120
- square: ["0 0 448 512", "M384 80c8.8 0 16 7.2 16 16V416c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16V96c0-8.8 7.2-16 16-16H384zM64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64z"],
2121
- code: ["0 0 640 512", "M392.8 1.2c-17-4.9-34.7 5-39.6 22l-128 448c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l128-448c4.9-17-5-34.7-22-39.6zm80.6 120.1c-12.5 12.5-12.5 32.8 0 45.3L562.7 256l-89.4 89.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l112-112c12.5-12.5 12.5-32.8 0-45.3l-112-112c-12.5-12.5-32.8-12.5-45.3 0zm-306.7 0c-12.5-12.5-32.8-12.5-45.3 0l-112 112c-12.5 12.5-12.5 32.8 0 45.3l112 112c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256l89.4-89.4c12.5-12.5 12.5-32.8 0-45.3z"],
2122
- book: ["0 0 448 512", "M96 0C43 0 0 43 0 96V416c0 53 43 96 96 96H384h32c17.7 0 32-14.3 32-32s-14.3-32-32-32V384c17.7 0 32-14.3 32-32V32c0-17.7-14.3-32-32-32H384 96zm0 384H352v64H96c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16H336c8.8 0 16 7.2 16 16s-7.2 16-16 16H144c-8.8 0-16-7.2-16-16zm16 48H336c8.8 0 16 7.2 16 16s-7.2 16-16 16H144c-8.8 0-16-7.2-16-16s7.2-16 16-16z"]
2547
+ // <lucide-glyphs>
2548
+ "arrow-up-wide-short": outline("M3 8l4-4 4 4M7 4v16M11 12h10M11 16h7M11 20h4"),
2549
+ // arrow-up-wide-narrow
2550
+ "arrow-down-wide-short": outline("M3 16l4 4 4-4M7 20V4M11 4h10M11 8h7M11 12h4"),
2551
+ // arrow-down-wide-narrow
2552
+ "chevron-down": outline("M6 9l6 6 6-6"),
2553
+ "chevron-up": outline("M18 15l-6-6-6 6"),
2554
+ "chevron-left": outline("M15 18l-6-6 6-6"),
2555
+ "chevron-right": outline("M9 18l6-6-6-6"),
2556
+ check: outline("M20 6 9 17l-5-5"),
2557
+ x: outline("M18 6 6 18M6 6l12 12"),
2558
+ plus: outline("M5 12h14M12 5v14"),
2559
+ minus: outline("M5 12h14"),
2560
+ search: outline("M21 21l-4.34-4.34M3 11a8 8 0 1 0 16 0a8 8 0 1 0-16 0z"),
2561
+ calendar: outline("M8 2v3M16 2v3M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-14a2 2 0 0 1-2-2v-14a2 2 0 0 1 2-2zM3 9h18"),
2562
+ clock: outline("M2 12a10 10 0 1 0 20 0a10 10 0 1 0-20 0zM12 6v6l4 2"),
2563
+ trash: outline("M10 11v6M14 11v6M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"),
2564
+ gear: outline("M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915M9 12a3 3 0 1 0 6 0a3 3 0 1 0-6 0z"),
2565
+ // settings
2566
+ eye: outline("M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0M9 12a3 3 0 1 0 6 0a3 3 0 1 0-6 0z"),
2567
+ lock: outline("M5 11h14a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2h-14a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2zM7 11V7a5 5 0 0 1 10 0v4"),
2568
+ reload: outline("M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8M21 3v5h-5"),
2569
+ // rotate-cw
2570
+ list: outline("M3 5h.01M3 12h.01M3 19h.01M8 5h13M8 12h13M8 19h13"),
2571
+ filter: outline("M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z"),
2572
+ // funnel
2573
+ dots: outline("M11 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0zM18 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0zM4 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0z"),
2574
+ // ellipsis
2575
+ info: outline("M2 12a10 10 0 1 0 20 0a10 10 0 1 0-20 0zM12 16v-4M12 8h.01"),
2576
+ warning: outline("M21.73 18l-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3M12 9v4M12 17h.01"),
2577
+ // triangle-alert
2578
+ error: outline("M2 12a10 10 0 1 0 20 0a10 10 0 1 0-20 0zM12 8V12M12 16H12.01"),
2579
+ // circle-alert
2580
+ success: outline("M2 12a10 10 0 1 0 20 0a10 10 0 1 0-20 0zM16 9l-5.5 5.5L8 12"),
2581
+ // circle-check
2582
+ upload: outline("M12 3v12M17 8l-5-5-5 5M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"),
2583
+ drag: outline("M8 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0zM8 5a1 1 0 1 0 2 0a1 1 0 1 0-2 0zM8 19a1 1 0 1 0 2 0a1 1 0 1 0-2 0zM14 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0zM14 5a1 1 0 1 0 2 0a1 1 0 1 0-2 0zM14 19a1 1 0 1 0 2 0a1 1 0 1 0-2 0z"),
2584
+ // grip-vertical
2585
+ star: outline("M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"),
2586
+ heart: outline("M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5"),
2587
+ tag: outline("M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42zM7 7.5a.5 .5 0 1 0 1 0a.5 .5 0 1 0-1 0z"),
2588
+ folder: outline("M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"),
2589
+ "folder-open": outline("M6 14l1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"),
2590
+ file: outline("M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2zM14 2v5a1 1 0 0 0 1 1h5"),
2591
+ copy: outline("M10 8h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-10a2 2 0 0 1-2-2v-10a2 2 0 0 1 2-2zM4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"),
2592
+ square: outline("M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-14a2 2 0 0 1-2-2v-14a2 2 0 0 1 2-2z"),
2593
+ code: outline("M18 16l4-4-4-4M6 8l-4 4 4 4M14.5 4l-5 16"),
2594
+ // code-xml
2595
+ book: outline("M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20")
2596
+ // </lucide-glyphs>
2123
2597
  });
2124
2598
  var aliases = Object.freeze({
2125
2599
  settings: "gear",
@@ -2157,8 +2631,8 @@ async function loadFontAwesome(options) {
2157
2631
  await loadFontAwesomeKit(kit);
2158
2632
  return activate ? useFontAwesome() : getIconConfig();
2159
2633
  }
2160
- function icon(name, options = {}) {
2161
- const spec = parseIconSpec(name);
2634
+ function icon(name2, options = {}) {
2635
+ const spec = parseIconSpec(name2);
2162
2636
  if (spec.classes) return faIcon(spec.classes, options);
2163
2637
  const resolved = aliases[spec.name] ?? spec.name;
2164
2638
  if (spec.provider === "builtin") return builtinIcon(resolved, options);
@@ -2178,24 +2652,60 @@ function icon(name, options = {}) {
2178
2652
  }
2179
2653
  return builtinIcon(resolved, options);
2180
2654
  }
2181
- function builtinIcon(name, options) {
2182
- const entry = extraGlyphs[name] ?? icons[name];
2183
- if (!entry) throw new RangeError(`Unknown icon: ${name}`);
2184
- const [viewBox, pathData] = entry;
2655
+ function builtinIcon(name2, options) {
2656
+ const entry = extraGlyphs[name2] ?? icons[name2];
2657
+ if (!entry) {
2658
+ if (options.strict) throw new RangeError(`Unknown icon: ${name2}`);
2659
+ warnMissingIcon(name2);
2660
+ return missingIcon(name2, options);
2661
+ }
2662
+ const [viewBox, pathData, paint = "fill"] = entry;
2663
+ const svg = iconSvg(viewBox, paint, options);
2664
+ const path = document.createElementNS(SVG_NS, "path");
2665
+ path.setAttribute("d", pathData);
2666
+ svg.append(path);
2667
+ return svg;
2668
+ }
2669
+ function iconSvg(viewBox, paint, options) {
2185
2670
  const { size = 16 } = options;
2186
2671
  const svg = document.createElementNS(SVG_NS, "svg");
2187
- const path = document.createElementNS(SVG_NS, "path");
2188
2672
  svg.setAttribute("class", classList("zx-icon", options.class));
2189
2673
  svg.setAttribute("viewBox", viewBox);
2190
2674
  svg.setAttribute("width", String(size));
2191
2675
  svg.setAttribute("height", String(size));
2192
- svg.setAttribute("fill", "currentColor");
2676
+ if (paint === "stroke") {
2677
+ svg.setAttribute("fill", "none");
2678
+ svg.setAttribute("stroke", "currentColor");
2679
+ svg.setAttribute("stroke-width", strokeWidth(viewBox, size));
2680
+ svg.setAttribute("stroke-linecap", "round");
2681
+ svg.setAttribute("stroke-linejoin", "round");
2682
+ } else {
2683
+ svg.setAttribute("fill", "currentColor");
2684
+ }
2193
2685
  svg.setAttribute("focusable", "false");
2194
2686
  applyLabel(svg, options.label ?? null);
2195
- path.setAttribute("d", pathData);
2196
- svg.append(path);
2197
2687
  return svg;
2198
2688
  }
2689
+ function strokeWidth(viewBox, size) {
2690
+ const [, , width, height] = viewBox.trim().split(/[\s,]+/).map(Number);
2691
+ const extent = Math.max(width, height) || 24;
2692
+ let units = STROKE_WIDTH * extent / 24;
2693
+ const pixels = typeof size === "number" ? size : /^\d*\.?\d+(px)?$/.test(String(size).trim()) ? parseFloat(String(size)) : NaN;
2694
+ if (pixels > 0) units = Math.max(units, MIN_STROKE_PX * extent / pixels);
2695
+ return String(Math.round(units * 1e3) / 1e3);
2696
+ }
2697
+ var PLACEHOLDER_VIEW_BOX = VIEW_BOX;
2698
+ function missingIcon(name2, options) {
2699
+ const svg = iconSvg(PLACEHOLDER_VIEW_BOX, "stroke", options);
2700
+ svg.setAttribute("data-zx-icon-missing", name2);
2701
+ return svg;
2702
+ }
2703
+ var warnedMissing = /* @__PURE__ */ new Set();
2704
+ function warnMissingIcon(name2) {
2705
+ if (warnedMissing.has(name2)) return;
2706
+ warnedMissing.add(name2);
2707
+ console.warn(`[zx.icon] Unknown icon "${name2}" \u2014 rendered an empty placeholder. Add it with registerIcons(), or render Font Awesome with useFontAwesome().`);
2708
+ }
2199
2709
  function faIcon(classes, options) {
2200
2710
  const { size = 16 } = options;
2201
2711
  const element = document.createElement("i");
@@ -2239,15 +2749,15 @@ var TimePicker = class extends Component {
2239
2749
  const content = h("div", {
2240
2750
  class: "zx-time-picker__segments",
2241
2751
  role: "group",
2242
- ariaLabel: "Time"
2752
+ ariaLabel: this._message("timePicker.label", "Time")
2243
2753
  });
2244
2754
  this._content = content;
2245
- this._addSpin(content, "h", "Hour", 23);
2755
+ this._addSpin(content, "h", this._message("timePicker.hour", "Hour"), 23);
2246
2756
  content.append(h("span", { class: "zx-time-picker__separator", ariaHidden: "true" }, ":"));
2247
- this._addSpin(content, "m", "Minute", 59);
2757
+ this._addSpin(content, "m", this._message("timePicker.minute", "Minute"), 59);
2248
2758
  if (this.options.seconds) {
2249
2759
  content.append(h("span", { class: "zx-time-picker__separator", ariaHidden: "true" }, ":"));
2250
- this._addSpin(content, "s", "Second", 59);
2760
+ this._addSpin(content, "s", this._message("timePicker.second", "Second"), 59);
2251
2761
  }
2252
2762
  root.append(content);
2253
2763
  if (this.options.clock) {
@@ -2256,7 +2766,7 @@ var TimePicker = class extends Component {
2256
2766
  ref: "clockToggle",
2257
2767
  class: "zx-icon-btn zx-time-picker__clock-toggle",
2258
2768
  type: "button",
2259
- ariaLabel: "Pick the time on a clock",
2769
+ ariaLabel: this._message("timePicker.clock", "Pick the time on a clock"),
2260
2770
  ariaExpanded: "false",
2261
2771
  ariaControls: clockId
2262
2772
  }, icon("clock", { size: 15 })));
@@ -2403,7 +2913,7 @@ var TimePicker = class extends Component {
2403
2913
  ref: "headHour",
2404
2914
  class: "zx-time-picker__clock-unit",
2405
2915
  type: "button",
2406
- ariaLabel: "Select the hour",
2916
+ ariaLabel: this._message("timePicker.selectHour", "Select the hour"),
2407
2917
  dataset: { unit: "h" }
2408
2918
  }),
2409
2919
  h("span", { class: "zx-time-picker__clock-colon", ariaHidden: "true" }, ":"),
@@ -2411,7 +2921,7 @@ var TimePicker = class extends Component {
2411
2921
  ref: "headMinute",
2412
2922
  class: "zx-time-picker__clock-unit",
2413
2923
  type: "button",
2414
- ariaLabel: "Select the minutes",
2924
+ ariaLabel: this._message("timePicker.selectMinutes", "Select the minutes"),
2415
2925
  dataset: { unit: "m" }
2416
2926
  })
2417
2927
  ),
@@ -2469,7 +2979,7 @@ var TimePicker = class extends Component {
2469
2979
  this.refs.headMinute.textContent = pad2(this._display.m);
2470
2980
  this.refs.headHour.setAttribute("aria-pressed", String(hours));
2471
2981
  this.refs.headMinute.setAttribute("aria-pressed", String(!hours));
2472
- this.refs.dial.setAttribute("aria-label", hours ? "Hour" : "Minutes");
2982
+ this.refs.dial.setAttribute("aria-label", hours ? this._message("timePicker.hour", "Hour") : this._message("timePicker.minutes", "Minutes"));
2473
2983
  if (!this._clockOpen) return;
2474
2984
  const selected = hours ? this._display.h : this._display.m;
2475
2985
  const marks = hours ? hourMarks() : minuteMarks();
@@ -2504,6 +3014,16 @@ var TimePicker = class extends Component {
2504
3014
  element.setAttribute("aria-checked", String(checked));
2505
3015
  element.tabIndex = checked ? 0 : -1;
2506
3016
  }
3017
+ /**
3018
+ * Resolves a message through the host translator, falling back to the built-in English text.
3019
+ * @param {string} key Message key.
3020
+ * @param {string} fallback English default.
3021
+ * @returns {string}
3022
+ */
3023
+ _message(key, fallback) {
3024
+ const message = this.msg(key);
3025
+ return message === key ? fallback : message;
3026
+ }
2507
3027
  };
2508
3028
  function hourMarks() {
2509
3029
  const marks = [];
@@ -2590,7 +3110,7 @@ var DatePicker = class extends Component {
2590
3110
  ref: "previous",
2591
3111
  class: "zx-date-picker__nav",
2592
3112
  type: "button",
2593
- ariaLabel: "Previous month"
3113
+ ariaLabel: this._message("datePicker.previousMonth", "Previous month")
2594
3114
  }, icon("chevron-left")),
2595
3115
  h("button", {
2596
3116
  ref: "heading",
@@ -2603,7 +3123,7 @@ var DatePicker = class extends Component {
2603
3123
  ref: "next",
2604
3124
  class: "zx-date-picker__nav",
2605
3125
  type: "button",
2606
- ariaLabel: "Next month"
3126
+ ariaLabel: this._message("datePicker.nextMonth", "Next month")
2607
3127
  }, icon("chevron-right"))
2608
3128
  ),
2609
3129
  h(
@@ -2620,14 +3140,14 @@ var DatePicker = class extends Component {
2620
3140
  ref: "quickPrevious",
2621
3141
  class: "zx-date-picker__nav",
2622
3142
  type: "button",
2623
- ariaLabel: "Previous year"
3143
+ ariaLabel: this._message("datePicker.previousYear", "Previous year")
2624
3144
  }, icon("chevron-left")),
2625
3145
  h("span", { ref: "quickYear", class: "zx-date-picker__quick-year", ariaLive: "polite" }),
2626
3146
  h("button", {
2627
3147
  ref: "quickNext",
2628
3148
  class: "zx-date-picker__nav",
2629
3149
  type: "button",
2630
- ariaLabel: "Next year"
3150
+ ariaLabel: this._message("datePicker.nextYear", "Next year")
2631
3151
  }, icon("chevron-right"))
2632
3152
  ),
2633
3153
  h("div", { ref: "quickGrid", class: "zx-date-picker__quick-grid", role: "grid" })
@@ -2638,7 +3158,7 @@ var DatePicker = class extends Component {
2638
3158
  ref: "grid",
2639
3159
  class: "zx-date-picker__grid",
2640
3160
  role: "grid",
2641
- ariaLabel: "Calendar"
3161
+ ariaLabel: this._message("datePicker.calendar", "Calendar")
2642
3162
  },
2643
3163
  h("thead", { ref: "weekdays" }),
2644
3164
  h("tbody", { ref: "days" })
@@ -2703,8 +3223,8 @@ var DatePicker = class extends Component {
2703
3223
  */
2704
3224
  focus() {
2705
3225
  this._closeQuickPick();
2706
- const button = this._buttonForDate(this._focused);
2707
- button?.focus();
3226
+ const button2 = this._buttonForDate(this._focused);
3227
+ button2?.focus();
2708
3228
  return this;
2709
3229
  }
2710
3230
  /** Cleans up an appended time picker and generated host content. @returns {void} */
@@ -2736,7 +3256,7 @@ var DatePicker = class extends Component {
2736
3256
  row.append(h("th", {
2737
3257
  class: "zx-date-picker__week-heading",
2738
3258
  scope: "col",
2739
- ariaLabel: "Week number"
3259
+ ariaLabel: this._message("datePicker.weekNumber", "Week number")
2740
3260
  }, "#"));
2741
3261
  }
2742
3262
  const sunday = new Date(2023, 0, 1);
@@ -2756,7 +3276,10 @@ var DatePicker = class extends Component {
2756
3276
  const offset = (first.getDay() - this._weekStart + 7) % 7;
2757
3277
  const start = addDays(first, -offset);
2758
3278
  this.refs.heading.textContent = formatDate(first, "%B %Y");
2759
- this.refs.heading.setAttribute("aria-label", `Choose month and year, ${formatDate(first, "%B %Y")}`);
3279
+ this.refs.heading.setAttribute(
3280
+ "aria-label",
3281
+ this._message("datePicker.chooseMonthYear", "Choose month and year, %1", formatDate(first, "%B %Y"))
3282
+ );
2760
3283
  const rows = [];
2761
3284
  for (let week = 0; week < 6; week += 1) {
2762
3285
  const rowStart = addDays(start, week * 7);
@@ -2765,14 +3288,14 @@ var DatePicker = class extends Component {
2765
3288
  row.append(h("th", {
2766
3289
  class: "zx-date-picker__week",
2767
3290
  scope: "row",
2768
- ariaLabel: `Week ${isoWeekNumber(rowStart)}`
3291
+ ariaLabel: this._message("datePicker.week", "Week %1", isoWeekNumber(rowStart))
2769
3292
  }, String(isoWeekNumber(rowStart))));
2770
3293
  }
2771
3294
  for (let column = 0; column < 7; column += 1) {
2772
3295
  const date = addDays(rowStart, column);
2773
3296
  const selected = Boolean(this._value && isSameDay(date, this._value));
2774
3297
  const unavailable = !this._inRange(date);
2775
- const button = h("button", {
3298
+ const button2 = h("button", {
2776
3299
  class: "zx-date-picker__day",
2777
3300
  type: "button",
2778
3301
  role: "gridcell",
@@ -2786,7 +3309,7 @@ var DatePicker = class extends Component {
2786
3309
  otherMonth: date.getMonth() === this._viewMonth ? null : "true"
2787
3310
  }
2788
3311
  }, formatDate(date, "%e"));
2789
- row.append(h("td", { role: "presentation" }, button));
3312
+ row.append(h("td", { role: "presentation" }, button2));
2790
3313
  }
2791
3314
  rows.push(row);
2792
3315
  }
@@ -2795,16 +3318,16 @@ var DatePicker = class extends Component {
2795
3318
  }
2796
3319
  /** @param {Event} event @returns {void} */
2797
3320
  _onDayClick(event) {
2798
- const button = event.target?.closest?.("[data-date]");
2799
- if (!button || !this.refs.days.contains(button)) return;
2800
- const date = parseDate(button.dataset.date, "%Y-%m-%d");
3321
+ const button2 = event.target?.closest?.("[data-date]");
3322
+ if (!button2 || !this.refs.days.contains(button2)) return;
3323
+ const date = parseDate(button2.dataset.date, "%Y-%m-%d");
2801
3324
  if (date && this._inRange(date)) this._selectDate(date);
2802
3325
  }
2803
3326
  /** @param {KeyboardEvent} event @returns {void} */
2804
3327
  _onDayKeydown(event) {
2805
- const button = event.target?.closest?.("[data-date]");
2806
- if (!button || !this.refs.days.contains(button)) return;
2807
- const current = parseDate(button.dataset.date, "%Y-%m-%d");
3328
+ const button2 = event.target?.closest?.("[data-date]");
3329
+ if (!button2 || !this.refs.days.contains(button2)) return;
3330
+ const current = parseDate(button2.dataset.date, "%Y-%m-%d");
2808
3331
  if (!current) return;
2809
3332
  let next = null;
2810
3333
  if (event.key === "ArrowLeft") next = addDays(current, -1);
@@ -2915,15 +3438,15 @@ var DatePicker = class extends Component {
2915
3438
  }
2916
3439
  /** @param {Event} event @returns {void} */
2917
3440
  _onQuickClick(event) {
2918
- const button = event.target?.closest?.("[data-month]");
2919
- if (!button || !this.refs.quickGrid.contains(button)) return;
2920
- this._chooseQuickMonth(Number(button.dataset.month));
3441
+ const button2 = event.target?.closest?.("[data-month]");
3442
+ if (!button2 || !this.refs.quickGrid.contains(button2)) return;
3443
+ this._chooseQuickMonth(Number(button2.dataset.month));
2921
3444
  }
2922
3445
  /** @param {KeyboardEvent} event @returns {void} */
2923
3446
  _onQuickKeydown(event) {
2924
- const button = event.target?.closest?.("[data-month]");
2925
- if (!button || !this.refs.quickGrid.contains(button)) return;
2926
- const month = Number(button.dataset.month);
3447
+ const button2 = event.target?.closest?.("[data-month]");
3448
+ if (!button2 || !this.refs.quickGrid.contains(button2)) return;
3449
+ const month = Number(button2.dataset.month);
2927
3450
  let next = null;
2928
3451
  if (event.key === "ArrowLeft") next = month - 1;
2929
3452
  else if (event.key === "ArrowRight") next = month + 1;
@@ -2970,6 +3493,17 @@ var DatePicker = class extends Component {
2970
3493
  _buttonForDate(date) {
2971
3494
  return this.refs.days.querySelector(`[data-date="${formatDate(date, "%Y-%m-%d")}"]`);
2972
3495
  }
3496
+ /**
3497
+ * Resolves a message through the host translator, falling back to the built-in English text.
3498
+ * @param {string} key Message key.
3499
+ * @param {string} fallback Built-in text, with `%1`-style placeholders.
3500
+ * @param {...unknown} args Interpolation values.
3501
+ * @returns {string}
3502
+ */
3503
+ _message(key, fallback, ...args) {
3504
+ const message = this.msg(key, ...args);
3505
+ return message === key ? printf(fallback, args) : message;
3506
+ }
2973
3507
  };
2974
3508
  function validDate(value) {
2975
3509
  return value instanceof Date && !Number.isNaN(value.getTime()) ? new Date(value.getTime()) : null;
@@ -3031,21 +3565,21 @@ var MonthPicker = class extends DatePicker {
3031
3565
  ref: "previousYear",
3032
3566
  class: "zx-month-picker__nav",
3033
3567
  type: "button",
3034
- ariaLabel: "Previous year"
3568
+ ariaLabel: this._message("monthPicker.previousYear", "Previous year")
3035
3569
  }, icon("chevron-left")),
3036
3570
  h("span", { ref: "year", class: "zx-month-picker__year", ariaLive: "polite" }),
3037
3571
  h("button", {
3038
3572
  ref: "nextYear",
3039
3573
  class: "zx-month-picker__nav",
3040
3574
  type: "button",
3041
- ariaLabel: "Next year"
3575
+ ariaLabel: this._message("monthPicker.nextYear", "Next year")
3042
3576
  }, icon("chevron-right"))
3043
3577
  ),
3044
3578
  h("div", {
3045
3579
  ref: "grid",
3046
3580
  class: "zx-month-picker__grid",
3047
3581
  role: "grid",
3048
- ariaLabel: "Choose month"
3582
+ ariaLabel: this._message("monthPicker.chooseMonth", "Choose month")
3049
3583
  })
3050
3584
  );
3051
3585
  this._content = content;
@@ -3124,15 +3658,15 @@ var MonthPicker = class extends DatePicker {
3124
3658
  }
3125
3659
  /** @param {Event} event @returns {void} */
3126
3660
  _onMonthClick(event) {
3127
- const button = event.target?.closest?.("[data-month]");
3128
- if (!button || !this.refs.grid.contains(button)) return;
3129
- this._selectMonth(Number(button.dataset.month));
3661
+ const button2 = event.target?.closest?.("[data-month]");
3662
+ if (!button2 || !this.refs.grid.contains(button2)) return;
3663
+ this._selectMonth(Number(button2.dataset.month));
3130
3664
  }
3131
3665
  /** @param {KeyboardEvent} event @returns {void} */
3132
3666
  _onMonthKeydown(event) {
3133
- const button = event.target?.closest?.("[data-month]");
3134
- if (!button || !this.refs.grid.contains(button)) return;
3135
- const current = Number(button.dataset.month);
3667
+ const button2 = event.target?.closest?.("[data-month]");
3668
+ if (!button2 || !this.refs.grid.contains(button2)) return;
3669
+ const current = Number(button2.dataset.month);
3136
3670
  let delta = null;
3137
3671
  if (event.key === "ArrowLeft") delta = -1;
3138
3672
  else if (event.key === "ArrowRight") delta = 1;
@@ -3201,7 +3735,7 @@ function registerDatePickerFieldAdapters() {
3201
3735
  let disabled = false;
3202
3736
  const syncDisabled = () => {
3203
3737
  picker.el.setAttribute("aria-disabled", String(disabled));
3204
- for (const button of picker.el.querySelectorAll("button")) button.disabled = disabled;
3738
+ for (const button2 of picker.el.querySelectorAll("button")) button2.disabled = disabled;
3205
3739
  };
3206
3740
  field.listen(picker.el, "change", (event) => event.stopImmediatePropagation());
3207
3741
  picker.on("change", (event) => field.emit("change", { value: event.detail.date }));
@@ -3399,9 +3933,9 @@ function hidePopover(element) {
3399
3933
  } catch {
3400
3934
  }
3401
3935
  }
3402
- function restoreAttribute(element, name, value) {
3403
- if (value === null) element.removeAttribute(name);
3404
- else element.setAttribute(name, value);
3936
+ function restoreAttribute(element, name2, value) {
3937
+ if (value === null) element.removeAttribute(name2);
3938
+ else element.setAttribute(name2, value);
3405
3939
  }
3406
3940
 
3407
3941
  // src/components/date-picker/date-range-picker.js
@@ -3784,12 +4318,12 @@ var DateRangePicker = class extends Component {
3784
4318
  /* ---------------------------------------------------------------------- events -- */
3785
4319
  /** @param {Event} event @returns {void} */
3786
4320
  _onPresetClick(event) {
3787
- const button = (
4321
+ const button2 = (
3788
4322
  /** @type {Element} */
3789
4323
  event.target?.closest?.("[data-preset]")
3790
4324
  );
3791
- if (!button || this._disabled) return;
3792
- const preset = this._presets.find((entry) => entry.name === button.dataset.preset);
4325
+ if (!button2 || this._disabled) return;
4326
+ const preset = this._presets.find((entry) => entry.name === button2.dataset.preset);
3793
4327
  if (!preset) return;
3794
4328
  const range = preset.range();
3795
4329
  this.set({ start: range?.start ?? null, end: range?.end ?? null }, { silent: true });
@@ -3892,15 +4426,15 @@ var DateRangePicker = class extends Component {
3892
4426
  const date = addDays(rowStart, column);
3893
4427
  const cell = h("td", { role: "presentation" });
3894
4428
  if (date.getMonth() === first.getMonth() && date.getFullYear() === first.getFullYear()) {
3895
- const button = h("button", {
4429
+ const button2 = h("button", {
3896
4430
  class: "zx-date-range-picker__day",
3897
4431
  type: "button",
3898
4432
  role: "gridcell",
3899
4433
  tabindex: -1,
3900
4434
  dataset: { date: formatDate(date, "%Y-%m-%d") }
3901
4435
  }, formatDate(date, "%e"));
3902
- cell.append(button);
3903
- this._dayCells.push({ date, cell, button });
4436
+ cell.append(button2);
4437
+ this._dayCells.push({ date, cell, button: button2 });
3904
4438
  }
3905
4439
  row.append(cell);
3906
4440
  }
@@ -3971,17 +4505,17 @@ var DateRangePicker = class extends Component {
3971
4505
  }
3972
4506
  if (!roving) roving = this._dayCells[0] ?? null;
3973
4507
  for (const entry of this._dayCells) {
3974
- const { date, cell, button } = entry;
4508
+ const { date, cell, button: button2 } = entry;
3975
4509
  const state = this._dayState(date);
3976
4510
  setDataset(cell, "range", state.range);
3977
4511
  setDataset(cell, "rangeEdge", state.edge);
3978
- setDataset(button, "range", state.range);
3979
- button.tabIndex = entry === roving ? 0 : -1;
3980
- button.setAttribute("aria-selected", String(state.range !== null && state.range !== "preview"));
3981
- button.setAttribute("aria-disabled", String(!this._selectable(date)));
3982
- button.setAttribute("aria-label", this._dayLabel(date, state.range));
3983
- if (isSameDay(date, today2)) button.setAttribute("aria-current", "date");
3984
- else button.removeAttribute("aria-current");
4512
+ setDataset(button2, "range", state.range);
4513
+ button2.tabIndex = entry === roving ? 0 : -1;
4514
+ button2.setAttribute("aria-selected", String(state.range !== null && state.range !== "preview"));
4515
+ button2.setAttribute("aria-disabled", String(!this._selectable(date)));
4516
+ button2.setAttribute("aria-label", this._dayLabel(date, state.range));
4517
+ if (isSameDay(date, today2)) button2.setAttribute("aria-current", "date");
4518
+ else button2.removeAttribute("aria-current");
3985
4519
  }
3986
4520
  }
3987
4521
  /**
@@ -4032,7 +4566,7 @@ var DateRangePicker = class extends Component {
4032
4566
  /** @returns {void} */
4033
4567
  _syncDisabled() {
4034
4568
  this.el.setAttribute("aria-disabled", String(this._disabled));
4035
- for (const button of this.el.querySelectorAll("button")) button.disabled = this._disabled;
4569
+ for (const button2 of this.el.querySelectorAll("button")) button2.disabled = this._disabled;
4036
4570
  }
4037
4571
  /** Writes the current selection into the visually hidden live region. @returns {void} */
4038
4572
  _announce() {
@@ -4055,9 +4589,9 @@ var DateRangePicker = class extends Component {
4055
4589
  }
4056
4590
  /** @param {Element|null} target @returns {{date: Date, cell: Element, button: Element}|null} */
4057
4591
  _cellFor(target) {
4058
- const button = target?.closest?.("[data-date]");
4059
- if (!button) return null;
4060
- return this._dayCells.find((cell) => cell.button === button) ?? null;
4592
+ const button2 = target?.closest?.("[data-date]");
4593
+ if (!button2) return null;
4594
+ return this._dayCells.find((cell) => cell.button === button2) ?? null;
4061
4595
  }
4062
4596
  /** @param {Date} date @returns {HTMLButtonElement|null} */
4063
4597
  _buttonForDate(date) {
@@ -4195,17 +4729,17 @@ function isoWeekNumber2(date) {
4195
4729
  const yearStart = new Date(Date.UTC(utc.getUTCFullYear(), 0, 1));
4196
4730
  return Math.ceil(((utc.getTime() - yearStart.getTime()) / DAY_MS + 1) / 7);
4197
4731
  }
4198
- function setDataset(element, name, value) {
4199
- if (value === null || value === void 0) delete element.dataset[name];
4200
- else element.dataset[name] = value;
4732
+ function setDataset(element, name2, value) {
4733
+ if (value === null || value === void 0) delete element.dataset[name2];
4734
+ else element.dataset[name2] = value;
4201
4735
  }
4202
4736
  function rememberAttributes2(element, names) {
4203
- return new Map(names.map((name) => [name, element.getAttribute(name)]));
4737
+ return new Map(names.map((name2) => [name2, element.getAttribute(name2)]));
4204
4738
  }
4205
4739
  function restoreAttributes2(element, attributes) {
4206
- for (const [name, value] of attributes) {
4207
- if (value === null) element.removeAttribute(name);
4208
- else element.setAttribute(name, value);
4740
+ for (const [name2, value] of attributes) {
4741
+ if (value === null) element.removeAttribute(name2);
4742
+ else element.setAttribute(name2, value);
4209
4743
  }
4210
4744
  }
4211
4745
 
@@ -4274,6 +4808,8 @@ var DateRangeBox = class extends Component {
4274
4808
  this.el = root;
4275
4809
  this._originalState = root.getAttribute("data-state");
4276
4810
  this._originalAriaDisabled = root.getAttribute("aria-disabled");
4811
+ this._originalClearable = root.getAttribute("data-clearable");
4812
+ root.toggleAttribute("data-clearable", Boolean(this.options.clearable));
4277
4813
  this._format = String(this.options.format || "%d.%m.%Y");
4278
4814
  this._separator = String(this.options.separator ?? " \u2013 ");
4279
4815
  this._disabled = Boolean(this.options.disabled);
@@ -4466,6 +5002,7 @@ var DateRangeBox = class extends Component {
4466
5002
  this._content?.remove();
4467
5003
  restoreAttribute2(this.el, "data-state", this._originalState);
4468
5004
  restoreAttribute2(this.el, "aria-disabled", this._originalAriaDisabled);
5005
+ restoreAttribute2(this.el, "data-clearable", this._originalClearable);
4469
5006
  super.destroy();
4470
5007
  }
4471
5008
  /* --------------------------------------------------------------------- internals -- */
@@ -4562,7 +5099,11 @@ var DateRangeBox = class extends Component {
4562
5099
  /** @returns {void} */
4563
5100
  _syncDisplay() {
4564
5101
  this.refs.input.value = formatRangeText(this._range, this._format, this._separator);
4565
- this.refs.clear.hidden = !this.options.clearable || !(this._range.start || this._range.end);
5102
+ this._syncClear();
5103
+ }
5104
+ /** A disabled box offers no clear button: there is nothing it could do. @returns {void} */
5105
+ _syncClear() {
5106
+ this.refs.clear.hidden = !this.options.clearable || !(this._range.start || this._range.end) || this._disabled;
4566
5107
  }
4567
5108
  /** @returns {void} */
4568
5109
  _syncDisabled() {
@@ -4570,6 +5111,7 @@ var DateRangeBox = class extends Component {
4570
5111
  this.refs.toggle.disabled = this._disabled;
4571
5112
  this.refs.clear.disabled = this._disabled;
4572
5113
  this.el.setAttribute("aria-disabled", String(this._disabled));
5114
+ this._syncClear();
4573
5115
  if (this._disabled) this._picker?.disable();
4574
5116
  else this._picker?.enable();
4575
5117
  }
@@ -4579,7 +5121,7 @@ var DateRangeBox = class extends Component {
4579
5121
  this.refs.input.removeAttribute("aria-invalid");
4580
5122
  }
4581
5123
  /** @param {boolean} returnFocus @returns {void} */
4582
- _close(returnFocus) {
5124
+ _close(returnFocus2) {
4583
5125
  if (!this._open) return;
4584
5126
  this._position?.destroy();
4585
5127
  this._position = null;
@@ -4587,7 +5129,7 @@ var DateRangeBox = class extends Component {
4587
5129
  this.refs.popover.dataset.state = "closed";
4588
5130
  this.refs.input.setAttribute("aria-expanded", "false");
4589
5131
  this.refs.toggle.setAttribute("aria-expanded", "false");
4590
- if (returnFocus) this.focus();
5132
+ if (returnFocus2) this.focus();
4591
5133
  this.emit("close");
4592
5134
  }
4593
5135
  };
@@ -4595,9 +5137,9 @@ function text2(key, fallback, args) {
4595
5137
  const translated = translate(key);
4596
5138
  return printf(translated === key ? fallback : translated, args);
4597
5139
  }
4598
- function restoreAttribute2(element, name, value) {
4599
- if (value === null) element.removeAttribute(name);
4600
- else element.setAttribute(name, value);
5140
+ function restoreAttribute2(element, name2, value) {
5141
+ if (value === null) element.removeAttribute(name2);
5142
+ else element.setAttribute(name2, value);
4601
5143
  }
4602
5144
 
4603
5145
  // src/components/date-picker/range-field-adapter.js
@@ -4646,6 +5188,8 @@ var Datebox = class extends Component {
4646
5188
  this.el = root;
4647
5189
  this._originalState = root.getAttribute("data-state");
4648
5190
  this._originalAriaDisabled = root.getAttribute("aria-disabled");
5191
+ this._originalClearable = root.getAttribute("data-clearable");
5192
+ root.toggleAttribute("data-clearable", Boolean(this.options.clearable));
4649
5193
  this._format = effectiveFormat(this.options.format, this.options.time);
4650
5194
  this._disabled = Boolean(this.options.disabled);
4651
5195
  this._open = false;
@@ -4815,6 +5359,8 @@ var Datebox = class extends Component {
4815
5359
  else this.el.setAttribute("data-state", this._originalState);
4816
5360
  if (this._originalAriaDisabled === null) this.el.removeAttribute("aria-disabled");
4817
5361
  else this.el.setAttribute("aria-disabled", this._originalAriaDisabled);
5362
+ if (this._originalClearable === null) this.el.removeAttribute("data-clearable");
5363
+ else this.el.setAttribute("data-clearable", this._originalClearable);
4818
5364
  super.destroy();
4819
5365
  }
4820
5366
  /** @param {KeyboardEvent} event @returns {void} */
@@ -4859,7 +5405,11 @@ var Datebox = class extends Component {
4859
5405
  /** @returns {void} */
4860
5406
  _syncDisplay() {
4861
5407
  this.refs.input.value = this._value ? formatDate(this._value, this._format) : "";
4862
- this.refs.clear.hidden = !this.options.clearable || !this._value;
5408
+ this._syncClear();
5409
+ }
5410
+ /** A disabled box offers no clear button: there is nothing it could do. @returns {void} */
5411
+ _syncClear() {
5412
+ this.refs.clear.hidden = !this.options.clearable || !this._value || this._disabled;
4863
5413
  }
4864
5414
  /** @returns {void} */
4865
5415
  _syncDisabled() {
@@ -4867,6 +5417,7 @@ var Datebox = class extends Component {
4867
5417
  this.refs.toggle.disabled = this._disabled;
4868
5418
  this.refs.clear.disabled = this._disabled;
4869
5419
  this.el.setAttribute("aria-disabled", String(this._disabled));
5420
+ this._syncClear();
4870
5421
  }
4871
5422
  /** @returns {void} */
4872
5423
  _setValid() {
@@ -4877,7 +5428,7 @@ var Datebox = class extends Component {
4877
5428
  this.refs.input.removeAttribute("aria-invalid");
4878
5429
  }
4879
5430
  /** @param {boolean} returnFocus @returns {void} */
4880
- _close(returnFocus) {
5431
+ _close(returnFocus2) {
4881
5432
  if (!this._open) return;
4882
5433
  this._position?.destroy();
4883
5434
  this._position = null;
@@ -4885,7 +5436,7 @@ var Datebox = class extends Component {
4885
5436
  this.refs.popover.dataset.state = "closed";
4886
5437
  this.refs.input.setAttribute("aria-expanded", "false");
4887
5438
  this.refs.toggle.setAttribute("aria-expanded", "false");
4888
- if (returnFocus) this.focus();
5439
+ if (returnFocus2) this.focus();
4889
5440
  this.emit("close");
4890
5441
  }
4891
5442
  /** @returns {Date|null} */
@@ -5187,7 +5738,7 @@ var FieldUpload = class extends Component {
5187
5738
  const xhr = new XMLHttpRequest();
5188
5739
  this._xhr = xhr;
5189
5740
  xhr.open("POST", String(this.options.url));
5190
- for (const [name, value] of Object.entries(this.options.headers ?? {})) xhr.setRequestHeader(name, String(value));
5741
+ for (const [name2, value] of Object.entries(this.options.headers ?? {})) xhr.setRequestHeader(name2, String(value));
5191
5742
  this.listen(xhr.upload, "progress", (event) => {
5192
5743
  if (event.lengthComputable) this._setProgress(event.loaded / event.total * 100);
5193
5744
  });
@@ -5286,18 +5837,18 @@ function matchesAccept(file, accept) {
5286
5837
  function createFormData(files, paramName, params) {
5287
5838
  const form = new FormData();
5288
5839
  for (const file of files) form.append(paramName, file);
5289
- for (const [name, value] of Object.entries(params ?? {})) appendFormValue(form, name, value);
5840
+ for (const [name2, value] of Object.entries(params ?? {})) appendFormValue(form, name2, value);
5290
5841
  return form;
5291
5842
  }
5292
- function appendFormValue(form, name, value) {
5843
+ function appendFormValue(form, name2, value) {
5293
5844
  if (value == null) return;
5294
5845
  if (Array.isArray(value)) {
5295
- for (const item of value) appendFormValue(form, name, item);
5846
+ for (const item of value) appendFormValue(form, name2, item);
5296
5847
  return;
5297
5848
  }
5298
5849
  const isBlob = typeof Blob === "function" && value instanceof Blob;
5299
- if (isBlob) form.append(name, value);
5300
- else form.append(name, typeof value === "object" ? JSON.stringify(value) : String(value));
5850
+ if (isBlob) form.append(name2, value);
5851
+ else form.append(name2, typeof value === "object" ? JSON.stringify(value) : String(value));
5301
5852
  }
5302
5853
  function parseResponse(text3) {
5303
5854
  if (!text3) return null;
@@ -5478,11 +6029,11 @@ var MultiValueEditor = class extends Component {
5478
6029
  }
5479
6030
  /** @param {MouseEvent} event @returns {void} */
5480
6031
  _handleClick(event) {
5481
- const button = event.target instanceof Element ? event.target.closest("[data-action]") : null;
5482
- if (!button || !this.el.contains(button)) return;
5483
- if (button.getAttribute("aria-disabled") === "true") return;
5484
- const action = button.dataset.action;
5485
- const index = Number(button.dataset.index);
6032
+ const button2 = event.target instanceof Element ? event.target.closest("[data-action]") : null;
6033
+ if (!button2 || !this.el.contains(button2)) return;
6034
+ if (button2.getAttribute("aria-disabled") === "true") return;
6035
+ const action = button2.dataset.action;
6036
+ const index = Number(button2.dataset.index);
5486
6037
  if (action === "add") {
5487
6038
  const choices = normalizeOptions2(this.options.options);
5488
6039
  this._values.push(this.options.options === null ? "" : choices[0]?.[0] ?? "");
@@ -5969,6 +6520,7 @@ var NumberField = class extends Component {
5969
6520
  /** @param {KeyboardEvent} event @returns {void} */
5970
6521
  _onKeydown(event) {
5971
6522
  if (this._disabled || this._readonly) return;
6523
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
5972
6524
  const large = Math.max(1, Math.trunc(Number(this.options.largeStep) || 1));
5973
6525
  const actions = {
5974
6526
  ArrowUp: () => this._stepBy(1),
@@ -6122,6 +6674,7 @@ function registerNumberFieldAdapter() {
6122
6674
  label: options.label ? String(options.label) : void 0,
6123
6675
  placeholder: options.placeholder,
6124
6676
  required: options.required,
6677
+ name: field.name || null,
6125
6678
  ...options.props ?? {}
6126
6679
  }));
6127
6680
  let setting = false;
@@ -6316,7 +6869,7 @@ var Rating = class extends Component {
6316
6869
  * @returns {HTMLButtonElement}
6317
6870
  */
6318
6871
  _radio(value, part) {
6319
- const button = (
6872
+ const button2 = (
6320
6873
  /** @type {HTMLButtonElement} */
6321
6874
  h("button", {
6322
6875
  class: "zx-rating__radio",
@@ -6328,18 +6881,18 @@ var Rating = class extends Component {
6328
6881
  dataset: { value: String(value), part }
6329
6882
  })
6330
6883
  );
6331
- this.listen(button, "click", (event) => {
6884
+ this.listen(button2, "click", (event) => {
6332
6885
  this._choose(
6333
6886
  value,
6334
6887
  /** @type {MouseEvent} */
6335
6888
  event.detail > 0
6336
6889
  );
6337
6890
  });
6338
- this.listen(button, "pointerenter", () => this._setPreview(value));
6339
- this.listen(button, "focus", () => this._setPreview(value));
6340
- this.listen(button, "blur", () => this._setPreview(null));
6341
- this._radios.push(button);
6342
- return button;
6891
+ this.listen(button2, "pointerenter", () => this._setPreview(value));
6892
+ this.listen(button2, "focus", () => this._setPreview(value));
6893
+ this.listen(button2, "blur", () => this._setPreview(null));
6894
+ this._radios.push(button2);
6895
+ return button2;
6343
6896
  }
6344
6897
  /**
6345
6898
  * @param {number} value Step value.
@@ -6556,15 +7109,36 @@ function itemText(item) {
6556
7109
  return String(item ?? "").trim().toLocaleLowerCase();
6557
7110
  }
6558
7111
 
6559
- // src/components/select/select.js
6560
- var PRIORITY_KEYS = [
6561
- "priority.lowest",
6562
- "priority.low",
6563
- "priority.normal",
6564
- "priority.high",
6565
- "priority.highest"
6566
- ];
6567
- var PRIORITY_FALLBACKS = ["Lowest", "Low", "Medium", "High", "Highest"];
7112
+ // src/components/select/color.js
7113
+ var TOKEN_REFERENCE = /^var\(\s*--[\w-]+\s*\)$/;
7114
+ function itemColor(value) {
7115
+ if (typeof value !== "string") return null;
7116
+ const text3 = value.trim();
7117
+ if (TOKEN_REFERENCE.test(text3)) return text3;
7118
+ return isCssColor(text3) ? text3 : null;
7119
+ }
7120
+
7121
+ // src/components/select/glyphs.js
7122
+ var STATUS_INDICATORS = Object.freeze([
7123
+ "dotted",
7124
+ "ring",
7125
+ "progress",
7126
+ "review",
7127
+ "ready",
7128
+ "waiting",
7129
+ "done",
7130
+ "canceled",
7131
+ "duplicate"
7132
+ ]);
7133
+ var STATUS_TONES = Object.freeze([
7134
+ "muted",
7135
+ "neutral",
7136
+ "warning",
7137
+ "info",
7138
+ "accent",
7139
+ "success",
7140
+ "danger"
7141
+ ]);
6568
7142
  var DEFAULT_STATUSES = Object.freeze([
6569
7143
  ["backlog", "status.backlog", "Backlog", "dotted", "muted"],
6570
7144
  ["todo", "status.todo", "Todo", "ring", "neutral"],
@@ -6576,6 +7150,79 @@ var DEFAULT_STATUSES = Object.freeze([
6576
7150
  ["canceled", "status.canceled", "Canceled", "canceled", "muted"],
6577
7151
  ["duplicate", "status.duplicate", "Duplicate", "duplicate", "muted"]
6578
7152
  ]);
7153
+ var STATUS_ALIASES = Object.freeze({ review: "in-review", cancelled: "canceled" });
7154
+ var PRIORITY_URGENT = 4;
7155
+ function resolveStatusGlyph(status, options = {}) {
7156
+ const item = status && typeof status === "object" ? status : null;
7157
+ const id = normalizeStatusId(item ? item.ID ?? item.id : status);
7158
+ const preset = DEFAULT_STATUSES.find(([key]) => key === id);
7159
+ const indicator = String(options.indicator ?? item?.indicator ?? preset?.[3] ?? "ring");
7160
+ const tone = String(options.tone ?? item?.tone ?? preset?.[4] ?? "muted");
7161
+ return {
7162
+ indicator: (
7163
+ /** @type {StatusIndicator} */
7164
+ STATUS_INDICATORS.includes(indicator) ? indicator : "ring"
7165
+ ),
7166
+ tone: (
7167
+ /** @type {StatusTone} */
7168
+ STATUS_TONES.includes(tone) ? tone : "muted"
7169
+ )
7170
+ };
7171
+ }
7172
+ function normalizeStatusId(id) {
7173
+ const key = String(id ?? "").trim().toLowerCase();
7174
+ return STATUS_ALIASES[key] ?? key;
7175
+ }
7176
+ function normalizePriorityLevel(level) {
7177
+ if (level === null || level === void 0 || level === "") return null;
7178
+ return Math.max(0, Math.min(PRIORITY_URGENT, Math.trunc(Number(level) || 0)));
7179
+ }
7180
+ function statusIcon(status, options = {}) {
7181
+ const { indicator, tone } = resolveStatusGlyph(status, options ?? {});
7182
+ const marker = (
7183
+ /** @type {HTMLSpanElement} */
7184
+ h("span", {
7185
+ class: "zx-status-icon",
7186
+ dataset: { indicator, tone }
7187
+ })
7188
+ );
7189
+ if (indicator === "done") marker.append(icon("check", { size: 8 }));
7190
+ if (indicator === "canceled") marker.append(icon("x", { size: 7 }));
7191
+ return name(marker, options?.label);
7192
+ }
7193
+ function priorityIcon(level, options = {}) {
7194
+ const normalized = normalizePriorityLevel(level);
7195
+ const glyph = (
7196
+ /** @type {HTMLSpanElement} */
7197
+ h("span", {
7198
+ class: "zx-priority-icon",
7199
+ dataset: { level: normalized === null ? "none" : normalized }
7200
+ }, normalized === PRIORITY_URGENT ? null : Array.from({ length: PRIORITY_URGENT }, (_, index) => h("span", {
7201
+ dataset: { filled: String(normalized !== null && index <= normalized) }
7202
+ })))
7203
+ );
7204
+ return name(glyph, options?.label);
7205
+ }
7206
+ function name(glyph, label) {
7207
+ const text3 = label == null ? "" : String(label).trim();
7208
+ if (text3) {
7209
+ glyph.setAttribute("role", "img");
7210
+ glyph.setAttribute("aria-label", text3);
7211
+ } else {
7212
+ glyph.setAttribute("aria-hidden", "true");
7213
+ }
7214
+ return glyph;
7215
+ }
7216
+
7217
+ // src/components/select/select.js
7218
+ var PRIORITY_KEYS = [
7219
+ "priority.lowest",
7220
+ "priority.low",
7221
+ "priority.normal",
7222
+ "priority.high",
7223
+ "priority.highest"
7224
+ ];
7225
+ var PRIORITY_FALLBACKS = ["Lowest", "Low", "Medium", "High", "Highest"];
6579
7226
  var Select = class _Select extends Component {
6580
7227
  static cssName = "select";
6581
7228
  /** @type {SelectOptions} */
@@ -6594,6 +7241,7 @@ var Select = class _Select extends Component {
6594
7241
  value: null,
6595
7242
  disabled: false,
6596
7243
  placeholder: "",
7244
+ label: "",
6597
7245
  clearable: false,
6598
7246
  filter: false,
6599
7247
  searchKeys: null,
@@ -6621,6 +7269,7 @@ var Select = class _Select extends Component {
6621
7269
  this._createdRoot = false;
6622
7270
  this._root = null;
6623
7271
  this._snapshot = null;
7272
+ this._pointerModality = false;
6624
7273
  this._createdRoot = this.el === null;
6625
7274
  const root = (
6626
7275
  /** @type {HTMLElement} */
@@ -6635,6 +7284,7 @@ var Select = class _Select extends Component {
6635
7284
  class: "zx-select__input",
6636
7285
  type: "text",
6637
7286
  role: "combobox",
7287
+ ariaLabel: String(this.options.label || this.options.placeholder || "Select"),
6638
7288
  autocomplete: "off",
6639
7289
  spellcheck: false,
6640
7290
  ariaExpanded: "false",
@@ -6708,10 +7358,29 @@ var Select = class _Select extends Component {
6708
7358
  );
6709
7359
  this.listen(input, "click", () => this.open());
6710
7360
  this.listen(input, "input", () => this._onInput());
6711
- this.listen(input, "keydown", (event) => this._onKeydown(
6712
- /** @type {KeyboardEvent} */
6713
- event
6714
- ));
7361
+ this.listen(input, "keydown", (event) => {
7362
+ if (!isModifierKey(
7363
+ /** @type {KeyboardEvent} */
7364
+ event
7365
+ )) root.removeAttribute("data-focus-origin");
7366
+ this._onKeydown(
7367
+ /** @type {KeyboardEvent} */
7368
+ event
7369
+ );
7370
+ });
7371
+ this.listen(input, "focus", () => {
7372
+ if (this._pointerModality && /** @type {HTMLInputElement} */
7373
+ input.readOnly) {
7374
+ root.setAttribute("data-focus-origin", "pointer");
7375
+ } else root.removeAttribute("data-focus-origin");
7376
+ });
7377
+ this.listen(input, "blur", () => root.removeAttribute("data-focus-origin"));
7378
+ this.listen(document, "keydown", (event) => {
7379
+ if (!isModifierKey(
7380
+ /** @type {KeyboardEvent} */
7381
+ event
7382
+ )) this._pointerModality = false;
7383
+ }, { capture: true });
6715
7384
  this.listen(toggle, "click", () => {
6716
7385
  this.focus();
6717
7386
  if (this._open) this.close();
@@ -6724,6 +7393,7 @@ var Select = class _Select extends Component {
6724
7393
  this.listen(panel, "pointerdown", (event) => event.preventDefault());
6725
7394
  this.listen(panel, "click", (event) => this._onOptionClick(event));
6726
7395
  this.listen(document, "pointerdown", (event) => {
7396
+ this._pointerModality = true;
6727
7397
  if (this._open && !root.contains(
6728
7398
  /** @type {Node} */
6729
7399
  event.target
@@ -6874,29 +7544,43 @@ var Select = class _Select extends Component {
6874
7544
  return this;
6875
7545
  }
6876
7546
  /**
6877
- * Creates the five-level priority preset.
7547
+ * Creates the five-level priority preset: values 0 (lowest) to 4 (highest), each drawn as grey
7548
+ * signal bars filled to its level, except the highest, which is the one coloured level — a
7549
+ * danger square with an exclamation mark. A "No priority" row (value null, a row of dashes)
7550
+ * heads the list and labels the empty control; `clearable: false` removes it.
6878
7551
  * @param {Element|string|null} target Component target.
6879
- * @param {SelectOptions} [options={}] Select overrides other than fixed items and renderer.
7552
+ * @param {SelectOptions} [options={}] Select overrides other than items and renderers.
7553
+ * `clearable` defaults to true here; `noneLabel` and `placeholder` default to the
7554
+ * `priority.none` message ("No priority").
6880
7555
  * @returns {Select}
6881
7556
  */
6882
7557
  static priority(target, options = {}) {
6883
7558
  const requestedValue = options.value ?? null;
6884
- const select = new _Select(target, {
7559
+ const clearable = options.clearable ?? true;
7560
+ let select = null;
7561
+ const noneLabel = () => options.noneLabel ?? (select ? select._message("priority.none", "No priority") : "No priority");
7562
+ select = new _Select(target, {
6885
7563
  ...options,
7564
+ clearable,
6886
7565
  value: null,
6887
7566
  items: [],
6888
7567
  renderItem(item) {
6889
7568
  return priorityContent(item, true);
6890
7569
  },
7570
+ renderNone: options.renderNone ?? (() => priorityContent({ level: null, name: noneLabel() }, true)),
6891
7571
  renderValueAdornment(item) {
6892
7572
  return priorityIndicator(item.level);
6893
7573
  }
6894
7574
  });
7575
+ if (clearable && !options.placeholder) {
7576
+ select.refs.input.placeholder = noneLabel();
7577
+ }
7578
+ const preset = select;
6895
7579
  select.setItems(PRIORITY_KEYS.map((key, level) => {
6896
- let name = level === 2 ? select.msg("priority.medium") : key;
6897
- if (name === "priority.medium") name = select._message(key, PRIORITY_FALLBACKS[level]);
6898
- else if (level !== 2) name = select._message(key, PRIORITY_FALLBACKS[level]);
6899
- return { ID: level, name, level };
7580
+ let name2 = level === 2 ? preset.msg("priority.medium") : key;
7581
+ if (name2 === "priority.medium") name2 = preset._message(key, PRIORITY_FALLBACKS[level]);
7582
+ else if (level !== 2) name2 = preset._message(key, PRIORITY_FALLBACKS[level]);
7583
+ return { ID: level, name: name2, level };
6900
7584
  }));
6901
7585
  select.set(requestedValue, { silent: true });
6902
7586
  return select;
@@ -6957,10 +7641,10 @@ var Select = class _Select extends Component {
6957
7641
  const value = readSelectItem(item, iconKey);
6958
7642
  if (value && typeof value === "object" && typeof value.nodeType === "number") return value.cloneNode(true);
6959
7643
  if (typeof value !== "string" || !value) return null;
6960
- const color = readSelectItem(item, colorKey);
7644
+ const color = itemColor(readSelectItem(item, colorKey));
6961
7645
  return h("span", {
6962
7646
  class: "zx-select__entity-glyph",
6963
- style: isCssColor(color) ? { "--zx-select-entity-color": String(color) } : null,
7647
+ style: color ? { "--zx-select-entity-color": color } : null,
6964
7648
  ariaHidden: "true"
6965
7649
  }, icon(value, { size: 14 }));
6966
7650
  };
@@ -7492,24 +8176,20 @@ function priorityContent(item, label) {
7492
8176
  );
7493
8177
  }
7494
8178
  function priorityIndicator(level) {
7495
- const normalized = Math.max(0, Math.min(4, Math.trunc(Number(level) || 0)));
7496
- return h("span", {
7497
- class: "zx-select__priority-icon",
7498
- dataset: { level: normalized },
7499
- ariaHidden: "true"
7500
- }, Array.from({ length: 5 }, (_, index) => h("span", {
7501
- dataset: { filled: String(index <= normalized) }
7502
- })));
8179
+ const glyph = priorityIcon(
8180
+ /** @type {number|null} */
8181
+ normalizePriorityLevel(level)
8182
+ );
8183
+ glyph.classList.add("zx-select__priority-icon");
8184
+ return glyph;
7503
8185
  }
7504
8186
  function normalizeStatusItem(input) {
7505
8187
  if (!input || typeof input !== "object" || input.ID == null || input.name == null) return null;
7506
- const indicators = ["dotted", "ring", "progress", "review", "ready", "waiting", "done", "canceled", "duplicate"];
7507
- const tones = ["muted", "neutral", "warning", "info", "accent", "success", "danger"];
7508
8188
  return {
7509
8189
  ...input,
7510
8190
  name: String(input.name),
7511
- indicator: indicators.includes(String(input.indicator)) ? String(input.indicator) : "ring",
7512
- tone: tones.includes(String(input.tone)) ? String(input.tone) : "muted",
8191
+ indicator: STATUS_INDICATORS.includes(String(input.indicator)) ? String(input.indicator) : "ring",
8192
+ tone: STATUS_TONES.includes(String(input.tone)) ? String(input.tone) : "muted",
7513
8193
  description: input.description == null ? "" : String(input.description),
7514
8194
  shortcut: input.shortcut == null ? "" : String(input.shortcut)
7515
8195
  };
@@ -7529,21 +8209,18 @@ function statusContent(item) {
7529
8209
  );
7530
8210
  }
7531
8211
  function statusIndicator(item) {
7532
- const indicator = String(item.indicator ?? "ring");
7533
- const marker = h("span", {
7534
- class: "zx-select__status-indicator",
7535
- dataset: { indicator, tone: String(item.tone ?? "muted") },
7536
- ariaHidden: "true"
7537
- });
7538
- if (indicator === "done") marker.append(icon("check", { size: 8 }));
7539
- if (indicator === "canceled") marker.append(icon("x", { size: 7 }));
7540
- return marker;
8212
+ const glyph = statusIcon({ indicator: item.indicator ?? "ring", tone: item.tone ?? "muted" });
8213
+ glyph.classList.add("zx-select__status-indicator");
8214
+ return glyph;
7541
8215
  }
7542
8216
  function readItem(item, reader) {
7543
8217
  if (typeof reader === "function") return reader(item);
7544
8218
  if (item === null || typeof item !== "object") return item;
7545
8219
  return item[reader];
7546
8220
  }
8221
+ function isModifierKey(event) {
8222
+ return ["Shift", "Control", "Alt", "Meta", "CapsLock"].includes(event.key) || event.ctrlKey || event.metaKey;
8223
+ }
7547
8224
  function isPrintable(event) {
7548
8225
  return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
7549
8226
  }
@@ -7553,8 +8230,12 @@ function registerSelectFieldAdapter() {
7553
8230
  Field.register("zxselect", (field, options) => {
7554
8231
  const props = options.props ?? {};
7555
8232
  const preset = props.preset;
7556
- const selectOptions = { ...props, value: options.value };
8233
+ const selectOptions = { ...props, value: options.value, label: options.label || props.label, placeholder: props.placeholder ?? options.placeholder };
8234
+ field.listen(field.el, "click", (event) => {
8235
+ if (event.target.closest?.(".zx-field__label")) field.adapter.focus();
8236
+ });
7557
8237
  delete selectOptions.preset;
8238
+ if (preset === "priority" && props.clearable === void 0) selectOptions.clearable = !options.required;
7558
8239
  const select = field.own(preset === "priority" ? Select.priority(null, selectOptions) : preset === "status" ? Select.status(null, selectOptions) : new Select(null, selectOptions));
7559
8240
  field.listen(select.el, "change", (event) => event.stopImmediatePropagation());
7560
8241
  select.on("change", (event) => field.emit("change", { value: event.detail.value }));
@@ -7804,7 +8485,7 @@ var Slider = class extends Component {
7804
8485
  const label = typeof mark === "number" ? this._format(value) : String(mark.label ?? this._format(value));
7805
8486
  element.append(h("span", {
7806
8487
  class: "zx-slider__mark",
7807
- style: { "--zx-slider-mark": `${this._ratio(value) * 100}%` }
8488
+ style: { "--zx-slider-mark": String(this._ratio(value)) }
7808
8489
  }, label));
7809
8490
  }
7810
8491
  return element;
@@ -7930,6 +8611,29 @@ function registerSliderFieldAdapter() {
7930
8611
  });
7931
8612
  }
7932
8613
 
8614
+ // src/components/badge/badge.js
8615
+ var KINDS = Object.freeze(["neutral", "accent", "success", "warning", "danger", "info"]);
8616
+ var VARIANTS = Object.freeze(["soft", "solid", "outline"]);
8617
+ var BADGE_TONES = Object.freeze(["blue", "teal", "green", "amber", "orange", "red", "pink", "violet", "slate"]);
8618
+ var defaults = Object.freeze({
8619
+ label: "",
8620
+ icon: null,
8621
+ kind: "neutral",
8622
+ tone: null,
8623
+ variant: "soft",
8624
+ size: "md",
8625
+ dot: false
8626
+ });
8627
+ function normalizeBadgeTone(value) {
8628
+ return BADGE_TONES.includes(
8629
+ /** @type {BadgeTone} */
8630
+ value
8631
+ ) ? (
8632
+ /** @type {BadgeTone} */
8633
+ value
8634
+ ) : null;
8635
+ }
8636
+
7933
8637
  // src/components/tag-picker/tag-picker.js
7934
8638
  var TagPicker = class extends Component {
7935
8639
  static cssName = "tag-picker";
@@ -7941,6 +8645,7 @@ var TagPicker = class extends Component {
7941
8645
  labelKey: "name",
7942
8646
  iconKey: "icon",
7943
8647
  colorKey: "color",
8648
+ toneKey: "tone",
7944
8649
  searchKeys: null,
7945
8650
  filter: "local",
7946
8651
  minQuery: 0,
@@ -8281,11 +8986,12 @@ var TagPicker = class extends Component {
8281
8986
  _renderTags() {
8282
8987
  const tags = this._selected.map((item) => {
8283
8988
  const label = this._labelOf(item);
8284
- const color = this._colorOf(item);
8989
+ const tone = this._toneOf(item);
8990
+ const color = tone ? null : this._colorOf(item);
8285
8991
  const content = this.options.renderTag ? this.options.renderTag(item) : [this._iconOf(item), h("span", { class: "zx-tag-picker__tag-label" }, label)];
8286
8992
  const tag = h("span", {
8287
8993
  class: "zx-tag-picker__tag",
8288
- dataset: { colored: String(Boolean(color)) },
8994
+ dataset: { colored: String(Boolean(color)), tone: tone ?? void 0 },
8289
8995
  style: color ? { "--zx-tag-color": color } : null
8290
8996
  }, content);
8291
8997
  if (!this._readonly && !this._disabled) {
@@ -8654,16 +9360,22 @@ var TagPicker = class extends Component {
8654
9360
  value.cloneNode(true)
8655
9361
  ) : typeof value === "string" && value.trim() ? icon(value, { size: 12 }) : null;
8656
9362
  if (!visual) return null;
9363
+ const tone = this._toneOf(item);
9364
+ const color = tone ? null : this._colorOf(item);
8657
9365
  return h("span", {
8658
9366
  class: "zx-tag-picker__visual",
8659
- style: this._colorOf(item) ? { "--zx-tag-color": this._colorOf(item) } : null,
9367
+ dataset: { tone: tone ?? void 0 },
9368
+ style: color ? { "--zx-tag-color": color } : null,
8660
9369
  ariaHidden: "true"
8661
9370
  }, visual);
8662
9371
  }
8663
9372
  /** @param {unknown} item @returns {string|null} */
8664
9373
  _colorOf(item) {
8665
- const value = this._readItem(item, this.options.colorKey);
8666
- return isCssColor(value) ? value : null;
9374
+ return itemColor(this._readItem(item, this.options.colorKey));
9375
+ }
9376
+ /** @param {unknown} item @returns {string|null} */
9377
+ _toneOf(item) {
9378
+ return normalizeBadgeTone(this._readItem(item, this.options.toneKey));
8667
9379
  }
8668
9380
  /** @param {unknown} item @param {unknown} reader @returns {unknown} */
8669
9381
  _readItem(item, reader) {
@@ -8793,7 +9505,7 @@ var Timebox = class extends Component {
8793
9505
  const content = h("div", {
8794
9506
  class: "zx-timebox__content",
8795
9507
  role: "group",
8796
- ariaLabel: this.options.seconds ? "Duration in hours, minutes, and seconds" : "Duration in hours and minutes"
9508
+ ariaLabel: this.options.seconds ? this._message("timebox.durationWithSeconds", "Duration in hours, minutes, and seconds") : this._message("timebox.duration", "Duration in hours and minutes")
8797
9509
  });
8798
9510
  this._content = content;
8799
9511
  if (this.options.signed) {
@@ -8801,7 +9513,7 @@ var Timebox = class extends Component {
8801
9513
  ref: "sign",
8802
9514
  class: "zx-timebox__sign",
8803
9515
  type: "button",
8804
- ariaLabel: "Toggle duration sign",
9516
+ ariaLabel: this._message("timebox.toggleSign", "Toggle duration sign"),
8805
9517
  ariaPressed: "false"
8806
9518
  }, "+"));
8807
9519
  this.listen(this.refs.sign, "click", () => {
@@ -8811,12 +9523,12 @@ var Timebox = class extends Component {
8811
9523
  this._emitChange();
8812
9524
  });
8813
9525
  }
8814
- this._addSegment(content, "hours", "Hours", false);
9526
+ this._addSegment(content, "hours", this._message("timebox.hours", "Hours"), false);
8815
9527
  content.append(h("span", { class: "zx-timebox__separator", ariaHidden: "true" }, ":"));
8816
- this._addSegment(content, "minutes", "Minutes", true);
9528
+ this._addSegment(content, "minutes", this._message("timebox.minutes", "Minutes"), true);
8817
9529
  if (this.options.seconds) {
8818
9530
  content.append(h("span", { class: "zx-timebox__separator", ariaHidden: "true" }, ":"));
8819
- this._addSegment(content, "seconds", "Seconds", true);
9531
+ this._addSegment(content, "seconds", this._message("timebox.seconds", "Seconds"), true);
8820
9532
  }
8821
9533
  root.append(content);
8822
9534
  this.set(this.options.value, this.options.unit, { silent: true });
@@ -8876,7 +9588,7 @@ var Timebox = class extends Component {
8876
9588
  super.destroy();
8877
9589
  }
8878
9590
  /** @param {HTMLElement} parent @param {'hours'|'minutes'|'seconds'} name @param {string} label @param {boolean} bounded @returns {void} */
8879
- _addSegment(parent, name, label, bounded) {
9591
+ _addSegment(parent, name2, label, bounded) {
8880
9592
  const input = h("input", {
8881
9593
  class: "zx-timebox__segment",
8882
9594
  type: "text",
@@ -8884,13 +9596,13 @@ var Timebox = class extends Component {
8884
9596
  autocomplete: "off",
8885
9597
  ariaLabel: label,
8886
9598
  maxLength: bounded ? 2 : null,
8887
- dataset: { segment: name, bounded: String(bounded) }
9599
+ dataset: { segment: name2, bounded: String(bounded) }
8888
9600
  });
8889
9601
  this._inputs.push(input);
8890
9602
  parent.append(input);
8891
9603
  this.listen(input, "focus", () => input.select());
8892
9604
  this.listen(input, "input", () => {
8893
- if (name === "hours" && this.options.signed && input.value.trim().startsWith("-")) {
9605
+ if (name2 === "hours" && this.options.signed && input.value.trim().startsWith("-")) {
8894
9606
  this._parts.negative = true;
8895
9607
  }
8896
9608
  const digits = input.value.replace(/\D/g, "");
@@ -8908,9 +9620,9 @@ var Timebox = class extends Component {
8908
9620
  _readParts() {
8909
9621
  const parts = { ...this._parts };
8910
9622
  for (const input of this._inputs) {
8911
- const name = input.dataset.segment;
9623
+ const name2 = input.dataset.segment;
8912
9624
  const maximum = input.dataset.bounded === "true" ? 59 : Number.MAX_SAFE_INTEGER;
8913
- parts[name] = Math.max(0, Math.min(maximum, Math.trunc(finiteNumber2(input.value))));
9625
+ parts[name2] = Math.max(0, Math.min(maximum, Math.trunc(finiteNumber2(input.value))));
8914
9626
  }
8915
9627
  return parts;
8916
9628
  }
@@ -8923,8 +9635,8 @@ var Timebox = class extends Component {
8923
9635
  /** @returns {void} */
8924
9636
  _writeParts() {
8925
9637
  for (const input of this._inputs) {
8926
- const name = input.dataset.segment;
8927
- input.value = name === "hours" ? String(this._parts[name]) : pad3(this._parts[name]);
9638
+ const name2 = input.dataset.segment;
9639
+ input.value = name2 === "hours" ? String(this._parts[name2]) : pad3(this._parts[name2]);
8928
9640
  }
8929
9641
  if (this.refs.sign) {
8930
9642
  this.refs.sign.textContent = this._parts.negative ? "-" : "+";
@@ -8942,6 +9654,16 @@ var Timebox = class extends Component {
8942
9654
  _emitChange() {
8943
9655
  this.emit("change", { value: this.get(this.options.unit) });
8944
9656
  }
9657
+ /**
9658
+ * Resolves a message through the host translator, falling back to the built-in English text.
9659
+ * @param {string} key Message key.
9660
+ * @param {string} fallback English default.
9661
+ * @returns {string}
9662
+ */
9663
+ _message(key, fallback) {
9664
+ const message = this.msg(key);
9665
+ return message === key ? fallback : message;
9666
+ }
8945
9667
  };
8946
9668
  function finiteNumber2(value) {
8947
9669
  const number = Number(value);
@@ -9103,7 +9825,7 @@ function snapshot(element) {
9103
9825
  }
9104
9826
  function restore(element, state) {
9105
9827
  for (const attribute of Array.from(element.attributes)) element.removeAttribute(attribute.name);
9106
- for (const [name, value] of state.attributes) element.setAttribute(name, value);
9828
+ for (const [name2, value] of state.attributes) element.setAttribute(name2, value);
9107
9829
  element.replaceChildren(...state.children);
9108
9830
  }
9109
9831
 
@@ -9154,14 +9876,14 @@ var ValueList = class extends Component {
9154
9876
  this._list = h("ul", {
9155
9877
  class: "zx-value-list__list",
9156
9878
  role: "listbox",
9157
- ariaLabel: "Values",
9879
+ ariaLabel: this._message("valueList.values", "Values"),
9158
9880
  ariaOrientation: "horizontal"
9159
9881
  });
9160
9882
  this._input = h("input", {
9161
9883
  class: "zx-value-list__input",
9162
9884
  type: "text",
9163
9885
  placeholder: String(this.options.placeholder ?? ""),
9164
- ariaLabel: "Add value",
9886
+ ariaLabel: this._message("valueList.add", "Add value"),
9165
9887
  ariaDescribedby: instructionsId
9166
9888
  });
9167
9889
  this._status = h("div", {
@@ -9172,7 +9894,10 @@ var ValueList = class extends Component {
9172
9894
  this._instructions = h("span", {
9173
9895
  class: "zx-value-list__instructions",
9174
9896
  id: instructionsId
9175
- }, "Press Enter to add. Focus a value and press Ctrl plus Left or Right to reorder.");
9897
+ }, this._message(
9898
+ "valueList.instructions",
9899
+ "Press Enter to add. Focus a value and press Ctrl plus Left or Right to reorder."
9900
+ ));
9176
9901
  this._ownedNodes.push(this._list, this._input, this._status, this._instructions);
9177
9902
  root.append(this._list, this._input, this._status, this._instructions);
9178
9903
  this.listen(this._input, "keydown", (event) => this._handleInputKeydown(event));
@@ -9221,13 +9946,13 @@ var ValueList = class extends Component {
9221
9946
  const normalized = String(value ?? "").trim();
9222
9947
  if (!normalized) return false;
9223
9948
  if (this.options.unique && this._values.includes(normalized)) {
9224
- this._showError("This value already exists.");
9949
+ this._showError(this._message("valueList.duplicate", "This value already exists."));
9225
9950
  return false;
9226
9951
  }
9227
9952
  if (typeof this.options.validate === "function") {
9228
9953
  const result = this.options.validate(normalized);
9229
9954
  if (result !== true) {
9230
- this._showError(typeof result === "string" ? result : "This value is not valid.");
9955
+ this._showError(typeof result === "string" ? result : this._message("valueList.invalid", "This value is not valid."));
9231
9956
  return false;
9232
9957
  }
9233
9958
  }
@@ -9292,7 +10017,7 @@ var ValueList = class extends Component {
9292
10017
  draggable: Boolean(this.options.sortable) && !this._disabled,
9293
10018
  ariaSelected: "false",
9294
10019
  ariaDisabled: String(this._disabled),
9295
- ariaLabel: this.options.deletable ? `${value}. Press Delete to remove.` : value,
10020
+ ariaLabel: this.options.deletable ? this._message("valueList.removable", "%1. Press Delete to remove.", value) : value,
9296
10021
  dataset: { index }
9297
10022
  }, h("span", { class: "zx-value-list__value" }, value), this.options.deletable ? h("span", {
9298
10023
  class: "zx-value-list__remove",
@@ -9388,6 +10113,17 @@ var ValueList = class extends Component {
9388
10113
  this._status.textContent = message;
9389
10114
  this._input.setAttribute("aria-invalid", "true");
9390
10115
  }
10116
+ /**
10117
+ * Resolves a message through the host translator, falling back to the built-in English text.
10118
+ * @param {string} key Message key.
10119
+ * @param {string} fallback Built-in text, with `%1`-style placeholders.
10120
+ * @param {...unknown} args Interpolation values.
10121
+ * @returns {string}
10122
+ */
10123
+ _message(key, fallback, ...args) {
10124
+ const message = this.msg(key, ...args);
10125
+ return message === key ? printf(fallback, args) : message;
10126
+ }
9391
10127
  /** @returns {void} */
9392
10128
  destroy() {
9393
10129
  for (const node of this._ownedNodes ?? []) node.remove();
@@ -9432,8 +10168,8 @@ var ORDER = (
9432
10168
  function breakpointOf(width) {
9433
10169
  const value = Number(width);
9434
10170
  if (!Number.isFinite(value)) return "xs";
9435
- for (const name of ORDER) {
9436
- if (value >= breakpoints[name]) return name;
10171
+ for (const name2 of ORDER) {
10172
+ if (value >= breakpoints[name2]) return name2;
9437
10173
  }
9438
10174
  return "xs";
9439
10175
  }
@@ -9449,7 +10185,7 @@ function onBreakpoint(handler, options = {}) {
9449
10185
  /** @type {Element} */
9450
10186
  target
9451
10187
  ) : null;
9452
- let name = (
10188
+ let name2 = (
9453
10189
  /** @type {BreakpointName|null} */
9454
10190
  null
9455
10191
  );
@@ -9461,9 +10197,9 @@ function onBreakpoint(handler, options = {}) {
9461
10197
  const width = measure();
9462
10198
  if (element && width === 0) return;
9463
10199
  const next = breakpointOf(width);
9464
- if (next === name) return;
9465
- name = next;
9466
- handler(name, width);
10200
+ if (next === name2) return;
10201
+ name2 = next;
10202
+ handler(name2, width);
9467
10203
  };
9468
10204
  update();
9469
10205
  let frame = 0;
@@ -9481,7 +10217,7 @@ function onBreakpoint(handler, options = {}) {
9481
10217
  globalThis.addEventListener?.("resize", update, { passive: true });
9482
10218
  }
9483
10219
  return {
9484
- current: () => name ?? breakpointOf(measure()),
10220
+ current: () => name2 ?? breakpointOf(measure()),
9485
10221
  destroy: () => {
9486
10222
  if (destroyed) return;
9487
10223
  destroyed = true;
@@ -9569,13 +10305,119 @@ function currencyCode(value) {
9569
10305
  }
9570
10306
 
9571
10307
  // src/core/overlay-host.js
9572
- var SCOPE_SELECTOR = ".zx-scope, [data-zx-theme], [data-zx-density], [data-zx-preset]";
10308
+ var SCOPE_SELECTOR = ".zx-scope, [data-zx-theme], [data-zx-preset]";
9573
10309
  function overlayHost(source = null) {
9574
10310
  const context = source == null ? document.activeElement instanceof Element ? document.activeElement : null : resolveElement(source);
9575
10311
  const scope2 = context?.closest?.(SCOPE_SELECTOR) ?? null;
9576
10312
  if (!scope2 || scope2 === document.documentElement || scope2 === document.body) return document.body;
9577
10313
  return scope2;
9578
10314
  }
10315
+ function connectedOverlayHost(source = null) {
10316
+ if (source == null) return overlayHost(null);
10317
+ const context = resolveElement(source);
10318
+ return context?.isConnected ? overlayHost(context) : null;
10319
+ }
10320
+
10321
+ // src/components/loading/spinner.js
10322
+ var defaults2 = Object.freeze({
10323
+ size: "md",
10324
+ label: "",
10325
+ showLabel: false,
10326
+ kind: "accent"
10327
+ });
10328
+ var SIZES2 = Object.freeze({ sm: true, md: true, lg: true });
10329
+ function spinner(opts = {}) {
10330
+ const options = { ...defaults2, ...opts };
10331
+ const label = String(options.label ?? "");
10332
+ const element = (
10333
+ /** @type {HTMLSpanElement} */
10334
+ h("span", {
10335
+ class: "zx-spinner",
10336
+ dataset: {
10337
+ size: SIZES2[options.size] ? options.size : "md",
10338
+ kind: options.kind === "current" ? "current" : "accent"
10339
+ }
10340
+ }, h("span", { class: "zx-spinner__ring", ariaHidden: "true" }))
10341
+ );
10342
+ if (label) {
10343
+ element.setAttribute("role", "status");
10344
+ element.append(h("span", {
10345
+ class: "zx-spinner__label",
10346
+ dataset: { visible: options.showLabel ? "true" : null }
10347
+ }, label));
10348
+ } else {
10349
+ element.setAttribute("aria-hidden", "true");
10350
+ }
10351
+ return element;
10352
+ }
10353
+
10354
+ // src/components/button/button.js
10355
+ var defaults3 = Object.freeze({
10356
+ label: "",
10357
+ icon: null,
10358
+ kind: "default",
10359
+ size: "md",
10360
+ disabled: false
10361
+ });
10362
+ function button(opts = {}) {
10363
+ const options = { ...defaults3, ...opts };
10364
+ const element = (
10365
+ /** @type {HTMLButtonElement} */
10366
+ h("button", {
10367
+ class: "zx-btn",
10368
+ type: "button",
10369
+ disabled: Boolean(options.disabled),
10370
+ title: options.title ?? (options.shortcut ? `${options.label} (${options.shortcut.label})` : void 0),
10371
+ ariaKeyshortcuts: options.shortcut?.keys,
10372
+ dataset: {
10373
+ kind: normalizeKind(options.kind),
10374
+ size: normalizeSize(options.size)
10375
+ }
10376
+ })
10377
+ );
10378
+ if (options.icon) {
10379
+ element.append(icon(options.icon, { size: 16 }));
10380
+ }
10381
+ if (options.label !== "") {
10382
+ element.append(h("span", { class: "zx-btn__label" }, String(options.label)));
10383
+ }
10384
+ if (options.shortcut) element.append(h("kbd", { class: "zx-btn__shortcut", ariaHidden: "true" }, options.shortcut.label));
10385
+ if (!options.label && options.title) element.setAttribute("aria-label", options.title);
10386
+ if (typeof options.onclick === "function") element.onclick = options.onclick;
10387
+ if (options.busy) setButtonBusy(element, true);
10388
+ return element;
10389
+ }
10390
+ function setButtonBusy(element, busy) {
10391
+ if (!element.hasAttribute("data-busy-guard")) {
10392
+ element.setAttribute("data-busy-guard", "");
10393
+ element.addEventListener("click", (event) => {
10394
+ if (element.getAttribute("aria-busy") !== "true") return;
10395
+ event.preventDefault();
10396
+ event.stopImmediatePropagation();
10397
+ }, { capture: true });
10398
+ }
10399
+ element.querySelector(":scope > .zx-btn__spinner")?.remove();
10400
+ if (busy) {
10401
+ element.setAttribute("aria-busy", "true");
10402
+ element.setAttribute("aria-disabled", "true");
10403
+ const ring = spinner({ size: "sm", kind: "current" });
10404
+ ring.classList.add("zx-btn__spinner");
10405
+ element.prepend(ring);
10406
+ } else {
10407
+ element.removeAttribute("aria-busy");
10408
+ element.removeAttribute("aria-disabled");
10409
+ }
10410
+ return element;
10411
+ }
10412
+ function normalizeKind(value) {
10413
+ return ["primary", "danger", "ghost"].includes(String(value)) ? (
10414
+ /** @type {'primary'|'danger'|'ghost'} */
10415
+ value
10416
+ ) : "default";
10417
+ }
10418
+ function normalizeSize(value) {
10419
+ return value === "sm" ? "sm" : "md";
10420
+ }
9579
10421
 
9580
10422
  // src/components/table/sort.js
9581
10423
  function compareStrings(left, right) {
@@ -9631,7 +10473,10 @@ var Table = class extends Component {
9631
10473
  height: null,
9632
10474
  emptyText: null,
9633
10475
  rowClass: null,
10476
+ rowKind: null,
10477
+ rowKinds: null,
9634
10478
  zebra: true,
10479
+ variant: "default",
9635
10480
  editMode: false,
9636
10481
  editTrigger: "double",
9637
10482
  hierarchy: false,
@@ -9660,6 +10505,10 @@ var Table = class extends Component {
9660
10505
  this._original = created ? null : snapshotTarget(root);
9661
10506
  this._columns = normalizeTableColumns(this.options.columns);
9662
10507
  this._data = Array.isArray(this.options.data) ? [...this.options.data] : [];
10508
+ this._rowKind = normalizeRowKind(this.options.rowKind);
10509
+ this._rowKinds = normalizeRowKinds(this.options.rowKinds);
10510
+ this._spanning = this._hasSpanningRow();
10511
+ this._sortWarned = false;
9663
10512
  this._selected = /* @__PURE__ */ new Set();
9664
10513
  this._selectionAnchorId = null;
9665
10514
  this._sort = normalizeSort(this.options.sort, this._columns);
@@ -9693,6 +10542,7 @@ var Table = class extends Component {
9693
10542
  }
9694
10543
  }
9695
10544
  this._reconcileExpanded();
10545
+ if (normalizeTableVariant(this.options.variant) === "plain") root.dataset.variant = "plain";
9696
10546
  this._table = h("table", { class: "zx-table__table" });
9697
10547
  if (this._hierarchy) this._table.setAttribute("role", "treegrid");
9698
10548
  this._colgroup = h("colgroup", { class: "zx-table__columns" });
@@ -9976,7 +10826,7 @@ var Table = class extends Component {
9976
10826
  const column = this._columns.find((candidate) => candidate.id === String(id));
9977
10827
  const index = this.getColumnOrder().indexOf(String(id));
9978
10828
  if (column && index >= 0) {
9979
- this._columnStatus.textContent = `Moved ${column.label} to position ${index + 1} of ${this._columns.length}.`;
10829
+ this._columnStatus.textContent = `Moved ${tableColumnName(column)} to position ${index + 1} of ${this._columns.length}.`;
9980
10830
  }
9981
10831
  }
9982
10832
  return this;
@@ -10035,6 +10885,7 @@ var Table = class extends Component {
10035
10885
  const row = this.getRow(id);
10036
10886
  const target = this.getRow(targetId);
10037
10887
  if (!row || !target || Object.is(id, targetId)) return this;
10888
+ if (this._isSpanningRow(row)) return this;
10038
10889
  const next = reorderTableRows(this._data, this.options.rowId, id, targetId, position2, this._hierarchy?.parentId);
10039
10890
  if (!next) return this;
10040
10891
  const event = this.emit("rowmove", { row, id, target, targetId, position: position2 });
@@ -10071,6 +10922,10 @@ var Table = class extends Component {
10071
10922
  /**
10072
10923
  * Activates a column sort. Local mode stably reorders data; server mode only updates the
10073
10924
  * header state and emits the request event.
10925
+ *
10926
+ * Inert while the data holds a spanning row: a sort would scatter the positions out of the
10927
+ * sections that group them, in either sort mode. The header withholds its controls for the same
10928
+ * reason, so nothing on screen invites the call.
10074
10929
  * @param {string} id Column id.
10075
10930
  * @param {TableSortDirection} [dir] Direction; omitted toggles the current column.
10076
10931
  * @param {{silent?: boolean}} [options={}] Event options.
@@ -10079,6 +10934,13 @@ var Table = class extends Component {
10079
10934
  * @fires Table#editcancel
10080
10935
  */
10081
10936
  setSort(id, dir, options = {}) {
10937
+ if (this._spanning) {
10938
+ if (!this._sortWarned) {
10939
+ this._sortWarned = true;
10940
+ 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.");
10941
+ }
10942
+ return this;
10943
+ }
10082
10944
  this._abortEdit({ focus: false });
10083
10945
  const column = this._columns.find((candidate) => candidate.id === id);
10084
10946
  if (!column) throw new RangeError(`Unknown table column: ${id}`);
@@ -10130,7 +10992,7 @@ var Table = class extends Component {
10130
10992
  */
10131
10993
  setSelection(ids) {
10132
10994
  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)));
10995
+ const valid = this._selectableIds();
10134
10996
  const next = /* @__PURE__ */ new Set();
10135
10997
  if (this.options.selectable !== false) {
10136
10998
  for (const id of ids) {
@@ -10242,6 +11104,7 @@ var Table = class extends Component {
10242
11104
  if (!requested) return this;
10243
11105
  const current = this._data.find((candidate) => Object.is(this._idFor(candidate), rowId));
10244
11106
  if (current === void 0) return this;
11107
+ if (this._isSpanningRow(current)) return this;
10245
11108
  const column = this._editMode === "row" && this._hiddenColumns.has(requested.id) ? this._visibleColumns().find((candidate) => resolveEditable(candidate, current) !== false) ?? null : requested;
10246
11109
  if (!column || resolveEditable(column, current) === false) return this;
10247
11110
  const activeColumnId = column.id;
@@ -10530,6 +11393,41 @@ var Table = class extends Component {
10530
11393
  _visibleColumns() {
10531
11394
  return this._columns.filter((column) => !this._hiddenColumns.has(column.id));
10532
11395
  }
11396
+ /**
11397
+ * Whether a row draws as one cell across the table instead of as a record.
11398
+ * @param {TableRow} row Row to classify.
11399
+ * @returns {boolean}
11400
+ */
11401
+ _isSpanningRow(row) {
11402
+ if (this._rowKind === null || this._rowKinds === null) return false;
11403
+ return isSpanningRow(row, this._rowKind, this._rowKinds);
11404
+ }
11405
+ /**
11406
+ * Whether any row in the current data — not only the rendered prefix — spans. This is what
11407
+ * withholds the sort controls, and the reader must not be offered a control that the next
11408
+ * `growBy()` would have to take away again.
11409
+ * @returns {boolean}
11410
+ */
11411
+ _hasSpanningRow() {
11412
+ return hasSpanningRow(this._data, this._rowKind, this._rowKinds);
11413
+ }
11414
+ /**
11415
+ * How many cells one row occupies: every visible column plus the structural columns the table
11416
+ * renders beside them. Shared by the spanning cell and the empty-state cell so the two can never
11417
+ * disagree about the table's width.
11418
+ * @returns {number}
11419
+ */
11420
+ _spanColspan() {
11421
+ return tableSpanColspan(this._visibleColumns().length, {
11422
+ selectable: this.options.selectable,
11423
+ rowReorder: this.options.rowReorder
11424
+ });
11425
+ }
11426
+ /** @returns {TableRow[]} Rendered rows that can carry a selection; spanning rows cannot. */
11427
+ _selectableData() {
11428
+ const visible = this._visibleData();
11429
+ return this._rowKind === null ? visible : visible.filter((row) => !this._isSpanningRow(row));
11430
+ }
10533
11431
  /** @returns {HTMLElement} */
10534
11432
  _createColumnControls() {
10535
11433
  const list = h("div", { class: "zx-table__column-list" });
@@ -10542,7 +11440,12 @@ var Table = class extends Component {
10542
11440
  const details = h(
10543
11441
  "details",
10544
11442
  { class: "zx-table__column-controls" },
10545
- h("summary", { class: "zx-button zx-button--sm" }, icon("fields", { size: 13 }), "Columns"),
11443
+ h(
11444
+ "summary",
11445
+ { class: "zx-btn", dataset: { size: "sm" } },
11446
+ icon("fields"),
11447
+ this._message("table.columns", "Columns", [])
11448
+ ),
10546
11449
  list
10547
11450
  );
10548
11451
  this._renderColumnControls();
@@ -10571,26 +11474,27 @@ var Table = class extends Component {
10571
11474
  disabled: checked && visible.length === 1,
10572
11475
  dataset: { columnId: column.id, columnAction: "toggle" }
10573
11476
  }) : null;
10574
- const label = toggle ? h("label", { for: controlId }, toggle, h("span", {}, column.label)) : h("span", { class: "zx-table__column-label" }, column.label);
11477
+ const name2 = tableColumnName(column);
11478
+ const label = toggle ? h("label", { for: controlId }, toggle, h("span", {}, name2)) : h("span", { class: "zx-table__column-label" }, name2);
10575
11479
  const order = this.options.columnReorder ? h(
10576
11480
  "span",
10577
11481
  {
10578
11482
  class: "zx-table__column-order",
10579
11483
  role: "group",
10580
- ariaLabel: `Move ${column.label}`
11484
+ ariaLabel: this._message("table.moveColumn", "Move %1", [name2])
10581
11485
  },
10582
11486
  h("button", {
10583
11487
  class: "zx-table__column-move",
10584
11488
  type: "button",
10585
11489
  disabled: index === 0,
10586
- ariaLabel: `Move ${column.label} earlier`,
11490
+ ariaLabel: `Move ${name2} earlier`,
10587
11491
  dataset: { columnId: column.id, columnAction: "up" }
10588
11492
  }, icon("chevron-up", { size: 11 })),
10589
11493
  h("button", {
10590
11494
  class: "zx-table__column-move",
10591
11495
  type: "button",
10592
11496
  disabled: index === this._columns.length - 1,
10593
- ariaLabel: `Move ${column.label} later`,
11497
+ ariaLabel: `Move ${name2} later`,
10594
11498
  dataset: { columnId: column.id, columnAction: "down" }
10595
11499
  }, icon("chevron-down", { size: 11 }))
10596
11500
  ) : null;
@@ -10641,18 +11545,18 @@ var Table = class extends Component {
10641
11545
  }
10642
11546
  /** @param {Event} event @returns {void} */
10643
11547
  _handleColumnControlClick(event) {
10644
- const button = (
11548
+ const button2 = (
10645
11549
  /** @type {Element|null} */
10646
11550
  event.target?.closest?.(".zx-table__column-move")
10647
11551
  );
10648
- if (!button || !this._columnList?.contains(button)) return;
11552
+ if (!button2 || !this._columnList?.contains(button2)) return;
10649
11553
  const id = (
10650
11554
  /** @type {HTMLElement} */
10651
- button.dataset.columnId
11555
+ button2.dataset.columnId
10652
11556
  );
10653
11557
  const action = (
10654
11558
  /** @type {HTMLElement} */
10655
- button.dataset.columnAction
11559
+ button2.dataset.columnAction
10656
11560
  );
10657
11561
  const index = this.getColumnOrder().indexOf(String(id));
10658
11562
  if (action === "up" && index > 0) this.moveColumn(String(id), this._columns[index - 1].id, "before");
@@ -10850,9 +11754,9 @@ var Table = class extends Component {
10850
11754
  row.append(selectionHeader);
10851
11755
  this.listen(this._selectAll, "click", () => {
10852
11756
  if (this._selectAll.checked) {
10853
- for (const dataRow of this._visibleData()) this._selected.add(this._idFor(dataRow));
11757
+ for (const dataRow of this._selectableData()) this._selected.add(this._idFor(dataRow));
10854
11758
  } else {
10855
- for (const dataRow of this._visibleData()) this._selected.delete(this._idFor(dataRow));
11759
+ for (const dataRow of this._selectableData()) this._selected.delete(this._idFor(dataRow));
10856
11760
  }
10857
11761
  this._selectionAnchorId = null;
10858
11762
  this._syncSelection();
@@ -10862,21 +11766,25 @@ var Table = class extends Component {
10862
11766
  this._selectAll = null;
10863
11767
  }
10864
11768
  this._headers = /* @__PURE__ */ new Map();
11769
+ this._headerSpanning = this._spanning;
10865
11770
  for (const column of this._visibleColumns()) {
10866
- const th = h("th", { scope: "col" });
11771
+ const th = h("th", { scope: "col", dataset: { column: column.id } });
10867
11772
  const align = column.align ?? (isNumericColumn(column) ? "end" : null);
10868
11773
  if (align) th.dataset.align = align;
10869
11774
  if (column.headerTitle) th.title = column.headerTitle;
10870
- if (column.sortable) {
10871
- const button = h("button", {
11775
+ const name2 = tableHeaderName(column);
11776
+ if (name2 !== null) th.setAttribute("aria-label", name2);
11777
+ if (column.sortable && !this._spanning) {
11778
+ const button2 = h("button", {
10872
11779
  class: "zx-table__sort-button",
10873
- type: "button"
11780
+ type: "button",
11781
+ ariaLabel: name2 ?? void 0
10874
11782
  }, h("span", { class: "zx-table__header-label" }, column.label));
10875
- this.listen(button, "click", () => {
11783
+ this.listen(button2, "click", () => {
10876
11784
  const direction = this._sort?.id === column.id && this._sort.dir === "asc" ? "desc" : "asc";
10877
11785
  this.setSort(column.id, direction);
10878
11786
  });
10879
- th.append(button);
11787
+ th.append(button2);
10880
11788
  } else {
10881
11789
  th.append(h("span", { class: "zx-table__header-label" }, column.label));
10882
11790
  }
@@ -10889,6 +11797,8 @@ var Table = class extends Component {
10889
11797
  /** @returns {void} @fires Table#editcancel */
10890
11798
  _renderBody() {
10891
11799
  this._abortEdit({ focus: false });
11800
+ this._spanning = this._hasSpanningRow();
11801
+ if (this._headers && this._spanning !== this._headerSpanning) this._renderHeader();
10892
11802
  const fragment2 = document.createDocumentFragment();
10893
11803
  this._rowMeta = /* @__PURE__ */ new WeakMap();
10894
11804
  this._rowElements = /* @__PURE__ */ new Map();
@@ -10900,10 +11810,7 @@ var Table = class extends Component {
10900
11810
  fragment2.append(h(
10901
11811
  "tr",
10902
11812
  { 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())
11813
+ h("td", { class: "zx-table__empty", colspan: this._spanColspan() }, this._emptyContent())
10907
11814
  ));
10908
11815
  } else {
10909
11816
  this._visibleEntries().forEach((entry, visibleIndex) => {
@@ -10946,7 +11853,14 @@ var Table = class extends Component {
10946
11853
  if (this._growStep) tr.setAttribute("aria-rowindex", String(tree.visibleIndex + 2));
10947
11854
  const rowClass = typeof this.options.rowClass === "function" ? this.options.rowClass(row) : "";
10948
11855
  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)));
11856
+ const kind = this._rowKind === null ? null : resolveRowKind(row, this._rowKind);
11857
+ const kindSpec = kind === null ? null : resolveRowKindSpec(this._rowKinds, kind);
11858
+ const spanning = Boolean(kindSpec?.span);
11859
+ if (kind !== null) tr.dataset.rowKind = kind;
11860
+ if (kindSpec?.class) tr.classList.add(...String(kindSpec.class).split(/\s+/).filter(Boolean));
11861
+ if (this.options.selectable !== false && !spanning) {
11862
+ tr.setAttribute("aria-selected", String(this._selected.has(id)));
11863
+ }
10950
11864
  if (this._hierarchy) {
10951
11865
  tr.setAttribute("aria-level", String(tree.depth + 1));
10952
11866
  if (tree.hasChildren) tr.setAttribute("aria-expanded", String(this._expanded.has(id)));
@@ -10955,6 +11869,23 @@ var Table = class extends Component {
10955
11869
  const elements = this._rowElements.get(id) ?? [];
10956
11870
  elements.push(tr);
10957
11871
  this._rowElements.set(id, elements);
11872
+ if (spanning) {
11873
+ tr.dataset.span = "true";
11874
+ const cell = (
11875
+ /** @type {HTMLTableCellElement} */
11876
+ h("th", {
11877
+ class: "zx-table__section-cell",
11878
+ scope: "colgroup",
11879
+ colspan: this._spanColspan()
11880
+ })
11881
+ );
11882
+ const content = spanRowContent(row, kindSpec, this._visibleColumns(), index);
11883
+ if (content && typeof content === "object" && typeof content.nodeType === "number") {
11884
+ cell.append(content);
11885
+ } else if (content != null) cell.append(document.createTextNode(String(content)));
11886
+ tr.append(cell);
11887
+ return tr;
11888
+ }
10958
11889
  if (this.options.rowReorder) {
10959
11890
  const handle = h("button", {
10960
11891
  class: "zx-table__row-handle",
@@ -10985,9 +11916,9 @@ var Table = class extends Component {
10985
11916
  const align = column.align ?? (isNumericColumn(column) ? "end" : null);
10986
11917
  if (align) cell.dataset.align = align;
10987
11918
  cell.dataset.label = column.label;
11919
+ cell.dataset.column = column.id;
10988
11920
  if (column.popin === false) cell.dataset.popin = "false";
10989
11921
  if (this._editMode !== false) {
10990
- cell.dataset.column = column.id;
10991
11922
  if (resolveEditable(column, row) !== false) {
10992
11923
  cell.dataset.editable = "true";
10993
11924
  cell.tabIndex = -1;
@@ -11064,7 +11995,7 @@ var Table = class extends Component {
11064
11995
  );
11065
11996
  return;
11066
11997
  }
11067
- if (isInteractiveCellContent(target)) return;
11998
+ if (this._editMode !== false && isInteractiveCellContent(target)) return;
11068
11999
  if (this._editMode !== false && this._editTrigger === "single") {
11069
12000
  const cell = target?.closest('td[data-editable="true"]');
11070
12001
  if (cell && this._tbody.contains(cell)) {
@@ -11073,7 +12004,7 @@ var Table = class extends Component {
11073
12004
  }
11074
12005
  }
11075
12006
  this.emit("rowclick", { ...meta, event });
11076
- if (this.options.selectable === "single" && !this._selected.has(meta.id)) {
12007
+ if (this.options.selectable === "single" && !this._selected.has(meta.id) && !this._isSpanningRow(meta.row)) {
11077
12008
  this._selected = /* @__PURE__ */ new Set([meta.id]);
11078
12009
  this._selectionAnchorId = meta.id;
11079
12010
  this._syncSelection();
@@ -11092,7 +12023,7 @@ var Table = class extends Component {
11092
12023
  if (!rowElement || !this._tbody.contains(rowElement)) return;
11093
12024
  const meta = this._rowMeta.get(rowElement);
11094
12025
  if (!meta) return;
11095
- if (isInteractiveCellContent(target)) return;
12026
+ if (this._editMode !== false && isInteractiveCellContent(target)) return;
11096
12027
  if (this._editMode !== false && this._editTrigger === "double") {
11097
12028
  const cell = target?.closest('td[data-editable="true"]');
11098
12029
  if (cell && this._tbody.contains(cell)) {
@@ -11228,7 +12159,7 @@ var Table = class extends Component {
11228
12159
  ...props,
11229
12160
  class: textarea ? "zx-table__editor-input zx-table__editor-textarea" : "zx-table__editor-input",
11230
12161
  ...textarea ? {} : { type: "text" },
11231
- ariaLabel: props.ariaLabel ?? `${column.label} row ${index + 1}`,
12162
+ ariaLabel: props.ariaLabel ?? `${tableColumnName(column)} row ${index + 1}`,
11232
12163
  value: formatCellValue(column, value, row)
11233
12164
  })
11234
12165
  );
@@ -11247,7 +12178,7 @@ var Table = class extends Component {
11247
12178
  const control = entry?.control;
11248
12179
  if (!control || typeof control.focus !== "function") return;
11249
12180
  control.focus();
11250
- if (entry.type === "text" && typeof control.select === "function") control.select();
12181
+ if (editorSelectsOnEntry(entry.type) && typeof control.select === "function") control.select();
11251
12182
  }
11252
12183
  /**
11253
12184
  * Moves focus back onto a cell.
@@ -11416,6 +12347,7 @@ var Table = class extends Component {
11416
12347
  if (row < 0 || row >= visible.length) return null;
11417
12348
  const candidateRow = visible[row];
11418
12349
  const candidateColumn = columns[column];
12350
+ if (this._isSpanningRow(candidateRow)) continue;
11419
12351
  if (resolveEditable(candidateColumn, candidateRow) !== false) {
11420
12352
  return {
11421
12353
  id: this._idFor(candidateRow),
@@ -11497,6 +12429,7 @@ var Table = class extends Component {
11497
12429
  const start = Math.min(anchorIndex, currentIndex);
11498
12430
  const end = Math.max(anchorIndex, currentIndex);
11499
12431
  for (let index = start; index <= end; index += 1) {
12432
+ if (this._isSpanningRow(visible[index])) continue;
11500
12433
  const id = this._idFor(visible[index]);
11501
12434
  if (checked) this._selected.add(id);
11502
12435
  else this._selected.delete(id);
@@ -11515,6 +12448,7 @@ var Table = class extends Component {
11515
12448
  for (const [id, rows] of this._rowElements) {
11516
12449
  const selected = this._selected.has(id);
11517
12450
  for (const row of rows) {
12451
+ if (row.hasAttribute("data-span")) continue;
11518
12452
  row.setAttribute("aria-selected", String(selected));
11519
12453
  const checkbox = row.querySelector(".zx-table__row-checkbox");
11520
12454
  if (checkbox) checkbox.checked = selected;
@@ -11525,7 +12459,7 @@ var Table = class extends Component {
11525
12459
  /** @returns {void} */
11526
12460
  _syncSelectAll() {
11527
12461
  if (!this._selectAll) return;
11528
- const visible = this._visibleData();
12462
+ const visible = this._selectableData();
11529
12463
  const selectedRows = visible.reduce(
11530
12464
  (count, row) => count + Number(this._selected.has(this._idFor(row))),
11531
12465
  0
@@ -11538,13 +12472,15 @@ var Table = class extends Component {
11538
12472
  _syncSortHeader() {
11539
12473
  if (!this._headers) return;
11540
12474
  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");
12475
+ if (!this._spanning && this._sort?.id === id) {
12476
+ header.setAttribute("aria-sort", this._sort.dir === "asc" ? "ascending" : "descending");
12477
+ } else header.removeAttribute("aria-sort");
11543
12478
  }
11544
12479
  }
11545
12480
  /** @returns {void} */
11546
12481
  _sortData() {
11547
- if (!this._sort) return;
12482
+ this._spanning = this._hasSpanningRow();
12483
+ if (!this._sort || this._spanning) return;
11548
12484
  const column = this._columns.find((candidate) => candidate.id === this._sort.id);
11549
12485
  if (!column) return;
11550
12486
  const getValue = typeof column.sortValue === "function" ? column.sortValue : (row) => row?.[column.id];
@@ -11577,9 +12513,17 @@ var Table = class extends Component {
11577
12513
  const valid = new Set(branches);
11578
12514
  for (const id of this._expanded) if (!valid.has(id)) this._expanded.delete(id);
11579
12515
  }
12516
+ /** @returns {Set<unknown>} Ids of every row in the data that may hold a selection. */
12517
+ _selectableIds() {
12518
+ const ids = /* @__PURE__ */ new Set();
12519
+ for (const row of this._data) {
12520
+ if (!this._isSpanningRow(row)) ids.add(this._idFor(row));
12521
+ }
12522
+ return ids;
12523
+ }
11580
12524
  /** @returns {boolean} Whether selection changed. */
11581
12525
  _pruneSelection() {
11582
- const valid = new Set(this._data.map((row) => this._idFor(row)));
12526
+ const valid = this._selectableIds();
11583
12527
  const previousSize = this._selected.size;
11584
12528
  for (const id of this._selected) if (!valid.has(id)) this._selected.delete(id);
11585
12529
  if (!valid.has(this._selectionAnchorId)) this._selectionAnchorId = null;
@@ -11599,6 +12543,34 @@ var Table = class extends Component {
11599
12543
  this.emit("datachange", { rows: this.getData() });
11600
12544
  }
11601
12545
  };
12546
+ function resolveRowKind(row, rowKind) {
12547
+ if (rowKind == null || rowKind === "") return null;
12548
+ const raw2 = typeof rowKind === "function" ? rowKind(row) : row?.[rowKind];
12549
+ return raw2 == null ? null : String(raw2);
12550
+ }
12551
+ function resolveRowKindSpec(rowKinds, kind) {
12552
+ if (kind === null || rowKinds == null || typeof rowKinds !== "object") return null;
12553
+ if (!Object.prototype.hasOwnProperty.call(rowKinds, kind)) return null;
12554
+ const spec = rowKinds[kind];
12555
+ return spec && typeof spec === "object" ? spec : null;
12556
+ }
12557
+ function isSpanningRow(row, rowKind, rowKinds) {
12558
+ return Boolean(resolveRowKindSpec(rowKinds, resolveRowKind(row, rowKind))?.span);
12559
+ }
12560
+ function hasSpanningRow(rows, rowKind, rowKinds) {
12561
+ if (!Array.isArray(rows) || rowKind == null || rowKinds == null) return false;
12562
+ return rows.some((row) => isSpanningRow(row, rowKind, rowKinds));
12563
+ }
12564
+ function tableSpanColspan(columnCount, structure = {}) {
12565
+ const columns = Number.isFinite(Number(columnCount)) ? Math.max(0, Math.trunc(Number(columnCount))) : 0;
12566
+ const extras = (structure.selectable === "multi" ? 1 : 0) + (structure.rowReorder ? 1 : 0);
12567
+ return Math.max(1, columns + extras);
12568
+ }
12569
+ function spanRowContent(row, spec, visibleColumns, index = 0) {
12570
+ if (typeof spec?.render === "function") return spec.render(row, index);
12571
+ const first = visibleColumns?.[0];
12572
+ return first ? row?.[first.id] : null;
12573
+ }
11602
12574
  function formatTableCell(column, row, index = 0) {
11603
12575
  if (typeof column?.render === "function") return column.render(row, index);
11604
12576
  const value = row?.[column?.id];
@@ -11827,9 +12799,26 @@ function normalizeHierarchy(option, columns) {
11827
12799
  if (!column) return null;
11828
12800
  return { ...option, parentId, column };
11829
12801
  }
12802
+ function normalizeRowKind(option) {
12803
+ if (typeof option === "function") return (
12804
+ /** @type {(row: TableRow) => unknown} */
12805
+ option
12806
+ );
12807
+ return typeof option === "string" && option !== "" ? option : null;
12808
+ }
12809
+ function normalizeRowKinds(option) {
12810
+ if (!option || typeof option !== "object" || Array.isArray(option)) return null;
12811
+ return (
12812
+ /** @type {Record<string, TableRowKind>} */
12813
+ option
12814
+ );
12815
+ }
11830
12816
  function initialExpanded(option) {
11831
12817
  return new Set(Array.isArray(option) ? option : []);
11832
12818
  }
12819
+ function normalizeTableVariant(value) {
12820
+ return value === "plain" ? "plain" : "default";
12821
+ }
11833
12822
  function normalizeEditMode(mode) {
11834
12823
  if (mode === "cell" || mode === "row") return mode;
11835
12824
  return mode === true ? "cell" : false;
@@ -11872,6 +12861,22 @@ function normalizeTableColumns(columns) {
11872
12861
  return { ...column, id, label: String(column.label ?? id) };
11873
12862
  });
11874
12863
  }
12864
+ function tableColumnName(column) {
12865
+ for (const candidate of [column?.ariaLabel, column?.label]) {
12866
+ const text3 = candidate == null ? "" : String(candidate).trim();
12867
+ if (text3) return text3;
12868
+ }
12869
+ return String(column?.id ?? "");
12870
+ }
12871
+ function tableHeaderName(column) {
12872
+ const label = column?.label == null ? "" : String(column.label).trim();
12873
+ const explicit = column?.ariaLabel == null ? "" : String(column.ariaLabel).trim();
12874
+ if (explicit) return explicit;
12875
+ return label ? null : tableColumnName(column) || null;
12876
+ }
12877
+ function editorSelectsOnEntry(type) {
12878
+ return type === "text" || type === "number" || type === "date";
12879
+ }
11875
12880
  function reconcileTableColumnOrder(columns, order) {
11876
12881
  const available = columns.map((column) => column.id);
11877
12882
  const allowed = new Set(available);
@@ -12048,8 +13053,8 @@ function normalizeGlass(value) {
12048
13053
  function normalizeShape(value) {
12049
13054
  return value === "circle" ? "circle" : "tile";
12050
13055
  }
12051
- function originalAttribute(snapshot3, name) {
12052
- return snapshot3?.attributes.find(([attribute]) => attribute === name)?.[1] ?? null;
13056
+ function originalAttribute(snapshot3, name2) {
13057
+ return snapshot3?.attributes.find(([attribute]) => attribute === name2)?.[1] ?? null;
12053
13058
  }
12054
13059
 
12055
13060
  // src/components/launcher/launcher.js
@@ -12063,14 +13068,20 @@ var Launcher = class extends Component {
12063
13068
  debounce: 250,
12064
13069
  minQuery: 0,
12065
13070
  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",
13071
+ /*
13072
+ * The six labels default to null rather than to their English text so that an application
13073
+ * which installs a translator and passes no labels still gets a translated launcher. The
13074
+ * documented default is what `_message()` falls back to, so an untranslated application
13075
+ * renders exactly the same words as before.
13076
+ */
13077
+ placeholder: null,
13078
+ label: null,
13079
+ closeLabel: null,
13080
+ resultsLabel: null,
13081
+ emptyText: null,
13082
+ loadingText: null,
12072
13083
  shortcut: "mod+k",
12073
- hints: { move: "Move", open: "Open", close: "Close" },
13084
+ hints: null,
12074
13085
  scope: null
12075
13086
  };
12076
13087
  /**
@@ -12107,8 +13118,17 @@ var Launcher = class extends Component {
12107
13118
  this._requestSequence = 0;
12108
13119
  this._queryTimer = null;
12109
13120
  this._busy = false;
13121
+ this._settled = true;
13122
+ this._pages = [];
13123
+ this._restoreActiveId = null;
13124
+ this._announcePending = false;
13125
+ this._announceToggle = false;
12110
13126
  this._destroyed = false;
12111
13127
  const listId = uid("zx-launcher-results");
13128
+ const label = String(this.options.label ?? this._message("launcher.label", "Launcher"));
13129
+ const placeholder = String(this.options.placeholder ?? this._message("launcher.placeholder", "Search applications and records"));
13130
+ this._label = label;
13131
+ this._placeholder = placeholder;
12112
13132
  const input = h("input", {
12113
13133
  ref: "input",
12114
13134
  class: "zx-launcher__input",
@@ -12119,15 +13139,26 @@ var Launcher = class extends Component {
12119
13139
  ariaAutocomplete: "list",
12120
13140
  ariaControls: listId,
12121
13141
  ariaExpanded: "false",
12122
- ariaLabel: this.options.label,
12123
- placeholder: this.options.placeholder,
13142
+ ariaLabel: label,
13143
+ placeholder,
12124
13144
  value: this._query
12125
13145
  });
13146
+ const scopeBack = h("button", {
13147
+ ref: "scopeBack",
13148
+ class: "zx-icon-btn zx-launcher__scope-back",
13149
+ type: "button"
13150
+ }, icon("x"));
13151
+ const scope2 = h(
13152
+ "span",
13153
+ { ref: "scope", class: "zx-launcher__scope", hidden: true },
13154
+ h("span", { ref: "scopeLabel", class: "zx-launcher__scope-label" }),
13155
+ scopeBack
13156
+ );
12126
13157
  const close = h("button", {
12127
13158
  ref: "close",
12128
13159
  class: "zx-icon-btn zx-launcher__close",
12129
13160
  type: "button",
12130
- ariaLabel: this.options.closeLabel
13161
+ ariaLabel: this.options.closeLabel ?? this._message("launcher.close", "Close launcher")
12131
13162
  }, icon("x"));
12132
13163
  const status = h("div", {
12133
13164
  ref: "status",
@@ -12135,19 +13166,31 @@ var Launcher = class extends Component {
12135
13166
  role: "status",
12136
13167
  ariaLive: "polite"
12137
13168
  });
13169
+ const announce = h("div", {
13170
+ ref: "announce",
13171
+ class: "zx-launcher__announce",
13172
+ role: "status",
13173
+ ariaLive: "polite",
13174
+ ariaAtomic: "true"
13175
+ });
12138
13176
  const results = h("div", {
12139
13177
  ref: "results",
12140
13178
  class: "zx-launcher__results",
12141
13179
  id: listId,
12142
13180
  role: "listbox",
12143
- ariaLabel: this.options.resultsLabel
13181
+ ariaLabel: this.options.resultsLabel ?? this._message("launcher.results", "Launcher results")
12144
13182
  });
12145
13183
  const shortcut = this.options.shortcut === "mod+k" ? h("kbd", {
12146
13184
  class: "zx-launcher__shortcut",
12147
13185
  ariaHidden: "true"
12148
13186
  }, launcherShortcutLabel()) : null;
12149
- const footer = launcherHints(this.options.hints);
12150
- dialog.setAttribute("aria-label", String(this.options.label));
13187
+ const footer = launcherHints(this.options.hints, {
13188
+ move: this._message("launcher.hintMove", "Move"),
13189
+ open: this._message("launcher.hintOpen", "Open"),
13190
+ back: this._message("launcher.hintBack", "Back"),
13191
+ close: this._message("launcher.hintClose", "Close")
13192
+ });
13193
+ dialog.setAttribute("aria-label", label);
12151
13194
  dialog.replaceChildren(
12152
13195
  h(
12153
13196
  "div",
@@ -12155,17 +13198,19 @@ var Launcher = class extends Component {
12155
13198
  h("div", {
12156
13199
  class: "zx-launcher__search",
12157
13200
  dataset: { shortcut: shortcut ? "true" : "false" }
12158
- }, icon("search"), input, shortcut, close),
13201
+ }, icon("search"), scope2, input, shortcut, close),
12159
13202
  status,
12160
13203
  results,
12161
- footer
13204
+ footer,
13205
+ announce
12162
13206
  )
12163
13207
  );
12164
13208
  dialog.dataset.state = "closed";
12165
- if (this._createdRoot) overlayHost(this.options.scope).append(dialog);
13209
+ if (this._createdRoot) connectedOverlayHost(this.options.scope)?.append(dialog);
12166
13210
  this.listen(input, "input", () => {
12167
13211
  this._query = /** @type {HTMLInputElement} */
12168
13212
  input.value;
13213
+ this._announcePending = false;
12169
13214
  this.emit("query", { query: this._query.trim() });
12170
13215
  this._refresh();
12171
13216
  });
@@ -12173,6 +13218,10 @@ var Launcher = class extends Component {
12173
13218
  /** @type {KeyboardEvent} */
12174
13219
  event
12175
13220
  ));
13221
+ this.listen(scopeBack, "click", () => {
13222
+ this.back();
13223
+ input.focus();
13224
+ });
12176
13225
  this.listen(close, "click", () => this.close());
12177
13226
  this.listen(results, "click", (event) => {
12178
13227
  const option = (
@@ -12201,6 +13250,13 @@ var Launcher = class extends Component {
12201
13250
  dialog.dataset.state = "closed";
12202
13251
  input.setAttribute("aria-expanded", "false");
12203
13252
  this._abortRequest();
13253
+ if (this._pages.length && !dialog.open) {
13254
+ this._unwind();
13255
+ this._announcePending = false;
13256
+ this._syncPage();
13257
+ this._renderResults();
13258
+ this._emitPage();
13259
+ }
12204
13260
  this.emit("close");
12205
13261
  });
12206
13262
  this.listen(document, "keydown", (event) => this._onDocumentKeydown(
@@ -12210,25 +13266,60 @@ var Launcher = class extends Component {
12210
13266
  this._renderResults();
12211
13267
  return dialog;
12212
13268
  }
12213
- /** Opens the launcher and focuses/selects its query. @returns {this} @fires Launcher#open */
12214
- open() {
12215
- if (this.isOpen() || this._destroyed) return this;
13269
+ /**
13270
+ * Opens the launcher and focuses/selects its query. It opens on the root unless `page` names one;
13271
+ * already open, `page` navigates there.
13272
+ * @param {LauncherOpenOptions} [options={}] Open options.
13273
+ * @returns {this}
13274
+ * @fires Launcher#open
13275
+ * @fires Launcher#page
13276
+ */
13277
+ open(options = {}) {
13278
+ if (this._destroyed) return this;
13279
+ const target = options && typeof options === "object" ? options.page : void 0;
13280
+ if (this.isOpen()) {
13281
+ const path = target == null ? [] : this._pagePath(target);
13282
+ if (path.length) this._goTo(path);
13283
+ return this;
13284
+ }
12216
13285
  if (this._createdRoot) {
12217
13286
  const host = overlayHost(this.options.scope);
12218
13287
  if (this.el.parentElement !== host) host.append(this.el);
12219
13288
  }
13289
+ const before = this._pages.map((entry) => entry.page);
12220
13290
  this.el.showModal();
12221
13291
  this.el.dataset.state = "open";
12222
13292
  this.refs.input.setAttribute("aria-expanded", "true");
12223
- this._refresh();
13293
+ this._goTo(target == null ? [] : this._pagePath(target), { emit: false });
12224
13294
  queueMicrotask(() => {
12225
13295
  if (!this.isOpen()) return;
12226
13296
  this.refs.input.focus();
12227
13297
  this.refs.input.select();
12228
13298
  });
12229
13299
  this.emit("open");
13300
+ if (!samePath(before, this._pages.map((entry) => entry.page))) this._emitPage();
12230
13301
  return this;
12231
13302
  }
13303
+ /**
13304
+ * Goes back one page, restoring the level beneath with its query and highlighted option. Does
13305
+ * nothing on the root.
13306
+ * @returns {this}
13307
+ * @fires Launcher#page
13308
+ */
13309
+ back() {
13310
+ if (!this._pages.length) return this;
13311
+ const settled = this._popPage();
13312
+ this._syncPage();
13313
+ this._announcePending = this.isOpen();
13314
+ if (settled) this._renderResults();
13315
+ else this._refresh(true);
13316
+ this._emitPage();
13317
+ return this;
13318
+ }
13319
+ /** Returns the page shown, or null on the root. @returns {LauncherItem|null} */
13320
+ getPage() {
13321
+ return this._pages.at(-1)?.page ?? null;
13322
+ }
12232
13323
  /** Closes the launcher. @returns {this} */
12233
13324
  close() {
12234
13325
  if (this.isOpen()) this.el.close();
@@ -12252,6 +13343,7 @@ var Launcher = class extends Component {
12252
13343
  setQuery(query) {
12253
13344
  this._query = String(query ?? "");
12254
13345
  this.refs.input.value = this._query;
13346
+ this._announcePending = false;
12255
13347
  this.emit("query", { query: this._query.trim() });
12256
13348
  this._refresh();
12257
13349
  return this;
@@ -12260,7 +13352,7 @@ var Launcher = class extends Component {
12260
13352
  getQuery() {
12261
13353
  return this._query;
12262
13354
  }
12263
- /** Replaces local catalogue items. @param {LauncherItem[]} items @returns {this} */
13355
+ /** Replaces the root's local catalogue items; an open page keeps its own. @param {LauncherItem[]} items @returns {this} */
12264
13356
  setItems(items) {
12265
13357
  this._items = normalizeItems(items);
12266
13358
  this._renderResults();
@@ -12273,10 +13365,14 @@ var Launcher = class extends Component {
12273
13365
  this._renderResults();
12274
13366
  return this;
12275
13367
  }
12276
- /** Replaces asynchronous sources and aborts work from the old set. @param {LauncherSource[]} sources @returns {this} */
13368
+ /** Replaces the root's asynchronous sources and aborts work from the old set; on a page they load once the root is back. @param {LauncherSource[]} sources @returns {this} */
12277
13369
  setSources(sources) {
12278
- this._abortRequest();
12279
13370
  this._sources = normalizeSources(sources);
13371
+ if (this._pages.length) {
13372
+ this._pages[0].parent.sourceItems = null;
13373
+ return this;
13374
+ }
13375
+ this._abortRequest();
12280
13376
  this._sourceItems.clear();
12281
13377
  this._refresh();
12282
13378
  return this;
@@ -12291,16 +13387,24 @@ var Launcher = class extends Component {
12291
13387
  super.destroy();
12292
13388
  if (!this._createdRoot && original) {
12293
13389
  for (const attribute of Array.from(this.el.attributes)) this.el.removeAttribute(attribute.name);
12294
- for (const [name, value] of original.attributes) this.el.setAttribute(name, value);
13390
+ for (const [name2, value] of original.attributes) this.el.setAttribute(name2, value);
12295
13391
  this.el.replaceChildren(...original.children);
12296
13392
  }
12297
13393
  }
12298
- /** @returns {void} */
12299
- _refresh() {
13394
+ /**
13395
+ * Re-renders the current level and reloads its sources: debounced while typing, at once when a
13396
+ * page opens (so its loading state shows instead of a passing "No results").
13397
+ * @param {boolean} [immediate=false] Load without the debounce.
13398
+ * @returns {void}
13399
+ */
13400
+ _refresh(immediate = false) {
13401
+ this._abortRequest();
12300
13402
  this._sourceItems.clear();
13403
+ const { sources } = this._level();
13404
+ this._settled = sources.length === 0;
13405
+ if (immediate && sources.length) void this._loadSources();
12301
13406
  this._renderResults();
12302
- this._abortRequest();
12303
- if (this._sources.length === 0) return;
13407
+ if (immediate || sources.length === 0) return;
12304
13408
  const delay = Math.max(0, Number(this.options.debounce) || 0);
12305
13409
  this._queryTimer = setTimeout(() => this._loadSources(), delay);
12306
13410
  }
@@ -12308,8 +13412,15 @@ var Launcher = class extends Component {
12308
13412
  async _loadSources() {
12309
13413
  this._queryTimer = null;
12310
13414
  const query = this._query.trim();
12311
- const sources = this._sources.filter((source) => visibleForQuery(source.when, query) && query.length >= Math.max(Number(this.options.minQuery) || 0, Number(source.minQuery) || 0));
12312
- if (sources.length === 0) return;
13415
+ const page = this._pages?.at(-1)?.page ?? null;
13416
+ const sources = this._level().sources.filter((source) => visibleForQuery(source.when, query) && query.length >= Math.max(
13417
+ source._page ? 0 : Number(this.options.minQuery) || 0,
13418
+ Number(source.minQuery) || 0
13419
+ ));
13420
+ if (sources.length === 0) {
13421
+ this._settled = true;
13422
+ return;
13423
+ }
12313
13424
  const request = new AbortController();
12314
13425
  const sequence = ++this._requestSequence;
12315
13426
  this._request = request;
@@ -12320,7 +13431,7 @@ var Launcher = class extends Component {
12320
13431
  if (request.signal.aborted || sequence !== this._requestSequence) return;
12321
13432
  this._sourceItems.set(source.id, normalizeItems(loaded).map((item, itemOrder) => ({
12322
13433
  ...item,
12323
- _source: source.id,
13434
+ _source: source._page ? null : source.id,
12324
13435
  _sourceOrder: source.order === "source",
12325
13436
  group: item.group ?? source.label ?? "",
12326
13437
  itemOrder: item.itemOrder ?? (source.order === "source" ? itemOrder : void 0)
@@ -12328,13 +13439,157 @@ var Launcher = class extends Component {
12328
13439
  this._renderResults();
12329
13440
  } catch (error) {
12330
13441
  if (request.signal.aborted || sequence !== this._requestSequence) return;
12331
- this.emit("error", { source, error });
13442
+ this.emit("error", { source, error, page });
12332
13443
  }
12333
13444
  }));
12334
13445
  if (request.signal.aborted || sequence !== this._requestSequence) return;
12335
13446
  this._request = null;
13447
+ this._settled = true;
12336
13448
  this._setBusy(false);
12337
13449
  }
13450
+ /**
13451
+ * The level on screen: the root's items and sources, or the open page's.
13452
+ * @returns {{items: LauncherItem[], sources: LevelSource[]}}
13453
+ */
13454
+ _level() {
13455
+ return this._pages?.at(-1) ?? { items: this._items, sources: this._sources };
13456
+ }
13457
+ /**
13458
+ * Opens a page on top of the current level: what the user activated, or ArrowRight on its row.
13459
+ * @param {LauncherItem} item Page item.
13460
+ * @returns {void}
13461
+ */
13462
+ _openPage(item) {
13463
+ this._pushPage(item);
13464
+ this._syncPage();
13465
+ this._announcePending = this.isOpen();
13466
+ this._refresh(true);
13467
+ this._emitPage();
13468
+ }
13469
+ /**
13470
+ * Replaces the whole stack with `path` (empty for the root) and refreshes what it lands on.
13471
+ * @param {LauncherItem[]} path Pages from the root down.
13472
+ * @param {{emit?: boolean}} [options={}] Whether to report a change through `page`.
13473
+ * @returns {void}
13474
+ */
13475
+ _goTo(path, { emit = true } = {}) {
13476
+ const before = this._pages.map((entry) => entry.page);
13477
+ this._unwind();
13478
+ if (path.length) {
13479
+ this._abortRequest();
13480
+ this._sourceItems.clear();
13481
+ this._settled = false;
13482
+ for (const item of path) this._pushPage(item);
13483
+ }
13484
+ this._syncPage();
13485
+ this._announcePending = this.isOpen() && path.length > 0;
13486
+ this._refresh(path.length > 0);
13487
+ if (emit && !samePath(before, path)) this._emitPage();
13488
+ }
13489
+ /** Pops every page, restoring the root's query, without rendering. @returns {void} */
13490
+ _unwind() {
13491
+ while (this._pages.length) this._popPage();
13492
+ }
13493
+ /**
13494
+ * Pushes a page, remembering the level beneath, and clears the query for it.
13495
+ * @param {LauncherItem} item Page item.
13496
+ * @returns {void}
13497
+ */
13498
+ _pushPage(item) {
13499
+ const active = this._active >= 0 ? this._results[this._active] : null;
13500
+ this._pages.push({
13501
+ page: item,
13502
+ items: Array.isArray(item.items) ? normalizeItems(item.items) : [],
13503
+ sources: typeof item.items === "function" ? [launcherPageSource(item)] : [],
13504
+ parent: {
13505
+ query: this._query,
13506
+ activeId: active ? String(active.id) : null,
13507
+ sourceItems: this._settled ? new Map(this._sourceItems) : null
13508
+ }
13509
+ });
13510
+ this._setInputQuery("");
13511
+ }
13512
+ /**
13513
+ * Pops the top page and restores the level beneath.
13514
+ * @returns {boolean} Whether that level's results came back complete (no reload needed).
13515
+ */
13516
+ _popPage() {
13517
+ const entry = this._pages.pop();
13518
+ if (!entry) return true;
13519
+ this._announcePending = false;
13520
+ this._abortRequest();
13521
+ const { parent } = entry;
13522
+ this._setInputQuery(parent.query);
13523
+ this._sourceItems = parent.sourceItems ? new Map(parent.sourceItems) : /* @__PURE__ */ new Map();
13524
+ this._settled = parent.sourceItems !== null;
13525
+ this._restoreActiveId = parent.activeId;
13526
+ return this._settled;
13527
+ }
13528
+ /** @param {string} query @returns {void} */
13529
+ _setInputQuery(query) {
13530
+ this._query = query;
13531
+ this.refs.input.value = query;
13532
+ this._renderedQuery = null;
13533
+ }
13534
+ /**
13535
+ * Shows the open page in the search row: the chip, the combobox name and placeholder, and the
13536
+ * footer's Back hint.
13537
+ * @returns {void}
13538
+ */
13539
+ _syncPage() {
13540
+ const page = this.getPage();
13541
+ const { input, scope: scope2, scopeLabel, scopeBack, backHint } = this.refs;
13542
+ scope2.hidden = page === null;
13543
+ if (backHint) backHint.hidden = page === null;
13544
+ this.el.toggleAttribute("data-page", page !== null);
13545
+ if (page === null) {
13546
+ input.setAttribute("aria-label", this._label);
13547
+ input.setAttribute("placeholder", this._placeholder);
13548
+ scopeLabel.textContent = "";
13549
+ return;
13550
+ }
13551
+ const parent = this._pages.at(-2)?.page.label ?? this._label;
13552
+ scopeLabel.textContent = page.label;
13553
+ scopeBack.setAttribute("aria-label", this._message("launcher.back", "Back to %1", parent));
13554
+ input.setAttribute("aria-label", this._message("launcher.pageLabel", "Search %1", page.label));
13555
+ input.setAttribute("placeholder", page.placeholder != null ? String(page.placeholder) : this._message("launcher.pagePlaceholder", "Search"));
13556
+ }
13557
+ /** @returns {void} @fires Launcher#page */
13558
+ _emitPage() {
13559
+ const path = this._pages.map((entry) => entry.page);
13560
+ this.emit("page", { page: path.at(-1) ?? null, path });
13561
+ }
13562
+ /**
13563
+ * Resolves `open({ page })`: a page item as given, or the path to a local page by id.
13564
+ * @param {string|number|LauncherItem} target Page id or item.
13565
+ * @returns {LauncherItem[]}
13566
+ */
13567
+ _pagePath(target) {
13568
+ if (target && typeof target === "object") {
13569
+ const item = (
13570
+ /** @type {Record<string, any>} */
13571
+ target
13572
+ );
13573
+ if (!isLauncherPage(item) || item.label == null) return [];
13574
+ return [normalizeLauncherItem({ ...item, id: item.id ?? item.label })];
13575
+ }
13576
+ return findLauncherPage(
13577
+ this._items,
13578
+ /** @type {string|number} */
13579
+ target
13580
+ );
13581
+ }
13582
+ /**
13583
+ * The polite line a page change ends on: where the user is and how much is there.
13584
+ * @returns {string}
13585
+ */
13586
+ _pageStatus() {
13587
+ const label = this.getPage()?.label ?? this._label;
13588
+ const count = this._results.length;
13589
+ if (count === 0) return this._message("launcher.pageEmpty", "%1, no results", label);
13590
+ if (count === 1) return this._message("launcher.pageResult", "%1, 1 result", label);
13591
+ return this._message("launcher.pageResults", "%1, %2 results", label, count);
13592
+ }
12338
13593
  /** @param {boolean} busy @returns {void} */
12339
13594
  _setBusy(busy) {
12340
13595
  this._busy = busy;
@@ -12353,11 +13608,14 @@ var Launcher = class extends Component {
12353
13608
  /** @returns {void} */
12354
13609
  _renderResults() {
12355
13610
  const queryKey = this._query;
12356
- const activeId = this._renderedQuery === queryKey && this._active >= 0 && this._results[this._active] ? String(this._results[this._active].id) : null;
13611
+ const restored = this._restoreActiveId ?? null;
13612
+ this._restoreActiveId = null;
13613
+ const activeId = restored ?? (this._renderedQuery === queryKey && this._active >= 0 && this._results[this._active] ? String(this._results[this._active].id) : null);
12357
13614
  const query = this._query.trim();
12358
- const local = this._items.filter((item) => visibleForQuery(item.when, query));
13615
+ const level = this._level();
13616
+ const local = level.items.filter((item) => visibleForQuery(item.when, query));
12359
13617
  const rankedLocal = rankLauncherItems(local, query, this.options.maxResults);
12360
- const remote = this._sources.flatMap((source) => {
13618
+ const remote = level.sources.flatMap((source) => {
12361
13619
  const loaded = this._sourceItems.get(String(source.id)) ?? [];
12362
13620
  return source.order === "source" ? loaded.filter((item) => visibleForQuery(item.when, query)) : rankLauncherItems(
12363
13621
  loaded.filter((item) => visibleForQuery(item.when, query)),
@@ -12400,7 +13658,8 @@ var Launcher = class extends Component {
12400
13658
  }
12401
13659
  /** @param {RankedLauncherItem} item @param {number} index @returns {HTMLElement} */
12402
13660
  _option(item, index) {
12403
- const href = safeHref(item.href);
13661
+ const page = isLauncherPage(item);
13662
+ const href = page ? null : safeHref(item.href);
12404
13663
  const tag = href ? "a" : "button";
12405
13664
  const children = [];
12406
13665
  const visual = launcherItemIcon(item);
@@ -12415,6 +13674,17 @@ var Launcher = class extends Component {
12415
13674
  item.description ? h("span", { class: "zx-launcher__description" }, item.description) : null
12416
13675
  ));
12417
13676
  if (item.badge != null) children.push(h("span", { class: "zx-launcher__badge" }, String(item.badge)));
13677
+ if (item.hint != null && item.hint !== "") {
13678
+ children.push(h("span", { class: "zx-launcher__hint" }, String(item.hint)));
13679
+ }
13680
+ const keys = launcherShortcutKeys(item);
13681
+ const caps = launcherShortcutCaps(item.shortcut);
13682
+ if (caps.length) children.push(h(
13683
+ "span",
13684
+ { class: "zx-launcher__keys", ariaHidden: keys ? "true" : null },
13685
+ caps.map((cap) => h("kbd", { class: "zx-launcher__key" }, cap))
13686
+ ));
13687
+ if (page) children.push(h("span", { class: "zx-launcher__chevron", ariaHidden: "true" }, icon("chevron-right")));
12418
13688
  return (
12419
13689
  /** @type {HTMLElement} */
12420
13690
  h(tag, {
@@ -12429,20 +13699,31 @@ var Launcher = class extends Component {
12429
13699
  ariaSelected: "false",
12430
13700
  ariaDisabled: item.disabled ? "true" : null,
12431
13701
  ariaCurrent: item.current ? "page" : null,
13702
+ ariaKeyShortcuts: keys,
12432
13703
  dataset: {
12433
13704
  launcherIndex: index,
12434
13705
  pinned: item.pinned !== void 0 && item.pinned !== false ? "true" : null,
12435
- kind: normalizeLauncherKind(item.kind)
13706
+ kind: normalizeLauncherKind(item.kind),
13707
+ page: page ? "true" : null
12436
13708
  }
12437
13709
  }, children)
12438
13710
  );
12439
13711
  }
12440
13712
  /** @returns {void} */
12441
13713
  _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 = "";
13714
+ if (this._busy) {
13715
+ this.refs.status.textContent = String(this.options.loadingText ?? this._message("launcher.loading", "Searching\u2026"));
13716
+ } else if (this._results.length === 0) {
13717
+ this.refs.status.textContent = String(this.options.emptyText ?? this._message("launcher.empty", "No results"));
13718
+ } else {
13719
+ this.refs.status.textContent = "";
13720
+ }
12445
13721
  this.refs.status.hidden = !this.refs.status.textContent;
13722
+ if (this._announcePending && !this._busy && this._renderedQuery === this._query) {
13723
+ this._announcePending = false;
13724
+ this._announceToggle = !this._announceToggle;
13725
+ this.refs.announce.textContent = this._pageStatus() + (this._announceToggle ? "\xA0" : "");
13726
+ }
12446
13727
  }
12447
13728
  /** @returns {void} */
12448
13729
  _syncActive() {
@@ -12461,6 +13742,12 @@ var Launcher = class extends Component {
12461
13742
  this.close();
12462
13743
  return;
12463
13744
  }
13745
+ const plain = !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey;
13746
+ if (event.key === "Backspace" && plain && !event.repeat && this._pages.length && this._query === "") {
13747
+ event.preventDefault();
13748
+ this.back();
13749
+ return;
13750
+ }
12464
13751
  if (event.key === "Enter") {
12465
13752
  if (this._active < 0) return;
12466
13753
  event.preventDefault();
@@ -12469,9 +13756,8 @@ var Launcher = class extends Component {
12469
13756
  return;
12470
13757
  }
12471
13758
  const enabled = this._results.map((item, index) => item.disabled ? -1 : index).filter((index) => index >= 0);
12472
- if (enabled.length === 0) return;
12473
13759
  if (/^Arrow(?:Left|Right|Up|Down)$/.test(event.key)) {
12474
- const next = this._spatialIndex(event.key, enabled);
13760
+ const next = enabled.length ? this._spatialIndex(event.key, enabled) : null;
12475
13761
  if (next !== null) {
12476
13762
  event.preventDefault();
12477
13763
  this._active = next;
@@ -12479,8 +13765,12 @@ var Launcher = class extends Component {
12479
13765
  this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`)?.scrollIntoView({ block: "nearest", inline: "nearest" });
12480
13766
  return;
12481
13767
  }
12482
- if (event.key === "ArrowLeft" || event.key === "ArrowRight") return;
13768
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
13769
+ if (plain && !event.repeat && this._pageKey(event.key)) event.preventDefault();
13770
+ return;
13771
+ }
12483
13772
  }
13773
+ if (enabled.length === 0) return;
12484
13774
  let position2 = enabled.indexOf(this._active);
12485
13775
  if (event.key === "Home" || event.key === "PageUp") position2 = 0;
12486
13776
  else if (event.key === "End" || event.key === "PageDown") position2 = enabled.length - 1;
@@ -12492,6 +13782,30 @@ var Launcher = class extends Component {
12492
13782
  this._syncActive();
12493
13783
  this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`)?.scrollIntoView({ block: "nearest" });
12494
13784
  }
13785
+ /**
13786
+ * ArrowRight on a page row opens it once the caret is at the end of the query (before that it
13787
+ * moves the caret); ArrowLeft on an empty query goes back. Application tiles keep both arrows
13788
+ * for moving through their grid, which the caller tries first.
13789
+ * @param {string} key ArrowLeft or ArrowRight.
13790
+ * @returns {boolean} Whether the key navigated.
13791
+ */
13792
+ _pageKey(key) {
13793
+ if (key === "ArrowLeft") {
13794
+ if (!this._pages.length || this._query !== "") return false;
13795
+ this.back();
13796
+ return true;
13797
+ }
13798
+ const item = this._active >= 0 ? this._results[this._active] : null;
13799
+ if (!item || item.disabled || !isLauncherPage(item) || item.kind === "application") return false;
13800
+ const input = (
13801
+ /** @type {HTMLInputElement} */
13802
+ this.refs.input
13803
+ );
13804
+ const end = input.value.length;
13805
+ if (input.selectionStart !== end || input.selectionEnd !== end) return false;
13806
+ this._openPage(item);
13807
+ return true;
13808
+ }
12495
13809
  /** @param {string} direction @param {number[]} enabled @returns {number|null} */
12496
13810
  _spatialIndex(direction, enabled) {
12497
13811
  const current = this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`);
@@ -12508,21 +13822,28 @@ var Launcher = class extends Component {
12508
13822
  event.preventDefault();
12509
13823
  return;
12510
13824
  }
13825
+ if (isLauncherPage(item)) {
13826
+ event.preventDefault();
13827
+ this._openPage(item);
13828
+ this.refs.input.focus();
13829
+ return;
13830
+ }
12511
13831
  const selected = this.emit("select", {
12512
13832
  item,
12513
13833
  value: item.value ?? item.id,
12514
13834
  source: item._source ?? null,
12515
- query: this._query.trim()
13835
+ query: this._query.trim(),
13836
+ page: this.getPage()
12516
13837
  });
12517
13838
  if (selected.defaultPrevented) {
12518
13839
  event.preventDefault();
12519
13840
  return;
12520
13841
  }
13842
+ this.close();
12521
13843
  if (!item.href) {
12522
13844
  event.preventDefault();
12523
13845
  item.invoke?.();
12524
13846
  }
12525
- this.close();
12526
13847
  }
12527
13848
  /** @param {KeyboardEvent} event @returns {void} */
12528
13849
  _onDocumentKeydown(event) {
@@ -12534,6 +13855,17 @@ var Launcher = class extends Component {
12534
13855
  event.preventDefault();
12535
13856
  this.open();
12536
13857
  }
13858
+ /**
13859
+ * Resolves a message through the host translator, falling back to the built-in English text.
13860
+ * @param {string} key Message key.
13861
+ * @param {string} fallback English default.
13862
+ * @param {...unknown} args Positional `%1`, `%2` values.
13863
+ * @returns {string}
13864
+ */
13865
+ _message(key, fallback, ...args) {
13866
+ const message = this.msg(key, ...args);
13867
+ return message === key ? printf(fallback, args) : message;
13868
+ }
12537
13869
  };
12538
13870
  function launcherShortcutLabel() {
12539
13871
  const platform = String(
@@ -12542,16 +13874,73 @@ function launcherShortcutLabel() {
12542
13874
  );
12543
13875
  return /mac|iphone|ipad/i.test(platform) ? "\u2318 K" : "Ctrl K";
12544
13876
  }
12545
- function launcherHints(hints) {
13877
+ function launcherHints(hints, defaults4) {
12546
13878
  if (hints === false) return null;
12547
13879
  const labels = hints && typeof hints === "object" ? hints : {};
12548
13880
  return h(
12549
13881
  "footer",
12550
13882
  { 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")
13883
+ h("span", {}, h("kbd", {}, "\u2191\u2193\u2190\u2192"), labels.move ?? defaults4.move),
13884
+ h("span", {}, h("kbd", {}, "\u21B5"), labels.open ?? defaults4.open),
13885
+ // Shown only on a page (`_syncPage()`).
13886
+ h("span", { ref: "backHint", hidden: true }, h("kbd", {}, "\u232B"), labels.back ?? defaults4.back),
13887
+ h("span", {}, h("kbd", {}, "Esc"), labels.close ?? defaults4.close)
13888
+ );
13889
+ }
13890
+ function isLauncherPage(item) {
13891
+ const items = (
13892
+ /** @type {Record<string, any>|null} */
13893
+ item?.items
12554
13894
  );
13895
+ return Boolean(item) && typeof item === "object" && (Array.isArray(items) || typeof items === "function");
13896
+ }
13897
+ function findLauncherPage(items, id) {
13898
+ if (id == null) return [];
13899
+ const key = String(id);
13900
+ const seen = /* @__PURE__ */ new Set();
13901
+ const visit = (list, trail) => {
13902
+ if (!Array.isArray(list) || seen.has(list)) return null;
13903
+ seen.add(list);
13904
+ for (const item of normalizeItems(list)) {
13905
+ if (!isLauncherPage(item)) continue;
13906
+ const path = [...trail, item];
13907
+ if (String(item.id) === key) return path;
13908
+ const found = visit(item.items, path);
13909
+ if (found) return found;
13910
+ }
13911
+ return null;
13912
+ };
13913
+ return visit(items, []) ?? [];
13914
+ }
13915
+ function launcherShortcutCaps(shortcut) {
13916
+ const labels = shortcut && typeof shortcut === "object" && !Array.isArray(shortcut) ? (
13917
+ /** @type {{label?: unknown}} */
13918
+ shortcut.label
13919
+ ) : shortcut;
13920
+ const caps = Array.isArray(labels) ? labels : labels == null ? [] : [labels];
13921
+ return caps.filter((cap) => cap != null && String(cap).trim() !== "").map((cap) => String(cap).trim());
13922
+ }
13923
+ function launcherShortcutKeys(item) {
13924
+ const shortcut = (
13925
+ /** @type {any} */
13926
+ item.shortcut
13927
+ );
13928
+ const keys = item.keys ?? (shortcut && typeof shortcut === "object" && !Array.isArray(shortcut) ? shortcut.keys : null);
13929
+ return typeof keys === "string" && keys.trim() ? keys.trim() : null;
13930
+ }
13931
+ function launcherPageSource(item) {
13932
+ return {
13933
+ id: `page:${String(item.id)}`,
13934
+ load: (
13935
+ /** @type {LauncherPageLoader} */
13936
+ item.items
13937
+ ),
13938
+ order: item.order === "source" ? "source" : "rank",
13939
+ _page: true
13940
+ };
13941
+ }
13942
+ function samePath(left, right) {
13943
+ return left.length === right.length && left.every((item, index) => item === right[index] || String(item.id) === String(right[index].id));
12555
13944
  }
12556
13945
  function launcherItemIcon(item) {
12557
13946
  let visual = item.icon;
@@ -12672,7 +14061,7 @@ function normalizeLauncherItem(item) {
12672
14061
  if (href !== null) normalized.href = href;
12673
14062
  else {
12674
14063
  delete normalized.href;
12675
- if (typeof normalized.invoke !== "function") normalized.disabled = true;
14064
+ if (typeof normalized.invoke !== "function" && !isLauncherPage(normalized)) normalized.disabled = true;
12676
14065
  }
12677
14066
  return normalized;
12678
14067
  }
@@ -12682,6 +14071,7 @@ function normalizeSources(sources) {
12682
14071
  }
12683
14072
 
12684
14073
  // src/components/message/message.js
14074
+ var MESSAGE_ACTION_MIN_TIMEOUT = 8e3;
12685
14075
  var MessageQueue = class {
12686
14076
  #limit;
12687
14077
  #active = [];
@@ -12772,7 +14162,7 @@ var Message = class _Message extends Component {
12772
14162
  closable: true,
12773
14163
  maxVisible: 5
12774
14164
  };
12775
- /** @type {WeakMap<Element, Message>} */
14165
+ /** @type {WeakMap<Element, Message>} Floating region per overlay host. */
12776
14166
  static #floating = /* @__PURE__ */ new WeakMap();
12777
14167
  /** @returns {HTMLElement} */
12778
14168
  render() {
@@ -12784,6 +14174,7 @@ var Message = class _Message extends Component {
12784
14174
  this._createdRoot = created;
12785
14175
  this._original = created ? null : snapshot2(root);
12786
14176
  this._entries = /* @__PURE__ */ new Set();
14177
+ this._floatingHost = null;
12787
14178
  this._queue = new MessageQueue(normalizeLimit(this.options.maxVisible), (entry) => {
12788
14179
  this._activate(entry);
12789
14180
  });
@@ -12799,10 +14190,13 @@ var Message = class _Message extends Component {
12799
14190
  * @returns {MessageHandle}
12800
14191
  */
12801
14192
  show(msg, options = {}) {
14193
+ const action = normalizeMessageAction(options.action);
12802
14194
  const settings = {
12803
- kind: normalizeKind(options.kind ?? this.options.kind),
12804
- timeout: normalizeTimeout(options.timeout ?? this.options.timeout),
12805
- closable: options.closable ?? Boolean(this.options.closable)
14195
+ kind: normalizeKind2(options.kind ?? this.options.kind),
14196
+ timeout: messageTimeout(options.timeout ?? this.options.timeout, action !== null),
14197
+ closable: options.closable ?? Boolean(this.options.closable),
14198
+ action,
14199
+ leading: resolveLeading(options.leading)
12806
14200
  };
12807
14201
  const entry = this._createEntry(msg, settings);
12808
14202
  this._entries.add(entry);
@@ -12863,10 +14257,32 @@ var Message = class _Message extends Component {
12863
14257
  static progress(text3, options = {}) {
12864
14258
  return this.#floatingRegion(options.scope)._progress(text3);
12865
14259
  }
14260
+ /**
14261
+ * Removes a floating region at once, toasts and all, without waiting for them to time out.
14262
+ *
14263
+ * A region already removes itself when its last toast closes, and one whose host has left the
14264
+ * document is replaced on the next toast, so this is only for a host being torn down while
14265
+ * toasts are still showing — an application view unmounting its themed shell, say.
14266
+ * @param {Element|string} scope Element or selector whose nearest Zx theme scope owns the region,
14267
+ * as passed to `show()`.
14268
+ * @returns {boolean} Whether there was a region to remove.
14269
+ */
14270
+ static disposeRegion(scope2) {
14271
+ if (scope2 == null) return false;
14272
+ const context = resolveElement(scope2);
14273
+ if (!context) return false;
14274
+ const host = overlayHost(context);
14275
+ const region = _Message.#floating.get(host);
14276
+ if (!region) return false;
14277
+ _Message.#floating.delete(host);
14278
+ region._disposeFloating();
14279
+ return true;
14280
+ }
12866
14281
  /** @returns {void} */
12867
14282
  destroy() {
12868
14283
  if (this._cleaned) return;
12869
14284
  this._cleaned = true;
14285
+ this._floatingHost = null;
12870
14286
  for (const entry of this._entries) {
12871
14287
  clearEntryTimers(entry);
12872
14288
  entry.node.remove();
@@ -12918,7 +14334,7 @@ var Message = class _Message extends Component {
12918
14334
  }
12919
14335
  /**
12920
14336
  * @param {string|Node} msg
12921
- * @param {{kind: MessageKind, timeout: number, closable: boolean}} settings
14337
+ * @param {{kind: MessageKind, timeout: number, closable: boolean, action?: MessageAction|null, leading?: Node|null}} settings
12922
14338
  * @param {{pct: number, text: string}|null} [progress=null]
12923
14339
  * @returns {any}
12924
14340
  */
@@ -12926,7 +14342,7 @@ var Message = class _Message extends Component {
12926
14342
  const iconHost = h(
12927
14343
  "span",
12928
14344
  { class: "zx-message__icon", ariaHidden: "true" },
12929
- icon(settings.kind, { size: 18 })
14345
+ settings.leading ?? icon(settings.kind, { size: 18 })
12930
14346
  );
12931
14347
  const content = h("div", { class: "zx-message__content" });
12932
14348
  if (isNode(msg)) content.append(msg);
@@ -12947,9 +14363,13 @@ var Message = class _Message extends Component {
12947
14363
  timer: null,
12948
14364
  exitTimer: null,
12949
14365
  closed: false,
14366
+ hovered: false,
14367
+ focused: false,
14368
+ returnFocus: null,
12950
14369
  progressBar: null,
12951
14370
  progressFill: null
12952
14371
  };
14372
+ if (settings.leading) node.dataset.leading = "";
12953
14373
  if (progress) {
12954
14374
  node.dataset.progress = "";
12955
14375
  const fill = h("span", { class: "zx-message__progress-fill" });
@@ -12966,18 +14386,67 @@ var Message = class _Message extends Component {
12966
14386
  entry.progressFill = fill;
12967
14387
  syncProgress(entry, progress);
12968
14388
  }
14389
+ if (settings.action) {
14390
+ const { label, kind, onclick } = settings.action;
14391
+ const control = button({ label, kind, size: "sm" });
14392
+ control.classList.add("zx-message__action");
14393
+ node.append(control);
14394
+ this.listen(control, "click", (event) => {
14395
+ if (entry.closed) return;
14396
+ try {
14397
+ onclick?.(
14398
+ /** @type {MouseEvent} */
14399
+ event
14400
+ );
14401
+ } finally {
14402
+ this._close(entry);
14403
+ }
14404
+ });
14405
+ }
12969
14406
  if (settings.closable) {
12970
14407
  const close = h("button", {
12971
14408
  class: "zx-icon-btn zx-message__close",
12972
14409
  type: "button",
12973
- ariaLabel: "Close message",
12974
- title: "Close"
14410
+ ariaLabel: this._message("message.close", "Close message"),
14411
+ title: this._message("message.closeTitle", "Close")
12975
14412
  }, icon("x", { size: 14 }));
12976
14413
  node.append(close);
12977
14414
  this.listen(close, "click", () => this._close(entry));
12978
14415
  }
12979
- this.listen(node, "mouseenter", () => this._pause(entry));
12980
- this.listen(node, "mouseleave", () => this._resume(entry));
14416
+ this.listen(node, "mouseenter", () => {
14417
+ entry.hovered = true;
14418
+ this._pause(entry);
14419
+ });
14420
+ this.listen(node, "mouseleave", () => {
14421
+ entry.hovered = false;
14422
+ this._resume(entry);
14423
+ });
14424
+ this.listen(node, "focusin", (event) => {
14425
+ if (!entry.focused) {
14426
+ const from = (
14427
+ /** @type {FocusEvent} */
14428
+ event.relatedTarget
14429
+ );
14430
+ entry.returnFocus = isNode(from) && !node.contains(
14431
+ /** @type {Node} */
14432
+ from
14433
+ ) ? from : null;
14434
+ }
14435
+ entry.focused = true;
14436
+ this._pause(entry);
14437
+ });
14438
+ this.listen(node, "focusout", (event) => {
14439
+ const to = (
14440
+ /** @type {FocusEvent} */
14441
+ event.relatedTarget
14442
+ );
14443
+ if (isNode(to) && node.contains(
14444
+ /** @type {Node} */
14445
+ to
14446
+ )) return;
14447
+ entry.focused = false;
14448
+ this._resume(entry);
14449
+ });
12981
14450
  return entry;
12982
14451
  }
12983
14452
  /** @param {any} entry @returns {void} */
@@ -12989,7 +14458,7 @@ var Message = class _Message extends Component {
12989
14458
  }
12990
14459
  /** @param {any} entry @returns {void} */
12991
14460
  _schedule(entry) {
12992
- if (entry.closed || entry.remaining <= 0) return;
14461
+ if (entry.closed || entry.remaining <= 0 || entry.hovered || entry.focused) return;
12993
14462
  entry.startedAt = Date.now();
12994
14463
  entry.timer = setTimeout(() => this._close(entry), entry.remaining);
12995
14464
  }
@@ -13002,7 +14471,7 @@ var Message = class _Message extends Component {
13002
14471
  }
13003
14472
  /** @param {any} entry @returns {void} */
13004
14473
  _resume(entry) {
13005
- if (entry.closed || entry.timer !== null) return;
14474
+ if (entry.closed || entry.timer !== null || entry.hovered || entry.focused) return;
13006
14475
  if (entry.remaining <= 0) {
13007
14476
  this._close(entry);
13008
14477
  return;
@@ -13014,10 +14483,12 @@ var Message = class _Message extends Component {
13014
14483
  if (entry.closed) return;
13015
14484
  entry.closed = true;
13016
14485
  clearEntryTimers(entry);
14486
+ returnFocus(entry);
13017
14487
  if (!this._queue.isActive(entry)) {
13018
14488
  this._queue.remove(entry);
13019
14489
  entry.node.remove();
13020
14490
  this._entries.delete(entry);
14491
+ this._releaseIfEmpty();
13021
14492
  return;
13022
14493
  }
13023
14494
  entry.node.dataset.state = "closing";
@@ -13038,12 +14509,52 @@ var Message = class _Message extends Component {
13038
14509
  entry.node.remove();
13039
14510
  this._entries.delete(entry);
13040
14511
  this._queue.remove(entry);
14512
+ this._releaseIfEmpty();
14513
+ }
14514
+ /**
14515
+ * Takes a floating region out of the document once its last toast has gone, so an emptied host
14516
+ * is not left holding a region nobody uses. Inline areas stay: their element is the caller's.
14517
+ * @returns {void}
14518
+ */
14519
+ _releaseIfEmpty() {
14520
+ if (!this._floatingHost || this._entries.size > 0) return;
14521
+ _Message.#release(this);
14522
+ }
14523
+ /**
14524
+ * Destroys a floating region and removes its element.
14525
+ * @returns {void}
14526
+ */
14527
+ _disposeFloating() {
14528
+ const region = this.el;
14529
+ this.destroy();
14530
+ region.remove();
14531
+ }
14532
+ /**
14533
+ * Resolves a message through the host translator, falling back to the built-in English text.
14534
+ * @param {string} key Message key.
14535
+ * @param {string} fallback English default.
14536
+ * @returns {string}
14537
+ */
14538
+ _message(key, fallback) {
14539
+ const message = this.msg(key);
14540
+ return message === key ? fallback : message;
13041
14541
  }
13042
- /** @param {Element|string|null|undefined} scope @returns {Message} */
14542
+ /**
14543
+ * Finds or creates the floating region for a scope. A cached region is reused only while it is
14544
+ * still where it was put: a host emptied by `replaceChildren()` would otherwise keep receiving
14545
+ * toasts in a node nobody can see.
14546
+ * @param {Element|string|null|undefined} scope
14547
+ * @returns {Message}
14548
+ */
13043
14549
  static #floatingRegion(scope2) {
13044
- const host = overlayHost(scope2 ?? null);
14550
+ let host = overlayHost(scope2 ?? null);
14551
+ if (!host.isConnected) host = overlayHost(null);
13045
14552
  const existing = this.#floating.get(host);
13046
- if (existing) return existing;
14553
+ if (existing && existing.el.isConnected && existing.el.parentElement === host) return existing;
14554
+ if (existing) {
14555
+ this.#floating.delete(host);
14556
+ existing._disposeFloating();
14557
+ }
13047
14558
  const region = h("div", {
13048
14559
  class: "zx-message-region",
13049
14560
  popover: "manual",
@@ -13052,12 +14563,19 @@ var Message = class _Message extends Component {
13052
14563
  });
13053
14564
  host.append(region);
13054
14565
  const floating = new _Message(region);
14566
+ floating._floatingHost = host;
13055
14567
  this.#floating.set(host, floating);
13056
14568
  if (typeof region.showPopover === "function") region.showPopover();
13057
14569
  return floating;
13058
14570
  }
14571
+ /** @param {Message} region Floating region whose last toast closed. @returns {void} */
14572
+ static #release(region) {
14573
+ const host = region._floatingHost;
14574
+ if (host && _Message.#floating.get(host) === region) _Message.#floating.delete(host);
14575
+ region._disposeFloating();
14576
+ }
13059
14577
  };
13060
- function normalizeKind(value) {
14578
+ function normalizeKind2(value) {
13061
14579
  if (value === "danger") return "error";
13062
14580
  return ["success", "warning", "error"].includes(String(value)) ? (
13063
14581
  /** @type {MessageKind} */
@@ -13068,6 +14586,50 @@ function normalizeTimeout(value) {
13068
14586
  const timeout = Number(value);
13069
14587
  return Number.isFinite(timeout) ? Math.max(0, timeout) : 4e3;
13070
14588
  }
14589
+ function messageTimeout(timeout, hasAction = false) {
14590
+ const settled = normalizeTimeout(timeout);
14591
+ return hasAction && settled > 0 ? Math.max(settled, MESSAGE_ACTION_MIN_TIMEOUT) : settled;
14592
+ }
14593
+ function normalizeMessageAction(value) {
14594
+ if (!value || typeof value !== "object") return null;
14595
+ const action = (
14596
+ /** @type {Record<string, unknown>} */
14597
+ value
14598
+ );
14599
+ const label = action.label == null ? "" : String(action.label).trim();
14600
+ if (!label) return null;
14601
+ const kind = ["primary", "danger", "ghost"].includes(String(action.kind)) ? (
14602
+ /** @type {'primary'|'danger'|'ghost'} */
14603
+ action.kind
14604
+ ) : "default";
14605
+ return {
14606
+ label,
14607
+ kind,
14608
+ onclick: typeof action.onclick === "function" ? (
14609
+ /** @type {(event: MouseEvent) => void} */
14610
+ action.onclick
14611
+ ) : null
14612
+ };
14613
+ }
14614
+ function resolveLeading(value) {
14615
+ if (isNode(value)) return (
14616
+ /** @type {Node} */
14617
+ value
14618
+ );
14619
+ if (value && typeof value === "object" && typeof value.toElement === "function") {
14620
+ const element = value.toElement();
14621
+ return isNode(element) ? element : null;
14622
+ }
14623
+ return null;
14624
+ }
14625
+ function returnFocus(entry) {
14626
+ const root = entry.node.getRootNode?.();
14627
+ const active = root?.activeElement ?? globalThis.document?.activeElement;
14628
+ if (!active || !entry.node.contains(active)) return;
14629
+ const target = entry.returnFocus;
14630
+ entry.returnFocus = null;
14631
+ if (target?.isConnected && typeof target.focus === "function") target.focus({ preventScroll: true });
14632
+ }
13071
14633
  function normalizeLimit(value) {
13072
14634
  const limit = Number(value);
13073
14635
  return Number.isInteger(limit) && limit > 0 ? limit : 5;
@@ -13103,7 +14665,7 @@ function snapshot2(element) {
13103
14665
  }
13104
14666
  function restore2(element, state) {
13105
14667
  for (const attribute of Array.from(element.attributes)) element.removeAttribute(attribute.name);
13106
- for (const [name, value] of state.attributes) element.setAttribute(name, value);
14668
+ for (const [name2, value] of state.attributes) element.setAttribute(name2, value);
13107
14669
  element.replaceChildren(...state.children);
13108
14670
  }
13109
14671
 
@@ -13472,7 +15034,7 @@ var Calendar = class extends Component {
13472
15034
  if (!this.el) this.el = root;
13473
15035
  this._createdRoot = createdRoot;
13474
15036
  this._original = this._createdRoot ? null : {
13475
- attributes: Array.from(root.attributes).map(({ name, value }) => [name, value]),
15037
+ attributes: Array.from(root.attributes).map(({ name: name2, value }) => [name2, value]),
13476
15038
  children: Array.from(root.childNodes)
13477
15039
  };
13478
15040
  this._views = normalizeViews(this.options.views);
@@ -13506,7 +15068,7 @@ var Calendar = class extends Component {
13506
15068
  ref: "views",
13507
15069
  class: "zx-calendar__views",
13508
15070
  role: "group",
13509
- ariaLabel: "Calendar view"
15071
+ ariaLabel: this._message("calendar.viewSwitch", "Calendar view")
13510
15072
  }, this._views.map((view) => h("button", {
13511
15073
  type: "button",
13512
15074
  class: "zx-calendar__view-button",
@@ -13682,7 +15244,7 @@ var Calendar = class extends Component {
13682
15244
  super.destroy();
13683
15245
  if (!this._createdRoot && original) {
13684
15246
  for (const attribute of Array.from(this.el.attributes)) this.el.removeAttribute(attribute.name);
13685
- for (const [name, value] of original.attributes) this.el.setAttribute(name, value);
15247
+ for (const [name2, value] of original.attributes) this.el.setAttribute(name2, value);
13686
15248
  this.el.replaceChildren(...original.children);
13687
15249
  }
13688
15250
  }
@@ -13724,8 +15286,8 @@ var Calendar = class extends Component {
13724
15286
  });
13725
15287
  this._range = range;
13726
15288
  this.refs.heading.textContent = this._rangeTitle(range);
13727
- for (const button of this.refs.views.querySelectorAll("[data-view]")) {
13728
- button.setAttribute("aria-pressed", String(button.dataset.view === this._view));
15289
+ for (const button2 of this.refs.views.querySelectorAll("[data-view]")) {
15290
+ button2.setAttribute("aria-pressed", String(button2.dataset.view === this._view));
13729
15291
  }
13730
15292
  const content = this._view === "agenda" ? this._renderAgenda(range) : this._view === "day" || this._view === "week" ? this._renderTimeGrid(range) : this._view === "month" ? this._renderMonth(range) : this._renderYear(range);
13731
15293
  this.refs.body.replaceChildren(content);
@@ -13954,7 +15516,7 @@ var Calendar = class extends Component {
13954
15516
  eventCount: String(count)
13955
15517
  },
13956
15518
  ariaCurrent: sameCalendarDay(day, this._now()) ? "date" : null,
13957
- ariaLabel: `${this._format(day, { dateStyle: "full" })}, ${count} ${count === 1 ? "event" : "events"}`,
15519
+ ariaLabel: this._dayCountLabel(day, count),
13958
15520
  disabled: this.options.disabled
13959
15521
  }, this._format(day, { day: "numeric" })));
13960
15522
  }
@@ -14024,7 +15586,7 @@ var Calendar = class extends Component {
14024
15586
  activity: String(Math.min(3, count))
14025
15587
  },
14026
15588
  ariaCurrent: sameCalendarDay(day, this._now()) ? "date" : null,
14027
- ariaLabel: `${this._format(day, { dateStyle: "full" })}, ${count} ${count === 1 ? "event" : "events"}`,
15589
+ ariaLabel: this._dayCountLabel(day, count),
14028
15590
  disabled: this.options.disabled
14029
15591
  }, String(day.getDate())));
14030
15592
  }
@@ -14049,7 +15611,7 @@ var Calendar = class extends Component {
14049
15611
  },
14050
15612
  style: event.color ? { "--zx-calendar-event-color": event.color } : null
14051
15613
  });
14052
- const button = h("button", {
15614
+ const button2 = h("button", {
14053
15615
  type: "button",
14054
15616
  class: "zx-calendar__event-action",
14055
15617
  dataset: { eventAction: "activate", eventId: key },
@@ -14057,15 +15619,15 @@ var Calendar = class extends Component {
14057
15619
  disabled: this.options.disabled
14058
15620
  });
14059
15621
  const custom = typeof this.options.renderEvent === "function" ? this.options.renderEvent(cloneCalendarEvent(event)) : null;
14060
- if (custom instanceof Node) button.append(custom);
14061
- else if (typeof custom === "string") button.append(document.createTextNode(custom));
15622
+ if (custom instanceof Node) button2.append(custom);
15623
+ else if (typeof custom === "string") button2.append(document.createTextNode(custom));
14062
15624
  else {
14063
15625
  const time = h("span", { class: "zx-calendar__event-time" }, this._eventTime(event, context.day));
14064
15626
  const title = h("span", { class: "zx-calendar__event-title" }, event.title);
14065
- button.append(time, title);
14066
- if (event.location) button.append(h("span", { class: "zx-calendar__event-location" }, event.location));
15627
+ button2.append(time, title);
15628
+ if (event.location) button2.append(h("span", { class: "zx-calendar__event-location" }, event.location));
14067
15629
  }
14068
- wrapper.append(button);
15630
+ wrapper.append(button2);
14069
15631
  if (context.resizable && this._canResize(event)) {
14070
15632
  wrapper.append(h("button", {
14071
15633
  type: "button",
@@ -14755,6 +16317,31 @@ var Calendar = class extends Component {
14755
16317
  if (!this._destroyed) this.refs.status.textContent = message;
14756
16318
  });
14757
16319
  }
16320
+ /**
16321
+ * Names a month or year cell by its date and how many events it holds. Singular and plural are
16322
+ * separate keys because a language that inflects the noun cannot reach it from one template.
16323
+ * @param {Date} day Day the cell stands for.
16324
+ * @param {number} count Events on that day.
16325
+ * @returns {string}
16326
+ */
16327
+ _dayCountLabel(day, count) {
16328
+ const date = this._format(day, { dateStyle: "full" });
16329
+ return count === 1 ? this._message("calendar.dayEvent", "%1, %2 event", date, count) : this._message("calendar.dayEvents", "%1, %2 events", date, count);
16330
+ }
16331
+ /**
16332
+ * Resolves a message through the host translator, falling back to the built-in English text.
16333
+ *
16334
+ * Distinct from `msg()`: the built-in `msg` option answers the calendar's own vocabulary, so a
16335
+ * dotted key falls past it to the host translator, which is what these strings never had.
16336
+ * @param {string} key Message key.
16337
+ * @param {string} fallback Built-in text, with `%1`-style placeholders.
16338
+ * @param {...unknown} args Interpolation values.
16339
+ * @returns {string}
16340
+ */
16341
+ _message(key, fallback, ...args) {
16342
+ const message = this.msg(key, ...args);
16343
+ return message === key ? printf(fallback, args) : message;
16344
+ }
14758
16345
  };
14759
16346
  function normalizeViews(value) {
14760
16347
  if (!Array.isArray(value) || value.length === 0) throw new TypeError("Calendar views must be a non-empty array");
@@ -14867,7 +16454,7 @@ function cssEscape(value) {
14867
16454
  var Fieldset = class extends Component {
14868
16455
  static cssName = "fieldset";
14869
16456
  /** @type {FieldsetOptions} */
14870
- static defaults = { title: "", columns: 1, fields: {} };
16457
+ static defaults = { title: "", columns: 1, fields: {}, idPrefix: null };
14871
16458
  /** @returns {HTMLElement} */
14872
16459
  render() {
14873
16460
  const root = (
@@ -14879,6 +16466,7 @@ var Fieldset = class extends Component {
14879
16466
  this._origins = /* @__PURE__ */ new Map();
14880
16467
  this._fieldIdAttributes = /* @__PURE__ */ new Map();
14881
16468
  this._ownedNodes = [];
16469
+ this._idPrefix = this.options.idPrefix ? String(this.options.idPrefix) : uid("zx-fieldset");
14882
16470
  this._columnAttribute = root.getAttribute("data-columns");
14883
16471
  const columns = [1, 2, 3].includes(Number(this.options.columns)) ? Number(this.options.columns) : 1;
14884
16472
  root.dataset.columns = String(columns);
@@ -14895,7 +16483,9 @@ var Fieldset = class extends Component {
14895
16483
  return root;
14896
16484
  }
14897
16485
  /**
14898
- * Adds a field from options, an existing field, an element, or a component.
16486
+ * Adds a field from options, an existing field, an element, or a component. The key becomes the
16487
+ * field's `name` and its `data-field-id`; the control's DOM id is `<idPrefix>-<key>`, unless the
16488
+ * field options give an `id` of their own that differs from the key.
14899
16489
  * @param {string} id Logical field identifier.
14900
16490
  * @param {FieldOptions|Field|Element|Component} field Field source.
14901
16491
  * @returns {Field}
@@ -14910,7 +16500,8 @@ var Fieldset = class extends Component {
14910
16500
  } else if (isElement(field) || field instanceof Component) {
14911
16501
  const source = field;
14912
16502
  instance = new Field(null, {
14913
- id: key,
16503
+ id: this._controlId(key),
16504
+ name: key,
14914
16505
  type: "custom",
14915
16506
  label: key,
14916
16507
  value: readExternalValue(source),
@@ -14920,7 +16511,12 @@ var Fieldset = class extends Component {
14920
16511
  owned = true;
14921
16512
  } else {
14922
16513
  const options = field && typeof field === "object" ? field : {};
14923
- instance = new Field(null, { ...options, id: options.id ?? key });
16514
+ const explicit = options.id != null && String(options.id) !== "" && String(options.id) !== key;
16515
+ instance = new Field(null, {
16516
+ ...options,
16517
+ id: explicit ? String(options.id) : this._controlId(key),
16518
+ name: options.name ?? key
16519
+ });
14924
16520
  owned = true;
14925
16521
  }
14926
16522
  if (!owned) {
@@ -15050,6 +16646,14 @@ var Fieldset = class extends Component {
15050
16646
  for (const field of this._fields.values()) field.clearHighlight();
15051
16647
  return this;
15052
16648
  }
16649
+ /**
16650
+ * The DOM id generated for a field key: unique per instance unless `idPrefix` was given.
16651
+ * @param {string} key Field key.
16652
+ * @returns {string}
16653
+ */
16654
+ _controlId(key) {
16655
+ return `${this._idPrefix}-${key.trim().replace(/\s+/g, "-")}`;
16656
+ }
15053
16657
  /** @param {Field} field @returns {void} */
15054
16658
  _releaseField(field) {
15055
16659
  const fieldId = this._fieldIdAttributes.get(field);
@@ -15082,7 +16686,7 @@ function externalAdapter(source) {
15082
16686
  const el = isElement(source) ? source : source.toElement();
15083
16687
  const parent = el.parentNode;
15084
16688
  const next = el.nextSibling;
15085
- const attributes = new Map(["id", "aria-labelledby", "aria-describedby", "aria-disabled", "aria-invalid"].map((name) => [name, el.getAttribute(name)]));
16689
+ const attributes = new Map(["id", "aria-labelledby", "aria-describedby", "aria-disabled", "aria-invalid"].map((name2) => [name2, el.getAttribute(name2)]));
15086
16690
  const originalDisabled = "disabled" in el ? el.disabled : void 0;
15087
16691
  const originalValue = "value" in el ? el.value : void 0;
15088
16692
  return {
@@ -15108,9 +16712,9 @@ function externalAdapter(source) {
15108
16712
  else el.setAttribute("aria-disabled", String(disabled));
15109
16713
  },
15110
16714
  destroy: () => {
15111
- for (const [name, value] of attributes) {
15112
- if (value === null) el.removeAttribute(name);
15113
- else el.setAttribute(name, value);
16715
+ for (const [name2, value] of attributes) {
16716
+ if (value === null) el.removeAttribute(name2);
16717
+ else el.setAttribute(name2, value);
15114
16718
  }
15115
16719
  if (originalDisabled !== void 0) el.disabled = originalDisabled;
15116
16720
  if (originalValue !== void 0) el.value = originalValue;
@@ -15138,6 +16742,7 @@ var Form = class extends Component {
15138
16742
  fieldsets: [],
15139
16743
  actions: [],
15140
16744
  novalidate: true,
16745
+ idPrefix: null,
15141
16746
  msg: {
15142
16747
  "form.required": "This field is required.",
15143
16748
  "form.int": "Enter a whole number.",
@@ -15154,6 +16759,7 @@ var Form = class extends Component {
15154
16759
  this._ownedFieldsets = /* @__PURE__ */ new Set();
15155
16760
  this._origins = /* @__PURE__ */ new Map();
15156
16761
  this._ownedNodes = [];
16762
+ this._idPrefix = this.options.idPrefix ? String(this.options.idPrefix) : uid("zx-form");
15157
16763
  this._novalidateAttribute = root.getAttribute("novalidate");
15158
16764
  root.toggleAttribute("novalidate", Boolean(this.options.novalidate));
15159
16765
  this._body = h("div", { class: "zx-form__body" });
@@ -15175,13 +16781,14 @@ var Form = class extends Component {
15175
16781
  return root;
15176
16782
  }
15177
16783
  /**
15178
- * Adds an existing fieldset or constructs one from options.
16784
+ * Adds an existing fieldset or constructs one from options. A constructed fieldset generates its
16785
+ * field ids from the form's `idPrefix` unless its options carry their own.
15179
16786
  * @param {Fieldset|import('../fieldset/fieldset.js').FieldsetOptions} fieldset Fieldset source.
15180
16787
  * @returns {Fieldset}
15181
16788
  */
15182
16789
  addFieldset(fieldset) {
15183
16790
  const owned = !(fieldset instanceof Fieldset);
15184
- const instance = owned ? new Fieldset(null, fieldset ?? {}) : fieldset;
16791
+ const instance = owned ? new Fieldset(null, { ...fieldset ?? {}, idPrefix: fieldset?.idPrefix ?? this._idPrefix }) : fieldset;
15185
16792
  if (this._fieldsets.includes(instance)) return instance;
15186
16793
  if (owned) this._ownedFieldsets.add(instance);
15187
16794
  else this._origins.set(instance, { parent: instance.el.parentNode, next: instance.el.nextSibling });
@@ -15339,9 +16946,9 @@ var Form = class extends Component {
15339
16946
  }
15340
16947
  /** @param {Event} event @returns {void} */
15341
16948
  _handleAction(event) {
15342
- const button = event.target instanceof Element ? event.target.closest("[data-action-index]") : null;
15343
- if (!button || !this._actions.contains(button)) return;
15344
- const action = this._actionDescriptors[Number(button.dataset.actionIndex)];
16949
+ const button2 = event.target instanceof Element ? event.target.closest("[data-action-index]") : null;
16950
+ if (!button2 || !this._actions.contains(button2)) return;
16951
+ const action = this._actionDescriptors[Number(button2.dataset.actionIndex)];
15345
16952
  const callback = action?.onClick ?? action?.onclick ?? action?.handler;
15346
16953
  if (typeof callback === "function") callback(event, this);
15347
16954
  }
@@ -15373,6 +16980,7 @@ function isEmpty(value) {
15373
16980
  function registerFieldAdapters() {
15374
16981
  registerSelectFieldAdapter();
15375
16982
  registerChecklistFieldAdapter();
16983
+ registerCodeEditorFieldAdapter();
15376
16984
  registerDateboxFieldAdapters();
15377
16985
  registerDatePickerFieldAdapters();
15378
16986
  registerDateRangeFieldAdapter();
@@ -15433,16 +17041,16 @@ function deriveLocale(client) {
15433
17041
  return client.locale ?? client.language ?? client.config?.locale ?? client.metadata?.locale ?? null;
15434
17042
  }
15435
17043
  function operationFor(client, action, resource) {
15436
- const name = String(resource ?? "").trim();
15437
- if (!name) throw new TypeError("ZeyOS resource must not be empty");
15438
- const resourceOperations = typeof client.schema?.operations === "function" ? client.schema.operations(name) : [];
17044
+ const name2 = String(resource ?? "").trim();
17045
+ if (!name2) throw new TypeError("ZeyOS resource must not be empty");
17046
+ const resourceOperations = typeof client.schema?.operations === "function" ? client.schema.operations(name2) : [];
15439
17047
  if (Array.isArray(resourceOperations) && resourceOperations.length) {
15440
17048
  const exactResourceMatches = resourceOperations.filter((operation) => typeof operation === "string" && operation.toLowerCase().startsWith(action)).sort((left, right) => left.length - right.length);
15441
17049
  if (exactResourceMatches.length) return exactResourceMatches[0];
15442
17050
  }
15443
17051
  const known = Array.isArray(resourceOperations) && resourceOperations.length ? resourceOperations : typeof client.schema?.operationIds === "function" ? client.schema.operationIds() : Object.keys(client.api);
15444
- if (known.includes(name) && name.toLowerCase().startsWith(action)) return name;
15445
- const normalizedResource = normalizeResource(name);
17052
+ if (known.includes(name2) && name2.toLowerCase().startsWith(action)) return name2;
17053
+ const normalizedResource = normalizeResource(name2);
15446
17054
  const singular = singularize(normalizedResource);
15447
17055
  const matches = known.filter((operation) => {
15448
17056
  if (typeof operation !== "string" || !operation.toLowerCase().startsWith(action)) return false;
@@ -15450,11 +17058,11 @@ function operationFor(client, action, resource) {
15450
17058
  return suffix === normalizedResource || suffix === singular || singularize(suffix) === singular;
15451
17059
  });
15452
17060
  if (matches.length) return matches.sort((left, right) => left.length - right.length)[0];
15453
- const candidates = action === "list" ? [`list${pascalCase(name)}`] : [`${action}${pascalCase(singularizeName(name))}`, `${action}${pascalCase(name)}`];
17061
+ const candidates = action === "list" ? [`list${pascalCase(name2)}`] : [`${action}${pascalCase(singularizeName(name2))}`, `${action}${pascalCase(name2)}`];
15454
17062
  const apiKeys = new Set(Object.keys(client.api));
15455
17063
  const fallback = candidates.find((candidate) => apiKeys.has(candidate));
15456
17064
  if (fallback) return fallback;
15457
- throw new RangeError(`No ${action} operation found for ZeyOS resource "${name}"`);
17065
+ throw new RangeError(`No ${action} operation found for ZeyOS resource "${name2}"`);
15458
17066
  }
15459
17067
  function isRecord(value) {
15460
17068
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -15590,31 +17198,31 @@ var moduleAliases = Object.freeze({
15590
17198
  });
15591
17199
  var glyphColors = Object.freeze({ light: "#ffffff", dark: "#141414" });
15592
17200
  var overrides = /* @__PURE__ */ Object.create(null);
15593
- function normalizeModuleName(name) {
15594
- const key = String(name ?? "").trim().toLowerCase().replaceAll(/[._\s]+/g, "-");
17201
+ function normalizeModuleName(name2) {
17202
+ const key = String(name2 ?? "").trim().toLowerCase().replaceAll(/[._\s]+/g, "-");
15595
17203
  if (!key) return "default";
15596
17204
  if (Object.hasOwn(overrides, key) || Object.hasOwn(zeyosModules, key)) return key;
15597
17205
  const alias = moduleAliases[key];
15598
17206
  if (alias) return alias;
15599
17207
  return "default";
15600
17208
  }
15601
- function moduleInfo(name) {
15602
- const key = normalizeModuleName(name);
17209
+ function moduleInfo(name2) {
17210
+ const key = normalizeModuleName(name2);
15603
17211
  return overrides[key] ?? zeyosModules[key] ?? zeyosModules.default;
15604
17212
  }
15605
- function moduleColor(name) {
15606
- return moduleInfo(name).color;
17213
+ function moduleColor(name2) {
17214
+ return moduleInfo(name2).color;
15607
17215
  }
15608
- function moduleIconName(name, { standard = false } = {}) {
15609
- const info = moduleInfo(name);
17216
+ function moduleIconName(name2, { standard = false } = {}) {
17217
+ const info = moduleInfo(name2);
15610
17218
  return standard ? info.fa : info.icon;
15611
17219
  }
15612
17220
  function moduleKeys() {
15613
17221
  return [.../* @__PURE__ */ new Set([...Object.keys(zeyosModules), ...Object.keys(overrides)])].sort();
15614
17222
  }
15615
17223
  function registerModules(modules) {
15616
- for (const [name, value] of Object.entries(modules)) {
15617
- const key = String(name).trim().toLowerCase().replaceAll(/[._\s]+/g, "-");
17224
+ for (const [name2, value] of Object.entries(modules)) {
17225
+ const key = String(name2).trim().toLowerCase().replaceAll(/[._\s]+/g, "-");
15618
17226
  const patch = typeof value === "string" ? { color: value } : value;
15619
17227
  const base = overrides[key] ?? zeyosModules[key] ?? zeyosModules.default;
15620
17228
  overrides[key] = Object.freeze({
@@ -15651,12 +17259,12 @@ var ZEYOS_ICON_KIT = "https://kit.fontawesome.com/ae8320b210.js";
15651
17259
  function useZeyosIcons(options = {}) {
15652
17260
  return loadFontAwesome({ kit: ZEYOS_ICON_KIT, ...options });
15653
17261
  }
15654
- function moduleIcon(name, options = {}) {
17262
+ function moduleIcon(name2, options = {}) {
15655
17263
  const { standard = false, ...rest } = options;
15656
- const info = moduleInfo(name);
17264
+ const info = moduleInfo(name2);
15657
17265
  return icon(standard ? `fa:${info.fa}` : `kit:${info.icon}`, rest);
15658
17266
  }
15659
- function moduleChip(name, options = {}) {
17267
+ function moduleChip(name2, options = {}) {
15660
17268
  const {
15661
17269
  size = 24,
15662
17270
  iconSize = "52%",
@@ -15667,13 +17275,13 @@ function moduleChip(name, options = {}) {
15667
17275
  badge = null,
15668
17276
  selected = false
15669
17277
  } = options;
15670
- const key = normalizeModuleName(name);
15671
- const info = moduleInfo(name);
17278
+ const key = normalizeModuleName(name2);
17279
+ const info = moduleInfo(name2);
15672
17280
  const background = isCssColor(color) ? color : info.color;
15673
17281
  const chip = appIcon({
15674
17282
  // AppIcon owns glyph sizing. Keep the provider glyph at 1em so the percentage is not applied
15675
17283
  // once to the wrapper and a second time to a Font Awesome <i>.
15676
- icon: options.icon ?? moduleIcon(name, { size: "1em", standard }),
17284
+ icon: options.icon ?? moduleIcon(name2, { size: "1em", standard }),
15677
17285
  size,
15678
17286
  iconSize,
15679
17287
  color: background,
@@ -15691,8 +17299,8 @@ function moduleChip(name, options = {}) {
15691
17299
  if (title) chip.title = info.label;
15692
17300
  return chip;
15693
17301
  }
15694
- function zeyosAppIcon(name, options = {}) {
15695
- return moduleChip(name, { shape: "circle", ...options });
17302
+ function zeyosAppIcon(name2, options = {}) {
17303
+ return moduleChip(name2, { shape: "circle", ...options });
15696
17304
  }
15697
17305
 
15698
17306
  // src/zeyos/launcher.js
@@ -16193,8 +17801,12 @@ function buildZeyosFormConfig(client, resource, opts = {}) {
16193
17801
  const fieldMeta = resolveFields(client, resource, opts);
16194
17802
  const fields = {};
16195
17803
  const entityFields = [];
17804
+ const dateKinds = /* @__PURE__ */ new Map();
16196
17805
  for (const meta of fieldMeta) {
16197
17806
  const descriptor = fieldToZxField(meta);
17807
+ if (descriptor.type === "date" || descriptor.type === "datetime") {
17808
+ dateKinds.set(String(meta.id), descriptor.type);
17809
+ }
16198
17810
  const referencedResource2 = descriptor.type === "zxselect" && typeof descriptor.props?.entity === "string" ? descriptor.props.entity : null;
16199
17811
  if (referencedResource2) {
16200
17812
  entityFields.push({ id: String(meta.id), resource: referencedResource2 });
@@ -16218,16 +17830,20 @@ function buildZeyosFormConfig(client, resource, opts = {}) {
16218
17830
  fields
16219
17831
  }]
16220
17832
  },
16221
- mapFromRecord: (record) => mapFromRecord(record, fieldMeta),
16222
- mapToRecord: (values) => mapToRecord(values, fieldMeta)
17833
+ mapFromRecord: (record) => mapFromRecord(record, fieldMeta, dateKinds),
17834
+ mapToRecord: (values) => mapToRecord(values, fieldMeta, dateKinds)
16223
17835
  };
16224
17836
  }
16225
17837
  function zeyosForm(client, resource, opts = {}) {
16226
17838
  if (opts.onSaved != null && typeof opts.onSaved !== "function") {
16227
17839
  throw new TypeError("zeyosForm onSaved must be a function");
16228
17840
  }
17841
+ const notify = opts.notify !== false;
16229
17842
  const config2 = buildZeyosFormConfig(client, resource, opts);
16230
- const facade = connect(client);
17843
+ const toastScope = () => opts.scope ?? form.el;
17844
+ const facade = connect(client, {
17845
+ onError: (error) => notify ? Message.error(errorMessage(error), { scope: toastScope() }) : void 0
17846
+ });
16231
17847
  const form = new Form(null, config2.formOptions);
16232
17848
  let currentId = opts.value?.ID ?? opts.id ?? null;
16233
17849
  let destroyed = false;
@@ -16257,6 +17873,7 @@ function zeyosForm(client, resource, opts = {}) {
16257
17873
  if (destroyed) return Promise.reject(new Error("Cannot save a destroyed ZeyOS form"));
16258
17874
  if (savePending) return savePending;
16259
17875
  if (!form.submit()) return Promise.resolve(null);
17876
+ const host = notify ? overlayHost(toastScope()) : null;
16260
17877
  savePending = (async () => {
16261
17878
  let payload;
16262
17879
  try {
@@ -16277,7 +17894,7 @@ function zeyosForm(client, resource, opts = {}) {
16277
17894
  throw error;
16278
17895
  }
16279
17896
  }
16280
- Message.success(`${recordLabel(record, resource)} saved successfully.`);
17897
+ if (host) Message.success(`${recordLabel(record, resource)} saved successfully.`, { scope: host });
16281
17898
  return record;
16282
17899
  })().finally(() => {
16283
17900
  savePending = null;
@@ -16298,25 +17915,39 @@ function zeyosForm(client, resource, opts = {}) {
16298
17915
  }
16299
17916
  };
16300
17917
  }
16301
- function mapFromRecord(record, meta) {
17918
+ function mapFromRecord(record, meta, dateKinds) {
16302
17919
  const values = {};
16303
17920
  for (const field of meta) {
16304
17921
  const id = String(field.id);
16305
17922
  const value = record[id];
16306
- values[id] = isDateField(field) && value != null && value !== "" ? unixSecondsToDate(
17923
+ const kind = dateKinds.get(id);
17924
+ if (!kind || value == null || value === "") values[id] = value;
17925
+ else if (kind === "date") values[id] = unixSecondsToCalendarDay(
17926
+ /** @type {number|string} */
17927
+ value,
17928
+ field.timeZone
17929
+ );
17930
+ else values[id] = unixSecondsToDate(
16307
17931
  /** @type {number|string} */
16308
17932
  value
16309
- ) : value;
17933
+ );
16310
17934
  }
16311
17935
  return values;
16312
17936
  }
16313
- function mapToRecord(values, meta) {
17937
+ function mapToRecord(values, meta, dateKinds) {
16314
17938
  const record = {};
16315
17939
  for (const field of meta) {
16316
17940
  const id = String(field.id);
16317
17941
  let value = values[id];
16318
- if (isDateField(field)) {
16319
- value = value == null || value === "" ? null : dateToUnixSeconds(
17942
+ const kind = dateKinds.get(id);
17943
+ if (kind) {
17944
+ if (value == null || value === "") value = null;
17945
+ else if (kind === "date") value = calendarDayToUnixSeconds(
17946
+ /** @type {Date} */
17947
+ value,
17948
+ field.timeZone
17949
+ );
17950
+ else value = dateToUnixSeconds(
16320
17951
  /** @type {Date} */
16321
17952
  value
16322
17953
  );
@@ -16328,10 +17959,6 @@ function mapToRecord(values, meta) {
16328
17959
  }
16329
17960
  return record;
16330
17961
  }
16331
- function isDateField(meta) {
16332
- const descriptor = fieldToZxField(meta);
16333
- return descriptor.type === "date" || descriptor.type === "datetime";
16334
- }
16335
17962
  function hasEnum(meta) {
16336
17963
  return meta.enums != null || meta.enum != null;
16337
17964
  }
@@ -16361,10 +17988,34 @@ function recordLabel(record, resource) {
16361
17988
  }
16362
17989
 
16363
17990
  // src/zeyos/table.js
17991
+ var TABLE_OPTION_KEYS = [
17992
+ "variant",
17993
+ "zebra",
17994
+ "stickyHeader",
17995
+ "responsive",
17996
+ "growing",
17997
+ "emptyText",
17998
+ "rowClass",
17999
+ "rowKind",
18000
+ "rowKinds",
18001
+ "columnVisibility",
18002
+ "columnReorder",
18003
+ "hiddenColumns",
18004
+ "onrowdblclick",
18005
+ "onsort",
18006
+ "onselectionchange",
18007
+ "ondatachange",
18008
+ "onstackedchange",
18009
+ "ongrow",
18010
+ "oncolumnvisibilitychange",
18011
+ "oncolumnorderchange"
18012
+ ];
16364
18013
  function buildZeyosTableConfig(client, resource, opts = {}) {
16365
18014
  const fieldMeta = resolveFields(client, resource, {
16366
18015
  fields: opts.fields,
16367
- labels: opts.labels
18016
+ labels: opts.labels,
18017
+ formats: opts.formats,
18018
+ timeZone: opts.timeZone
16368
18019
  });
16369
18020
  const projection = {};
16370
18021
  const columns = fieldMeta.map((sourceMeta) => {
@@ -16388,11 +18039,16 @@ function buildZeyosTableConfig(client, resource, opts = {}) {
16388
18039
  if (typeof rowId === "string" && !Object.prototype.hasOwnProperty.call(projection, rowId)) {
16389
18040
  projection[rowId] = rowId;
16390
18041
  }
18042
+ const passed = {};
18043
+ for (const key of TABLE_OPTION_KEYS) {
18044
+ if (opts[key] !== void 0) passed[key] = opts[key];
18045
+ }
16391
18046
  return {
16392
18047
  fieldMeta,
16393
18048
  columns,
16394
18049
  projection,
16395
18050
  tableOptions: {
18051
+ ...passed,
16396
18052
  columns,
16397
18053
  data: [],
16398
18054
  rowId,
@@ -16400,7 +18056,7 @@ function buildZeyosTableConfig(client, resource, opts = {}) {
16400
18056
  sortMode: "server",
16401
18057
  selectable: opts.selectable ?? false,
16402
18058
  height: opts.height ?? null,
16403
- onrowclick: typeof opts.onRowClick === "function" ? (event) => opts.onRowClick(event.detail.row, event) : void 0
18059
+ onrowclick: rowClickListener(opts)
16404
18060
  }
16405
18061
  };
16406
18062
  }
@@ -16486,6 +18142,15 @@ function zeyosTable(client, resource, opts = {}) {
16486
18142
  });
16487
18143
  return binding;
16488
18144
  }
18145
+ function rowClickListener(opts) {
18146
+ const listener = typeof opts.onrowclick === "function" ? opts.onrowclick : null;
18147
+ const callback = typeof opts.onRowClick === "function" ? opts.onRowClick : null;
18148
+ if (!listener && !callback) return void 0;
18149
+ return (event) => {
18150
+ listener?.(event);
18151
+ callback?.(event.detail.row, event);
18152
+ };
18153
+ }
16489
18154
  function referencedResourceFor(meta) {
16490
18155
  for (const value of [meta.entity, meta.fk, meta.relation, meta.reference, meta.references]) {
16491
18156
  if (typeof value === "string" && value) return value;
@@ -16551,8 +18216,8 @@ function zeyosAppointmentToEvent(record) {
16551
18216
  const end = unixSecondsToDate(record.dateto ?? record.datefrom);
16552
18217
  if (!(start instanceof Date) || !(end instanceof Date)) throw new TypeError("ZeyOS appointment requires datefrom and dateto");
16553
18218
  const association = String(record.assoc_name ?? "").trim();
16554
- const name = String(record.name ?? "").trim() || "Untitled appointment";
16555
- const title = record.entity && association ? `${association}: ${name}` : name;
18219
+ const name2 = String(record.name ?? "").trim() || "Untitled appointment";
18220
+ const title = record.entity && association ? `${association}: ${name2}` : name2;
16556
18221
  const allDay = record.allDay != null ? Boolean(record.allDay) : isMidnight(start) && isMidnight(end) && calendarDayDifference(start, end) >= 1;
16557
18222
  return {
16558
18223
  id: String(record.occurrenceid ?? `${baseId}@${record.datefrom}`),
@@ -16621,8 +18286,8 @@ var FORBIDDEN_STATE_KEYS = /* @__PURE__ */ new Set([
16621
18286
  ]);
16622
18287
  var SavedViewDuplicateNameError = class extends Error {
16623
18288
  /** @param {string} name Duplicate display name. */
16624
- constructor(name) {
16625
- super(`A saved view named "${name}" already exists`);
18289
+ constructor(name2) {
18290
+ super(`A saved view named "${name2}" already exists`);
16626
18291
  this.name = "SavedViewDuplicateNameError";
16627
18292
  }
16628
18293
  };
@@ -16713,15 +18378,15 @@ var SavedViewRegistry = class {
16713
18378
  */
16714
18379
  async save(input, options = {}) {
16715
18380
  return this._mutate(async (document2) => {
16716
- const name = normalizeName(input?.name);
18381
+ const name2 = normalizeName(input?.name);
16717
18382
  const policy = normalizeDuplicatePolicy(options.onDuplicate ?? this.options.duplicateNames);
16718
18383
  const requestedId = input?.id == null ? null : normalizeId(input.id);
16719
18384
  const byId = requestedId ? document2.views.find((entry2) => entry2.id === requestedId) ?? null : null;
16720
- const byName = document2.views.find((entry2) => nameKey(entry2.name) === nameKey(name)) ?? null;
18385
+ const byName = document2.views.find((entry2) => nameKey(entry2.name) === nameKey(name2)) ?? null;
16721
18386
  if (byId && byName && byId !== byName && policy === "reject") {
16722
- throw new SavedViewDuplicateNameError(name);
18387
+ throw new SavedViewDuplicateNameError(name2);
16723
18388
  }
16724
- if (!byId && byName && policy === "reject") throw new SavedViewDuplicateNameError(name);
18389
+ if (!byId && byName && policy === "reject") throw new SavedViewDuplicateNameError(name2);
16725
18390
  const existing = byId ?? (policy === "replace" ? byName : null);
16726
18391
  const replaced = policy === "replace" && byId && byName !== byId ? byName : null;
16727
18392
  const now = this._now();
@@ -16731,8 +18396,8 @@ var SavedViewRegistry = class {
16731
18396
  const filters = Object.prototype.hasOwnProperty.call(input ?? {}, "filters") ? normalizeFilters(input.filters) : existing ? cloneJsonObject(existing.filters, "Saved view filters") : {};
16732
18397
  const search = Object.prototype.hasOwnProperty.call(input ?? {}, "search") ? normalizeSearch(input.search) : existing?.search ?? "";
16733
18398
  const createdAt = existing?.createdAt ?? now;
16734
- const id = existing?.id ?? requestedId ?? this._createId(document2, { name, type, createdAt });
16735
- const entry = { id, name, type, state, filters, search, createdAt, updatedAt: now };
18399
+ const id = existing?.id ?? requestedId ?? this._createId(document2, { name: name2, type, createdAt });
18400
+ const entry = { id, name: name2, type, state, filters, search, createdAt, updatedAt: now };
16736
18401
  const views = document2.views.filter((candidate) => candidate !== existing && candidate !== replaced && candidate.id !== id);
16737
18402
  views.push(entry);
16738
18403
  document2.views = sortEntries(views);
@@ -16748,11 +18413,11 @@ var SavedViewRegistry = class {
16748
18413
  * @param {{onDuplicate?:SavedViewDuplicatePolicy}} [options={}] Duplicate policy.
16749
18414
  * @returns {Promise<SavedViewEntry|null>} Updated entry, or null when absent.
16750
18415
  */
16751
- async rename(idOrName, name, options = {}) {
18416
+ async rename(idOrName, name2, options = {}) {
16752
18417
  return this._mutate(async (document2) => {
16753
18418
  const entry = resolveEntry(document2, idOrName);
16754
18419
  if (!entry) return { changed: false, result: null };
16755
- const nextName = normalizeName(name);
18420
+ const nextName = normalizeName(name2);
16756
18421
  if (entry.name === nextName) return { changed: false, result: entry };
16757
18422
  const duplicate = document2.views.find((candidate) => candidate !== entry && nameKey(candidate.name) === nameKey(nextName)) ?? null;
16758
18423
  const policy = normalizeDuplicatePolicy(options.onDuplicate ?? this.options.duplicateNames);
@@ -17015,7 +18680,7 @@ function normalizePersistedEntry(value, index, scope2) {
17015
18680
  /** @type {Record<string,unknown>} */
17016
18681
  value
17017
18682
  );
17018
- const name = normalizeName(entry.name);
18683
+ const name2 = normalizeName(entry.name);
17019
18684
  const type = normalizeType2(entry.type);
17020
18685
  const state = normalizeRecordViewState(entry.state ?? {});
17021
18686
  const filters = normalizeFilters(entry.filters ?? {});
@@ -17023,8 +18688,8 @@ function normalizePersistedEntry(value, index, scope2) {
17023
18688
  const createdAt = normalizeTimestamp(entry.createdAt);
17024
18689
  const requestedUpdated = normalizeTimestamp(entry.updatedAt ?? createdAt);
17025
18690
  const updatedAt = requestedUpdated < createdAt ? createdAt : requestedUpdated;
17026
- const id = entry.id == null || String(entry.id).trim() === "" ? `legacy-${hashString(`${scopeKey(scope2)}\0${name}\0${type}\0${index}`)}` : normalizeId(entry.id);
17027
- return { id, name, type, state, filters, search, createdAt, updatedAt };
18691
+ const id = entry.id == null || String(entry.id).trim() === "" ? `legacy-${hashString(`${scopeKey(scope2)}\0${name2}\0${type}\0${index}`)}` : normalizeId(entry.id);
18692
+ return { id, name: name2, type, state, filters, search, createdAt, updatedAt };
17028
18693
  }
17029
18694
  function deduplicateEntries(entries) {
17030
18695
  const ranked = [...entries].sort((left, right) => compareText(right.updatedAt, left.updatedAt) || compareText(left.id, right.id));
@@ -17112,10 +18777,10 @@ function normalizeType2(value) {
17112
18777
  }
17113
18778
  function normalizeName(value) {
17114
18779
  if (typeof value !== "string") throw new TypeError("Saved view name must be a string");
17115
- const name = value.normalize("NFKC").trim().replace(/\s+/g, " ");
17116
- if (!name) throw new TypeError("Saved view name cannot be empty");
17117
- if (name.length > 160) throw new RangeError("Saved view name cannot exceed 160 characters");
17118
- return name;
18780
+ const name2 = value.normalize("NFKC").trim().replace(/\s+/g, " ");
18781
+ if (!name2) throw new TypeError("Saved view name cannot be empty");
18782
+ if (name2.length > 160) throw new RangeError("Saved view name cannot exceed 160 characters");
18783
+ return name2;
17119
18784
  }
17120
18785
  function nameKey(value) {
17121
18786
  return value.normalize("NFKC").toLocaleLowerCase("en-US");
@@ -17284,6 +18949,8 @@ function buildZeyosViewConfig(client, resource, opts = {}) {
17284
18949
  const table = buildZeyosTableConfig(client, resource, {
17285
18950
  fields: requested,
17286
18951
  labels: opts.labels,
18952
+ formats: opts.formats,
18953
+ timeZone: opts.timeZone,
17287
18954
  rowId: opts.recordId ?? "ID",
17288
18955
  sort: opts.sort ?? null
17289
18956
  });
@@ -17616,10 +19283,10 @@ function presentationType(View) {
17616
19283
  cssName
17617
19284
  );
17618
19285
  }
17619
- const name = String(View.name ?? "").toLowerCase().replace(/view$/, "");
17620
- return ["table", "card", "kanban"].includes(name) ? (
19286
+ const name2 = String(View.name ?? "").toLowerCase().replace(/view$/, "");
19287
+ return ["table", "card", "kanban"].includes(name2) ? (
17621
19288
  /** @type {'table'|'card'|'kanban'} */
17622
- name
19289
+ name2
17623
19290
  ) : null;
17624
19291
  }
17625
19292
  function normalizePresentationType(value) {
@@ -17712,7 +19379,7 @@ function findReservedDocument(response, recordName) {
17712
19379
  if (!Array.isArray(response) || !Array.isArray(response[0]) || !Array.isArray(response[1])) {
17713
19380
  return null;
17714
19381
  }
17715
- const index = response[0].findIndex((name) => String(name) === recordName);
19382
+ const index = response[0].findIndex((name2) => String(name2) === recordName);
17716
19383
  return index < 0 ? null : response[1][index];
17717
19384
  }
17718
19385
  function normalizeNonEmpty(value, fallback, label) {
@@ -17757,6 +19424,7 @@ export {
17757
19424
  buildZeyosLauncherConfig,
17758
19425
  buildZeyosSelectConfig,
17759
19426
  buildZeyosViewConfig,
19427
+ calendarDayToUnixSeconds,
17760
19428
  calendarEventToZeyosPatch,
17761
19429
  connect,
17762
19430
  createLegacySavedViewTransport,
@@ -17784,6 +19452,7 @@ export {
17784
19452
  registerModules,
17785
19453
  resolveFields,
17786
19454
  tableSortToQuery,
19455
+ unixSecondsToCalendarDay,
17787
19456
  unixSecondsToDate,
17788
19457
  useZeyosIcons,
17789
19458
  zeyosActiveIdentifier,
@@ -17799,3 +19468,51 @@ export {
17799
19468
  zeyosTable,
17800
19469
  zeyosView
17801
19470
  };
19471
+ /*
19472
+ * @license Lucide 1.53.0 (ISC), including icons derived from Feather (MIT) — notices below.
19473
+ *
19474
+ * The bundled glyphs are Lucide 1.53.0 (https://lucide.dev), outline icons drawn on a 24-unit grid.
19475
+ * Each Lucide icon is stored as one path: its circles, rectangles, lines, and polylines are
19476
+ * rewritten as equivalent path data, which renders identically because every part shares the same
19477
+ * stroke. Where a Zx name differs from the Lucide one, the Lucide name is noted beside the glyph.
19478
+ *
19479
+ * ISC License
19480
+ *
19481
+ * Copyright (c) 2026 Lucide Icons and Contributors
19482
+ *
19483
+ * Permission to use, copy, modify, and/or distribute this software for any
19484
+ * purpose with or without fee is hereby granted, provided that the above
19485
+ * copyright notice and this permission notice appear in all copies.
19486
+ *
19487
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
19488
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
19489
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
19490
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19491
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19492
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19493
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19494
+ *
19495
+ * Lucide icons derived from the Feather project (Lucide's LICENSE lists them) are also covered by:
19496
+ *
19497
+ * The MIT License (MIT)
19498
+ *
19499
+ * Copyright (c) 2013-present Cole Bemis
19500
+ *
19501
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
19502
+ * of this software and associated documentation files (the "Software"), to deal
19503
+ * in the Software without restriction, including without limitation the rights
19504
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19505
+ * copies of the Software, and to permit persons to whom the Software is
19506
+ * furnished to do so, subject to the following conditions:
19507
+ *
19508
+ * The above copyright notice and this permission notice shall be included in all
19509
+ * copies or substantial portions of the Software.
19510
+ *
19511
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19512
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19513
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19514
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19515
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19516
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19517
+ * SOFTWARE.
19518
+ */