@zeyos/zx 4.4.0 → 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 (182) hide show
  1. package/CHANGELOG.md +283 -0
  2. package/README.md +42 -10
  3. package/dist/types/components/activity-list/activity-list.d.ts +22 -0
  4. package/dist/types/components/app-sidebar/app-sidebar.d.ts +5 -3
  5. package/dist/types/components/avatar/avatar.d.ts +46 -7
  6. package/dist/types/components/badge/badge.d.ts +21 -1
  7. package/dist/types/components/button/button.d.ts +15 -0
  8. package/dist/types/components/chart/chart.d.ts +10 -0
  9. package/dist/types/components/checklist/checklist.d.ts +9 -0
  10. package/dist/types/components/code-editor/code-editor.d.ts +6 -17
  11. package/dist/types/components/datebox/date-range-box.d.ts +5 -1
  12. package/dist/types/components/datebox/datebox.d.ts +7 -2
  13. package/dist/types/components/dialog/dialog.d.ts +46 -6
  14. package/dist/types/components/dropdown/dropdown.d.ts +2 -2
  15. package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
  16. package/dist/types/components/field/field.d.ts +24 -2
  17. package/dist/types/components/fieldset/fieldset.d.ts +19 -1
  18. package/dist/types/components/file-item/file-item.d.ts +34 -6
  19. package/dist/types/components/form/form.d.ts +12 -1
  20. package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
  21. package/dist/types/components/launcher/launcher.d.ts +260 -15
  22. package/dist/types/components/list-toolbar/list-toolbar.d.ts +1 -1
  23. package/dist/types/components/menu-button/menu-button.d.ts +33 -5
  24. package/dist/types/components/message/message.d.ts +119 -17
  25. package/dist/types/components/modal/modal.d.ts +13 -2
  26. package/dist/types/components/select/color.d.ts +12 -0
  27. package/dist/types/components/select/glyphs.d.ts +124 -0
  28. package/dist/types/components/select/select.d.ts +11 -3
  29. package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
  30. package/dist/types/components/tabbox/tabbox.d.ts +9 -0
  31. package/dist/types/components/table/table.d.ts +46 -1
  32. package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
  33. package/dist/types/components/tree/tree.d.ts +13 -0
  34. package/dist/types/core/breakpoint.d.ts +1 -1
  35. package/dist/types/core/icons.d.ts +23 -25
  36. package/dist/types/core/motion.d.ts +59 -0
  37. package/dist/types/core/overlay-host.d.ts +15 -1
  38. package/dist/types/index.d.ts +5 -3
  39. package/dist/types/zeyos/connect.d.ts +6 -0
  40. package/dist/types/zeyos/form.d.ts +50 -9
  41. package/dist/types/zeyos/index.d.ts +1 -1
  42. package/dist/types/zeyos/query.d.ts +79 -0
  43. package/dist/types/zeyos/schema.d.ts +24 -1
  44. package/dist/types/zeyos/table.d.ts +100 -14
  45. package/dist/types/zeyos/view.d.ts +10 -0
  46. package/dist/zx-zeyos.esm.js +1621 -443
  47. package/dist/zx-zeyos.esm.min.js +53 -5
  48. package/dist/zx.css +1411 -660
  49. package/dist/zx.esm.js +1976 -703
  50. package/dist/zx.esm.min.js +56 -8
  51. package/dist/zx.global.js +1978 -705
  52. package/dist/zx.global.min.js +56 -8
  53. package/dist/zx.min.css +1 -1
  54. package/docs/api.json +219 -34
  55. package/docs/llms.md +472 -110
  56. package/docs/llms.txt +3 -3
  57. package/package.json +1 -1
  58. package/src/components/account-menu/account-menu.css +35 -10
  59. package/src/components/activity-item/activity-item.css +1 -1
  60. package/src/components/activity-item/activity-item.js +3 -0
  61. package/src/components/activity-list/activity-list.css +38 -3
  62. package/src/components/activity-list/activity-list.js +34 -0
  63. package/src/components/app-icon/app-icon.css +1 -1
  64. package/src/components/app-rail/app-rail.css +40 -79
  65. package/src/components/app-sidebar/app-sidebar.css +109 -174
  66. package/src/components/app-sidebar/app-sidebar.js +21 -6
  67. package/src/components/aurora/aurora.css +5 -3
  68. package/src/components/avatar/avatar.css +32 -7
  69. package/src/components/avatar/avatar.js +72 -8
  70. package/src/components/badge/badge.css +54 -2
  71. package/src/components/badge/badge.js +29 -2
  72. package/src/components/button/button.css +67 -9
  73. package/src/components/button/button.js +38 -0
  74. package/src/components/calendar/calendar.css +46 -46
  75. package/src/components/card/card.css +2 -2
  76. package/src/components/card-view/card-view.css +9 -7
  77. package/src/components/chart/chart.css +4 -1
  78. package/src/components/chart/chart.js +12 -0
  79. package/src/components/check-button/check-button.css +10 -2
  80. package/src/components/checklist/checklist.css +9 -2
  81. package/src/components/checklist/checklist.js +11 -5
  82. package/src/components/checklist/field-adapter.js +3 -1
  83. package/src/components/code-editor/code-editor.css +6 -5
  84. package/src/components/code-editor/code-editor.js +19 -1
  85. package/src/components/context-menu/context-menu.css +9 -4
  86. package/src/components/copy/copy-button.css +6 -0
  87. package/src/components/copy/copy-input.css +8 -3
  88. package/src/components/data-filter/data-filter.css +5 -4
  89. package/src/components/date-picker/date-picker.css +6 -6
  90. package/src/components/date-picker/date-range-picker.css +3 -3
  91. package/src/components/date-picker/time-picker.css +1 -1
  92. package/src/components/datebox/date-range-box.css +79 -2
  93. package/src/components/datebox/date-range-box.js +13 -2
  94. package/src/components/datebox/datebox.css +79 -2
  95. package/src/components/datebox/datebox.js +13 -2
  96. package/src/components/dialog/dialog.css +26 -0
  97. package/src/components/dialog/dialog.js +99 -17
  98. package/src/components/dropdown/dropdown.css +1 -1
  99. package/src/components/dropdown/dropdown.js +5 -3
  100. package/src/components/empty-state/empty-state.css +2 -2
  101. package/src/components/entity-ref/entity-ref.css +1 -1
  102. package/src/components/entity-ref/entity-ref.js +1 -1
  103. package/src/components/field/field.css +28 -9
  104. package/src/components/field/field.js +68 -26
  105. package/src/components/field-upload/field-upload.css +1 -1
  106. package/src/components/fieldset/fieldset.css +5 -3
  107. package/src/components/fieldset/fieldset.js +28 -5
  108. package/src/components/file-item/file-item.js +144 -10
  109. package/src/components/filter/filter.js +7 -3
  110. package/src/components/filter-panel/filter-panel.css +1 -1
  111. package/src/components/finder/finder.css +15 -13
  112. package/src/components/form/form.css +5 -4
  113. package/src/components/form/form.js +11 -2
  114. package/src/components/grid/grid.css +1 -1
  115. package/src/components/groupbox/groupbox.css +1 -1
  116. package/src/components/kanban-view/kanban-card.js +11 -3
  117. package/src/components/kanban-view/kanban-view.css +39 -15
  118. package/src/components/kanban-view/kanban-view.js +4 -4
  119. package/src/components/launcher/launcher.css +107 -25
  120. package/src/components/launcher/launcher.js +503 -41
  121. package/src/components/list-toolbar/list-toolbar.css +13 -8
  122. package/src/components/list-toolbar/list-toolbar.js +1 -1
  123. package/src/components/master-panel/master-panel.css +2 -0
  124. package/src/components/menu-button/menu-button.css +9 -4
  125. package/src/components/menu-button/menu-button.js +65 -9
  126. package/src/components/message/message.css +33 -3
  127. package/src/components/message/message.js +217 -15
  128. package/src/components/modal/modal.js +18 -3
  129. package/src/components/navigation-bar/navigation-bar.css +18 -9
  130. package/src/components/number-field/field-adapter.js +1 -0
  131. package/src/components/number-field/number-field.css +70 -0
  132. package/src/components/number-field/number-field.js +2 -0
  133. package/src/components/pagination/pagination.css +8 -6
  134. package/src/components/panel/panel.css +1 -1
  135. package/src/components/rating/rating.css +7 -0
  136. package/src/components/select/color.js +22 -0
  137. package/src/components/select/field-adapter.js +2 -0
  138. package/src/components/select/glyphs.js +165 -0
  139. package/src/components/select/select.css +138 -45
  140. package/src/components/select/select.js +83 -47
  141. package/src/components/sheet/sheet.css +11 -8
  142. package/src/components/slider/slider.css +16 -2
  143. package/src/components/slider/slider.js +1 -1
  144. package/src/components/sort-control/sort-control.css +4 -1
  145. package/src/components/stat-tile/stat-tile.css +41 -46
  146. package/src/components/stat-tile/stat-tile.js +17 -6
  147. package/src/components/tabbox/tabbox.css +32 -27
  148. package/src/components/tabbox/tabbox.js +5 -1
  149. package/src/components/table/table.css +104 -29
  150. package/src/components/table/table.js +90 -16
  151. package/src/components/tag-picker/tag-picker.css +83 -5
  152. package/src/components/tag-picker/tag-picker.js +25 -8
  153. package/src/components/timebox/timebox.css +22 -2
  154. package/src/components/toggle/toggle.css +12 -1
  155. package/src/components/toolbar/toolbar.js +2 -2
  156. package/src/components/tooltip/tooltip.css +4 -0
  157. package/src/components/tooltip/tooltip.js +10 -3
  158. package/src/components/tree/tree.css +25 -16
  159. package/src/components/tree/tree.js +31 -0
  160. package/src/components/value-list/value-list.css +3 -3
  161. package/src/components/view/record-view.js +1 -1
  162. package/src/core/breakpoint.js +1 -1
  163. package/src/core/icons.js +193 -60
  164. package/src/core/motion.js +147 -0
  165. package/src/core/overlay-host.js +21 -2
  166. package/src/index.js +5 -3
  167. package/src/internal/app-rail.js +1 -2
  168. package/src/zeyos/connect.js +6 -2
  169. package/src/zeyos/form.js +67 -27
  170. package/src/zeyos/index.js +1 -1
  171. package/src/zeyos/query.js +153 -0
  172. package/src/zeyos/schema.js +144 -27
  173. package/src/zeyos/table.js +68 -5
  174. package/src/zeyos/view.js +6 -0
  175. package/styles/base.css +56 -0
  176. package/styles/icons.css +11 -1
  177. package/styles/tokens/dark.css +113 -37
  178. package/styles/tokens/global.css +121 -27
  179. package/styles/tokens/semantic.css +100 -31
  180. package/styles/tokens/themes.css +7 -6
  181. package/styles/zx.css +0 -1
  182. 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 = () => {
@@ -1801,6 +1979,7 @@ function registerChecklistFieldAdapter() {
1801
1979
  }
1802
1980
 
1803
1981
  // src/components/code-editor/code-editor.js
1982
+ var MODIFIER_KEYS = /* @__PURE__ */ new Set(["Shift", "Control", "Alt", "Meta", "AltGraph", "CapsLock", "Fn", "OS"]);
1804
1983
  var CodeEditor = class extends Component {
1805
1984
  static cssName = "code-editor";
1806
1985
  /** @type {CodeEditorOptions} */
@@ -1827,7 +2006,7 @@ var CodeEditor = class extends Component {
1827
2006
  );
1828
2007
  this.el = root;
1829
2008
  this._initialChildren = this._createdRoot ? [] : [...root.childNodes];
1830
- this._initialAttributes = new Map(["data-language", "data-readonly", "data-disabled"].map((name) => [name, root.getAttribute(name)]));
2009
+ this._initialAttributes = new Map(["data-language", "data-readonly", "data-disabled"].map((name2) => [name2, root.getAttribute(name2)]));
1831
2010
  this._language = normalizeLanguage(this.options.language);
1832
2011
  this._languageLabel = h("span", { class: "zx-code-editor__language" }, this._language);
1833
2012
  this._position = h("span", {
@@ -1860,7 +2039,11 @@ var CodeEditor = class extends Component {
1860
2039
  root.dataset.readonly = String(Boolean(this.options.readOnly));
1861
2040
  root.dataset.disabled = String(Boolean(this.options.disabled));
1862
2041
  root.replaceChildren(this._header, this._input);
2042
+ this._tabReleased = false;
1863
2043
  this.listen(this._input, "keydown", (event) => this._handleKeydown(event));
2044
+ this.listen(this._input, "blur", () => {
2045
+ this._tabReleased = false;
2046
+ });
1864
2047
  this.listen(this._input, "input", () => {
1865
2048
  this._updatePosition();
1866
2049
  this.emit("input", { value: this.getValue() });
@@ -1922,7 +2105,14 @@ var CodeEditor = class extends Component {
1922
2105
  }
1923
2106
  /** @param {KeyboardEvent} event @returns {void} */
1924
2107
  _handleKeydown(event) {
1925
- if (event.key !== "Tab" || event.altKey || event.ctrlKey || event.metaKey || this._input.readOnly || this._input.disabled) return;
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;
1926
2116
  const indent = String(this.options.indent ?? " ");
1927
2117
  if (!indent) return;
1928
2118
  event.preventDefault();
@@ -1953,9 +2143,9 @@ var CodeEditor = class extends Component {
1953
2143
  destroy() {
1954
2144
  if (!this._createdRoot) {
1955
2145
  this.el.replaceChildren(...this._initialChildren);
1956
- for (const [name, value] of this._initialAttributes) {
1957
- if (value === null) this.el.removeAttribute(name);
1958
- else this.el.setAttribute(name, value);
2146
+ for (const [name2, value] of this._initialAttributes) {
2147
+ if (value === null) this.el.removeAttribute(name2);
2148
+ else this.el.setAttribute(name2, value);
1959
2149
  }
1960
2150
  }
1961
2151
  super.destroy();
@@ -2290,19 +2480,19 @@ function parseIconSpec(spec) {
2290
2480
  const separator = text3.indexOf(":");
2291
2481
  if (separator > 0) {
2292
2482
  const prefix = text3.slice(0, separator).toLowerCase();
2293
- const name = text3.slice(separator + 1);
2483
+ const name2 = text3.slice(separator + 1);
2294
2484
  if (prefix === "builtin" || prefix === "zx") {
2295
- return { provider: "builtin", name, style: null, classes: null };
2485
+ return { provider: "builtin", name: name2, style: null, classes: null };
2296
2486
  }
2297
2487
  if (Object.hasOwn(stylePrefixes, prefix)) {
2298
- return { provider: "fa", name, style: stylePrefixes[prefix], classes: null };
2488
+ return { provider: "fa", name: name2, style: stylePrefixes[prefix], classes: null };
2299
2489
  }
2300
2490
  }
2301
2491
  return { provider: null, name: text3, style: null, classes: null };
2302
2492
  }
2303
- function faIconClasses(name, options = {}) {
2493
+ function faIconClasses(name2, options = {}) {
2304
2494
  const { style = "solid", family = "classic", fixedWidth = false, translate: translate2 = true } = options;
2305
- const bare = String(name).replace(/^fa-/, "");
2495
+ const bare = String(name2).replace(/^fa-/, "");
2306
2496
  const resolved = translate2 ? faNames[bare] ?? bare : bare;
2307
2497
  const classes = [];
2308
2498
  const familyClass = faFamilies[family];
@@ -2349,44 +2539,61 @@ function loadFontAwesomeKit(kit) {
2349
2539
 
2350
2540
  // src/core/icons.js
2351
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"];
2352
2546
  var icons = Object.freeze({
2353
- "arrow-up-wide-short": ["0 0 576 512", "M151.6 42.4C145.5 35.8 137 32 128 32s-17.5 3.8-23.6 10.4l-88 96c-11.9 13-11.1 33.3 2 45.2s33.3 11.1 45.2-2L96 146.3 96 448c0 17.7 14.3 32 32 32s32-14.3 32-32l0-301.7 32.4 35.4c11.9 13 32.2 13.9 45.2 2s13.9-32.2 2-45.2l-88-96zM320 480l32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-32 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm0-128l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-96 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm0-128l160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-160 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm0-128l224 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L320 32c-17.7 0-32 14.3-32 32s14.3 32 32 32z"],
2354
- "arrow-down-wide-short": ["0 0 576 512", "M151.6 469.6C145.5 476.2 137 480 128 480s-17.5-3.8-23.6-10.4l-88-96c-11.9-13-11.1-33.3 2-45.2s33.3-11.1 45.2 2L96 365.7 96 64c0-17.7 14.3-32 32-32s32 14.3 32 32l0 301.7 32.4-35.4c11.9-13 32.2-13.9 45.2-2s13.9 32.2 2 45.2l-88 96zM320 480c-17.7 0-32-14.3-32-32s14.3-32 32-32l32 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-32 0zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32l96 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-96 0zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32l160 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-160 0zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32l224 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L320 96z"],
2355
- "chevron-down": ["0 0 512 512", "M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"],
2356
- "chevron-up": ["0 0 512 512", "M233.4 105.4c12.5-12.5 32.8-12.5 45.3 0l192 192c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L256 173.3 86.6 342.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l192-192z"],
2357
- "chevron-left": ["0 0 320 512", "M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"],
2358
- "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"],
2359
- 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"],
2360
- 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"],
2361
- 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"],
2362
- 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"],
2363
- 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"],
2364
- 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"],
2365
- 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"],
2366
- 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"],
2367
- 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"],
2368
- 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"],
2369
- 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"],
2370
- 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"],
2371
- 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"],
2372
- 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"],
2373
- 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"],
2374
- 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"],
2375
- 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"],
2376
- 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"],
2377
- 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"],
2378
- 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"],
2379
- 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"],
2380
- 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"],
2381
- 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"],
2382
- 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"],
2383
- 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"],
2384
- "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"],
2385
- 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"],
2386
- 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"],
2387
- 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"],
2388
- 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"],
2389
- 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>
2390
2597
  });
2391
2598
  var aliases = Object.freeze({
2392
2599
  settings: "gear",
@@ -2424,8 +2631,8 @@ async function loadFontAwesome(options) {
2424
2631
  await loadFontAwesomeKit(kit);
2425
2632
  return activate ? useFontAwesome() : getIconConfig();
2426
2633
  }
2427
- function icon(name, options = {}) {
2428
- const spec = parseIconSpec(name);
2634
+ function icon(name2, options = {}) {
2635
+ const spec = parseIconSpec(name2);
2429
2636
  if (spec.classes) return faIcon(spec.classes, options);
2430
2637
  const resolved = aliases[spec.name] ?? spec.name;
2431
2638
  if (spec.provider === "builtin") return builtinIcon(resolved, options);
@@ -2445,43 +2652,59 @@ function icon(name, options = {}) {
2445
2652
  }
2446
2653
  return builtinIcon(resolved, options);
2447
2654
  }
2448
- function builtinIcon(name, options) {
2449
- const entry = extraGlyphs[name] ?? icons[name];
2655
+ function builtinIcon(name2, options) {
2656
+ const entry = extraGlyphs[name2] ?? icons[name2];
2450
2657
  if (!entry) {
2451
- if (options.strict) throw new RangeError(`Unknown icon: ${name}`);
2452
- warnMissingIcon(name);
2453
- return missingIcon(name, options);
2658
+ if (options.strict) throw new RangeError(`Unknown icon: ${name2}`);
2659
+ warnMissingIcon(name2);
2660
+ return missingIcon(name2, options);
2454
2661
  }
2455
- const [viewBox, pathData] = entry;
2456
- const svg = iconSvg(viewBox, options);
2662
+ const [viewBox, pathData, paint = "fill"] = entry;
2663
+ const svg = iconSvg(viewBox, paint, options);
2457
2664
  const path = document.createElementNS(SVG_NS, "path");
2458
2665
  path.setAttribute("d", pathData);
2459
2666
  svg.append(path);
2460
2667
  return svg;
2461
2668
  }
2462
- function iconSvg(viewBox, options) {
2669
+ function iconSvg(viewBox, paint, options) {
2463
2670
  const { size = 16 } = options;
2464
2671
  const svg = document.createElementNS(SVG_NS, "svg");
2465
2672
  svg.setAttribute("class", classList("zx-icon", options.class));
2466
2673
  svg.setAttribute("viewBox", viewBox);
2467
2674
  svg.setAttribute("width", String(size));
2468
2675
  svg.setAttribute("height", String(size));
2469
- 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
+ }
2470
2685
  svg.setAttribute("focusable", "false");
2471
2686
  applyLabel(svg, options.label ?? null);
2472
2687
  return svg;
2473
2688
  }
2474
- var PLACEHOLDER_VIEW_BOX = "0 0 512 512";
2475
- function missingIcon(name, options) {
2476
- const svg = iconSvg(PLACEHOLDER_VIEW_BOX, options);
2477
- svg.setAttribute("data-zx-icon-missing", name);
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);
2478
2701
  return svg;
2479
2702
  }
2480
2703
  var warnedMissing = /* @__PURE__ */ new Set();
2481
- function warnMissingIcon(name) {
2482
- if (warnedMissing.has(name)) return;
2483
- warnedMissing.add(name);
2484
- console.warn(`[zx.icon] Unknown icon "${name}" \u2014 rendered an empty placeholder. Add it with registerIcons(), or render Font Awesome with useFontAwesome().`);
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().`);
2485
2708
  }
2486
2709
  function faIcon(classes, options) {
2487
2710
  const { size = 16 } = options;
@@ -3000,8 +3223,8 @@ var DatePicker = class extends Component {
3000
3223
  */
3001
3224
  focus() {
3002
3225
  this._closeQuickPick();
3003
- const button = this._buttonForDate(this._focused);
3004
- button?.focus();
3226
+ const button2 = this._buttonForDate(this._focused);
3227
+ button2?.focus();
3005
3228
  return this;
3006
3229
  }
3007
3230
  /** Cleans up an appended time picker and generated host content. @returns {void} */
@@ -3072,7 +3295,7 @@ var DatePicker = class extends Component {
3072
3295
  const date = addDays(rowStart, column);
3073
3296
  const selected = Boolean(this._value && isSameDay(date, this._value));
3074
3297
  const unavailable = !this._inRange(date);
3075
- const button = h("button", {
3298
+ const button2 = h("button", {
3076
3299
  class: "zx-date-picker__day",
3077
3300
  type: "button",
3078
3301
  role: "gridcell",
@@ -3086,7 +3309,7 @@ var DatePicker = class extends Component {
3086
3309
  otherMonth: date.getMonth() === this._viewMonth ? null : "true"
3087
3310
  }
3088
3311
  }, formatDate(date, "%e"));
3089
- row.append(h("td", { role: "presentation" }, button));
3312
+ row.append(h("td", { role: "presentation" }, button2));
3090
3313
  }
3091
3314
  rows.push(row);
3092
3315
  }
@@ -3095,16 +3318,16 @@ var DatePicker = class extends Component {
3095
3318
  }
3096
3319
  /** @param {Event} event @returns {void} */
3097
3320
  _onDayClick(event) {
3098
- const button = event.target?.closest?.("[data-date]");
3099
- if (!button || !this.refs.days.contains(button)) return;
3100
- 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");
3101
3324
  if (date && this._inRange(date)) this._selectDate(date);
3102
3325
  }
3103
3326
  /** @param {KeyboardEvent} event @returns {void} */
3104
3327
  _onDayKeydown(event) {
3105
- const button = event.target?.closest?.("[data-date]");
3106
- if (!button || !this.refs.days.contains(button)) return;
3107
- 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");
3108
3331
  if (!current) return;
3109
3332
  let next = null;
3110
3333
  if (event.key === "ArrowLeft") next = addDays(current, -1);
@@ -3215,15 +3438,15 @@ var DatePicker = class extends Component {
3215
3438
  }
3216
3439
  /** @param {Event} event @returns {void} */
3217
3440
  _onQuickClick(event) {
3218
- const button = event.target?.closest?.("[data-month]");
3219
- if (!button || !this.refs.quickGrid.contains(button)) return;
3220
- 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));
3221
3444
  }
3222
3445
  /** @param {KeyboardEvent} event @returns {void} */
3223
3446
  _onQuickKeydown(event) {
3224
- const button = event.target?.closest?.("[data-month]");
3225
- if (!button || !this.refs.quickGrid.contains(button)) return;
3226
- 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);
3227
3450
  let next = null;
3228
3451
  if (event.key === "ArrowLeft") next = month - 1;
3229
3452
  else if (event.key === "ArrowRight") next = month + 1;
@@ -3435,15 +3658,15 @@ var MonthPicker = class extends DatePicker {
3435
3658
  }
3436
3659
  /** @param {Event} event @returns {void} */
3437
3660
  _onMonthClick(event) {
3438
- const button = event.target?.closest?.("[data-month]");
3439
- if (!button || !this.refs.grid.contains(button)) return;
3440
- 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));
3441
3664
  }
3442
3665
  /** @param {KeyboardEvent} event @returns {void} */
3443
3666
  _onMonthKeydown(event) {
3444
- const button = event.target?.closest?.("[data-month]");
3445
- if (!button || !this.refs.grid.contains(button)) return;
3446
- 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);
3447
3670
  let delta = null;
3448
3671
  if (event.key === "ArrowLeft") delta = -1;
3449
3672
  else if (event.key === "ArrowRight") delta = 1;
@@ -3512,7 +3735,7 @@ function registerDatePickerFieldAdapters() {
3512
3735
  let disabled = false;
3513
3736
  const syncDisabled = () => {
3514
3737
  picker.el.setAttribute("aria-disabled", String(disabled));
3515
- for (const button of picker.el.querySelectorAll("button")) button.disabled = disabled;
3738
+ for (const button2 of picker.el.querySelectorAll("button")) button2.disabled = disabled;
3516
3739
  };
3517
3740
  field.listen(picker.el, "change", (event) => event.stopImmediatePropagation());
3518
3741
  picker.on("change", (event) => field.emit("change", { value: event.detail.date }));
@@ -3710,9 +3933,9 @@ function hidePopover(element) {
3710
3933
  } catch {
3711
3934
  }
3712
3935
  }
3713
- function restoreAttribute(element, name, value) {
3714
- if (value === null) element.removeAttribute(name);
3715
- else element.setAttribute(name, value);
3936
+ function restoreAttribute(element, name2, value) {
3937
+ if (value === null) element.removeAttribute(name2);
3938
+ else element.setAttribute(name2, value);
3716
3939
  }
3717
3940
 
3718
3941
  // src/components/date-picker/date-range-picker.js
@@ -4095,12 +4318,12 @@ var DateRangePicker = class extends Component {
4095
4318
  /* ---------------------------------------------------------------------- events -- */
4096
4319
  /** @param {Event} event @returns {void} */
4097
4320
  _onPresetClick(event) {
4098
- const button = (
4321
+ const button2 = (
4099
4322
  /** @type {Element} */
4100
4323
  event.target?.closest?.("[data-preset]")
4101
4324
  );
4102
- if (!button || this._disabled) return;
4103
- 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);
4104
4327
  if (!preset) return;
4105
4328
  const range = preset.range();
4106
4329
  this.set({ start: range?.start ?? null, end: range?.end ?? null }, { silent: true });
@@ -4203,15 +4426,15 @@ var DateRangePicker = class extends Component {
4203
4426
  const date = addDays(rowStart, column);
4204
4427
  const cell = h("td", { role: "presentation" });
4205
4428
  if (date.getMonth() === first.getMonth() && date.getFullYear() === first.getFullYear()) {
4206
- const button = h("button", {
4429
+ const button2 = h("button", {
4207
4430
  class: "zx-date-range-picker__day",
4208
4431
  type: "button",
4209
4432
  role: "gridcell",
4210
4433
  tabindex: -1,
4211
4434
  dataset: { date: formatDate(date, "%Y-%m-%d") }
4212
4435
  }, formatDate(date, "%e"));
4213
- cell.append(button);
4214
- this._dayCells.push({ date, cell, button });
4436
+ cell.append(button2);
4437
+ this._dayCells.push({ date, cell, button: button2 });
4215
4438
  }
4216
4439
  row.append(cell);
4217
4440
  }
@@ -4282,17 +4505,17 @@ var DateRangePicker = class extends Component {
4282
4505
  }
4283
4506
  if (!roving) roving = this._dayCells[0] ?? null;
4284
4507
  for (const entry of this._dayCells) {
4285
- const { date, cell, button } = entry;
4508
+ const { date, cell, button: button2 } = entry;
4286
4509
  const state = this._dayState(date);
4287
4510
  setDataset(cell, "range", state.range);
4288
4511
  setDataset(cell, "rangeEdge", state.edge);
4289
- setDataset(button, "range", state.range);
4290
- button.tabIndex = entry === roving ? 0 : -1;
4291
- button.setAttribute("aria-selected", String(state.range !== null && state.range !== "preview"));
4292
- button.setAttribute("aria-disabled", String(!this._selectable(date)));
4293
- button.setAttribute("aria-label", this._dayLabel(date, state.range));
4294
- if (isSameDay(date, today2)) button.setAttribute("aria-current", "date");
4295
- 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");
4296
4519
  }
4297
4520
  }
4298
4521
  /**
@@ -4343,7 +4566,7 @@ var DateRangePicker = class extends Component {
4343
4566
  /** @returns {void} */
4344
4567
  _syncDisabled() {
4345
4568
  this.el.setAttribute("aria-disabled", String(this._disabled));
4346
- 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;
4347
4570
  }
4348
4571
  /** Writes the current selection into the visually hidden live region. @returns {void} */
4349
4572
  _announce() {
@@ -4366,9 +4589,9 @@ var DateRangePicker = class extends Component {
4366
4589
  }
4367
4590
  /** @param {Element|null} target @returns {{date: Date, cell: Element, button: Element}|null} */
4368
4591
  _cellFor(target) {
4369
- const button = target?.closest?.("[data-date]");
4370
- if (!button) return null;
4371
- 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;
4372
4595
  }
4373
4596
  /** @param {Date} date @returns {HTMLButtonElement|null} */
4374
4597
  _buttonForDate(date) {
@@ -4506,17 +4729,17 @@ function isoWeekNumber2(date) {
4506
4729
  const yearStart = new Date(Date.UTC(utc.getUTCFullYear(), 0, 1));
4507
4730
  return Math.ceil(((utc.getTime() - yearStart.getTime()) / DAY_MS + 1) / 7);
4508
4731
  }
4509
- function setDataset(element, name, value) {
4510
- if (value === null || value === void 0) delete element.dataset[name];
4511
- 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;
4512
4735
  }
4513
4736
  function rememberAttributes2(element, names) {
4514
- return new Map(names.map((name) => [name, element.getAttribute(name)]));
4737
+ return new Map(names.map((name2) => [name2, element.getAttribute(name2)]));
4515
4738
  }
4516
4739
  function restoreAttributes2(element, attributes) {
4517
- for (const [name, value] of attributes) {
4518
- if (value === null) element.removeAttribute(name);
4519
- 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);
4520
4743
  }
4521
4744
  }
4522
4745
 
@@ -4585,6 +4808,8 @@ var DateRangeBox = class extends Component {
4585
4808
  this.el = root;
4586
4809
  this._originalState = root.getAttribute("data-state");
4587
4810
  this._originalAriaDisabled = root.getAttribute("aria-disabled");
4811
+ this._originalClearable = root.getAttribute("data-clearable");
4812
+ root.toggleAttribute("data-clearable", Boolean(this.options.clearable));
4588
4813
  this._format = String(this.options.format || "%d.%m.%Y");
4589
4814
  this._separator = String(this.options.separator ?? " \u2013 ");
4590
4815
  this._disabled = Boolean(this.options.disabled);
@@ -4777,6 +5002,7 @@ var DateRangeBox = class extends Component {
4777
5002
  this._content?.remove();
4778
5003
  restoreAttribute2(this.el, "data-state", this._originalState);
4779
5004
  restoreAttribute2(this.el, "aria-disabled", this._originalAriaDisabled);
5005
+ restoreAttribute2(this.el, "data-clearable", this._originalClearable);
4780
5006
  super.destroy();
4781
5007
  }
4782
5008
  /* --------------------------------------------------------------------- internals -- */
@@ -4873,7 +5099,11 @@ var DateRangeBox = class extends Component {
4873
5099
  /** @returns {void} */
4874
5100
  _syncDisplay() {
4875
5101
  this.refs.input.value = formatRangeText(this._range, this._format, this._separator);
4876
- 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;
4877
5107
  }
4878
5108
  /** @returns {void} */
4879
5109
  _syncDisabled() {
@@ -4881,6 +5111,7 @@ var DateRangeBox = class extends Component {
4881
5111
  this.refs.toggle.disabled = this._disabled;
4882
5112
  this.refs.clear.disabled = this._disabled;
4883
5113
  this.el.setAttribute("aria-disabled", String(this._disabled));
5114
+ this._syncClear();
4884
5115
  if (this._disabled) this._picker?.disable();
4885
5116
  else this._picker?.enable();
4886
5117
  }
@@ -4890,7 +5121,7 @@ var DateRangeBox = class extends Component {
4890
5121
  this.refs.input.removeAttribute("aria-invalid");
4891
5122
  }
4892
5123
  /** @param {boolean} returnFocus @returns {void} */
4893
- _close(returnFocus) {
5124
+ _close(returnFocus2) {
4894
5125
  if (!this._open) return;
4895
5126
  this._position?.destroy();
4896
5127
  this._position = null;
@@ -4898,7 +5129,7 @@ var DateRangeBox = class extends Component {
4898
5129
  this.refs.popover.dataset.state = "closed";
4899
5130
  this.refs.input.setAttribute("aria-expanded", "false");
4900
5131
  this.refs.toggle.setAttribute("aria-expanded", "false");
4901
- if (returnFocus) this.focus();
5132
+ if (returnFocus2) this.focus();
4902
5133
  this.emit("close");
4903
5134
  }
4904
5135
  };
@@ -4906,9 +5137,9 @@ function text2(key, fallback, args) {
4906
5137
  const translated = translate(key);
4907
5138
  return printf(translated === key ? fallback : translated, args);
4908
5139
  }
4909
- function restoreAttribute2(element, name, value) {
4910
- if (value === null) element.removeAttribute(name);
4911
- else element.setAttribute(name, value);
5140
+ function restoreAttribute2(element, name2, value) {
5141
+ if (value === null) element.removeAttribute(name2);
5142
+ else element.setAttribute(name2, value);
4912
5143
  }
4913
5144
 
4914
5145
  // src/components/date-picker/range-field-adapter.js
@@ -4957,6 +5188,8 @@ var Datebox = class extends Component {
4957
5188
  this.el = root;
4958
5189
  this._originalState = root.getAttribute("data-state");
4959
5190
  this._originalAriaDisabled = root.getAttribute("aria-disabled");
5191
+ this._originalClearable = root.getAttribute("data-clearable");
5192
+ root.toggleAttribute("data-clearable", Boolean(this.options.clearable));
4960
5193
  this._format = effectiveFormat(this.options.format, this.options.time);
4961
5194
  this._disabled = Boolean(this.options.disabled);
4962
5195
  this._open = false;
@@ -5126,6 +5359,8 @@ var Datebox = class extends Component {
5126
5359
  else this.el.setAttribute("data-state", this._originalState);
5127
5360
  if (this._originalAriaDisabled === null) this.el.removeAttribute("aria-disabled");
5128
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);
5129
5364
  super.destroy();
5130
5365
  }
5131
5366
  /** @param {KeyboardEvent} event @returns {void} */
@@ -5170,7 +5405,11 @@ var Datebox = class extends Component {
5170
5405
  /** @returns {void} */
5171
5406
  _syncDisplay() {
5172
5407
  this.refs.input.value = this._value ? formatDate(this._value, this._format) : "";
5173
- 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;
5174
5413
  }
5175
5414
  /** @returns {void} */
5176
5415
  _syncDisabled() {
@@ -5178,6 +5417,7 @@ var Datebox = class extends Component {
5178
5417
  this.refs.toggle.disabled = this._disabled;
5179
5418
  this.refs.clear.disabled = this._disabled;
5180
5419
  this.el.setAttribute("aria-disabled", String(this._disabled));
5420
+ this._syncClear();
5181
5421
  }
5182
5422
  /** @returns {void} */
5183
5423
  _setValid() {
@@ -5188,7 +5428,7 @@ var Datebox = class extends Component {
5188
5428
  this.refs.input.removeAttribute("aria-invalid");
5189
5429
  }
5190
5430
  /** @param {boolean} returnFocus @returns {void} */
5191
- _close(returnFocus) {
5431
+ _close(returnFocus2) {
5192
5432
  if (!this._open) return;
5193
5433
  this._position?.destroy();
5194
5434
  this._position = null;
@@ -5196,7 +5436,7 @@ var Datebox = class extends Component {
5196
5436
  this.refs.popover.dataset.state = "closed";
5197
5437
  this.refs.input.setAttribute("aria-expanded", "false");
5198
5438
  this.refs.toggle.setAttribute("aria-expanded", "false");
5199
- if (returnFocus) this.focus();
5439
+ if (returnFocus2) this.focus();
5200
5440
  this.emit("close");
5201
5441
  }
5202
5442
  /** @returns {Date|null} */
@@ -5498,7 +5738,7 @@ var FieldUpload = class extends Component {
5498
5738
  const xhr = new XMLHttpRequest();
5499
5739
  this._xhr = xhr;
5500
5740
  xhr.open("POST", String(this.options.url));
5501
- 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));
5502
5742
  this.listen(xhr.upload, "progress", (event) => {
5503
5743
  if (event.lengthComputable) this._setProgress(event.loaded / event.total * 100);
5504
5744
  });
@@ -5597,18 +5837,18 @@ function matchesAccept(file, accept) {
5597
5837
  function createFormData(files, paramName, params) {
5598
5838
  const form = new FormData();
5599
5839
  for (const file of files) form.append(paramName, file);
5600
- 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);
5601
5841
  return form;
5602
5842
  }
5603
- function appendFormValue(form, name, value) {
5843
+ function appendFormValue(form, name2, value) {
5604
5844
  if (value == null) return;
5605
5845
  if (Array.isArray(value)) {
5606
- for (const item of value) appendFormValue(form, name, item);
5846
+ for (const item of value) appendFormValue(form, name2, item);
5607
5847
  return;
5608
5848
  }
5609
5849
  const isBlob = typeof Blob === "function" && value instanceof Blob;
5610
- if (isBlob) form.append(name, value);
5611
- 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));
5612
5852
  }
5613
5853
  function parseResponse(text3) {
5614
5854
  if (!text3) return null;
@@ -5789,11 +6029,11 @@ var MultiValueEditor = class extends Component {
5789
6029
  }
5790
6030
  /** @param {MouseEvent} event @returns {void} */
5791
6031
  _handleClick(event) {
5792
- const button = event.target instanceof Element ? event.target.closest("[data-action]") : null;
5793
- if (!button || !this.el.contains(button)) return;
5794
- if (button.getAttribute("aria-disabled") === "true") return;
5795
- const action = button.dataset.action;
5796
- 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);
5797
6037
  if (action === "add") {
5798
6038
  const choices = normalizeOptions2(this.options.options);
5799
6039
  this._values.push(this.options.options === null ? "" : choices[0]?.[0] ?? "");
@@ -6280,6 +6520,7 @@ var NumberField = class extends Component {
6280
6520
  /** @param {KeyboardEvent} event @returns {void} */
6281
6521
  _onKeydown(event) {
6282
6522
  if (this._disabled || this._readonly) return;
6523
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
6283
6524
  const large = Math.max(1, Math.trunc(Number(this.options.largeStep) || 1));
6284
6525
  const actions = {
6285
6526
  ArrowUp: () => this._stepBy(1),
@@ -6433,6 +6674,7 @@ function registerNumberFieldAdapter() {
6433
6674
  label: options.label ? String(options.label) : void 0,
6434
6675
  placeholder: options.placeholder,
6435
6676
  required: options.required,
6677
+ name: field.name || null,
6436
6678
  ...options.props ?? {}
6437
6679
  }));
6438
6680
  let setting = false;
@@ -6627,7 +6869,7 @@ var Rating = class extends Component {
6627
6869
  * @returns {HTMLButtonElement}
6628
6870
  */
6629
6871
  _radio(value, part) {
6630
- const button = (
6872
+ const button2 = (
6631
6873
  /** @type {HTMLButtonElement} */
6632
6874
  h("button", {
6633
6875
  class: "zx-rating__radio",
@@ -6639,18 +6881,18 @@ var Rating = class extends Component {
6639
6881
  dataset: { value: String(value), part }
6640
6882
  })
6641
6883
  );
6642
- this.listen(button, "click", (event) => {
6884
+ this.listen(button2, "click", (event) => {
6643
6885
  this._choose(
6644
6886
  value,
6645
6887
  /** @type {MouseEvent} */
6646
6888
  event.detail > 0
6647
6889
  );
6648
6890
  });
6649
- this.listen(button, "pointerenter", () => this._setPreview(value));
6650
- this.listen(button, "focus", () => this._setPreview(value));
6651
- this.listen(button, "blur", () => this._setPreview(null));
6652
- this._radios.push(button);
6653
- 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;
6654
6896
  }
6655
6897
  /**
6656
6898
  * @param {number} value Step value.
@@ -6867,15 +7109,36 @@ function itemText(item) {
6867
7109
  return String(item ?? "").trim().toLocaleLowerCase();
6868
7110
  }
6869
7111
 
6870
- // src/components/select/select.js
6871
- var PRIORITY_KEYS = [
6872
- "priority.lowest",
6873
- "priority.low",
6874
- "priority.normal",
6875
- "priority.high",
6876
- "priority.highest"
6877
- ];
6878
- 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
+ ]);
6879
7142
  var DEFAULT_STATUSES = Object.freeze([
6880
7143
  ["backlog", "status.backlog", "Backlog", "dotted", "muted"],
6881
7144
  ["todo", "status.todo", "Todo", "ring", "neutral"],
@@ -6887,6 +7150,79 @@ var DEFAULT_STATUSES = Object.freeze([
6887
7150
  ["canceled", "status.canceled", "Canceled", "canceled", "muted"],
6888
7151
  ["duplicate", "status.duplicate", "Duplicate", "duplicate", "muted"]
6889
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"];
6890
7226
  var Select = class _Select extends Component {
6891
7227
  static cssName = "select";
6892
7228
  /** @type {SelectOptions} */
@@ -6933,6 +7269,7 @@ var Select = class _Select extends Component {
6933
7269
  this._createdRoot = false;
6934
7270
  this._root = null;
6935
7271
  this._snapshot = null;
7272
+ this._pointerModality = false;
6936
7273
  this._createdRoot = this.el === null;
6937
7274
  const root = (
6938
7275
  /** @type {HTMLElement} */
@@ -7021,10 +7358,29 @@ var Select = class _Select extends Component {
7021
7358
  );
7022
7359
  this.listen(input, "click", () => this.open());
7023
7360
  this.listen(input, "input", () => this._onInput());
7024
- this.listen(input, "keydown", (event) => this._onKeydown(
7025
- /** @type {KeyboardEvent} */
7026
- event
7027
- ));
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 });
7028
7384
  this.listen(toggle, "click", () => {
7029
7385
  this.focus();
7030
7386
  if (this._open) this.close();
@@ -7037,6 +7393,7 @@ var Select = class _Select extends Component {
7037
7393
  this.listen(panel, "pointerdown", (event) => event.preventDefault());
7038
7394
  this.listen(panel, "click", (event) => this._onOptionClick(event));
7039
7395
  this.listen(document, "pointerdown", (event) => {
7396
+ this._pointerModality = true;
7040
7397
  if (this._open && !root.contains(
7041
7398
  /** @type {Node} */
7042
7399
  event.target
@@ -7187,29 +7544,43 @@ var Select = class _Select extends Component {
7187
7544
  return this;
7188
7545
  }
7189
7546
  /**
7190
- * 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.
7191
7551
  * @param {Element|string|null} target Component target.
7192
- * @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").
7193
7555
  * @returns {Select}
7194
7556
  */
7195
7557
  static priority(target, options = {}) {
7196
7558
  const requestedValue = options.value ?? null;
7197
- 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, {
7198
7563
  ...options,
7564
+ clearable,
7199
7565
  value: null,
7200
7566
  items: [],
7201
7567
  renderItem(item) {
7202
7568
  return priorityContent(item, true);
7203
7569
  },
7570
+ renderNone: options.renderNone ?? (() => priorityContent({ level: null, name: noneLabel() }, true)),
7204
7571
  renderValueAdornment(item) {
7205
7572
  return priorityIndicator(item.level);
7206
7573
  }
7207
7574
  });
7575
+ if (clearable && !options.placeholder) {
7576
+ select.refs.input.placeholder = noneLabel();
7577
+ }
7578
+ const preset = select;
7208
7579
  select.setItems(PRIORITY_KEYS.map((key, level) => {
7209
- let name = level === 2 ? select.msg("priority.medium") : key;
7210
- if (name === "priority.medium") name = select._message(key, PRIORITY_FALLBACKS[level]);
7211
- else if (level !== 2) name = select._message(key, PRIORITY_FALLBACKS[level]);
7212
- 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 };
7213
7584
  }));
7214
7585
  select.set(requestedValue, { silent: true });
7215
7586
  return select;
@@ -7270,10 +7641,10 @@ var Select = class _Select extends Component {
7270
7641
  const value = readSelectItem(item, iconKey);
7271
7642
  if (value && typeof value === "object" && typeof value.nodeType === "number") return value.cloneNode(true);
7272
7643
  if (typeof value !== "string" || !value) return null;
7273
- const color = readSelectItem(item, colorKey);
7644
+ const color = itemColor(readSelectItem(item, colorKey));
7274
7645
  return h("span", {
7275
7646
  class: "zx-select__entity-glyph",
7276
- style: isCssColor(color) ? { "--zx-select-entity-color": String(color) } : null,
7647
+ style: color ? { "--zx-select-entity-color": color } : null,
7277
7648
  ariaHidden: "true"
7278
7649
  }, icon(value, { size: 14 }));
7279
7650
  };
@@ -7805,24 +8176,20 @@ function priorityContent(item, label) {
7805
8176
  );
7806
8177
  }
7807
8178
  function priorityIndicator(level) {
7808
- const normalized = Math.max(0, Math.min(4, Math.trunc(Number(level) || 0)));
7809
- return h("span", {
7810
- class: "zx-select__priority-icon",
7811
- dataset: { level: normalized },
7812
- ariaHidden: "true"
7813
- }, Array.from({ length: 5 }, (_, index) => h("span", {
7814
- dataset: { filled: String(index <= normalized) }
7815
- })));
8179
+ const glyph = priorityIcon(
8180
+ /** @type {number|null} */
8181
+ normalizePriorityLevel(level)
8182
+ );
8183
+ glyph.classList.add("zx-select__priority-icon");
8184
+ return glyph;
7816
8185
  }
7817
8186
  function normalizeStatusItem(input) {
7818
8187
  if (!input || typeof input !== "object" || input.ID == null || input.name == null) return null;
7819
- const indicators = ["dotted", "ring", "progress", "review", "ready", "waiting", "done", "canceled", "duplicate"];
7820
- const tones = ["muted", "neutral", "warning", "info", "accent", "success", "danger"];
7821
8188
  return {
7822
8189
  ...input,
7823
8190
  name: String(input.name),
7824
- indicator: indicators.includes(String(input.indicator)) ? String(input.indicator) : "ring",
7825
- 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",
7826
8193
  description: input.description == null ? "" : String(input.description),
7827
8194
  shortcut: input.shortcut == null ? "" : String(input.shortcut)
7828
8195
  };
@@ -7842,21 +8209,18 @@ function statusContent(item) {
7842
8209
  );
7843
8210
  }
7844
8211
  function statusIndicator(item) {
7845
- const indicator = String(item.indicator ?? "ring");
7846
- const marker = h("span", {
7847
- class: "zx-select__status-indicator",
7848
- dataset: { indicator, tone: String(item.tone ?? "muted") },
7849
- ariaHidden: "true"
7850
- });
7851
- if (indicator === "done") marker.append(icon("check", { size: 8 }));
7852
- if (indicator === "canceled") marker.append(icon("x", { size: 7 }));
7853
- 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;
7854
8215
  }
7855
8216
  function readItem(item, reader) {
7856
8217
  if (typeof reader === "function") return reader(item);
7857
8218
  if (item === null || typeof item !== "object") return item;
7858
8219
  return item[reader];
7859
8220
  }
8221
+ function isModifierKey(event) {
8222
+ return ["Shift", "Control", "Alt", "Meta", "CapsLock"].includes(event.key) || event.ctrlKey || event.metaKey;
8223
+ }
7860
8224
  function isPrintable(event) {
7861
8225
  return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
7862
8226
  }
@@ -7871,6 +8235,7 @@ function registerSelectFieldAdapter() {
7871
8235
  if (event.target.closest?.(".zx-field__label")) field.adapter.focus();
7872
8236
  });
7873
8237
  delete selectOptions.preset;
8238
+ if (preset === "priority" && props.clearable === void 0) selectOptions.clearable = !options.required;
7874
8239
  const select = field.own(preset === "priority" ? Select.priority(null, selectOptions) : preset === "status" ? Select.status(null, selectOptions) : new Select(null, selectOptions));
7875
8240
  field.listen(select.el, "change", (event) => event.stopImmediatePropagation());
7876
8241
  select.on("change", (event) => field.emit("change", { value: event.detail.value }));
@@ -8120,7 +8485,7 @@ var Slider = class extends Component {
8120
8485
  const label = typeof mark === "number" ? this._format(value) : String(mark.label ?? this._format(value));
8121
8486
  element.append(h("span", {
8122
8487
  class: "zx-slider__mark",
8123
- style: { "--zx-slider-mark": `${this._ratio(value) * 100}%` }
8488
+ style: { "--zx-slider-mark": String(this._ratio(value)) }
8124
8489
  }, label));
8125
8490
  }
8126
8491
  return element;
@@ -8246,6 +8611,29 @@ function registerSliderFieldAdapter() {
8246
8611
  });
8247
8612
  }
8248
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
+
8249
8637
  // src/components/tag-picker/tag-picker.js
8250
8638
  var TagPicker = class extends Component {
8251
8639
  static cssName = "tag-picker";
@@ -8257,6 +8645,7 @@ var TagPicker = class extends Component {
8257
8645
  labelKey: "name",
8258
8646
  iconKey: "icon",
8259
8647
  colorKey: "color",
8648
+ toneKey: "tone",
8260
8649
  searchKeys: null,
8261
8650
  filter: "local",
8262
8651
  minQuery: 0,
@@ -8597,11 +8986,12 @@ var TagPicker = class extends Component {
8597
8986
  _renderTags() {
8598
8987
  const tags = this._selected.map((item) => {
8599
8988
  const label = this._labelOf(item);
8600
- const color = this._colorOf(item);
8989
+ const tone = this._toneOf(item);
8990
+ const color = tone ? null : this._colorOf(item);
8601
8991
  const content = this.options.renderTag ? this.options.renderTag(item) : [this._iconOf(item), h("span", { class: "zx-tag-picker__tag-label" }, label)];
8602
8992
  const tag = h("span", {
8603
8993
  class: "zx-tag-picker__tag",
8604
- dataset: { colored: String(Boolean(color)) },
8994
+ dataset: { colored: String(Boolean(color)), tone: tone ?? void 0 },
8605
8995
  style: color ? { "--zx-tag-color": color } : null
8606
8996
  }, content);
8607
8997
  if (!this._readonly && !this._disabled) {
@@ -8970,16 +9360,22 @@ var TagPicker = class extends Component {
8970
9360
  value.cloneNode(true)
8971
9361
  ) : typeof value === "string" && value.trim() ? icon(value, { size: 12 }) : null;
8972
9362
  if (!visual) return null;
9363
+ const tone = this._toneOf(item);
9364
+ const color = tone ? null : this._colorOf(item);
8973
9365
  return h("span", {
8974
9366
  class: "zx-tag-picker__visual",
8975
- 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,
8976
9369
  ariaHidden: "true"
8977
9370
  }, visual);
8978
9371
  }
8979
9372
  /** @param {unknown} item @returns {string|null} */
8980
9373
  _colorOf(item) {
8981
- const value = this._readItem(item, this.options.colorKey);
8982
- 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));
8983
9379
  }
8984
9380
  /** @param {unknown} item @param {unknown} reader @returns {unknown} */
8985
9381
  _readItem(item, reader) {
@@ -9192,7 +9588,7 @@ var Timebox = class extends Component {
9192
9588
  super.destroy();
9193
9589
  }
9194
9590
  /** @param {HTMLElement} parent @param {'hours'|'minutes'|'seconds'} name @param {string} label @param {boolean} bounded @returns {void} */
9195
- _addSegment(parent, name, label, bounded) {
9591
+ _addSegment(parent, name2, label, bounded) {
9196
9592
  const input = h("input", {
9197
9593
  class: "zx-timebox__segment",
9198
9594
  type: "text",
@@ -9200,13 +9596,13 @@ var Timebox = class extends Component {
9200
9596
  autocomplete: "off",
9201
9597
  ariaLabel: label,
9202
9598
  maxLength: bounded ? 2 : null,
9203
- dataset: { segment: name, bounded: String(bounded) }
9599
+ dataset: { segment: name2, bounded: String(bounded) }
9204
9600
  });
9205
9601
  this._inputs.push(input);
9206
9602
  parent.append(input);
9207
9603
  this.listen(input, "focus", () => input.select());
9208
9604
  this.listen(input, "input", () => {
9209
- if (name === "hours" && this.options.signed && input.value.trim().startsWith("-")) {
9605
+ if (name2 === "hours" && this.options.signed && input.value.trim().startsWith("-")) {
9210
9606
  this._parts.negative = true;
9211
9607
  }
9212
9608
  const digits = input.value.replace(/\D/g, "");
@@ -9224,9 +9620,9 @@ var Timebox = class extends Component {
9224
9620
  _readParts() {
9225
9621
  const parts = { ...this._parts };
9226
9622
  for (const input of this._inputs) {
9227
- const name = input.dataset.segment;
9623
+ const name2 = input.dataset.segment;
9228
9624
  const maximum = input.dataset.bounded === "true" ? 59 : Number.MAX_SAFE_INTEGER;
9229
- 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))));
9230
9626
  }
9231
9627
  return parts;
9232
9628
  }
@@ -9239,8 +9635,8 @@ var Timebox = class extends Component {
9239
9635
  /** @returns {void} */
9240
9636
  _writeParts() {
9241
9637
  for (const input of this._inputs) {
9242
- const name = input.dataset.segment;
9243
- 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]);
9244
9640
  }
9245
9641
  if (this.refs.sign) {
9246
9642
  this.refs.sign.textContent = this._parts.negative ? "-" : "+";
@@ -9429,7 +9825,7 @@ function snapshot(element) {
9429
9825
  }
9430
9826
  function restore(element, state) {
9431
9827
  for (const attribute of Array.from(element.attributes)) element.removeAttribute(attribute.name);
9432
- for (const [name, value] of state.attributes) element.setAttribute(name, value);
9828
+ for (const [name2, value] of state.attributes) element.setAttribute(name2, value);
9433
9829
  element.replaceChildren(...state.children);
9434
9830
  }
9435
9831
 
@@ -9772,8 +10168,8 @@ var ORDER = (
9772
10168
  function breakpointOf(width) {
9773
10169
  const value = Number(width);
9774
10170
  if (!Number.isFinite(value)) return "xs";
9775
- for (const name of ORDER) {
9776
- if (value >= breakpoints[name]) return name;
10171
+ for (const name2 of ORDER) {
10172
+ if (value >= breakpoints[name2]) return name2;
9777
10173
  }
9778
10174
  return "xs";
9779
10175
  }
@@ -9789,7 +10185,7 @@ function onBreakpoint(handler, options = {}) {
9789
10185
  /** @type {Element} */
9790
10186
  target
9791
10187
  ) : null;
9792
- let name = (
10188
+ let name2 = (
9793
10189
  /** @type {BreakpointName|null} */
9794
10190
  null
9795
10191
  );
@@ -9801,9 +10197,9 @@ function onBreakpoint(handler, options = {}) {
9801
10197
  const width = measure();
9802
10198
  if (element && width === 0) return;
9803
10199
  const next = breakpointOf(width);
9804
- if (next === name) return;
9805
- name = next;
9806
- handler(name, width);
10200
+ if (next === name2) return;
10201
+ name2 = next;
10202
+ handler(name2, width);
9807
10203
  };
9808
10204
  update();
9809
10205
  let frame = 0;
@@ -9821,7 +10217,7 @@ function onBreakpoint(handler, options = {}) {
9821
10217
  globalThis.addEventListener?.("resize", update, { passive: true });
9822
10218
  }
9823
10219
  return {
9824
- current: () => name ?? breakpointOf(measure()),
10220
+ current: () => name2 ?? breakpointOf(measure()),
9825
10221
  destroy: () => {
9826
10222
  if (destroyed) return;
9827
10223
  destroyed = true;
@@ -9909,13 +10305,119 @@ function currencyCode(value) {
9909
10305
  }
9910
10306
 
9911
10307
  // src/core/overlay-host.js
9912
- 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]";
9913
10309
  function overlayHost(source = null) {
9914
10310
  const context = source == null ? document.activeElement instanceof Element ? document.activeElement : null : resolveElement(source);
9915
10311
  const scope2 = context?.closest?.(SCOPE_SELECTOR) ?? null;
9916
10312
  if (!scope2 || scope2 === document.documentElement || scope2 === document.body) return document.body;
9917
10313
  return scope2;
9918
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
+ }
9919
10421
 
9920
10422
  // src/components/table/sort.js
9921
10423
  function compareStrings(left, right) {
@@ -9974,6 +10476,7 @@ var Table = class extends Component {
9974
10476
  rowKind: null,
9975
10477
  rowKinds: null,
9976
10478
  zebra: true,
10479
+ variant: "default",
9977
10480
  editMode: false,
9978
10481
  editTrigger: "double",
9979
10482
  hierarchy: false,
@@ -10039,6 +10542,7 @@ var Table = class extends Component {
10039
10542
  }
10040
10543
  }
10041
10544
  this._reconcileExpanded();
10545
+ if (normalizeTableVariant(this.options.variant) === "plain") root.dataset.variant = "plain";
10042
10546
  this._table = h("table", { class: "zx-table__table" });
10043
10547
  if (this._hierarchy) this._table.setAttribute("role", "treegrid");
10044
10548
  this._colgroup = h("colgroup", { class: "zx-table__columns" });
@@ -10322,7 +10826,7 @@ var Table = class extends Component {
10322
10826
  const column = this._columns.find((candidate) => candidate.id === String(id));
10323
10827
  const index = this.getColumnOrder().indexOf(String(id));
10324
10828
  if (column && index >= 0) {
10325
- 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}.`;
10326
10830
  }
10327
10831
  }
10328
10832
  return this;
@@ -10938,8 +11442,8 @@ var Table = class extends Component {
10938
11442
  { class: "zx-table__column-controls" },
10939
11443
  h(
10940
11444
  "summary",
10941
- { class: "zx-button zx-button--sm" },
10942
- icon("fields", { size: 13 }),
11445
+ { class: "zx-btn", dataset: { size: "sm" } },
11446
+ icon("fields"),
10943
11447
  this._message("table.columns", "Columns", [])
10944
11448
  ),
10945
11449
  list
@@ -10970,26 +11474,27 @@ var Table = class extends Component {
10970
11474
  disabled: checked && visible.length === 1,
10971
11475
  dataset: { columnId: column.id, columnAction: "toggle" }
10972
11476
  }) : null;
10973
- 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);
10974
11479
  const order = this.options.columnReorder ? h(
10975
11480
  "span",
10976
11481
  {
10977
11482
  class: "zx-table__column-order",
10978
11483
  role: "group",
10979
- ariaLabel: this._message("table.moveColumn", "Move %1", [column.label])
11484
+ ariaLabel: this._message("table.moveColumn", "Move %1", [name2])
10980
11485
  },
10981
11486
  h("button", {
10982
11487
  class: "zx-table__column-move",
10983
11488
  type: "button",
10984
11489
  disabled: index === 0,
10985
- ariaLabel: `Move ${column.label} earlier`,
11490
+ ariaLabel: `Move ${name2} earlier`,
10986
11491
  dataset: { columnId: column.id, columnAction: "up" }
10987
11492
  }, icon("chevron-up", { size: 11 })),
10988
11493
  h("button", {
10989
11494
  class: "zx-table__column-move",
10990
11495
  type: "button",
10991
11496
  disabled: index === this._columns.length - 1,
10992
- ariaLabel: `Move ${column.label} later`,
11497
+ ariaLabel: `Move ${name2} later`,
10993
11498
  dataset: { columnId: column.id, columnAction: "down" }
10994
11499
  }, icon("chevron-down", { size: 11 }))
10995
11500
  ) : null;
@@ -11040,18 +11545,18 @@ var Table = class extends Component {
11040
11545
  }
11041
11546
  /** @param {Event} event @returns {void} */
11042
11547
  _handleColumnControlClick(event) {
11043
- const button = (
11548
+ const button2 = (
11044
11549
  /** @type {Element|null} */
11045
11550
  event.target?.closest?.(".zx-table__column-move")
11046
11551
  );
11047
- if (!button || !this._columnList?.contains(button)) return;
11552
+ if (!button2 || !this._columnList?.contains(button2)) return;
11048
11553
  const id = (
11049
11554
  /** @type {HTMLElement} */
11050
- button.dataset.columnId
11555
+ button2.dataset.columnId
11051
11556
  );
11052
11557
  const action = (
11053
11558
  /** @type {HTMLElement} */
11054
- button.dataset.columnAction
11559
+ button2.dataset.columnAction
11055
11560
  );
11056
11561
  const index = this.getColumnOrder().indexOf(String(id));
11057
11562
  if (action === "up" && index > 0) this.moveColumn(String(id), this._columns[index - 1].id, "before");
@@ -11263,20 +11768,23 @@ var Table = class extends Component {
11263
11768
  this._headers = /* @__PURE__ */ new Map();
11264
11769
  this._headerSpanning = this._spanning;
11265
11770
  for (const column of this._visibleColumns()) {
11266
- const th = h("th", { scope: "col" });
11771
+ const th = h("th", { scope: "col", dataset: { column: column.id } });
11267
11772
  const align = column.align ?? (isNumericColumn(column) ? "end" : null);
11268
11773
  if (align) th.dataset.align = align;
11269
11774
  if (column.headerTitle) th.title = column.headerTitle;
11775
+ const name2 = tableHeaderName(column);
11776
+ if (name2 !== null) th.setAttribute("aria-label", name2);
11270
11777
  if (column.sortable && !this._spanning) {
11271
- const button = h("button", {
11778
+ const button2 = h("button", {
11272
11779
  class: "zx-table__sort-button",
11273
- type: "button"
11780
+ type: "button",
11781
+ ariaLabel: name2 ?? void 0
11274
11782
  }, h("span", { class: "zx-table__header-label" }, column.label));
11275
- this.listen(button, "click", () => {
11783
+ this.listen(button2, "click", () => {
11276
11784
  const direction = this._sort?.id === column.id && this._sort.dir === "asc" ? "desc" : "asc";
11277
11785
  this.setSort(column.id, direction);
11278
11786
  });
11279
- th.append(button);
11787
+ th.append(button2);
11280
11788
  } else {
11281
11789
  th.append(h("span", { class: "zx-table__header-label" }, column.label));
11282
11790
  }
@@ -11408,9 +11916,9 @@ var Table = class extends Component {
11408
11916
  const align = column.align ?? (isNumericColumn(column) ? "end" : null);
11409
11917
  if (align) cell.dataset.align = align;
11410
11918
  cell.dataset.label = column.label;
11919
+ cell.dataset.column = column.id;
11411
11920
  if (column.popin === false) cell.dataset.popin = "false";
11412
11921
  if (this._editMode !== false) {
11413
- cell.dataset.column = column.id;
11414
11922
  if (resolveEditable(column, row) !== false) {
11415
11923
  cell.dataset.editable = "true";
11416
11924
  cell.tabIndex = -1;
@@ -11487,7 +11995,7 @@ var Table = class extends Component {
11487
11995
  );
11488
11996
  return;
11489
11997
  }
11490
- if (isInteractiveCellContent(target)) return;
11998
+ if (this._editMode !== false && isInteractiveCellContent(target)) return;
11491
11999
  if (this._editMode !== false && this._editTrigger === "single") {
11492
12000
  const cell = target?.closest('td[data-editable="true"]');
11493
12001
  if (cell && this._tbody.contains(cell)) {
@@ -11515,7 +12023,7 @@ var Table = class extends Component {
11515
12023
  if (!rowElement || !this._tbody.contains(rowElement)) return;
11516
12024
  const meta = this._rowMeta.get(rowElement);
11517
12025
  if (!meta) return;
11518
- if (isInteractiveCellContent(target)) return;
12026
+ if (this._editMode !== false && isInteractiveCellContent(target)) return;
11519
12027
  if (this._editMode !== false && this._editTrigger === "double") {
11520
12028
  const cell = target?.closest('td[data-editable="true"]');
11521
12029
  if (cell && this._tbody.contains(cell)) {
@@ -11651,7 +12159,7 @@ var Table = class extends Component {
11651
12159
  ...props,
11652
12160
  class: textarea ? "zx-table__editor-input zx-table__editor-textarea" : "zx-table__editor-input",
11653
12161
  ...textarea ? {} : { type: "text" },
11654
- ariaLabel: props.ariaLabel ?? `${column.label} row ${index + 1}`,
12162
+ ariaLabel: props.ariaLabel ?? `${tableColumnName(column)} row ${index + 1}`,
11655
12163
  value: formatCellValue(column, value, row)
11656
12164
  })
11657
12165
  );
@@ -11670,7 +12178,7 @@ var Table = class extends Component {
11670
12178
  const control = entry?.control;
11671
12179
  if (!control || typeof control.focus !== "function") return;
11672
12180
  control.focus();
11673
- if (entry.type === "text" && typeof control.select === "function") control.select();
12181
+ if (editorSelectsOnEntry(entry.type) && typeof control.select === "function") control.select();
11674
12182
  }
11675
12183
  /**
11676
12184
  * Moves focus back onto a cell.
@@ -12308,6 +12816,9 @@ function normalizeRowKinds(option) {
12308
12816
  function initialExpanded(option) {
12309
12817
  return new Set(Array.isArray(option) ? option : []);
12310
12818
  }
12819
+ function normalizeTableVariant(value) {
12820
+ return value === "plain" ? "plain" : "default";
12821
+ }
12311
12822
  function normalizeEditMode(mode) {
12312
12823
  if (mode === "cell" || mode === "row") return mode;
12313
12824
  return mode === true ? "cell" : false;
@@ -12350,6 +12861,22 @@ function normalizeTableColumns(columns) {
12350
12861
  return { ...column, id, label: String(column.label ?? id) };
12351
12862
  });
12352
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
+ }
12353
12880
  function reconcileTableColumnOrder(columns, order) {
12354
12881
  const available = columns.map((column) => column.id);
12355
12882
  const allowed = new Set(available);
@@ -12526,8 +13053,8 @@ function normalizeGlass(value) {
12526
13053
  function normalizeShape(value) {
12527
13054
  return value === "circle" ? "circle" : "tile";
12528
13055
  }
12529
- function originalAttribute(snapshot3, name) {
12530
- return snapshot3?.attributes.find(([attribute]) => attribute === name)?.[1] ?? null;
13056
+ function originalAttribute(snapshot3, name2) {
13057
+ return snapshot3?.attributes.find(([attribute]) => attribute === name2)?.[1] ?? null;
12531
13058
  }
12532
13059
 
12533
13060
  // src/components/launcher/launcher.js
@@ -12591,9 +13118,17 @@ var Launcher = class extends Component {
12591
13118
  this._requestSequence = 0;
12592
13119
  this._queryTimer = null;
12593
13120
  this._busy = false;
13121
+ this._settled = true;
13122
+ this._pages = [];
13123
+ this._restoreActiveId = null;
13124
+ this._announcePending = false;
13125
+ this._announceToggle = false;
12594
13126
  this._destroyed = false;
12595
13127
  const listId = uid("zx-launcher-results");
12596
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;
12597
13132
  const input = h("input", {
12598
13133
  ref: "input",
12599
13134
  class: "zx-launcher__input",
@@ -12605,9 +13140,20 @@ var Launcher = class extends Component {
12605
13140
  ariaControls: listId,
12606
13141
  ariaExpanded: "false",
12607
13142
  ariaLabel: label,
12608
- placeholder: this.options.placeholder ?? this._message("launcher.placeholder", "Search applications and records"),
13143
+ placeholder,
12609
13144
  value: this._query
12610
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
+ );
12611
13157
  const close = h("button", {
12612
13158
  ref: "close",
12613
13159
  class: "zx-icon-btn zx-launcher__close",
@@ -12620,6 +13166,13 @@ var Launcher = class extends Component {
12620
13166
  role: "status",
12621
13167
  ariaLive: "polite"
12622
13168
  });
13169
+ const announce = h("div", {
13170
+ ref: "announce",
13171
+ class: "zx-launcher__announce",
13172
+ role: "status",
13173
+ ariaLive: "polite",
13174
+ ariaAtomic: "true"
13175
+ });
12623
13176
  const results = h("div", {
12624
13177
  ref: "results",
12625
13178
  class: "zx-launcher__results",
@@ -12634,6 +13187,7 @@ var Launcher = class extends Component {
12634
13187
  const footer = launcherHints(this.options.hints, {
12635
13188
  move: this._message("launcher.hintMove", "Move"),
12636
13189
  open: this._message("launcher.hintOpen", "Open"),
13190
+ back: this._message("launcher.hintBack", "Back"),
12637
13191
  close: this._message("launcher.hintClose", "Close")
12638
13192
  });
12639
13193
  dialog.setAttribute("aria-label", label);
@@ -12644,17 +13198,19 @@ var Launcher = class extends Component {
12644
13198
  h("div", {
12645
13199
  class: "zx-launcher__search",
12646
13200
  dataset: { shortcut: shortcut ? "true" : "false" }
12647
- }, icon("search"), input, shortcut, close),
13201
+ }, icon("search"), scope2, input, shortcut, close),
12648
13202
  status,
12649
13203
  results,
12650
- footer
13204
+ footer,
13205
+ announce
12651
13206
  )
12652
13207
  );
12653
13208
  dialog.dataset.state = "closed";
12654
- if (this._createdRoot) overlayHost(this.options.scope).append(dialog);
13209
+ if (this._createdRoot) connectedOverlayHost(this.options.scope)?.append(dialog);
12655
13210
  this.listen(input, "input", () => {
12656
13211
  this._query = /** @type {HTMLInputElement} */
12657
13212
  input.value;
13213
+ this._announcePending = false;
12658
13214
  this.emit("query", { query: this._query.trim() });
12659
13215
  this._refresh();
12660
13216
  });
@@ -12662,6 +13218,10 @@ var Launcher = class extends Component {
12662
13218
  /** @type {KeyboardEvent} */
12663
13219
  event
12664
13220
  ));
13221
+ this.listen(scopeBack, "click", () => {
13222
+ this.back();
13223
+ input.focus();
13224
+ });
12665
13225
  this.listen(close, "click", () => this.close());
12666
13226
  this.listen(results, "click", (event) => {
12667
13227
  const option = (
@@ -12690,6 +13250,13 @@ var Launcher = class extends Component {
12690
13250
  dialog.dataset.state = "closed";
12691
13251
  input.setAttribute("aria-expanded", "false");
12692
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
+ }
12693
13260
  this.emit("close");
12694
13261
  });
12695
13262
  this.listen(document, "keydown", (event) => this._onDocumentKeydown(
@@ -12699,25 +13266,60 @@ var Launcher = class extends Component {
12699
13266
  this._renderResults();
12700
13267
  return dialog;
12701
13268
  }
12702
- /** Opens the launcher and focuses/selects its query. @returns {this} @fires Launcher#open */
12703
- open() {
12704
- 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
+ }
12705
13285
  if (this._createdRoot) {
12706
13286
  const host = overlayHost(this.options.scope);
12707
13287
  if (this.el.parentElement !== host) host.append(this.el);
12708
13288
  }
13289
+ const before = this._pages.map((entry) => entry.page);
12709
13290
  this.el.showModal();
12710
13291
  this.el.dataset.state = "open";
12711
13292
  this.refs.input.setAttribute("aria-expanded", "true");
12712
- this._refresh();
13293
+ this._goTo(target == null ? [] : this._pagePath(target), { emit: false });
12713
13294
  queueMicrotask(() => {
12714
13295
  if (!this.isOpen()) return;
12715
13296
  this.refs.input.focus();
12716
13297
  this.refs.input.select();
12717
13298
  });
12718
13299
  this.emit("open");
13300
+ if (!samePath(before, this._pages.map((entry) => entry.page))) this._emitPage();
13301
+ return this;
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();
12719
13317
  return this;
12720
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
+ }
12721
13323
  /** Closes the launcher. @returns {this} */
12722
13324
  close() {
12723
13325
  if (this.isOpen()) this.el.close();
@@ -12741,6 +13343,7 @@ var Launcher = class extends Component {
12741
13343
  setQuery(query) {
12742
13344
  this._query = String(query ?? "");
12743
13345
  this.refs.input.value = this._query;
13346
+ this._announcePending = false;
12744
13347
  this.emit("query", { query: this._query.trim() });
12745
13348
  this._refresh();
12746
13349
  return this;
@@ -12749,7 +13352,7 @@ var Launcher = class extends Component {
12749
13352
  getQuery() {
12750
13353
  return this._query;
12751
13354
  }
12752
- /** 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} */
12753
13356
  setItems(items) {
12754
13357
  this._items = normalizeItems(items);
12755
13358
  this._renderResults();
@@ -12762,10 +13365,14 @@ var Launcher = class extends Component {
12762
13365
  this._renderResults();
12763
13366
  return this;
12764
13367
  }
12765
- /** 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} */
12766
13369
  setSources(sources) {
12767
- this._abortRequest();
12768
13370
  this._sources = normalizeSources(sources);
13371
+ if (this._pages.length) {
13372
+ this._pages[0].parent.sourceItems = null;
13373
+ return this;
13374
+ }
13375
+ this._abortRequest();
12769
13376
  this._sourceItems.clear();
12770
13377
  this._refresh();
12771
13378
  return this;
@@ -12780,16 +13387,24 @@ var Launcher = class extends Component {
12780
13387
  super.destroy();
12781
13388
  if (!this._createdRoot && original) {
12782
13389
  for (const attribute of Array.from(this.el.attributes)) this.el.removeAttribute(attribute.name);
12783
- 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);
12784
13391
  this.el.replaceChildren(...original.children);
12785
13392
  }
12786
13393
  }
12787
- /** @returns {void} */
12788
- _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();
12789
13402
  this._sourceItems.clear();
13403
+ const { sources } = this._level();
13404
+ this._settled = sources.length === 0;
13405
+ if (immediate && sources.length) void this._loadSources();
12790
13406
  this._renderResults();
12791
- this._abortRequest();
12792
- if (this._sources.length === 0) return;
13407
+ if (immediate || sources.length === 0) return;
12793
13408
  const delay = Math.max(0, Number(this.options.debounce) || 0);
12794
13409
  this._queryTimer = setTimeout(() => this._loadSources(), delay);
12795
13410
  }
@@ -12797,8 +13412,15 @@ var Launcher = class extends Component {
12797
13412
  async _loadSources() {
12798
13413
  this._queryTimer = null;
12799
13414
  const query = this._query.trim();
12800
- const sources = this._sources.filter((source) => visibleForQuery(source.when, query) && query.length >= Math.max(Number(this.options.minQuery) || 0, Number(source.minQuery) || 0));
12801
- 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
+ }
12802
13424
  const request = new AbortController();
12803
13425
  const sequence = ++this._requestSequence;
12804
13426
  this._request = request;
@@ -12809,7 +13431,7 @@ var Launcher = class extends Component {
12809
13431
  if (request.signal.aborted || sequence !== this._requestSequence) return;
12810
13432
  this._sourceItems.set(source.id, normalizeItems(loaded).map((item, itemOrder) => ({
12811
13433
  ...item,
12812
- _source: source.id,
13434
+ _source: source._page ? null : source.id,
12813
13435
  _sourceOrder: source.order === "source",
12814
13436
  group: item.group ?? source.label ?? "",
12815
13437
  itemOrder: item.itemOrder ?? (source.order === "source" ? itemOrder : void 0)
@@ -12817,13 +13439,157 @@ var Launcher = class extends Component {
12817
13439
  this._renderResults();
12818
13440
  } catch (error) {
12819
13441
  if (request.signal.aborted || sequence !== this._requestSequence) return;
12820
- this.emit("error", { source, error });
13442
+ this.emit("error", { source, error, page });
12821
13443
  }
12822
13444
  }));
12823
13445
  if (request.signal.aborted || sequence !== this._requestSequence) return;
12824
13446
  this._request = null;
13447
+ this._settled = true;
12825
13448
  this._setBusy(false);
12826
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
+ }
12827
13593
  /** @param {boolean} busy @returns {void} */
12828
13594
  _setBusy(busy) {
12829
13595
  this._busy = busy;
@@ -12842,11 +13608,14 @@ var Launcher = class extends Component {
12842
13608
  /** @returns {void} */
12843
13609
  _renderResults() {
12844
13610
  const queryKey = this._query;
12845
- 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);
12846
13614
  const query = this._query.trim();
12847
- 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));
12848
13617
  const rankedLocal = rankLauncherItems(local, query, this.options.maxResults);
12849
- const remote = this._sources.flatMap((source) => {
13618
+ const remote = level.sources.flatMap((source) => {
12850
13619
  const loaded = this._sourceItems.get(String(source.id)) ?? [];
12851
13620
  return source.order === "source" ? loaded.filter((item) => visibleForQuery(item.when, query)) : rankLauncherItems(
12852
13621
  loaded.filter((item) => visibleForQuery(item.when, query)),
@@ -12889,7 +13658,8 @@ var Launcher = class extends Component {
12889
13658
  }
12890
13659
  /** @param {RankedLauncherItem} item @param {number} index @returns {HTMLElement} */
12891
13660
  _option(item, index) {
12892
- const href = safeHref(item.href);
13661
+ const page = isLauncherPage(item);
13662
+ const href = page ? null : safeHref(item.href);
12893
13663
  const tag = href ? "a" : "button";
12894
13664
  const children = [];
12895
13665
  const visual = launcherItemIcon(item);
@@ -12904,6 +13674,17 @@ var Launcher = class extends Component {
12904
13674
  item.description ? h("span", { class: "zx-launcher__description" }, item.description) : null
12905
13675
  ));
12906
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")));
12907
13688
  return (
12908
13689
  /** @type {HTMLElement} */
12909
13690
  h(tag, {
@@ -12918,10 +13699,12 @@ var Launcher = class extends Component {
12918
13699
  ariaSelected: "false",
12919
13700
  ariaDisabled: item.disabled ? "true" : null,
12920
13701
  ariaCurrent: item.current ? "page" : null,
13702
+ ariaKeyShortcuts: keys,
12921
13703
  dataset: {
12922
13704
  launcherIndex: index,
12923
13705
  pinned: item.pinned !== void 0 && item.pinned !== false ? "true" : null,
12924
- kind: normalizeLauncherKind(item.kind)
13706
+ kind: normalizeLauncherKind(item.kind),
13707
+ page: page ? "true" : null
12925
13708
  }
12926
13709
  }, children)
12927
13710
  );
@@ -12936,6 +13719,11 @@ var Launcher = class extends Component {
12936
13719
  this.refs.status.textContent = "";
12937
13720
  }
12938
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
+ }
12939
13727
  }
12940
13728
  /** @returns {void} */
12941
13729
  _syncActive() {
@@ -12954,6 +13742,12 @@ var Launcher = class extends Component {
12954
13742
  this.close();
12955
13743
  return;
12956
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
+ }
12957
13751
  if (event.key === "Enter") {
12958
13752
  if (this._active < 0) return;
12959
13753
  event.preventDefault();
@@ -12962,9 +13756,8 @@ var Launcher = class extends Component {
12962
13756
  return;
12963
13757
  }
12964
13758
  const enabled = this._results.map((item, index) => item.disabled ? -1 : index).filter((index) => index >= 0);
12965
- if (enabled.length === 0) return;
12966
13759
  if (/^Arrow(?:Left|Right|Up|Down)$/.test(event.key)) {
12967
- const next = this._spatialIndex(event.key, enabled);
13760
+ const next = enabled.length ? this._spatialIndex(event.key, enabled) : null;
12968
13761
  if (next !== null) {
12969
13762
  event.preventDefault();
12970
13763
  this._active = next;
@@ -12972,8 +13765,12 @@ var Launcher = class extends Component {
12972
13765
  this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`)?.scrollIntoView({ block: "nearest", inline: "nearest" });
12973
13766
  return;
12974
13767
  }
12975
- 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
+ }
12976
13772
  }
13773
+ if (enabled.length === 0) return;
12977
13774
  let position2 = enabled.indexOf(this._active);
12978
13775
  if (event.key === "Home" || event.key === "PageUp") position2 = 0;
12979
13776
  else if (event.key === "End" || event.key === "PageDown") position2 = enabled.length - 1;
@@ -12985,6 +13782,30 @@ var Launcher = class extends Component {
12985
13782
  this._syncActive();
12986
13783
  this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`)?.scrollIntoView({ block: "nearest" });
12987
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
+ }
12988
13809
  /** @param {string} direction @param {number[]} enabled @returns {number|null} */
12989
13810
  _spatialIndex(direction, enabled) {
12990
13811
  const current = this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`);
@@ -13001,21 +13822,28 @@ var Launcher = class extends Component {
13001
13822
  event.preventDefault();
13002
13823
  return;
13003
13824
  }
13825
+ if (isLauncherPage(item)) {
13826
+ event.preventDefault();
13827
+ this._openPage(item);
13828
+ this.refs.input.focus();
13829
+ return;
13830
+ }
13004
13831
  const selected = this.emit("select", {
13005
13832
  item,
13006
13833
  value: item.value ?? item.id,
13007
13834
  source: item._source ?? null,
13008
- query: this._query.trim()
13835
+ query: this._query.trim(),
13836
+ page: this.getPage()
13009
13837
  });
13010
13838
  if (selected.defaultPrevented) {
13011
13839
  event.preventDefault();
13012
13840
  return;
13013
13841
  }
13842
+ this.close();
13014
13843
  if (!item.href) {
13015
13844
  event.preventDefault();
13016
13845
  item.invoke?.();
13017
13846
  }
13018
- this.close();
13019
13847
  }
13020
13848
  /** @param {KeyboardEvent} event @returns {void} */
13021
13849
  _onDocumentKeydown(event) {
@@ -13031,11 +13859,12 @@ var Launcher = class extends Component {
13031
13859
  * Resolves a message through the host translator, falling back to the built-in English text.
13032
13860
  * @param {string} key Message key.
13033
13861
  * @param {string} fallback English default.
13862
+ * @param {...unknown} args Positional `%1`, `%2` values.
13034
13863
  * @returns {string}
13035
13864
  */
13036
- _message(key, fallback) {
13037
- const message = this.msg(key);
13038
- return message === key ? fallback : message;
13865
+ _message(key, fallback, ...args) {
13866
+ const message = this.msg(key, ...args);
13867
+ return message === key ? printf(fallback, args) : message;
13039
13868
  }
13040
13869
  };
13041
13870
  function launcherShortcutLabel() {
@@ -13045,16 +13874,73 @@ function launcherShortcutLabel() {
13045
13874
  );
13046
13875
  return /mac|iphone|ipad/i.test(platform) ? "\u2318 K" : "Ctrl K";
13047
13876
  }
13048
- function launcherHints(hints, defaults) {
13877
+ function launcherHints(hints, defaults4) {
13049
13878
  if (hints === false) return null;
13050
13879
  const labels = hints && typeof hints === "object" ? hints : {};
13051
13880
  return h(
13052
13881
  "footer",
13053
13882
  { class: "zx-launcher__hints", ariaHidden: "true" },
13054
- h("span", {}, h("kbd", {}, "\u2191\u2193\u2190\u2192"), labels.move ?? defaults.move),
13055
- h("span", {}, h("kbd", {}, "\u21B5"), labels.open ?? defaults.open),
13056
- h("span", {}, h("kbd", {}, "Esc"), labels.close ?? defaults.close)
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
13057
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));
13058
13944
  }
13059
13945
  function launcherItemIcon(item) {
13060
13946
  let visual = item.icon;
@@ -13175,7 +14061,7 @@ function normalizeLauncherItem(item) {
13175
14061
  if (href !== null) normalized.href = href;
13176
14062
  else {
13177
14063
  delete normalized.href;
13178
- if (typeof normalized.invoke !== "function") normalized.disabled = true;
14064
+ if (typeof normalized.invoke !== "function" && !isLauncherPage(normalized)) normalized.disabled = true;
13179
14065
  }
13180
14066
  return normalized;
13181
14067
  }
@@ -13185,6 +14071,7 @@ function normalizeSources(sources) {
13185
14071
  }
13186
14072
 
13187
14073
  // src/components/message/message.js
14074
+ var MESSAGE_ACTION_MIN_TIMEOUT = 8e3;
13188
14075
  var MessageQueue = class {
13189
14076
  #limit;
13190
14077
  #active = [];
@@ -13275,7 +14162,7 @@ var Message = class _Message extends Component {
13275
14162
  closable: true,
13276
14163
  maxVisible: 5
13277
14164
  };
13278
- /** @type {WeakMap<Element, Message>} */
14165
+ /** @type {WeakMap<Element, Message>} Floating region per overlay host. */
13279
14166
  static #floating = /* @__PURE__ */ new WeakMap();
13280
14167
  /** @returns {HTMLElement} */
13281
14168
  render() {
@@ -13287,6 +14174,7 @@ var Message = class _Message extends Component {
13287
14174
  this._createdRoot = created;
13288
14175
  this._original = created ? null : snapshot2(root);
13289
14176
  this._entries = /* @__PURE__ */ new Set();
14177
+ this._floatingHost = null;
13290
14178
  this._queue = new MessageQueue(normalizeLimit(this.options.maxVisible), (entry) => {
13291
14179
  this._activate(entry);
13292
14180
  });
@@ -13302,10 +14190,13 @@ var Message = class _Message extends Component {
13302
14190
  * @returns {MessageHandle}
13303
14191
  */
13304
14192
  show(msg, options = {}) {
14193
+ const action = normalizeMessageAction(options.action);
13305
14194
  const settings = {
13306
- kind: normalizeKind(options.kind ?? this.options.kind),
13307
- timeout: normalizeTimeout(options.timeout ?? this.options.timeout),
13308
- 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)
13309
14200
  };
13310
14201
  const entry = this._createEntry(msg, settings);
13311
14202
  this._entries.add(entry);
@@ -13366,10 +14257,32 @@ var Message = class _Message extends Component {
13366
14257
  static progress(text3, options = {}) {
13367
14258
  return this.#floatingRegion(options.scope)._progress(text3);
13368
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
+ }
13369
14281
  /** @returns {void} */
13370
14282
  destroy() {
13371
14283
  if (this._cleaned) return;
13372
14284
  this._cleaned = true;
14285
+ this._floatingHost = null;
13373
14286
  for (const entry of this._entries) {
13374
14287
  clearEntryTimers(entry);
13375
14288
  entry.node.remove();
@@ -13421,7 +14334,7 @@ var Message = class _Message extends Component {
13421
14334
  }
13422
14335
  /**
13423
14336
  * @param {string|Node} msg
13424
- * @param {{kind: MessageKind, timeout: number, closable: boolean}} settings
14337
+ * @param {{kind: MessageKind, timeout: number, closable: boolean, action?: MessageAction|null, leading?: Node|null}} settings
13425
14338
  * @param {{pct: number, text: string}|null} [progress=null]
13426
14339
  * @returns {any}
13427
14340
  */
@@ -13429,7 +14342,7 @@ var Message = class _Message extends Component {
13429
14342
  const iconHost = h(
13430
14343
  "span",
13431
14344
  { class: "zx-message__icon", ariaHidden: "true" },
13432
- icon(settings.kind, { size: 18 })
14345
+ settings.leading ?? icon(settings.kind, { size: 18 })
13433
14346
  );
13434
14347
  const content = h("div", { class: "zx-message__content" });
13435
14348
  if (isNode(msg)) content.append(msg);
@@ -13450,9 +14363,13 @@ var Message = class _Message extends Component {
13450
14363
  timer: null,
13451
14364
  exitTimer: null,
13452
14365
  closed: false,
14366
+ hovered: false,
14367
+ focused: false,
14368
+ returnFocus: null,
13453
14369
  progressBar: null,
13454
14370
  progressFill: null
13455
14371
  };
14372
+ if (settings.leading) node.dataset.leading = "";
13456
14373
  if (progress) {
13457
14374
  node.dataset.progress = "";
13458
14375
  const fill = h("span", { class: "zx-message__progress-fill" });
@@ -13469,6 +14386,23 @@ var Message = class _Message extends Component {
13469
14386
  entry.progressFill = fill;
13470
14387
  syncProgress(entry, progress);
13471
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
+ }
13472
14406
  if (settings.closable) {
13473
14407
  const close = h("button", {
13474
14408
  class: "zx-icon-btn zx-message__close",
@@ -13479,8 +14413,40 @@ var Message = class _Message extends Component {
13479
14413
  node.append(close);
13480
14414
  this.listen(close, "click", () => this._close(entry));
13481
14415
  }
13482
- this.listen(node, "mouseenter", () => this._pause(entry));
13483
- 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
+ });
13484
14450
  return entry;
13485
14451
  }
13486
14452
  /** @param {any} entry @returns {void} */
@@ -13492,7 +14458,7 @@ var Message = class _Message extends Component {
13492
14458
  }
13493
14459
  /** @param {any} entry @returns {void} */
13494
14460
  _schedule(entry) {
13495
- if (entry.closed || entry.remaining <= 0) return;
14461
+ if (entry.closed || entry.remaining <= 0 || entry.hovered || entry.focused) return;
13496
14462
  entry.startedAt = Date.now();
13497
14463
  entry.timer = setTimeout(() => this._close(entry), entry.remaining);
13498
14464
  }
@@ -13505,7 +14471,7 @@ var Message = class _Message extends Component {
13505
14471
  }
13506
14472
  /** @param {any} entry @returns {void} */
13507
14473
  _resume(entry) {
13508
- if (entry.closed || entry.timer !== null) return;
14474
+ if (entry.closed || entry.timer !== null || entry.hovered || entry.focused) return;
13509
14475
  if (entry.remaining <= 0) {
13510
14476
  this._close(entry);
13511
14477
  return;
@@ -13517,10 +14483,12 @@ var Message = class _Message extends Component {
13517
14483
  if (entry.closed) return;
13518
14484
  entry.closed = true;
13519
14485
  clearEntryTimers(entry);
14486
+ returnFocus(entry);
13520
14487
  if (!this._queue.isActive(entry)) {
13521
14488
  this._queue.remove(entry);
13522
14489
  entry.node.remove();
13523
14490
  this._entries.delete(entry);
14491
+ this._releaseIfEmpty();
13524
14492
  return;
13525
14493
  }
13526
14494
  entry.node.dataset.state = "closing";
@@ -13541,6 +14509,25 @@ var Message = class _Message extends Component {
13541
14509
  entry.node.remove();
13542
14510
  this._entries.delete(entry);
13543
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();
13544
14531
  }
13545
14532
  /**
13546
14533
  * Resolves a message through the host translator, falling back to the built-in English text.
@@ -13552,11 +14539,22 @@ var Message = class _Message extends Component {
13552
14539
  const message = this.msg(key);
13553
14540
  return message === key ? fallback : message;
13554
14541
  }
13555
- /** @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
+ */
13556
14549
  static #floatingRegion(scope2) {
13557
- const host = overlayHost(scope2 ?? null);
14550
+ let host = overlayHost(scope2 ?? null);
14551
+ if (!host.isConnected) host = overlayHost(null);
13558
14552
  const existing = this.#floating.get(host);
13559
- 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
+ }
13560
14558
  const region = h("div", {
13561
14559
  class: "zx-message-region",
13562
14560
  popover: "manual",
@@ -13565,12 +14563,19 @@ var Message = class _Message extends Component {
13565
14563
  });
13566
14564
  host.append(region);
13567
14565
  const floating = new _Message(region);
14566
+ floating._floatingHost = host;
13568
14567
  this.#floating.set(host, floating);
13569
14568
  if (typeof region.showPopover === "function") region.showPopover();
13570
14569
  return floating;
13571
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
+ }
13572
14577
  };
13573
- function normalizeKind(value) {
14578
+ function normalizeKind2(value) {
13574
14579
  if (value === "danger") return "error";
13575
14580
  return ["success", "warning", "error"].includes(String(value)) ? (
13576
14581
  /** @type {MessageKind} */
@@ -13581,6 +14586,50 @@ function normalizeTimeout(value) {
13581
14586
  const timeout = Number(value);
13582
14587
  return Number.isFinite(timeout) ? Math.max(0, timeout) : 4e3;
13583
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
+ }
13584
14633
  function normalizeLimit(value) {
13585
14634
  const limit = Number(value);
13586
14635
  return Number.isInteger(limit) && limit > 0 ? limit : 5;
@@ -13616,7 +14665,7 @@ function snapshot2(element) {
13616
14665
  }
13617
14666
  function restore2(element, state) {
13618
14667
  for (const attribute of Array.from(element.attributes)) element.removeAttribute(attribute.name);
13619
- for (const [name, value] of state.attributes) element.setAttribute(name, value);
14668
+ for (const [name2, value] of state.attributes) element.setAttribute(name2, value);
13620
14669
  element.replaceChildren(...state.children);
13621
14670
  }
13622
14671
 
@@ -13985,7 +15034,7 @@ var Calendar = class extends Component {
13985
15034
  if (!this.el) this.el = root;
13986
15035
  this._createdRoot = createdRoot;
13987
15036
  this._original = this._createdRoot ? null : {
13988
- attributes: Array.from(root.attributes).map(({ name, value }) => [name, value]),
15037
+ attributes: Array.from(root.attributes).map(({ name: name2, value }) => [name2, value]),
13989
15038
  children: Array.from(root.childNodes)
13990
15039
  };
13991
15040
  this._views = normalizeViews(this.options.views);
@@ -14195,7 +15244,7 @@ var Calendar = class extends Component {
14195
15244
  super.destroy();
14196
15245
  if (!this._createdRoot && original) {
14197
15246
  for (const attribute of Array.from(this.el.attributes)) this.el.removeAttribute(attribute.name);
14198
- 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);
14199
15248
  this.el.replaceChildren(...original.children);
14200
15249
  }
14201
15250
  }
@@ -14237,8 +15286,8 @@ var Calendar = class extends Component {
14237
15286
  });
14238
15287
  this._range = range;
14239
15288
  this.refs.heading.textContent = this._rangeTitle(range);
14240
- for (const button of this.refs.views.querySelectorAll("[data-view]")) {
14241
- 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));
14242
15291
  }
14243
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);
14244
15293
  this.refs.body.replaceChildren(content);
@@ -14562,7 +15611,7 @@ var Calendar = class extends Component {
14562
15611
  },
14563
15612
  style: event.color ? { "--zx-calendar-event-color": event.color } : null
14564
15613
  });
14565
- const button = h("button", {
15614
+ const button2 = h("button", {
14566
15615
  type: "button",
14567
15616
  class: "zx-calendar__event-action",
14568
15617
  dataset: { eventAction: "activate", eventId: key },
@@ -14570,15 +15619,15 @@ var Calendar = class extends Component {
14570
15619
  disabled: this.options.disabled
14571
15620
  });
14572
15621
  const custom = typeof this.options.renderEvent === "function" ? this.options.renderEvent(cloneCalendarEvent(event)) : null;
14573
- if (custom instanceof Node) button.append(custom);
14574
- 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));
14575
15624
  else {
14576
15625
  const time = h("span", { class: "zx-calendar__event-time" }, this._eventTime(event, context.day));
14577
15626
  const title = h("span", { class: "zx-calendar__event-title" }, event.title);
14578
- button.append(time, title);
14579
- 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));
14580
15629
  }
14581
- wrapper.append(button);
15630
+ wrapper.append(button2);
14582
15631
  if (context.resizable && this._canResize(event)) {
14583
15632
  wrapper.append(h("button", {
14584
15633
  type: "button",
@@ -15405,7 +16454,7 @@ function cssEscape(value) {
15405
16454
  var Fieldset = class extends Component {
15406
16455
  static cssName = "fieldset";
15407
16456
  /** @type {FieldsetOptions} */
15408
- static defaults = { title: "", columns: 1, fields: {} };
16457
+ static defaults = { title: "", columns: 1, fields: {}, idPrefix: null };
15409
16458
  /** @returns {HTMLElement} */
15410
16459
  render() {
15411
16460
  const root = (
@@ -15417,6 +16466,7 @@ var Fieldset = class extends Component {
15417
16466
  this._origins = /* @__PURE__ */ new Map();
15418
16467
  this._fieldIdAttributes = /* @__PURE__ */ new Map();
15419
16468
  this._ownedNodes = [];
16469
+ this._idPrefix = this.options.idPrefix ? String(this.options.idPrefix) : uid("zx-fieldset");
15420
16470
  this._columnAttribute = root.getAttribute("data-columns");
15421
16471
  const columns = [1, 2, 3].includes(Number(this.options.columns)) ? Number(this.options.columns) : 1;
15422
16472
  root.dataset.columns = String(columns);
@@ -15433,7 +16483,9 @@ var Fieldset = class extends Component {
15433
16483
  return root;
15434
16484
  }
15435
16485
  /**
15436
- * 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.
15437
16489
  * @param {string} id Logical field identifier.
15438
16490
  * @param {FieldOptions|Field|Element|Component} field Field source.
15439
16491
  * @returns {Field}
@@ -15448,7 +16500,8 @@ var Fieldset = class extends Component {
15448
16500
  } else if (isElement(field) || field instanceof Component) {
15449
16501
  const source = field;
15450
16502
  instance = new Field(null, {
15451
- id: key,
16503
+ id: this._controlId(key),
16504
+ name: key,
15452
16505
  type: "custom",
15453
16506
  label: key,
15454
16507
  value: readExternalValue(source),
@@ -15458,7 +16511,12 @@ var Fieldset = class extends Component {
15458
16511
  owned = true;
15459
16512
  } else {
15460
16513
  const options = field && typeof field === "object" ? field : {};
15461
- 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
+ });
15462
16520
  owned = true;
15463
16521
  }
15464
16522
  if (!owned) {
@@ -15588,6 +16646,14 @@ var Fieldset = class extends Component {
15588
16646
  for (const field of this._fields.values()) field.clearHighlight();
15589
16647
  return this;
15590
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
+ }
15591
16657
  /** @param {Field} field @returns {void} */
15592
16658
  _releaseField(field) {
15593
16659
  const fieldId = this._fieldIdAttributes.get(field);
@@ -15620,7 +16686,7 @@ function externalAdapter(source) {
15620
16686
  const el = isElement(source) ? source : source.toElement();
15621
16687
  const parent = el.parentNode;
15622
16688
  const next = el.nextSibling;
15623
- 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)]));
15624
16690
  const originalDisabled = "disabled" in el ? el.disabled : void 0;
15625
16691
  const originalValue = "value" in el ? el.value : void 0;
15626
16692
  return {
@@ -15646,9 +16712,9 @@ function externalAdapter(source) {
15646
16712
  else el.setAttribute("aria-disabled", String(disabled));
15647
16713
  },
15648
16714
  destroy: () => {
15649
- for (const [name, value] of attributes) {
15650
- if (value === null) el.removeAttribute(name);
15651
- 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);
15652
16718
  }
15653
16719
  if (originalDisabled !== void 0) el.disabled = originalDisabled;
15654
16720
  if (originalValue !== void 0) el.value = originalValue;
@@ -15676,6 +16742,7 @@ var Form = class extends Component {
15676
16742
  fieldsets: [],
15677
16743
  actions: [],
15678
16744
  novalidate: true,
16745
+ idPrefix: null,
15679
16746
  msg: {
15680
16747
  "form.required": "This field is required.",
15681
16748
  "form.int": "Enter a whole number.",
@@ -15692,6 +16759,7 @@ var Form = class extends Component {
15692
16759
  this._ownedFieldsets = /* @__PURE__ */ new Set();
15693
16760
  this._origins = /* @__PURE__ */ new Map();
15694
16761
  this._ownedNodes = [];
16762
+ this._idPrefix = this.options.idPrefix ? String(this.options.idPrefix) : uid("zx-form");
15695
16763
  this._novalidateAttribute = root.getAttribute("novalidate");
15696
16764
  root.toggleAttribute("novalidate", Boolean(this.options.novalidate));
15697
16765
  this._body = h("div", { class: "zx-form__body" });
@@ -15713,13 +16781,14 @@ var Form = class extends Component {
15713
16781
  return root;
15714
16782
  }
15715
16783
  /**
15716
- * 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.
15717
16786
  * @param {Fieldset|import('../fieldset/fieldset.js').FieldsetOptions} fieldset Fieldset source.
15718
16787
  * @returns {Fieldset}
15719
16788
  */
15720
16789
  addFieldset(fieldset) {
15721
16790
  const owned = !(fieldset instanceof Fieldset);
15722
- const instance = owned ? new Fieldset(null, fieldset ?? {}) : fieldset;
16791
+ const instance = owned ? new Fieldset(null, { ...fieldset ?? {}, idPrefix: fieldset?.idPrefix ?? this._idPrefix }) : fieldset;
15723
16792
  if (this._fieldsets.includes(instance)) return instance;
15724
16793
  if (owned) this._ownedFieldsets.add(instance);
15725
16794
  else this._origins.set(instance, { parent: instance.el.parentNode, next: instance.el.nextSibling });
@@ -15877,9 +16946,9 @@ var Form = class extends Component {
15877
16946
  }
15878
16947
  /** @param {Event} event @returns {void} */
15879
16948
  _handleAction(event) {
15880
- const button = event.target instanceof Element ? event.target.closest("[data-action-index]") : null;
15881
- if (!button || !this._actions.contains(button)) return;
15882
- 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)];
15883
16952
  const callback = action?.onClick ?? action?.onclick ?? action?.handler;
15884
16953
  if (typeof callback === "function") callback(event, this);
15885
16954
  }
@@ -15972,16 +17041,16 @@ function deriveLocale(client) {
15972
17041
  return client.locale ?? client.language ?? client.config?.locale ?? client.metadata?.locale ?? null;
15973
17042
  }
15974
17043
  function operationFor(client, action, resource) {
15975
- const name = String(resource ?? "").trim();
15976
- if (!name) throw new TypeError("ZeyOS resource must not be empty");
15977
- 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) : [];
15978
17047
  if (Array.isArray(resourceOperations) && resourceOperations.length) {
15979
17048
  const exactResourceMatches = resourceOperations.filter((operation) => typeof operation === "string" && operation.toLowerCase().startsWith(action)).sort((left, right) => left.length - right.length);
15980
17049
  if (exactResourceMatches.length) return exactResourceMatches[0];
15981
17050
  }
15982
17051
  const known = Array.isArray(resourceOperations) && resourceOperations.length ? resourceOperations : typeof client.schema?.operationIds === "function" ? client.schema.operationIds() : Object.keys(client.api);
15983
- if (known.includes(name) && name.toLowerCase().startsWith(action)) return name;
15984
- const normalizedResource = normalizeResource(name);
17052
+ if (known.includes(name2) && name2.toLowerCase().startsWith(action)) return name2;
17053
+ const normalizedResource = normalizeResource(name2);
15985
17054
  const singular = singularize(normalizedResource);
15986
17055
  const matches = known.filter((operation) => {
15987
17056
  if (typeof operation !== "string" || !operation.toLowerCase().startsWith(action)) return false;
@@ -15989,11 +17058,11 @@ function operationFor(client, action, resource) {
15989
17058
  return suffix === normalizedResource || suffix === singular || singularize(suffix) === singular;
15990
17059
  });
15991
17060
  if (matches.length) return matches.sort((left, right) => left.length - right.length)[0];
15992
- 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)}`];
15993
17062
  const apiKeys = new Set(Object.keys(client.api));
15994
17063
  const fallback = candidates.find((candidate) => apiKeys.has(candidate));
15995
17064
  if (fallback) return fallback;
15996
- throw new RangeError(`No ${action} operation found for ZeyOS resource "${name}"`);
17065
+ throw new RangeError(`No ${action} operation found for ZeyOS resource "${name2}"`);
15997
17066
  }
15998
17067
  function isRecord(value) {
15999
17068
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -16129,31 +17198,31 @@ var moduleAliases = Object.freeze({
16129
17198
  });
16130
17199
  var glyphColors = Object.freeze({ light: "#ffffff", dark: "#141414" });
16131
17200
  var overrides = /* @__PURE__ */ Object.create(null);
16132
- function normalizeModuleName(name) {
16133
- const key = String(name ?? "").trim().toLowerCase().replaceAll(/[._\s]+/g, "-");
17201
+ function normalizeModuleName(name2) {
17202
+ const key = String(name2 ?? "").trim().toLowerCase().replaceAll(/[._\s]+/g, "-");
16134
17203
  if (!key) return "default";
16135
17204
  if (Object.hasOwn(overrides, key) || Object.hasOwn(zeyosModules, key)) return key;
16136
17205
  const alias = moduleAliases[key];
16137
17206
  if (alias) return alias;
16138
17207
  return "default";
16139
17208
  }
16140
- function moduleInfo(name) {
16141
- const key = normalizeModuleName(name);
17209
+ function moduleInfo(name2) {
17210
+ const key = normalizeModuleName(name2);
16142
17211
  return overrides[key] ?? zeyosModules[key] ?? zeyosModules.default;
16143
17212
  }
16144
- function moduleColor(name) {
16145
- return moduleInfo(name).color;
17213
+ function moduleColor(name2) {
17214
+ return moduleInfo(name2).color;
16146
17215
  }
16147
- function moduleIconName(name, { standard = false } = {}) {
16148
- const info = moduleInfo(name);
17216
+ function moduleIconName(name2, { standard = false } = {}) {
17217
+ const info = moduleInfo(name2);
16149
17218
  return standard ? info.fa : info.icon;
16150
17219
  }
16151
17220
  function moduleKeys() {
16152
17221
  return [.../* @__PURE__ */ new Set([...Object.keys(zeyosModules), ...Object.keys(overrides)])].sort();
16153
17222
  }
16154
17223
  function registerModules(modules) {
16155
- for (const [name, value] of Object.entries(modules)) {
16156
- 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, "-");
16157
17226
  const patch = typeof value === "string" ? { color: value } : value;
16158
17227
  const base = overrides[key] ?? zeyosModules[key] ?? zeyosModules.default;
16159
17228
  overrides[key] = Object.freeze({
@@ -16190,12 +17259,12 @@ var ZEYOS_ICON_KIT = "https://kit.fontawesome.com/ae8320b210.js";
16190
17259
  function useZeyosIcons(options = {}) {
16191
17260
  return loadFontAwesome({ kit: ZEYOS_ICON_KIT, ...options });
16192
17261
  }
16193
- function moduleIcon(name, options = {}) {
17262
+ function moduleIcon(name2, options = {}) {
16194
17263
  const { standard = false, ...rest } = options;
16195
- const info = moduleInfo(name);
17264
+ const info = moduleInfo(name2);
16196
17265
  return icon(standard ? `fa:${info.fa}` : `kit:${info.icon}`, rest);
16197
17266
  }
16198
- function moduleChip(name, options = {}) {
17267
+ function moduleChip(name2, options = {}) {
16199
17268
  const {
16200
17269
  size = 24,
16201
17270
  iconSize = "52%",
@@ -16206,13 +17275,13 @@ function moduleChip(name, options = {}) {
16206
17275
  badge = null,
16207
17276
  selected = false
16208
17277
  } = options;
16209
- const key = normalizeModuleName(name);
16210
- const info = moduleInfo(name);
17278
+ const key = normalizeModuleName(name2);
17279
+ const info = moduleInfo(name2);
16211
17280
  const background = isCssColor(color) ? color : info.color;
16212
17281
  const chip = appIcon({
16213
17282
  // AppIcon owns glyph sizing. Keep the provider glyph at 1em so the percentage is not applied
16214
17283
  // once to the wrapper and a second time to a Font Awesome <i>.
16215
- icon: options.icon ?? moduleIcon(name, { size: "1em", standard }),
17284
+ icon: options.icon ?? moduleIcon(name2, { size: "1em", standard }),
16216
17285
  size,
16217
17286
  iconSize,
16218
17287
  color: background,
@@ -16230,8 +17299,8 @@ function moduleChip(name, options = {}) {
16230
17299
  if (title) chip.title = info.label;
16231
17300
  return chip;
16232
17301
  }
16233
- function zeyosAppIcon(name, options = {}) {
16234
- return moduleChip(name, { shape: "circle", ...options });
17302
+ function zeyosAppIcon(name2, options = {}) {
17303
+ return moduleChip(name2, { shape: "circle", ...options });
16235
17304
  }
16236
17305
 
16237
17306
  // src/zeyos/launcher.js
@@ -16732,8 +17801,12 @@ function buildZeyosFormConfig(client, resource, opts = {}) {
16732
17801
  const fieldMeta = resolveFields(client, resource, opts);
16733
17802
  const fields = {};
16734
17803
  const entityFields = [];
17804
+ const dateKinds = /* @__PURE__ */ new Map();
16735
17805
  for (const meta of fieldMeta) {
16736
17806
  const descriptor = fieldToZxField(meta);
17807
+ if (descriptor.type === "date" || descriptor.type === "datetime") {
17808
+ dateKinds.set(String(meta.id), descriptor.type);
17809
+ }
16737
17810
  const referencedResource2 = descriptor.type === "zxselect" && typeof descriptor.props?.entity === "string" ? descriptor.props.entity : null;
16738
17811
  if (referencedResource2) {
16739
17812
  entityFields.push({ id: String(meta.id), resource: referencedResource2 });
@@ -16757,16 +17830,20 @@ function buildZeyosFormConfig(client, resource, opts = {}) {
16757
17830
  fields
16758
17831
  }]
16759
17832
  },
16760
- mapFromRecord: (record) => mapFromRecord(record, fieldMeta),
16761
- mapToRecord: (values) => mapToRecord(values, fieldMeta)
17833
+ mapFromRecord: (record) => mapFromRecord(record, fieldMeta, dateKinds),
17834
+ mapToRecord: (values) => mapToRecord(values, fieldMeta, dateKinds)
16762
17835
  };
16763
17836
  }
16764
17837
  function zeyosForm(client, resource, opts = {}) {
16765
17838
  if (opts.onSaved != null && typeof opts.onSaved !== "function") {
16766
17839
  throw new TypeError("zeyosForm onSaved must be a function");
16767
17840
  }
17841
+ const notify = opts.notify !== false;
16768
17842
  const config2 = buildZeyosFormConfig(client, resource, opts);
16769
- 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
+ });
16770
17847
  const form = new Form(null, config2.formOptions);
16771
17848
  let currentId = opts.value?.ID ?? opts.id ?? null;
16772
17849
  let destroyed = false;
@@ -16796,6 +17873,7 @@ function zeyosForm(client, resource, opts = {}) {
16796
17873
  if (destroyed) return Promise.reject(new Error("Cannot save a destroyed ZeyOS form"));
16797
17874
  if (savePending) return savePending;
16798
17875
  if (!form.submit()) return Promise.resolve(null);
17876
+ const host = notify ? overlayHost(toastScope()) : null;
16799
17877
  savePending = (async () => {
16800
17878
  let payload;
16801
17879
  try {
@@ -16816,7 +17894,7 @@ function zeyosForm(client, resource, opts = {}) {
16816
17894
  throw error;
16817
17895
  }
16818
17896
  }
16819
- Message.success(`${recordLabel(record, resource)} saved successfully.`);
17897
+ if (host) Message.success(`${recordLabel(record, resource)} saved successfully.`, { scope: host });
16820
17898
  return record;
16821
17899
  })().finally(() => {
16822
17900
  savePending = null;
@@ -16837,25 +17915,39 @@ function zeyosForm(client, resource, opts = {}) {
16837
17915
  }
16838
17916
  };
16839
17917
  }
16840
- function mapFromRecord(record, meta) {
17918
+ function mapFromRecord(record, meta, dateKinds) {
16841
17919
  const values = {};
16842
17920
  for (const field of meta) {
16843
17921
  const id = String(field.id);
16844
17922
  const value = record[id];
16845
- 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(
16846
17931
  /** @type {number|string} */
16847
17932
  value
16848
- ) : value;
17933
+ );
16849
17934
  }
16850
17935
  return values;
16851
17936
  }
16852
- function mapToRecord(values, meta) {
17937
+ function mapToRecord(values, meta, dateKinds) {
16853
17938
  const record = {};
16854
17939
  for (const field of meta) {
16855
17940
  const id = String(field.id);
16856
17941
  let value = values[id];
16857
- if (isDateField(field)) {
16858
- 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(
16859
17951
  /** @type {Date} */
16860
17952
  value
16861
17953
  );
@@ -16867,10 +17959,6 @@ function mapToRecord(values, meta) {
16867
17959
  }
16868
17960
  return record;
16869
17961
  }
16870
- function isDateField(meta) {
16871
- const descriptor = fieldToZxField(meta);
16872
- return descriptor.type === "date" || descriptor.type === "datetime";
16873
- }
16874
17962
  function hasEnum(meta) {
16875
17963
  return meta.enums != null || meta.enum != null;
16876
17964
  }
@@ -16900,10 +17988,34 @@ function recordLabel(record, resource) {
16900
17988
  }
16901
17989
 
16902
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
+ ];
16903
18013
  function buildZeyosTableConfig(client, resource, opts = {}) {
16904
18014
  const fieldMeta = resolveFields(client, resource, {
16905
18015
  fields: opts.fields,
16906
- labels: opts.labels
18016
+ labels: opts.labels,
18017
+ formats: opts.formats,
18018
+ timeZone: opts.timeZone
16907
18019
  });
16908
18020
  const projection = {};
16909
18021
  const columns = fieldMeta.map((sourceMeta) => {
@@ -16927,11 +18039,16 @@ function buildZeyosTableConfig(client, resource, opts = {}) {
16927
18039
  if (typeof rowId === "string" && !Object.prototype.hasOwnProperty.call(projection, rowId)) {
16928
18040
  projection[rowId] = rowId;
16929
18041
  }
18042
+ const passed = {};
18043
+ for (const key of TABLE_OPTION_KEYS) {
18044
+ if (opts[key] !== void 0) passed[key] = opts[key];
18045
+ }
16930
18046
  return {
16931
18047
  fieldMeta,
16932
18048
  columns,
16933
18049
  projection,
16934
18050
  tableOptions: {
18051
+ ...passed,
16935
18052
  columns,
16936
18053
  data: [],
16937
18054
  rowId,
@@ -16939,7 +18056,7 @@ function buildZeyosTableConfig(client, resource, opts = {}) {
16939
18056
  sortMode: "server",
16940
18057
  selectable: opts.selectable ?? false,
16941
18058
  height: opts.height ?? null,
16942
- onrowclick: typeof opts.onRowClick === "function" ? (event) => opts.onRowClick(event.detail.row, event) : void 0
18059
+ onrowclick: rowClickListener(opts)
16943
18060
  }
16944
18061
  };
16945
18062
  }
@@ -17025,6 +18142,15 @@ function zeyosTable(client, resource, opts = {}) {
17025
18142
  });
17026
18143
  return binding;
17027
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
+ }
17028
18154
  function referencedResourceFor(meta) {
17029
18155
  for (const value of [meta.entity, meta.fk, meta.relation, meta.reference, meta.references]) {
17030
18156
  if (typeof value === "string" && value) return value;
@@ -17090,8 +18216,8 @@ function zeyosAppointmentToEvent(record) {
17090
18216
  const end = unixSecondsToDate(record.dateto ?? record.datefrom);
17091
18217
  if (!(start instanceof Date) || !(end instanceof Date)) throw new TypeError("ZeyOS appointment requires datefrom and dateto");
17092
18218
  const association = String(record.assoc_name ?? "").trim();
17093
- const name = String(record.name ?? "").trim() || "Untitled appointment";
17094
- 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;
17095
18221
  const allDay = record.allDay != null ? Boolean(record.allDay) : isMidnight(start) && isMidnight(end) && calendarDayDifference(start, end) >= 1;
17096
18222
  return {
17097
18223
  id: String(record.occurrenceid ?? `${baseId}@${record.datefrom}`),
@@ -17160,8 +18286,8 @@ var FORBIDDEN_STATE_KEYS = /* @__PURE__ */ new Set([
17160
18286
  ]);
17161
18287
  var SavedViewDuplicateNameError = class extends Error {
17162
18288
  /** @param {string} name Duplicate display name. */
17163
- constructor(name) {
17164
- super(`A saved view named "${name}" already exists`);
18289
+ constructor(name2) {
18290
+ super(`A saved view named "${name2}" already exists`);
17165
18291
  this.name = "SavedViewDuplicateNameError";
17166
18292
  }
17167
18293
  };
@@ -17252,15 +18378,15 @@ var SavedViewRegistry = class {
17252
18378
  */
17253
18379
  async save(input, options = {}) {
17254
18380
  return this._mutate(async (document2) => {
17255
- const name = normalizeName(input?.name);
18381
+ const name2 = normalizeName(input?.name);
17256
18382
  const policy = normalizeDuplicatePolicy(options.onDuplicate ?? this.options.duplicateNames);
17257
18383
  const requestedId = input?.id == null ? null : normalizeId(input.id);
17258
18384
  const byId = requestedId ? document2.views.find((entry2) => entry2.id === requestedId) ?? null : null;
17259
- 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;
17260
18386
  if (byId && byName && byId !== byName && policy === "reject") {
17261
- throw new SavedViewDuplicateNameError(name);
18387
+ throw new SavedViewDuplicateNameError(name2);
17262
18388
  }
17263
- if (!byId && byName && policy === "reject") throw new SavedViewDuplicateNameError(name);
18389
+ if (!byId && byName && policy === "reject") throw new SavedViewDuplicateNameError(name2);
17264
18390
  const existing = byId ?? (policy === "replace" ? byName : null);
17265
18391
  const replaced = policy === "replace" && byId && byName !== byId ? byName : null;
17266
18392
  const now = this._now();
@@ -17270,8 +18396,8 @@ var SavedViewRegistry = class {
17270
18396
  const filters = Object.prototype.hasOwnProperty.call(input ?? {}, "filters") ? normalizeFilters(input.filters) : existing ? cloneJsonObject(existing.filters, "Saved view filters") : {};
17271
18397
  const search = Object.prototype.hasOwnProperty.call(input ?? {}, "search") ? normalizeSearch(input.search) : existing?.search ?? "";
17272
18398
  const createdAt = existing?.createdAt ?? now;
17273
- const id = existing?.id ?? requestedId ?? this._createId(document2, { name, type, createdAt });
17274
- 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 };
17275
18401
  const views = document2.views.filter((candidate) => candidate !== existing && candidate !== replaced && candidate.id !== id);
17276
18402
  views.push(entry);
17277
18403
  document2.views = sortEntries(views);
@@ -17287,11 +18413,11 @@ var SavedViewRegistry = class {
17287
18413
  * @param {{onDuplicate?:SavedViewDuplicatePolicy}} [options={}] Duplicate policy.
17288
18414
  * @returns {Promise<SavedViewEntry|null>} Updated entry, or null when absent.
17289
18415
  */
17290
- async rename(idOrName, name, options = {}) {
18416
+ async rename(idOrName, name2, options = {}) {
17291
18417
  return this._mutate(async (document2) => {
17292
18418
  const entry = resolveEntry(document2, idOrName);
17293
18419
  if (!entry) return { changed: false, result: null };
17294
- const nextName = normalizeName(name);
18420
+ const nextName = normalizeName(name2);
17295
18421
  if (entry.name === nextName) return { changed: false, result: entry };
17296
18422
  const duplicate = document2.views.find((candidate) => candidate !== entry && nameKey(candidate.name) === nameKey(nextName)) ?? null;
17297
18423
  const policy = normalizeDuplicatePolicy(options.onDuplicate ?? this.options.duplicateNames);
@@ -17554,7 +18680,7 @@ function normalizePersistedEntry(value, index, scope2) {
17554
18680
  /** @type {Record<string,unknown>} */
17555
18681
  value
17556
18682
  );
17557
- const name = normalizeName(entry.name);
18683
+ const name2 = normalizeName(entry.name);
17558
18684
  const type = normalizeType2(entry.type);
17559
18685
  const state = normalizeRecordViewState(entry.state ?? {});
17560
18686
  const filters = normalizeFilters(entry.filters ?? {});
@@ -17562,8 +18688,8 @@ function normalizePersistedEntry(value, index, scope2) {
17562
18688
  const createdAt = normalizeTimestamp(entry.createdAt);
17563
18689
  const requestedUpdated = normalizeTimestamp(entry.updatedAt ?? createdAt);
17564
18690
  const updatedAt = requestedUpdated < createdAt ? createdAt : requestedUpdated;
17565
- const id = entry.id == null || String(entry.id).trim() === "" ? `legacy-${hashString(`${scopeKey(scope2)}\0${name}\0${type}\0${index}`)}` : normalizeId(entry.id);
17566
- 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 };
17567
18693
  }
17568
18694
  function deduplicateEntries(entries) {
17569
18695
  const ranked = [...entries].sort((left, right) => compareText(right.updatedAt, left.updatedAt) || compareText(left.id, right.id));
@@ -17651,10 +18777,10 @@ function normalizeType2(value) {
17651
18777
  }
17652
18778
  function normalizeName(value) {
17653
18779
  if (typeof value !== "string") throw new TypeError("Saved view name must be a string");
17654
- const name = value.normalize("NFKC").trim().replace(/\s+/g, " ");
17655
- if (!name) throw new TypeError("Saved view name cannot be empty");
17656
- if (name.length > 160) throw new RangeError("Saved view name cannot exceed 160 characters");
17657
- 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;
17658
18784
  }
17659
18785
  function nameKey(value) {
17660
18786
  return value.normalize("NFKC").toLocaleLowerCase("en-US");
@@ -17823,6 +18949,8 @@ function buildZeyosViewConfig(client, resource, opts = {}) {
17823
18949
  const table = buildZeyosTableConfig(client, resource, {
17824
18950
  fields: requested,
17825
18951
  labels: opts.labels,
18952
+ formats: opts.formats,
18953
+ timeZone: opts.timeZone,
17826
18954
  rowId: opts.recordId ?? "ID",
17827
18955
  sort: opts.sort ?? null
17828
18956
  });
@@ -18155,10 +19283,10 @@ function presentationType(View) {
18155
19283
  cssName
18156
19284
  );
18157
19285
  }
18158
- const name = String(View.name ?? "").toLowerCase().replace(/view$/, "");
18159
- return ["table", "card", "kanban"].includes(name) ? (
19286
+ const name2 = String(View.name ?? "").toLowerCase().replace(/view$/, "");
19287
+ return ["table", "card", "kanban"].includes(name2) ? (
18160
19288
  /** @type {'table'|'card'|'kanban'} */
18161
- name
19289
+ name2
18162
19290
  ) : null;
18163
19291
  }
18164
19292
  function normalizePresentationType(value) {
@@ -18251,7 +19379,7 @@ function findReservedDocument(response, recordName) {
18251
19379
  if (!Array.isArray(response) || !Array.isArray(response[0]) || !Array.isArray(response[1])) {
18252
19380
  return null;
18253
19381
  }
18254
- const index = response[0].findIndex((name) => String(name) === recordName);
19382
+ const index = response[0].findIndex((name2) => String(name2) === recordName);
18255
19383
  return index < 0 ? null : response[1][index];
18256
19384
  }
18257
19385
  function normalizeNonEmpty(value, fallback, label) {
@@ -18296,6 +19424,7 @@ export {
18296
19424
  buildZeyosLauncherConfig,
18297
19425
  buildZeyosSelectConfig,
18298
19426
  buildZeyosViewConfig,
19427
+ calendarDayToUnixSeconds,
18299
19428
  calendarEventToZeyosPatch,
18300
19429
  connect,
18301
19430
  createLegacySavedViewTransport,
@@ -18323,6 +19452,7 @@ export {
18323
19452
  registerModules,
18324
19453
  resolveFields,
18325
19454
  tableSortToQuery,
19455
+ unixSecondsToCalendarDay,
18326
19456
  unixSecondsToDate,
18327
19457
  useZeyosIcons,
18328
19458
  zeyosActiveIdentifier,
@@ -18338,3 +19468,51 @@ export {
18338
19468
  zeyosTable,
18339
19469
  zeyosView
18340
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
+ */