@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.
- package/CHANGELOG.md +447 -0
- package/README.md +44 -11
- package/dist/types/components/account-menu/account-menu.d.ts +8 -0
- package/dist/types/components/activity-list/activity-list.d.ts +31 -1
- package/dist/types/components/app-icon/app-icon.d.ts +14 -2
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +293 -17
- package/dist/types/components/avatar/avatar.d.ts +46 -7
- package/dist/types/components/badge/badge.d.ts +21 -1
- package/dist/types/components/button/button.d.ts +15 -0
- package/dist/types/components/calendar/calendar.d.ts +67 -5
- package/dist/types/components/card-view/card-view.d.ts +7 -4
- package/dist/types/components/card-view/record-card.d.ts +11 -0
- package/dist/types/components/chart/chart.d.ts +10 -0
- package/dist/types/components/checklist/checklist.d.ts +9 -0
- package/dist/types/components/code-editor/code-editor.d.ts +6 -17
- package/dist/types/components/context-menu/context-menu.d.ts +6 -2
- package/dist/types/components/data-filter/data-filter.d.ts +4 -4
- package/dist/types/components/datebox/date-range-box.d.ts +5 -1
- package/dist/types/components/datebox/datebox.d.ts +7 -2
- package/dist/types/components/dialog/dialog.d.ts +46 -6
- package/dist/types/components/dropdown/dropdown.d.ts +24 -9
- package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
- package/dist/types/components/field/field.d.ts +54 -2
- package/dist/types/components/fieldset/external-adapter.d.ts +83 -0
- package/dist/types/components/fieldset/fieldset.d.ts +26 -1
- package/dist/types/components/file-item/file-item.d.ts +34 -6
- package/dist/types/components/file-list/file-list.d.ts +35 -3
- package/dist/types/components/filter/filter.d.ts +127 -3
- package/dist/types/components/filter-panel/filter-panel.d.ts +36 -7
- package/dist/types/components/form/form.d.ts +12 -1
- package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
- package/dist/types/components/launcher/launcher.d.ts +268 -15
- package/dist/types/components/list-toolbar/list-toolbar.d.ts +16 -7
- package/dist/types/components/menu-button/menu-button.d.ts +39 -5
- package/dist/types/components/message/message.d.ts +132 -17
- package/dist/types/components/modal/modal.d.ts +43 -2
- package/dist/types/components/module-bar/module-bar.d.ts +395 -0
- package/dist/types/components/pagination/pagination.d.ts +17 -3
- package/dist/types/components/select/color.d.ts +12 -0
- package/dist/types/components/select/glyphs.d.ts +124 -0
- package/dist/types/components/select/select.d.ts +17 -3
- package/dist/types/components/sheet/sheet.d.ts +15 -0
- package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
- package/dist/types/components/tabbox/tabbox.d.ts +9 -0
- package/dist/types/components/table/table.d.ts +311 -18
- package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
- package/dist/types/components/tree/tree.d.ts +13 -0
- package/dist/types/components/view/record-view.d.ts +59 -2
- package/dist/types/components/view-settings/view-settings.d.ts +879 -0
- package/dist/types/components/workspace-switcher/workspace-switcher.d.ts +327 -0
- package/dist/types/core/breakpoint.d.ts +1 -1
- package/dist/types/core/component.d.ts +12 -2
- package/dist/types/core/i18n.d.ts +27 -3
- package/dist/types/core/icons.d.ts +23 -25
- package/dist/types/core/keyboard.d.ts +98 -1
- package/dist/types/core/messages.d.ts +382 -0
- package/dist/types/core/motion.d.ts +59 -0
- package/dist/types/core/overlay-host.d.ts +42 -1
- package/dist/types/core/overlay-stack.d.ts +260 -0
- package/dist/types/index.d.ts +10 -5
- package/dist/types/internal/app-rail.d.ts +38 -0
- package/dist/types/internal/menu-items.d.ts +71 -3
- package/dist/types/zeyos/connect.d.ts +6 -0
- package/dist/types/zeyos/form.d.ts +50 -9
- package/dist/types/zeyos/index.d.ts +2 -1
- package/dist/types/zeyos/launcher.d.ts +9 -0
- package/dist/types/zeyos/module-bar.d.ts +176 -0
- package/dist/types/zeyos/query.d.ts +110 -5
- package/dist/types/zeyos/schema.d.ts +24 -1
- package/dist/types/zeyos/table.d.ts +345 -22
- package/dist/types/zeyos/view.d.ts +26 -2
- package/dist/zx-zeyos.esm.js +6178 -1008
- package/dist/zx-zeyos.esm.min.js +53 -5
- package/dist/zx.css +2861 -827
- package/dist/zx.esm.js +8909 -1480
- package/dist/zx.esm.min.js +55 -7
- package/dist/zx.global.js +8911 -1482
- package/dist/zx.global.min.js +55 -7
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1079 -117
- package/docs/llms.md +956 -154
- package/docs/llms.txt +9 -5
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.css +35 -10
- package/src/components/account-menu/account-menu.js +12 -0
- package/src/components/activity-item/activity-item.css +1 -1
- package/src/components/activity-item/activity-item.js +3 -0
- package/src/components/activity-list/activity-list.css +38 -3
- package/src/components/activity-list/activity-list.js +61 -1
- package/src/components/app-icon/app-icon.css +14 -3
- package/src/components/app-icon/app-icon.js +11 -1
- package/src/components/app-rail/app-rail.css +59 -79
- package/src/components/app-sidebar/app-sidebar.css +304 -168
- package/src/components/app-sidebar/app-sidebar.js +667 -46
- package/src/components/aurora/aurora.css +5 -3
- package/src/components/avatar/avatar.css +32 -7
- package/src/components/avatar/avatar.js +72 -8
- package/src/components/badge/badge.css +54 -2
- package/src/components/badge/badge.js +29 -2
- package/src/components/breadcrumb/breadcrumb.css +1 -1
- package/src/components/button/button.css +67 -9
- package/src/components/button/button.js +38 -0
- package/src/components/calendar/calendar.css +47 -47
- package/src/components/calendar/calendar.js +26 -52
- package/src/components/card/card.css +4 -4
- package/src/components/card-view/card-view.css +14 -12
- package/src/components/card-view/card-view.js +13 -9
- package/src/components/card-view/record-card.js +14 -3
- package/src/components/chart/chart.css +4 -1
- package/src/components/chart/chart.js +12 -0
- package/src/components/check-button/check-button.css +10 -2
- package/src/components/checklist/checklist.css +9 -2
- package/src/components/checklist/checklist.js +11 -5
- package/src/components/checklist/field-adapter.js +3 -1
- package/src/components/code-editor/code-editor.css +7 -6
- package/src/components/code-editor/code-editor.js +19 -1
- package/src/components/code-editor/field-adapter.js +9 -2
- package/src/components/context-menu/context-menu.css +9 -4
- package/src/components/context-menu/context-menu.js +8 -2
- package/src/components/copy/copy-button.css +6 -0
- package/src/components/copy/copy-input.css +9 -4
- package/src/components/data-filter/data-filter.css +5 -4
- package/src/components/data-filter/data-filter.js +2 -2
- package/src/components/date-picker/date-picker.css +6 -6
- package/src/components/date-picker/date-range-picker.css +3 -3
- package/src/components/date-picker/time-picker.css +3 -3
- package/src/components/datebox/date-range-box.css +79 -2
- package/src/components/datebox/date-range-box.js +13 -2
- package/src/components/datebox/datebox.css +79 -2
- package/src/components/datebox/datebox.js +13 -2
- package/src/components/dialog/dialog.css +26 -0
- package/src/components/dialog/dialog.js +99 -17
- package/src/components/dock/dock.css +5 -4
- package/src/components/dropdown/dropdown.css +1 -1
- package/src/components/dropdown/dropdown.js +159 -72
- package/src/components/empty-state/empty-state.css +2 -2
- package/src/components/entity-ref/entity-ref.css +1 -1
- package/src/components/entity-ref/entity-ref.js +1 -1
- package/src/components/field/field.css +28 -9
- package/src/components/field/field.js +101 -29
- package/src/components/field-upload/field-upload.css +1 -1
- package/src/components/fieldset/external-adapter.js +363 -0
- package/src/components/fieldset/fieldset.css +5 -3
- package/src/components/fieldset/fieldset.js +38 -64
- package/src/components/file-item/file-item.js +144 -10
- package/src/components/file-list/file-list.js +144 -21
- package/src/components/filter/filter.css +86 -2
- package/src/components/filter/filter.js +307 -11
- package/src/components/filter-panel/filter-panel.css +1 -1
- package/src/components/filter-panel/filter-panel.js +149 -32
- package/src/components/finder/finder.css +16 -14
- package/src/components/form/form.css +5 -4
- package/src/components/form/form.js +11 -2
- package/src/components/grid/grid.css +1 -1
- package/src/components/groupbox/groupbox.css +1 -1
- package/src/components/kanban-view/kanban-card.js +11 -3
- package/src/components/kanban-view/kanban-view.css +42 -18
- package/src/components/kanban-view/kanban-view.js +9 -6
- package/src/components/launcher/launcher.css +108 -26
- package/src/components/launcher/launcher.js +530 -42
- package/src/components/list-toolbar/list-toolbar.css +13 -8
- package/src/components/list-toolbar/list-toolbar.js +40 -14
- package/src/components/master-panel/master-panel.css +2 -0
- package/src/components/master-panel/master-panel.js +0 -1
- package/src/components/menu-button/menu-button.css +72 -4
- package/src/components/menu-button/menu-button.js +160 -18
- package/src/components/message/message.css +33 -3
- package/src/components/message/message.js +264 -22
- package/src/components/modal/modal.css +1 -1
- package/src/components/modal/modal.js +83 -15
- package/src/components/module-bar/module-bar.css +486 -0
- package/src/components/module-bar/module-bar.js +1074 -0
- package/src/components/multi-value-editor/multi-value-editor.css +1 -1
- package/src/components/navigation-bar/navigation-bar.css +18 -9
- package/src/components/number-field/field-adapter.js +3 -1
- package/src/components/number-field/number-field.css +70 -0
- package/src/components/number-field/number-field.js +2 -0
- package/src/components/pagination/pagination.css +13 -11
- package/src/components/pagination/pagination.js +20 -4
- package/src/components/panel/panel.css +4 -3
- package/src/components/questionnaire/questionnaire.css +4 -4
- package/src/components/rating/field-adapter.js +2 -1
- package/src/components/rating/rating.css +8 -1
- package/src/components/search/search.css +4 -1
- package/src/components/select/color.js +22 -0
- package/src/components/select/field-adapter.js +2 -0
- package/src/components/select/glyphs.js +165 -0
- package/src/components/select/select.css +138 -45
- package/src/components/select/select.js +92 -47
- package/src/components/sheet/sheet.css +12 -9
- package/src/components/sheet/sheet.js +74 -32
- package/src/components/slider/field-adapter.js +6 -1
- package/src/components/slider/slider.css +18 -4
- package/src/components/slider/slider.js +1 -1
- package/src/components/sort-control/sort-control.css +4 -1
- package/src/components/split-view/split-view.css +1 -1
- package/src/components/stat-tile/stat-tile.css +42 -47
- package/src/components/stat-tile/stat-tile.js +17 -6
- package/src/components/stepper/stepper.css +1 -1
- package/src/components/tabbox/tabbox.css +41 -32
- package/src/components/tabbox/tabbox.js +5 -1
- package/src/components/table/table.css +187 -37
- package/src/components/table/table.js +718 -73
- package/src/components/tag-picker/field-adapter.js +2 -1
- package/src/components/tag-picker/tag-picker.css +83 -5
- package/src/components/tag-picker/tag-picker.js +25 -8
- package/src/components/timebox/timebox.css +22 -2
- package/src/components/toggle/toggle.css +12 -1
- package/src/components/toolbar/toolbar.js +2 -2
- package/src/components/tooltip/tooltip.css +4 -0
- package/src/components/tooltip/tooltip.js +44 -19
- package/src/components/tree/tree.css +26 -17
- package/src/components/tree/tree.js +31 -0
- package/src/components/value-list/value-list.css +3 -3
- package/src/components/view/record-view.css +1 -1
- package/src/components/view/record-view.js +178 -18
- package/src/components/view-settings/view-settings.css +285 -0
- package/src/components/view-settings/view-settings.js +1830 -0
- package/src/components/workspace-switcher/workspace-switcher.css +188 -0
- package/src/components/workspace-switcher/workspace-switcher.js +463 -0
- package/src/core/breakpoint.js +1 -1
- package/src/core/component.js +88 -17
- package/src/core/fontawesome.js +5 -1
- package/src/core/i18n.js +47 -4
- package/src/core/icons.js +204 -61
- package/src/core/keyboard.js +206 -11
- package/src/core/messages.js +427 -0
- package/src/core/motion.js +147 -0
- package/src/core/overlay-host.js +77 -3
- package/src/core/overlay-stack.js +487 -0
- package/src/core/position.js +22 -3
- package/src/index.js +10 -5
- package/src/internal/app-rail.js +135 -55
- package/src/internal/menu-items.js +61 -3
- package/src/zeyos/connect.js +6 -2
- package/src/zeyos/form.js +69 -27
- package/src/zeyos/index.js +4 -1
- package/src/zeyos/launcher.js +8 -3
- package/src/zeyos/module-bar.js +181 -0
- package/src/zeyos/query.js +197 -13
- package/src/zeyos/schema.js +144 -27
- package/src/zeyos/table.js +287 -84
- package/src/zeyos/view.js +49 -93
- package/styles/base.css +72 -1
- package/styles/icons.css +11 -1
- package/styles/print.css +1 -1
- package/styles/tokens/dark.css +117 -37
- package/styles/tokens/global.css +121 -27
- package/styles/tokens/semantic.css +140 -43
- package/styles/tokens/themes.css +7 -6
- package/styles/zx.css +3 -1
- package/styles/tokens/density.css +0 -9
package/src/core/component.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { h, resolveElement } from './dom.js';
|
|
3
|
-
import { getLanguage,
|
|
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
|
|
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
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
package/src/core/fontawesome.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
45
|
-
|
|
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
|
|
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
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
41
|
-
'arrow-
|
|
42
|
-
'
|
|
43
|
-
'chevron-
|
|
44
|
-
'chevron-
|
|
45
|
-
'chevron-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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,
|
|
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.
|
|
167
|
-
*
|
|
168
|
-
*
|
|
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
|
-
|
|
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] =
|
|
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
|
-
|
|
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
|
-
/**
|
|
276
|
-
|
|
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
|
}
|