@zeyos/zx 4.4.0 → 5.2.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 (252) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/README.md +44 -11
  3. package/dist/types/components/account-menu/account-menu.d.ts +8 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +31 -1
  5. package/dist/types/components/app-icon/app-icon.d.ts +14 -2
  6. package/dist/types/components/app-sidebar/app-sidebar.d.ts +293 -17
  7. package/dist/types/components/avatar/avatar.d.ts +46 -7
  8. package/dist/types/components/badge/badge.d.ts +21 -1
  9. package/dist/types/components/button/button.d.ts +15 -0
  10. package/dist/types/components/calendar/calendar.d.ts +67 -5
  11. package/dist/types/components/card-view/card-view.d.ts +7 -4
  12. package/dist/types/components/card-view/record-card.d.ts +11 -0
  13. package/dist/types/components/chart/chart.d.ts +10 -0
  14. package/dist/types/components/checklist/checklist.d.ts +9 -0
  15. package/dist/types/components/code-editor/code-editor.d.ts +6 -17
  16. package/dist/types/components/context-menu/context-menu.d.ts +6 -2
  17. package/dist/types/components/data-filter/data-filter.d.ts +4 -4
  18. package/dist/types/components/datebox/date-range-box.d.ts +5 -1
  19. package/dist/types/components/datebox/datebox.d.ts +7 -2
  20. package/dist/types/components/dialog/dialog.d.ts +46 -6
  21. package/dist/types/components/dropdown/dropdown.d.ts +24 -9
  22. package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
  23. package/dist/types/components/field/field.d.ts +54 -2
  24. package/dist/types/components/fieldset/external-adapter.d.ts +83 -0
  25. package/dist/types/components/fieldset/fieldset.d.ts +26 -1
  26. package/dist/types/components/file-item/file-item.d.ts +34 -6
  27. package/dist/types/components/file-list/file-list.d.ts +35 -3
  28. package/dist/types/components/filter/filter.d.ts +127 -3
  29. package/dist/types/components/filter-panel/filter-panel.d.ts +36 -7
  30. package/dist/types/components/form/form.d.ts +12 -1
  31. package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
  32. package/dist/types/components/launcher/launcher.d.ts +268 -15
  33. package/dist/types/components/list-toolbar/list-toolbar.d.ts +16 -7
  34. package/dist/types/components/menu-button/menu-button.d.ts +39 -5
  35. package/dist/types/components/message/message.d.ts +132 -17
  36. package/dist/types/components/modal/modal.d.ts +43 -2
  37. package/dist/types/components/module-bar/module-bar.d.ts +395 -0
  38. package/dist/types/components/pagination/pagination.d.ts +17 -3
  39. package/dist/types/components/select/color.d.ts +12 -0
  40. package/dist/types/components/select/glyphs.d.ts +124 -0
  41. package/dist/types/components/select/select.d.ts +17 -3
  42. package/dist/types/components/sheet/sheet.d.ts +15 -0
  43. package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
  44. package/dist/types/components/tabbox/tabbox.d.ts +9 -0
  45. package/dist/types/components/table/table.d.ts +311 -18
  46. package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
  47. package/dist/types/components/tree/tree.d.ts +13 -0
  48. package/dist/types/components/view/record-view.d.ts +59 -2
  49. package/dist/types/components/view-settings/view-settings.d.ts +879 -0
  50. package/dist/types/components/workspace-switcher/workspace-switcher.d.ts +327 -0
  51. package/dist/types/core/breakpoint.d.ts +1 -1
  52. package/dist/types/core/component.d.ts +12 -2
  53. package/dist/types/core/i18n.d.ts +27 -3
  54. package/dist/types/core/icons.d.ts +23 -25
  55. package/dist/types/core/keyboard.d.ts +98 -1
  56. package/dist/types/core/messages.d.ts +382 -0
  57. package/dist/types/core/motion.d.ts +59 -0
  58. package/dist/types/core/overlay-host.d.ts +42 -1
  59. package/dist/types/core/overlay-stack.d.ts +260 -0
  60. package/dist/types/index.d.ts +10 -5
  61. package/dist/types/internal/app-rail.d.ts +38 -0
  62. package/dist/types/internal/menu-items.d.ts +71 -3
  63. package/dist/types/zeyos/connect.d.ts +6 -0
  64. package/dist/types/zeyos/form.d.ts +50 -9
  65. package/dist/types/zeyos/index.d.ts +2 -1
  66. package/dist/types/zeyos/launcher.d.ts +9 -0
  67. package/dist/types/zeyos/module-bar.d.ts +176 -0
  68. package/dist/types/zeyos/query.d.ts +110 -5
  69. package/dist/types/zeyos/schema.d.ts +24 -1
  70. package/dist/types/zeyos/table.d.ts +345 -22
  71. package/dist/types/zeyos/view.d.ts +26 -2
  72. package/dist/zx-zeyos.esm.js +6178 -1008
  73. package/dist/zx-zeyos.esm.min.js +53 -5
  74. package/dist/zx.css +2861 -827
  75. package/dist/zx.esm.js +8909 -1480
  76. package/dist/zx.esm.min.js +55 -7
  77. package/dist/zx.global.js +8911 -1482
  78. package/dist/zx.global.min.js +55 -7
  79. package/dist/zx.min.css +1 -1
  80. package/docs/api.json +1079 -117
  81. package/docs/llms.md +956 -154
  82. package/docs/llms.txt +9 -5
  83. package/package.json +1 -1
  84. package/src/components/account-menu/account-menu.css +35 -10
  85. package/src/components/account-menu/account-menu.js +12 -0
  86. package/src/components/activity-item/activity-item.css +1 -1
  87. package/src/components/activity-item/activity-item.js +3 -0
  88. package/src/components/activity-list/activity-list.css +38 -3
  89. package/src/components/activity-list/activity-list.js +61 -1
  90. package/src/components/app-icon/app-icon.css +14 -3
  91. package/src/components/app-icon/app-icon.js +11 -1
  92. package/src/components/app-rail/app-rail.css +59 -79
  93. package/src/components/app-sidebar/app-sidebar.css +304 -168
  94. package/src/components/app-sidebar/app-sidebar.js +667 -46
  95. package/src/components/aurora/aurora.css +5 -3
  96. package/src/components/avatar/avatar.css +32 -7
  97. package/src/components/avatar/avatar.js +72 -8
  98. package/src/components/badge/badge.css +54 -2
  99. package/src/components/badge/badge.js +29 -2
  100. package/src/components/breadcrumb/breadcrumb.css +1 -1
  101. package/src/components/button/button.css +67 -9
  102. package/src/components/button/button.js +38 -0
  103. package/src/components/calendar/calendar.css +47 -47
  104. package/src/components/calendar/calendar.js +26 -52
  105. package/src/components/card/card.css +4 -4
  106. package/src/components/card-view/card-view.css +14 -12
  107. package/src/components/card-view/card-view.js +13 -9
  108. package/src/components/card-view/record-card.js +14 -3
  109. package/src/components/chart/chart.css +4 -1
  110. package/src/components/chart/chart.js +12 -0
  111. package/src/components/check-button/check-button.css +10 -2
  112. package/src/components/checklist/checklist.css +9 -2
  113. package/src/components/checklist/checklist.js +11 -5
  114. package/src/components/checklist/field-adapter.js +3 -1
  115. package/src/components/code-editor/code-editor.css +7 -6
  116. package/src/components/code-editor/code-editor.js +19 -1
  117. package/src/components/code-editor/field-adapter.js +9 -2
  118. package/src/components/context-menu/context-menu.css +9 -4
  119. package/src/components/context-menu/context-menu.js +8 -2
  120. package/src/components/copy/copy-button.css +6 -0
  121. package/src/components/copy/copy-input.css +9 -4
  122. package/src/components/data-filter/data-filter.css +5 -4
  123. package/src/components/data-filter/data-filter.js +2 -2
  124. package/src/components/date-picker/date-picker.css +6 -6
  125. package/src/components/date-picker/date-range-picker.css +3 -3
  126. package/src/components/date-picker/time-picker.css +3 -3
  127. package/src/components/datebox/date-range-box.css +79 -2
  128. package/src/components/datebox/date-range-box.js +13 -2
  129. package/src/components/datebox/datebox.css +79 -2
  130. package/src/components/datebox/datebox.js +13 -2
  131. package/src/components/dialog/dialog.css +26 -0
  132. package/src/components/dialog/dialog.js +99 -17
  133. package/src/components/dock/dock.css +5 -4
  134. package/src/components/dropdown/dropdown.css +1 -1
  135. package/src/components/dropdown/dropdown.js +159 -72
  136. package/src/components/empty-state/empty-state.css +2 -2
  137. package/src/components/entity-ref/entity-ref.css +1 -1
  138. package/src/components/entity-ref/entity-ref.js +1 -1
  139. package/src/components/field/field.css +28 -9
  140. package/src/components/field/field.js +101 -29
  141. package/src/components/field-upload/field-upload.css +1 -1
  142. package/src/components/fieldset/external-adapter.js +363 -0
  143. package/src/components/fieldset/fieldset.css +5 -3
  144. package/src/components/fieldset/fieldset.js +38 -64
  145. package/src/components/file-item/file-item.js +144 -10
  146. package/src/components/file-list/file-list.js +144 -21
  147. package/src/components/filter/filter.css +86 -2
  148. package/src/components/filter/filter.js +307 -11
  149. package/src/components/filter-panel/filter-panel.css +1 -1
  150. package/src/components/filter-panel/filter-panel.js +149 -32
  151. package/src/components/finder/finder.css +16 -14
  152. package/src/components/form/form.css +5 -4
  153. package/src/components/form/form.js +11 -2
  154. package/src/components/grid/grid.css +1 -1
  155. package/src/components/groupbox/groupbox.css +1 -1
  156. package/src/components/kanban-view/kanban-card.js +11 -3
  157. package/src/components/kanban-view/kanban-view.css +42 -18
  158. package/src/components/kanban-view/kanban-view.js +9 -6
  159. package/src/components/launcher/launcher.css +108 -26
  160. package/src/components/launcher/launcher.js +530 -42
  161. package/src/components/list-toolbar/list-toolbar.css +13 -8
  162. package/src/components/list-toolbar/list-toolbar.js +40 -14
  163. package/src/components/master-panel/master-panel.css +2 -0
  164. package/src/components/master-panel/master-panel.js +0 -1
  165. package/src/components/menu-button/menu-button.css +72 -4
  166. package/src/components/menu-button/menu-button.js +160 -18
  167. package/src/components/message/message.css +33 -3
  168. package/src/components/message/message.js +264 -22
  169. package/src/components/modal/modal.css +1 -1
  170. package/src/components/modal/modal.js +83 -15
  171. package/src/components/module-bar/module-bar.css +486 -0
  172. package/src/components/module-bar/module-bar.js +1074 -0
  173. package/src/components/multi-value-editor/multi-value-editor.css +1 -1
  174. package/src/components/navigation-bar/navigation-bar.css +18 -9
  175. package/src/components/number-field/field-adapter.js +3 -1
  176. package/src/components/number-field/number-field.css +70 -0
  177. package/src/components/number-field/number-field.js +2 -0
  178. package/src/components/pagination/pagination.css +13 -11
  179. package/src/components/pagination/pagination.js +20 -4
  180. package/src/components/panel/panel.css +4 -3
  181. package/src/components/questionnaire/questionnaire.css +4 -4
  182. package/src/components/rating/field-adapter.js +2 -1
  183. package/src/components/rating/rating.css +8 -1
  184. package/src/components/search/search.css +4 -1
  185. package/src/components/select/color.js +22 -0
  186. package/src/components/select/field-adapter.js +2 -0
  187. package/src/components/select/glyphs.js +165 -0
  188. package/src/components/select/select.css +138 -45
  189. package/src/components/select/select.js +92 -47
  190. package/src/components/sheet/sheet.css +12 -9
  191. package/src/components/sheet/sheet.js +74 -32
  192. package/src/components/slider/field-adapter.js +6 -1
  193. package/src/components/slider/slider.css +18 -4
  194. package/src/components/slider/slider.js +1 -1
  195. package/src/components/sort-control/sort-control.css +4 -1
  196. package/src/components/split-view/split-view.css +1 -1
  197. package/src/components/stat-tile/stat-tile.css +42 -47
  198. package/src/components/stat-tile/stat-tile.js +17 -6
  199. package/src/components/stepper/stepper.css +1 -1
  200. package/src/components/tabbox/tabbox.css +41 -32
  201. package/src/components/tabbox/tabbox.js +5 -1
  202. package/src/components/table/table.css +187 -37
  203. package/src/components/table/table.js +718 -73
  204. package/src/components/tag-picker/field-adapter.js +2 -1
  205. package/src/components/tag-picker/tag-picker.css +83 -5
  206. package/src/components/tag-picker/tag-picker.js +25 -8
  207. package/src/components/timebox/timebox.css +22 -2
  208. package/src/components/toggle/toggle.css +12 -1
  209. package/src/components/toolbar/toolbar.js +2 -2
  210. package/src/components/tooltip/tooltip.css +4 -0
  211. package/src/components/tooltip/tooltip.js +44 -19
  212. package/src/components/tree/tree.css +26 -17
  213. package/src/components/tree/tree.js +31 -0
  214. package/src/components/value-list/value-list.css +3 -3
  215. package/src/components/view/record-view.css +1 -1
  216. package/src/components/view/record-view.js +178 -18
  217. package/src/components/view-settings/view-settings.css +285 -0
  218. package/src/components/view-settings/view-settings.js +1830 -0
  219. package/src/components/workspace-switcher/workspace-switcher.css +188 -0
  220. package/src/components/workspace-switcher/workspace-switcher.js +463 -0
  221. package/src/core/breakpoint.js +1 -1
  222. package/src/core/component.js +88 -17
  223. package/src/core/fontawesome.js +5 -1
  224. package/src/core/i18n.js +47 -4
  225. package/src/core/icons.js +204 -61
  226. package/src/core/keyboard.js +206 -11
  227. package/src/core/messages.js +427 -0
  228. package/src/core/motion.js +147 -0
  229. package/src/core/overlay-host.js +77 -3
  230. package/src/core/overlay-stack.js +487 -0
  231. package/src/core/position.js +22 -3
  232. package/src/index.js +10 -5
  233. package/src/internal/app-rail.js +135 -55
  234. package/src/internal/menu-items.js +61 -3
  235. package/src/zeyos/connect.js +6 -2
  236. package/src/zeyos/form.js +69 -27
  237. package/src/zeyos/index.js +4 -1
  238. package/src/zeyos/launcher.js +8 -3
  239. package/src/zeyos/module-bar.js +181 -0
  240. package/src/zeyos/query.js +197 -13
  241. package/src/zeyos/schema.js +144 -27
  242. package/src/zeyos/table.js +287 -84
  243. package/src/zeyos/view.js +49 -93
  244. package/styles/base.css +72 -1
  245. package/styles/icons.css +11 -1
  246. package/styles/print.css +1 -1
  247. package/styles/tokens/dark.css +117 -37
  248. package/styles/tokens/global.css +121 -27
  249. package/styles/tokens/semantic.css +140 -43
  250. package/styles/tokens/themes.css +7 -6
  251. package/styles/zx.css +3 -1
  252. package/styles/tokens/density.css +0 -9
@@ -1,6 +1,6 @@
1
1
  // @ts-check
2
2
  import { h, resolveElement } from './dom.js';
3
- import { getLanguage, printf, translate } from './i18n.js';
3
+ import { defaultMessage, getLanguage, hostMessage, legacyMessageKey, printf } from './i18n.js';
4
4
  import { deepMerge, isElement } from './util.js';
5
5
 
6
6
  /** @typedef {Record<string, any>} ComponentOptions */
@@ -9,6 +9,13 @@ import { deepMerge, isElement } from './util.js';
9
9
  /** @type {WeakMap<Element, Component>} */
10
10
  const registry = new WeakMap();
11
11
 
12
+ /**
13
+ * Each instance's `msg` tables, kept apart so the host translator can sit between them: the
14
+ * option the instance was given, and the defaults its class chain declares.
15
+ * @type {WeakMap<object, {own: Record<string, any>|null, inherited: Record<string, any>|null}>}
16
+ */
17
+ const messageTables = new WeakMap();
18
+
12
19
  /**
13
20
  * Base class for lifecycle-safe Zx components.
14
21
  *
@@ -62,6 +69,10 @@ export class Component extends EventTarget {
62
69
  this.refs = {};
63
70
 
64
71
  const merged = /** @type {TOptions} */ (mergeOptions(self, options));
72
+ messageTables.set(this, {
73
+ own: isMessageTable(options?.msg) ? Object.freeze(deepMerge({}, options.msg)) : null,
74
+ inherited: inheritedMessages(self)
75
+ });
65
76
  const eventOptions = [];
66
77
  for (const [key, value] of Object.entries(merged)) {
67
78
  if (/^on[a-z]/.test(key) && typeof value === 'function') {
@@ -204,19 +215,38 @@ export class Component extends EventTarget {
204
215
  }
205
216
 
206
217
  /**
207
- * Resolves and interpolates a component-local or global translation.
218
+ * Resolves and interpolates one of the component's messages. The first answer wins:
219
+ *
220
+ * 1. the `msg` option this instance was given — a `{[lang]: {key: text}}` table for the active
221
+ * language (the `lang` option, else `getLanguage()`), then a flat `{key: text}` entry;
222
+ * 2. the host translator installed with `setTranslator()`;
223
+ * 3. the `msg` defaults the component class declares;
224
+ * 4. the default English catalogue (`defaultMessages`);
225
+ * 5. the key itself.
226
+ *
227
+ * A renamed key (`calendar.today`, formerly `today`) is also looked up under its former name,
228
+ * right after the new one, in steps 1 and 2.
208
229
  * @param {string} key Message key.
209
- * @param {...unknown} args Positional interpolation values.
230
+ * @param {...unknown} args Positional interpolation values (`%1`, `%2`, …).
210
231
  * @returns {string}
211
232
  */
212
233
  msg(key, ...args) {
213
- const messages = this.options.msg;
214
- const lang = this.options.lang ?? this.options.language ?? getLanguage();
215
- const localized = messages?.[lang]?.[key];
216
- if (localized != null) return printf(String(localized), args);
217
- const direct = messages?.[key];
218
- if (direct != null) return printf(String(direct), args);
219
- return translate(key, args);
234
+ const lang = this.options?.lang ?? this.options?.language ?? getLanguage();
235
+ const legacy = legacyMessageKey(key);
236
+ // An object that borrows this method without running the constructor reads its options' `msg`.
237
+ const tables = messageTables.get(this) ?? {
238
+ own: isMessageTable(this.options?.msg) ? this.options.msg : null,
239
+ inherited: null
240
+ };
241
+ const own = tables.own;
242
+ const text = pickMessage(own, lang, key)
243
+ ?? (legacy ? pickMessage(own, lang, legacy) : null)
244
+ ?? hostMessage(key)
245
+ ?? (legacy ? hostMessage(legacy) : null)
246
+ ?? pickMessage(tables.inherited, lang, key)
247
+ ?? defaultMessage(key)
248
+ ?? key;
249
+ return printf(text, args);
220
250
  }
221
251
 
222
252
  /**
@@ -254,13 +284,7 @@ export class Component extends EventTarget {
254
284
 
255
285
  /** @param {typeof Component} constructor @param {ComponentOptions} options @returns {ComponentOptions} */
256
286
  function mergeOptions(constructor, options) {
257
- const chain = [];
258
- let current = constructor;
259
- while (current && current !== Function.prototype) {
260
- if (Object.prototype.hasOwnProperty.call(current, 'defaults')) chain.unshift(current.defaults);
261
- current = Object.getPrototypeOf(current);
262
- }
263
-
287
+ const chain = defaultsChain(constructor);
264
288
  const result = {};
265
289
  let messages = {};
266
290
  let hasMessages = false;
@@ -281,6 +305,53 @@ function mergeOptions(constructor, options) {
281
305
  return result;
282
306
  }
283
307
 
308
+ /**
309
+ * The `defaults` objects a class and its ancestors declare, base class first.
310
+ * @param {typeof Component} constructor
311
+ * @returns {ComponentOptions[]}
312
+ */
313
+ function defaultsChain(constructor) {
314
+ const chain = [];
315
+ let current = constructor;
316
+ while (current && current !== Function.prototype) {
317
+ if (Object.prototype.hasOwnProperty.call(current, 'defaults')) chain.unshift(current.defaults);
318
+ current = Object.getPrototypeOf(current);
319
+ }
320
+ return chain;
321
+ }
322
+
323
+ /** @param {unknown} value @returns {value is Record<string, any>} */
324
+ function isMessageTable(value) {
325
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
326
+ }
327
+
328
+ /** @param {typeof Component} constructor @returns {Record<string, any>|null} */
329
+ function inheritedMessages(constructor) {
330
+ let messages = null;
331
+ for (const defaults of defaultsChain(constructor)) {
332
+ if (defaults && typeof defaults === 'object' && isMessageTable(defaults.msg)) {
333
+ messages = deepMerge(messages ?? {}, defaults.msg);
334
+ }
335
+ }
336
+ return messages ? Object.freeze(messages) : null;
337
+ }
338
+
339
+ /**
340
+ * Reads one key from a `msg` table: the active language's sub-table first, then a flat entry.
341
+ * @param {Record<string, any>|null} table Message table.
342
+ * @param {string} lang Active language.
343
+ * @param {string} key Message key.
344
+ * @returns {string|null}
345
+ */
346
+ function pickMessage(table, lang, key) {
347
+ if (!table) return null;
348
+ const localized = table[lang]?.[key];
349
+ if (localized != null && typeof localized !== 'object') return String(localized);
350
+ const direct = Object.prototype.hasOwnProperty.call(table, key) ? table[key] : null;
351
+ if (direct != null && typeof direct !== 'object') return String(direct);
352
+ return null;
353
+ }
354
+
284
355
  /**
285
356
  * @param {string} type
286
357
  * @param {Record<string, unknown>} detail
@@ -55,7 +55,11 @@ export const faNames = Object.freeze({
55
55
  warning: 'triangle-exclamation',
56
56
  error: 'circle-exclamation',
57
57
  success: 'circle-check',
58
- drag: 'grip-vertical'
58
+ drag: 'grip-vertical',
59
+ type: 'font',
60
+ hash: 'hashtag',
61
+ mail: 'envelope',
62
+ 'link-2': 'link'
59
63
  });
60
64
 
61
65
  /** @type {Map<string, Promise<void>>} In-flight and settled kit loads, keyed by URL. */
package/src/core/i18n.js CHANGED
@@ -1,4 +1,8 @@
1
1
  // @ts-check
2
+ import { defaultMessages, legacyMessageKeys } from './messages.js';
3
+
4
+ export { defaultMessages };
5
+
2
6
  /** @typedef {(key: string) => string|null|undefined} Translator */
3
7
 
4
8
  /** @type {Translator|null} */
@@ -6,7 +10,9 @@ let translator = null;
6
10
  let language = 'en';
7
11
 
8
12
  /**
9
- * Installs the host application's translation function, or clears it with null.
13
+ * Installs the host application's translation function, or clears it with null. The function
14
+ * answers one key at a time; returning null, undefined, or the key itself means "no translation",
15
+ * and resolution moves on to the default English catalogue (`defaultMessages`).
10
16
  * @param {Translator|null} fn Translator function.
11
17
  * @returns {void}
12
18
  */
@@ -35,14 +41,51 @@ export function getLanguage() {
35
41
  }
36
42
 
37
43
  /**
38
- * Translates a key through the injected host translator and interpolates arguments.
44
+ * Asks the host translator for one key, without interpolating. A translator that returns null,
45
+ * undefined, or the key itself has no answer.
46
+ * @param {string} key Message key.
47
+ * @returns {string|null} The translation, or null when there is none.
48
+ */
49
+ export function hostMessage(key) {
50
+ if (!translator) return null;
51
+ const translated = translator(key);
52
+ if (translated == null) return null;
53
+ const text = String(translated);
54
+ return text === key ? null : text;
55
+ }
56
+
57
+ /**
58
+ * Looks one key up in the default English catalogue.
59
+ * @param {string} key Message key.
60
+ * @returns {string|null} The English text, or null when the catalogue has no entry.
61
+ */
62
+ export function defaultMessage(key) {
63
+ return Object.prototype.hasOwnProperty.call(defaultMessages, key)
64
+ ? /** @type {Record<string, string>} */ (defaultMessages)[key] : null;
65
+ }
66
+
67
+ /**
68
+ * The key a renamed message key replaced, which is still honoured for one major version.
69
+ * @param {string} key Current message key.
70
+ * @returns {string|null} The former key, or null when the key was never renamed.
71
+ */
72
+ export function legacyMessageKey(key) {
73
+ return Object.prototype.hasOwnProperty.call(legacyMessageKeys, key)
74
+ ? /** @type {Record<string, string>} */ (legacyMessageKeys)[key] : null;
75
+ }
76
+
77
+ /**
78
+ * Translates a key and interpolates arguments. The host translator answers first (then, for a
79
+ * renamed key, the translator again under its former name), then the default English catalogue;
80
+ * a key neither knows comes back unchanged.
39
81
  * @param {string} key Translation key.
40
82
  * @param {unknown[]|Record<string, unknown>|string|number} [args] Interpolation arguments.
41
83
  * @returns {string}
42
84
  */
43
85
  export function translate(key, args) {
44
- const translated = translator ? translator(key) : key;
45
- return printf(translated == null ? key : String(translated), args);
86
+ const legacy = legacyMessageKey(key);
87
+ const text = hostMessage(key) ?? (legacy ? hostMessage(legacy) : null) ?? defaultMessage(key) ?? key;
88
+ return printf(text, args);
46
89
  }
47
90
 
48
91
  /**
package/src/core/icons.js CHANGED
@@ -2,7 +2,8 @@
2
2
  /**
3
3
  * The icon layer. Two renderers sit behind one `icon()` call:
4
4
  *
5
- * - **built-in** (default) — the inline SVG glyphs below, no network, no stylesheet, no font.
5
+ * - **built-in** (default) — the inline SVG outline glyphs below (Lucide), no network, no
6
+ * stylesheet, no font. Applications can add their own, filled or outlined, with `registerIcons()`.
6
7
  * - **Font Awesome** — `<i>` elements carrying Font Awesome classes, after an application opts in
7
8
  * with `useFontAwesome()` or `loadFontAwesome(kit)`. Zx never loads a kit by itself.
8
9
  *
@@ -28,52 +29,148 @@ const SVG_NS = 'http://www.w3.org/2000/svg';
28
29
  */
29
30
 
30
31
  /**
31
- * Icon glyphs from Font Awesome Free 6 (solid, plus `square` from regular), fontawesome.com,
32
- * licensed CC BY 4.0 (https://fontawesome.com/license/free). Path data is embedded unmodified so
33
- * the library carries no webfont, stylesheet, or package dependency. Each entry is
34
- * `[viewBox, path]`; Font Awesome glyphs are filled (not stroked) and use per-glyph 512-unit view
35
- * boxes — outlines such as `square` are a filled path with a counter-wound hole.
36
- * Extend the set with `registerIcons()`; read every available name with `iconNames()`.
37
- * @type {Readonly<Record<string, [string, string]>>}
32
+ * How a glyph's path is painted. `'fill'` fills the shape — the form Font Awesome and other solid
33
+ * sets ship in. `'stroke'` draws the outline with the icon pen: `currentColor`, round caps and
34
+ * joins, and a width of 1.75 units in a 24-unit view box — the form the bundled set uses.
35
+ * @typedef {'fill'|'stroke'} IconPaint
36
+ */
37
+
38
+ /**
39
+ * One inline SVG glyph: `[viewBox, path]` for a filled shape, or `[viewBox, path, 'stroke']` for
40
+ * an outline. A path may hold any number of subpaths; the two-element form is the original format
41
+ * and always renders filled.
42
+ * @typedef {[string, string] | [string, string, IconPaint]} IconGlyph
43
+ */
44
+
45
+ /** The view box every bundled glyph is drawn in: Lucide's 24-unit square. */
46
+ const VIEW_BOX = '0 0 24 24';
47
+
48
+ /**
49
+ * Stroke width of an outline glyph, in units of a 24-unit view box. At the default 16px it draws
50
+ * a line of about 1.2px — the stem weight of 13–14px interface text, so an icon sits beside a
51
+ * label without outweighing it.
52
+ */
53
+ const STROKE_WIDTH = 1.75;
54
+
55
+ /**
56
+ * Thinnest line an outline glyph is drawn with, in CSS pixels. Below 16px the proportional width
57
+ * would fall under a pixel and wash out (a 12px chevron, an 8px tick), so small glyphs keep one
58
+ * full pixel instead.
59
+ */
60
+ const MIN_STROKE_PX = 1;
61
+
62
+ /**
63
+ * Wraps bundled path data as an outline glyph in the shared view box.
64
+ * @param {string} path Path data.
65
+ * @returns {IconGlyph}
66
+ */
67
+ const outline = (path) => [VIEW_BOX, path, 'stroke'];
68
+
69
+ /*
70
+ * @license Lucide 1.53.0 (ISC), including icons derived from Feather (MIT) — notices below.
71
+ *
72
+ * The bundled glyphs are Lucide 1.53.0 (https://lucide.dev), outline icons drawn on a 24-unit grid.
73
+ * Each Lucide icon is stored as one path: its circles, rectangles, lines, and polylines are
74
+ * rewritten as equivalent path data, which renders identically because every part shares the same
75
+ * stroke. Where a Zx name differs from the Lucide one, the Lucide name is noted beside the glyph.
76
+ *
77
+ * ISC License
78
+ *
79
+ * Copyright (c) 2026 Lucide Icons and Contributors
80
+ *
81
+ * Permission to use, copy, modify, and/or distribute this software for any
82
+ * purpose with or without fee is hereby granted, provided that the above
83
+ * copyright notice and this permission notice appear in all copies.
84
+ *
85
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
86
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
87
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
88
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
89
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
90
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
91
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
92
+ *
93
+ * Lucide icons derived from the Feather project (Lucide's LICENSE lists them) are also covered by:
94
+ *
95
+ * The MIT License (MIT)
96
+ *
97
+ * Copyright (c) 2013-present Cole Bemis
98
+ *
99
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
100
+ * of this software and associated documentation files (the "Software"), to deal
101
+ * in the Software without restriction, including without limitation the rights
102
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
103
+ * copies of the Software, and to permit persons to whom the Software is
104
+ * furnished to do so, subject to the following conditions:
105
+ *
106
+ * The above copyright notice and this permission notice shall be included in all
107
+ * copies or substantial portions of the Software.
108
+ *
109
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
110
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
111
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
112
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
113
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
114
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
115
+ * SOFTWARE.
116
+ */
117
+
118
+ /**
119
+ * The bundled glyph set: outline icons from Lucide (ISC; see the notice above), embedded as path
120
+ * data so the library carries no webfont, stylesheet, or package dependency. Every entry is
121
+ * `[viewBox, path, 'stroke']` in a 24-unit view box. Extend the set with `registerIcons()`; read
122
+ * every available name with `iconNames()`.
123
+ * @type {Readonly<Record<string, IconGlyph>>}
38
124
  */
39
125
  export const icons = Object.freeze({
40
- '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'],
41
- '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'],
42
- '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'],
43
- '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'],
44
- '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'],
45
- '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'],
46
- 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'],
47
- 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'],
48
- 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'],
49
- 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'],
50
- 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'],
51
- 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'],
52
- 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'],
53
- 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'],
54
- 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'],
55
- 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'],
56
- 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'],
57
- 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'],
58
- 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'],
59
- 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'],
60
- 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'],
61
- 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'],
62
- 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'],
63
- 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'],
64
- 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'],
65
- 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'],
66
- 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'],
67
- 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'],
68
- 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'],
69
- 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'],
70
- 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'],
71
- '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'],
72
- 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'],
73
- 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'],
74
- 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'],
75
- 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'],
76
- 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']
126
+ // <lucide-glyphs>
127
+ 'arrow-up-wide-short': outline('M3 8l4-4 4 4M7 4v16M11 12h10M11 16h7M11 20h4'), // arrow-up-wide-narrow
128
+ 'arrow-down-wide-short': outline('M3 16l4 4 4-4M7 20V4M11 4h10M11 8h7M11 12h4'), // arrow-down-wide-narrow
129
+ 'chevron-down': outline('M6 9l6 6 6-6'),
130
+ 'chevron-up': outline('M18 15l-6-6-6 6'),
131
+ 'chevron-left': outline('M15 18l-6-6 6-6'),
132
+ 'chevron-right': outline('M9 18l6-6-6-6'),
133
+ check: outline('M20 6 9 17l-5-5'),
134
+ x: outline('M18 6 6 18M6 6l12 12'),
135
+ plus: outline('M5 12h14M12 5v14'),
136
+ minus: outline('M5 12h14'),
137
+ search: outline('M21 21l-4.34-4.34M3 11a8 8 0 1 0 16 0a8 8 0 1 0-16 0z'),
138
+ 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'),
139
+ clock: outline('M2 12a10 10 0 1 0 20 0a10 10 0 1 0-20 0zM12 6v6l4 2'),
140
+ 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'),
141
+ 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'), // settings
142
+ 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'),
143
+ 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'),
144
+ reload: outline('M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8M21 3v5h-5'), // rotate-cw
145
+ list: outline('M3 5h.01M3 12h.01M3 19h.01M8 5h13M8 12h13M8 19h13'),
146
+ 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'), // funnel
147
+ 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'), // ellipsis
148
+ info: outline('M2 12a10 10 0 1 0 20 0a10 10 0 1 0-20 0zM12 16v-4M12 8h.01'),
149
+ 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'), // triangle-alert
150
+ error: outline('M2 12a10 10 0 1 0 20 0a10 10 0 1 0-20 0zM12 8V12M12 16H12.01'), // circle-alert
151
+ success: outline('M2 12a10 10 0 1 0 20 0a10 10 0 1 0-20 0zM16 9l-5.5 5.5L8 12'), // circle-check
152
+ upload: outline('M12 3v12M17 8l-5-5-5 5M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'),
153
+ 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'), // grip-vertical
154
+ 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'),
155
+ 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'),
156
+ 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'),
157
+ 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'),
158
+ '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'),
159
+ 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'),
160
+ 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'),
161
+ 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'),
162
+ code: outline('M18 16l4-4-4-4M6 8l-4 4 4 4M14.5 4l-5 16'), // code-xml
163
+ 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'),
164
+ 'table-cells-large': outline('M4 3h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1zM15 3h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1zM15 14h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1zM4 14h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1z'), // layout-grid
165
+ table: outline('M12 3v18M5 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 9h18M3 15h18'),
166
+ image: 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-2zM7 9a2 2 0 1 0 4 0a2 2 0 1 0-4 0zM21 15l-3.086-3.086a2 2 0 0 0-2.828 0L6 21'),
167
+ columns: 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-2zM9 3v18M15 3v18'), // columns-3
168
+ type: outline('M12 4v16M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2M9 20h6'),
169
+ hash: outline('M4 9h16M4 15h16M10 3L8 21M16 3l-2 18'),
170
+ mail: outline('M22 7l-8.991 5.727a2 2 0 0 1-2.009 0L2 7M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-16a2 2 0 0 1-2-2v-12a2 2 0 0 1 2-2z'),
171
+ 'link-2': outline('M9 17H7A5 5 0 0 1 7 7h2M15 7h2a5 5 0 1 1 0 10h-2M8 12h8'),
172
+ 'circle-chevron-down': outline('M2 12a10 10 0 1 0 20 0a10 10 0 1 0-20 0zM16 10l-4 4-4-4')
173
+ // </lucide-glyphs>
77
174
  });
78
175
 
79
176
  /** Legacy gx Factory icon names mapped to their Zx equivalents. */
@@ -83,10 +180,11 @@ const aliases = Object.freeze({
83
180
  checked: 'check',
84
181
  question: 'info',
85
182
  range: 'filter',
86
- fields: 'list'
183
+ fields: 'list',
184
+ grid: 'table-cells-large'
87
185
  });
88
186
 
89
- /** @type {Record<string, [string, string]>} Glyphs added at runtime by `registerIcons`. */
187
+ /** @type {Record<string, IconGlyph>} Glyphs added at runtime by `registerIcons`. */
90
188
  const extraGlyphs = Object.create(null);
91
189
 
92
190
  /**
@@ -163,17 +261,31 @@ export async function loadFontAwesome(options) {
163
261
  }
164
262
 
165
263
  /**
166
- * Adds inline SVG glyphs to the built-in set, or replaces existing ones. Entries use the same
167
- * `[viewBox, path]` shape as `icons`.
168
- * @param {Record<string, [string, string]>} glyphs Glyphs keyed by icon name.
264
+ * Adds inline SVG glyphs to the built-in set, or replaces existing ones.
265
+ *
266
+ * An entry is `[viewBox, path]` — a filled shape, the format Font Awesome path data comes in, and
267
+ * how it always rendered — or `[viewBox, path, 'stroke']` — an outline drawn with the same pen as
268
+ * the bundled set: `currentColor`, round caps and joins, and a stroke scaled to the view box, so a
269
+ * glyph from a 24-unit outline set such as Lucide renders exactly like a bundled one.
270
+ *
271
+ * registerIcons({ rocket: ['0 0 512 512', 'M…'] }); // filled
272
+ * registerIcons({ anchor: ['0 0 24 24', 'M12 6v16…', 'stroke'] }); // outline
273
+ * @param {Record<string, IconGlyph>} glyphs Glyphs keyed by icon name.
169
274
  * @returns {void}
275
+ * @throws {TypeError} When an entry is neither a `[viewBox, path]` pair nor a
276
+ * `[viewBox, path, 'fill'|'stroke']` triple.
170
277
  */
171
278
  export function registerIcons(glyphs) {
172
279
  for (const [name, entry] of Object.entries(glyphs)) {
173
- if (!Array.isArray(entry) || entry.length !== 2) {
174
- throw new TypeError(`Icon "${name}" must be a [viewBox, path] pair`);
280
+ if (!Array.isArray(entry) || (entry.length !== 2 && entry.length !== 3)
281
+ || (entry.length === 3 && entry[2] !== 'fill' && entry[2] !== 'stroke')) {
282
+ throw new TypeError(
283
+ `Icon "${name}" must be a [viewBox, path] pair or a [viewBox, path, 'stroke'] triple`
284
+ );
175
285
  }
176
- extraGlyphs[name] = [String(entry[0]), String(entry[1])];
286
+ extraGlyphs[name] = entry[2] === 'stroke'
287
+ ? [String(entry[0]), String(entry[1]), 'stroke']
288
+ : [String(entry[0]), String(entry[1])];
177
289
  }
178
290
  }
179
291
 
@@ -244,8 +356,8 @@ function builtinIcon(name, options) {
244
356
  warnMissingIcon(name);
245
357
  return missingIcon(name, options);
246
358
  }
247
- const [viewBox, pathData] = entry;
248
- const svg = iconSvg(viewBox, options);
359
+ const [viewBox, pathData, paint = 'fill'] = entry;
360
+ const svg = iconSvg(viewBox, paint, options);
249
361
  const path = document.createElementNS(SVG_NS, 'path');
250
362
  path.setAttribute('d', pathData);
251
363
  svg.append(path);
@@ -254,26 +366,57 @@ function builtinIcon(name, options) {
254
366
 
255
367
  /**
256
368
  * Builds the `<svg>` root every inline glyph shares, so the placeholder is the same element in
257
- * the same box as the icon it stands in for.
369
+ * the same box as the icon it stands in for. The paint attributes sit on the root and the path
370
+ * inherits them, so component CSS can restyle a glyph with `fill`, `stroke`, or `color` on the
371
+ * `.zx-icon` element alone — `fill: currentColor` turns an outline into its solid form, for a set
372
+ * star or a liked heart.
258
373
  * @param {string} viewBox The glyph's view box.
374
+ * @param {IconPaint} paint Whether the glyph is filled or stroked.
259
375
  * @param {IconOptions} options Display and accessibility options.
260
376
  * @returns {SVGSVGElement}
261
377
  */
262
- function iconSvg(viewBox, options) {
378
+ function iconSvg(viewBox, paint, options) {
263
379
  const { size = 16 } = options;
264
380
  const svg = document.createElementNS(SVG_NS, 'svg');
265
381
  svg.setAttribute('class', classList('zx-icon', options.class));
266
382
  svg.setAttribute('viewBox', viewBox);
267
383
  svg.setAttribute('width', String(size));
268
384
  svg.setAttribute('height', String(size));
269
- svg.setAttribute('fill', 'currentColor');
385
+ if (paint === 'stroke') {
386
+ svg.setAttribute('fill', 'none');
387
+ svg.setAttribute('stroke', 'currentColor');
388
+ svg.setAttribute('stroke-width', strokeWidth(viewBox, size));
389
+ svg.setAttribute('stroke-linecap', 'round');
390
+ svg.setAttribute('stroke-linejoin', 'round');
391
+ } else {
392
+ svg.setAttribute('fill', 'currentColor');
393
+ }
270
394
  svg.setAttribute('focusable', 'false');
271
395
  applyLabel(svg, options.label ?? null);
272
396
  return svg;
273
397
  }
274
398
 
275
- /** View box the placeholder reserves — the square most Font Awesome glyphs are drawn in. */
276
- const PLACEHOLDER_VIEW_BOX = '0 0 512 512';
399
+ /**
400
+ * The stroke width an outline glyph is drawn with, in its own view-box units: `STROKE_WIDTH` per
401
+ * 24 units, so outline sets drawn on another grid keep the same weight, and never thinner than
402
+ * `MIN_STROKE_PX` at the rendered size. A size given as a CSS length other than pixels (`'1em'`)
403
+ * cannot be measured here and keeps the proportional width.
404
+ * @param {string} viewBox The glyph's view box.
405
+ * @param {number|string} size The rendered size the caller asked for.
406
+ * @returns {string}
407
+ */
408
+ function strokeWidth(viewBox, size) {
409
+ const [, , width, height] = viewBox.trim().split(/[\s,]+/).map(Number);
410
+ const extent = Math.max(width, height) || 24;
411
+ let units = STROKE_WIDTH * extent / 24;
412
+ const pixels = typeof size === 'number' ? size
413
+ : /^\d*\.?\d+(px)?$/.test(String(size).trim()) ? parseFloat(String(size)) : NaN;
414
+ if (pixels > 0) units = Math.max(units, MIN_STROKE_PX * extent / pixels);
415
+ return String(Math.round(units * 1000) / 1000);
416
+ }
417
+
418
+ /** View box the placeholder reserves — the square the bundled glyphs are drawn in. */
419
+ const PLACEHOLDER_VIEW_BOX = VIEW_BOX;
277
420
 
278
421
  /**
279
422
  * The element an unresolvable name renders as: the ordinary `zx-icon` root at the requested size,
@@ -290,7 +433,7 @@ const PLACEHOLDER_VIEW_BOX = '0 0 512 512';
290
433
  * @returns {SVGSVGElement}
291
434
  */
292
435
  function missingIcon(name, options) {
293
- const svg = iconSvg(PLACEHOLDER_VIEW_BOX, options);
436
+ const svg = iconSvg(PLACEHOLDER_VIEW_BOX, 'stroke', options);
294
437
  svg.setAttribute('data-zx-icon-missing', name);
295
438
  return svg;
296
439
  }