@zeyos/zx 4.3.4 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/README.md +7 -0
  3. package/dist/types/components/activity-item/activity-item.d.ts +351 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +195 -0
  5. package/dist/types/components/app-sidebar/app-sidebar.d.ts +28 -0
  6. package/dist/types/components/button/button.d.ts +8 -0
  7. package/dist/types/components/calendar/calendar.d.ts +19 -0
  8. package/dist/types/components/card/card.d.ts +7 -0
  9. package/dist/types/components/card-view/card-view.d.ts +11 -3
  10. package/dist/types/components/card-view/record-card.d.ts +7 -1
  11. package/dist/types/components/code-editor/code-editor.d.ts +160 -0
  12. package/dist/types/components/code-editor/field-adapter.d.ts +5 -0
  13. package/dist/types/components/context-menu/context-menu.d.ts +18 -2
  14. package/dist/types/components/date-picker/date-picker.d.ts +8 -0
  15. package/dist/types/components/date-picker/time-picker.d.ts +7 -0
  16. package/dist/types/components/dialog/dialog.d.ts +29 -0
  17. package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
  18. package/dist/types/components/file-item/file-item.d.ts +234 -0
  19. package/dist/types/components/file-list/file-list.d.ts +171 -0
  20. package/dist/types/components/filter/filter.d.ts +91 -36
  21. package/dist/types/components/filter-panel/filter-panel.d.ts +449 -0
  22. package/dist/types/components/kanban-view/kanban-card.d.ts +138 -0
  23. package/dist/types/components/kanban-view/kanban-view.d.ts +170 -4
  24. package/dist/types/components/launcher/launcher.d.ts +19 -12
  25. package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
  26. package/dist/types/components/menu-button/menu-button.d.ts +4 -37
  27. package/dist/types/components/message/message.d.ts +7 -0
  28. package/dist/types/components/modal/modal.d.ts +48 -0
  29. package/dist/types/components/navigation-bar/navigation-bar.d.ts +105 -21
  30. package/dist/types/components/panel/panel.d.ts +7 -0
  31. package/dist/types/components/search/search.d.ts +7 -0
  32. package/dist/types/components/select/select.d.ts +4 -0
  33. package/dist/types/components/sort-control/sort-control.d.ts +105 -0
  34. package/dist/types/components/stat-tile/stat-tile.d.ts +277 -0
  35. package/dist/types/components/tabbox/tabbox.d.ts +7 -0
  36. package/dist/types/components/table/table.d.ts +144 -0
  37. package/dist/types/components/table-view/table-view.d.ts +11 -5
  38. package/dist/types/components/timebox/timebox.d.ts +7 -0
  39. package/dist/types/components/value-list/value-list.d.ts +8 -0
  40. package/dist/types/components/view/record-view.d.ts +96 -5
  41. package/dist/types/core/icons.d.ts +17 -0
  42. package/dist/types/index.d.ts +11 -0
  43. package/dist/types/internal/app-rail.d.ts +10 -0
  44. package/dist/types/internal/menu-items.d.ts +134 -0
  45. package/dist/zx-zeyos.esm.js +616 -77
  46. package/dist/zx-zeyos.esm.min.js +8 -1
  47. package/dist/zx.css +2085 -238
  48. package/dist/zx.esm.js +6704 -1542
  49. package/dist/zx.esm.min.js +10 -3
  50. package/dist/zx.global.js +6704 -1542
  51. package/dist/zx.global.min.js +10 -3
  52. package/dist/zx.min.css +1 -1
  53. package/docs/api.json +1479 -127
  54. package/docs/llms.md +315 -10
  55. package/docs/llms.txt +11 -5
  56. package/package.json +1 -1
  57. package/src/components/activity-item/activity-item.css +137 -0
  58. package/src/components/activity-item/activity-item.js +599 -0
  59. package/src/components/activity-list/activity-list.css +64 -0
  60. package/src/components/activity-list/activity-list.js +375 -0
  61. package/src/components/app-rail/app-rail.css +227 -19
  62. package/src/components/app-sidebar/app-sidebar.css +414 -24
  63. package/src/components/app-sidebar/app-sidebar.js +65 -5
  64. package/src/components/button/button.css +8 -0
  65. package/src/components/button/button.js +6 -1
  66. package/src/components/calendar/calendar.js +33 -3
  67. package/src/components/card/card.js +12 -1
  68. package/src/components/card-view/card-view.css +1 -0
  69. package/src/components/card-view/card-view.js +25 -9
  70. package/src/components/card-view/record-card.js +7 -3
  71. package/src/components/code-editor/code-editor.css +67 -0
  72. package/src/components/code-editor/code-editor.js +288 -0
  73. package/src/components/code-editor/field-adapter.js +34 -0
  74. package/src/components/context-menu/context-menu.css +80 -3
  75. package/src/components/context-menu/context-menu.js +22 -30
  76. package/src/components/date-picker/date-picker.js +22 -8
  77. package/src/components/date-picker/month-picker.js +3 -3
  78. package/src/components/date-picker/time-picker.js +20 -8
  79. package/src/components/dialog/dialog.js +88 -66
  80. package/src/components/dropdown/dropdown.js +2 -2
  81. package/src/components/entity-ref/entity-ref.css +182 -0
  82. package/src/components/entity-ref/entity-ref.js +496 -0
  83. package/src/components/file-item/file-item.css +88 -0
  84. package/src/components/file-item/file-item.js +371 -0
  85. package/src/components/file-list/file-list.css +51 -0
  86. package/src/components/file-list/file-list.js +320 -0
  87. package/src/components/filter/filter.css +27 -1
  88. package/src/components/filter/filter.js +199 -45
  89. package/src/components/filter-panel/filter-panel.css +104 -0
  90. package/src/components/filter-panel/filter-panel.js +964 -0
  91. package/src/components/kanban-view/kanban-card.js +239 -0
  92. package/src/components/kanban-view/kanban-view.css +194 -0
  93. package/src/components/kanban-view/kanban-view.js +251 -8
  94. package/src/components/launcher/launcher.js +59 -27
  95. package/src/components/list-toolbar/list-toolbar.css +189 -0
  96. package/src/components/list-toolbar/list-toolbar.js +851 -0
  97. package/src/components/menu-button/menu-button.css +80 -3
  98. package/src/components/menu-button/menu-button.js +21 -37
  99. package/src/components/message/message.css +5 -2
  100. package/src/components/message/message.js +13 -2
  101. package/src/components/modal/modal.css +15 -0
  102. package/src/components/modal/modal.js +141 -0
  103. package/src/components/navigation-bar/navigation-bar.css +77 -2
  104. package/src/components/navigation-bar/navigation-bar.js +171 -4
  105. package/src/components/panel/panel.js +13 -2
  106. package/src/components/search/search.js +17 -5
  107. package/src/components/select/field-adapter.js +4 -1
  108. package/src/components/select/select.js +3 -0
  109. package/src/components/sort-control/sort-control.css +17 -0
  110. package/src/components/sort-control/sort-control.js +140 -0
  111. package/src/components/stat-tile/stat-tile.css +213 -0
  112. package/src/components/stat-tile/stat-tile.js +590 -0
  113. package/src/components/tabbox/tabbox.js +12 -1
  114. package/src/components/table/table.css +51 -2
  115. package/src/components/table/table.js +298 -19
  116. package/src/components/table-view/table-view.css +8 -2
  117. package/src/components/table-view/table-view.js +21 -6
  118. package/src/components/tag-picker/tag-picker.css +3 -1
  119. package/src/components/timebox/timebox.js +18 -5
  120. package/src/components/value-list/value-list.js +22 -6
  121. package/src/components/view/record-view.css +5 -0
  122. package/src/components/view/record-view.js +100 -7
  123. package/src/core/icons.js +74 -5
  124. package/src/index.js +25 -0
  125. package/src/internal/app-rail.js +19 -1
  126. package/src/internal/menu-items.js +142 -0
  127. package/styles/print.css +192 -0
  128. package/styles/zx.css +11 -0
@@ -0,0 +1,371 @@
1
+ // @ts-check
2
+ import { Component } from '../../core/component.js';
3
+ import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
4
+ import { formatFileSize } from '../../core/format.js';
5
+ import { isElement } from '../../core/util.js';
6
+ import { badge } from '../badge/badge.js';
7
+ import {
8
+ EntityRef, normalizeEntityRefActions, normalizeEntityRefLink, normalizeEntityRefMetadata
9
+ } from '../entity-ref/entity-ref.js';
10
+
11
+ /** @typedef {import('../entity-ref/entity-ref.js').EntityRefContent} EntityRefContent */
12
+ /** @typedef {import('../entity-ref/entity-ref.js').EntityRefIcon} EntityRefIcon */
13
+ /** @typedef {import('../entity-ref/entity-ref.js').EntityRefMetadata} EntityRefMetadata */
14
+ /** @typedef {import('../entity-ref/entity-ref.js').EntityRefLinkValue} EntityRefLinkValue */
15
+ /** @typedef {import('../entity-ref/entity-ref.js').EntityRefActionEntry} EntityRefActionEntry */
16
+ /** @typedef {'ready'|'temporary'|'waiting'|'uploading'|'processing'|'success'|'error'} FileStatus */
17
+
18
+ /**
19
+ * @typedef {Object} FileItemOptions
20
+ * @property {unknown} [id=null] Application-owned file identifier included in events.
21
+ * @property {string} [name=''] File name.
22
+ * @property {number|string|null} [size=null] Byte count; invalid and negative values are omitted.
23
+ * @property {string} [mime=''] MIME type.
24
+ * @property {EntityRefContent} [subtitle=null] Optional supporting text before generated metadata.
25
+ * @property {EntityRefIcon} [icon='file'] File icon name, node, component, or lazy renderer.
26
+ * @property {EntityRefMetadata[]} [metadata=[]] Additional labelled metadata.
27
+ * @property {EntityRefLinkValue} [link=null] Optional safe native file/download link.
28
+ * @property {EntityRefActionEntry[]} [actions=[]] File actions; application policy stays external.
29
+ * @property {FileStatus} [status='ready'] Durable or transient presentation state.
30
+ * @property {string|null} [statusLabel=null] Overrides the generated state label; an empty string hides it.
31
+ * @property {number|null} [progress=null] Completed percentage, clamped to 0–100.
32
+ * @property {boolean} [indeterminate=false] Whether active progress has no known amount.
33
+ * @property {'sm'|'md'|'lg'} [density='md'] File-identity presentation size.
34
+ * @property {boolean} [wrap=false] Whether long identity text may wrap.
35
+ * @property {string} [actionsLabel='File actions'] Accessible name for secondary actions.
36
+ * @property {string} [locale] Locale passed to `formatFileSize`.
37
+ * @property {(event: CustomEvent<{id: unknown, file: Readonly<FileItemState>, href: string, event: MouseEvent}>) => void} [onactivate]
38
+ * Preventable native-link listener.
39
+ * @property {(event: CustomEvent<{id: string, fileId: unknown, action: object, file: Readonly<FileItemState>, event: MouseEvent}>) => void} [onaction]
40
+ * Preventable file-action listener.
41
+ */
42
+
43
+ /**
44
+ * @typedef {Object} FileItemState
45
+ * @property {unknown} id File identifier.
46
+ * @property {string} name File name.
47
+ * @property {number|null} size Byte count.
48
+ * @property {string} mime MIME type.
49
+ * @property {EntityRefContent} subtitle Supporting content.
50
+ * @property {EntityRefIcon} icon File visual.
51
+ * @property {EntityRefMetadata[]} metadata Additional metadata.
52
+ * @property {import('../entity-ref/entity-ref.js').EntityRefLink|null} link Safe native link.
53
+ * @property {EntityRefActionEntry[]} actions Normalized actions.
54
+ * @property {FileStatus} status Presentation state.
55
+ * @property {string|null} statusLabel Explicit state label, or null for the generated label.
56
+ * @property {number|null} progress Completed percentage.
57
+ * @property {boolean} indeterminate Unknown-progress state.
58
+ * @property {'sm'|'md'|'lg'} density Identity size.
59
+ * @property {boolean} wrap Wrapping state.
60
+ * @property {string} actionsLabel Action-group label.
61
+ * @property {string|undefined} locale File-size locale.
62
+ */
63
+
64
+ const FILE_STATUSES = new Set([
65
+ 'ready', 'temporary', 'waiting', 'uploading', 'processing', 'success', 'error'
66
+ ]);
67
+
68
+ const STATUS_LABELS = Object.freeze({
69
+ ready: '',
70
+ temporary: 'Temporary',
71
+ waiting: 'Waiting',
72
+ uploading: 'Uploading',
73
+ processing: 'Processing',
74
+ success: 'Complete',
75
+ error: 'Failed'
76
+ });
77
+
78
+ /**
79
+ * Durable or transient file presentation. The host owns upload/download transport, authorization,
80
+ * preview behavior, retry policy, and persistence.
81
+ * @fires FileItem#activate
82
+ * @fires FileItem#action
83
+ * @extends {Component<FileItemOptions>}
84
+ */
85
+ export class FileItem extends Component {
86
+ static cssName = 'file-item';
87
+
88
+ /** @type {Readonly<FileItemOptions>} */
89
+ static defaults = {
90
+ id: null,
91
+ name: '',
92
+ size: null,
93
+ mime: '',
94
+ subtitle: null,
95
+ icon: 'file',
96
+ metadata: [],
97
+ link: null,
98
+ actions: [],
99
+ status: 'ready',
100
+ statusLabel: null,
101
+ progress: null,
102
+ indeterminate: false,
103
+ density: 'md',
104
+ wrap: false,
105
+ actionsLabel: 'File actions'
106
+ };
107
+
108
+ /**
109
+ * Creates or enhances a file row.
110
+ * @param {Element|string|null} [target=null] Existing host, selector, or null.
111
+ * @param {FileItemOptions} [options={}] File presentation.
112
+ */
113
+ constructor(target = null, options = {}) {
114
+ super(target, options);
115
+ }
116
+
117
+ /** @returns {HTMLElement} */
118
+ render() {
119
+ this._createdRoot = this.el === null;
120
+ const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
121
+ this.el = root;
122
+ this._snapshot = this._createdRoot ? null : snapshotTarget(root);
123
+ this._destroyed = false;
124
+ this._state = normalizeFileItem(this.options);
125
+
126
+ const entityHost = h('div', { ref: 'entity', class: 'zx-file-item__entity' });
127
+ root.replaceChildren(
128
+ entityHost,
129
+ h('div', { ref: 'status', class: 'zx-file-item__status', role: 'status' }),
130
+ h('div', { ref: 'progress', class: 'zx-file-item__progress' },
131
+ h('div', { ref: 'progressHead', class: 'zx-file-item__progress-head' }),
132
+ h('div', {
133
+ ref: 'progressTrack',
134
+ class: 'zx-file-item__progress-track',
135
+ role: 'progressbar',
136
+ ariaValuemin: '0',
137
+ ariaValuemax: '100'
138
+ }, h('div', { ref: 'progressFill', class: 'zx-file-item__progress-fill' })))
139
+ );
140
+
141
+ this._entity = new EntityRef(entityHost, entityOptions(this._state));
142
+ this.listen(this._entity.el, 'zx-activate', (event) => this._forwardActivate(/** @type {CustomEvent} */ (event)));
143
+ this.listen(this._entity.el, 'zx-action', (event) => this._forwardAction(/** @type {CustomEvent} */ (event)));
144
+ this._sync();
145
+ return root;
146
+ }
147
+
148
+ /**
149
+ * Updates any subset of the file without replacing the row root.
150
+ * @param {Partial<FileItemOptions>} values Next values.
151
+ * @returns {this}
152
+ */
153
+ set(values = {}) {
154
+ if (!values || typeof values !== 'object' || Array.isArray(values)) return this;
155
+ this._state = normalizeFileItem({ ...this._state, ...values });
156
+ this._sync();
157
+ return this;
158
+ }
159
+
160
+ /**
161
+ * Updates the file lifecycle state and, optionally, its visible label.
162
+ * @param {FileStatus} status State.
163
+ * @param {string|null} [statusLabel] Explicit label.
164
+ * @returns {this}
165
+ */
166
+ setStatus(status, statusLabel) {
167
+ return this.set({ status, ...(statusLabel === undefined ? {} : { statusLabel }) });
168
+ }
169
+
170
+ /**
171
+ * Updates the progress meter.
172
+ * @param {number|null} progress Completed percentage, or null.
173
+ * @param {{indeterminate?: boolean}} [options={}] Unknown-progress override.
174
+ * @returns {this}
175
+ */
176
+ setProgress(progress, { indeterminate = false } = {}) {
177
+ return this.set({ progress, indeterminate });
178
+ }
179
+
180
+ /** Returns a defensive file snapshot. @returns {Readonly<FileItemState>} */
181
+ getFile() {
182
+ return fileSnapshot(this._state);
183
+ }
184
+
185
+ /** Focuses the native destination or first enabled action. @returns {this} */
186
+ focus() {
187
+ this._entity.focus();
188
+ return this;
189
+ }
190
+
191
+ /** Restores an enhanced target exactly, or removes an owned row. @returns {void} */
192
+ destroy() {
193
+ if (this._destroyed) return;
194
+ this._destroyed = true;
195
+ const root = this.el;
196
+ this._entity?.destroy();
197
+ super.destroy();
198
+ if (!this._createdRoot && root) restoreTarget(root, this._snapshot);
199
+ }
200
+
201
+ /** @returns {void} */
202
+ _sync() {
203
+ const state = this._state;
204
+ const root = /** @type {HTMLElement} */ (this.el);
205
+ const status = /** @type {HTMLElement} */ (this.refs.status);
206
+ const progress = /** @type {HTMLElement} */ (this.refs.progress);
207
+ const track = /** @type {HTMLElement} */ (this.refs.progressTrack);
208
+ const fill = /** @type {HTMLElement} */ (this.refs.progressFill);
209
+ root.dataset.status = state.status;
210
+ root.dataset.density = state.density;
211
+ this._entity.set(entityOptions(state));
212
+
213
+ const label = fileStatusLabel(state);
214
+ status.replaceChildren(...(label ? [badge({
215
+ label,
216
+ kind: statusKind(state.status),
217
+ size: 'sm',
218
+ dot: state.status !== 'ready'
219
+ })] : []));
220
+ status.hidden = label === '';
221
+
222
+ const visible = state.indeterminate || state.progress !== null;
223
+ progress.hidden = !visible;
224
+ progress.toggleAttribute('data-indeterminate', state.indeterminate);
225
+ if (!visible) return;
226
+
227
+ const progressLabel = `${label || 'Progress'}${state.indeterminate || state.progress === null
228
+ ? '' : ` — ${Math.round(state.progress)}%`}`;
229
+ this.refs.progressHead.textContent = progressLabel;
230
+ track.setAttribute('aria-label', `${state.name || 'File'}: ${label || 'Progress'}`);
231
+ if (state.indeterminate) {
232
+ track.removeAttribute('aria-valuenow');
233
+ track.removeAttribute('aria-valuetext');
234
+ fill.style.removeProperty('--zx-file-progress');
235
+ } else {
236
+ const value = state.progress ?? 0;
237
+ track.setAttribute('aria-valuenow', String(value));
238
+ track.setAttribute('aria-valuetext', `${Math.round(value)}%`);
239
+ fill.style.setProperty('--zx-file-progress', `${value}%`);
240
+ }
241
+ }
242
+
243
+ /** @param {CustomEvent} event @returns {void} */
244
+ _forwardActivate(event) {
245
+ const detail = /** @type {{href?:string,event?:MouseEvent}} */ (event.detail ?? {});
246
+ const forwarded = this.emit('activate', {
247
+ id: this._state.id,
248
+ file: this.getFile(),
249
+ href: String(detail.href ?? ''),
250
+ event: detail.event
251
+ }, { honorDomCancellation: true });
252
+ if (forwarded.defaultPrevented) event.preventDefault();
253
+ }
254
+
255
+ /** @param {CustomEvent} event @returns {void} */
256
+ _forwardAction(event) {
257
+ const detail = /** @type {{id?:string,action?:object,event?:MouseEvent}} */ (event.detail ?? {});
258
+ const forwarded = this.emit('action', {
259
+ id: String(detail.id ?? ''),
260
+ fileId: this._state.id,
261
+ action: detail.action ?? {},
262
+ file: this.getFile(),
263
+ event: detail.event
264
+ }, { honorDomCancellation: true });
265
+ if (forwarded.defaultPrevented) event.preventDefault();
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Normalizes a file presentation without touching the DOM.
271
+ * @param {Partial<FileItemOptions>|null|undefined} values Candidate values.
272
+ * @returns {FileItemState}
273
+ */
274
+ export function normalizeFileItem(values = {}) {
275
+ const source = values && typeof values === 'object' && !Array.isArray(values) ? values : {};
276
+ const rawSize = source.size == null || source.size === '' ? null : Number(source.size);
277
+ const rawProgress = source.progress == null ? null : Number(source.progress);
278
+ const status = FILE_STATUSES.has(String(source.status))
279
+ ? /** @type {FileStatus} */ (String(source.status)) : 'ready';
280
+ return {
281
+ id: source.id ?? null,
282
+ name: String(source.name ?? ''),
283
+ size: rawSize !== null && Number.isFinite(rawSize) && rawSize >= 0 ? rawSize : null,
284
+ mime: String(source.mime ?? ''),
285
+ subtitle: normalizeFileContent(source.subtitle),
286
+ icon: source.icon === undefined ? 'file' : source.icon,
287
+ metadata: normalizeEntityRefMetadata(source.metadata),
288
+ link: normalizeEntityRefLink(source.link),
289
+ actions: normalizeEntityRefActions(source.actions),
290
+ status,
291
+ statusLabel: source.statusLabel == null ? null : String(source.statusLabel),
292
+ progress: rawProgress === null || !Number.isFinite(rawProgress)
293
+ ? null : Math.min(100, Math.max(0, rawProgress)),
294
+ indeterminate: Boolean(source.indeterminate),
295
+ density: source.density === 'sm' || source.density === 'lg' ? source.density : 'md',
296
+ wrap: Boolean(source.wrap),
297
+ actionsLabel: String(source.actionsLabel || 'File actions'),
298
+ locale: normalizeLocale(source.locale)
299
+ };
300
+ }
301
+
302
+ /** @param {FileItemState} state @returns {import('../entity-ref/entity-ref.js').EntityRefOptions} */
303
+ function entityOptions(state) {
304
+ const metadata = [];
305
+ if (state.mime) metadata.push({ label: 'Type', value: state.mime, showLabel: false });
306
+ if (state.size !== null) {
307
+ metadata.push({
308
+ label: 'Size',
309
+ value: formatFileSize(state.size, { locale: state.locale }),
310
+ showLabel: false
311
+ });
312
+ }
313
+ metadata.push(...state.metadata);
314
+ return {
315
+ id: state.id,
316
+ title: state.name,
317
+ subtitle: state.subtitle,
318
+ icon: state.icon,
319
+ metadata,
320
+ link: state.link,
321
+ actions: state.actions,
322
+ size: state.density,
323
+ wrap: state.wrap,
324
+ actionsLabel: state.actionsLabel
325
+ };
326
+ }
327
+
328
+ /** @param {FileItemState} state @returns {string} */
329
+ function fileStatusLabel(state) {
330
+ return state.statusLabel === null ? STATUS_LABELS[state.status] : state.statusLabel;
331
+ }
332
+
333
+ /** @param {FileStatus} status @returns {'neutral'|'accent'|'success'|'warning'|'danger'|'info'} */
334
+ function statusKind(status) {
335
+ if (status === 'error') return 'danger';
336
+ if (status === 'success') return 'success';
337
+ if (status === 'temporary' || status === 'waiting') return 'warning';
338
+ if (status === 'uploading') return 'info';
339
+ if (status === 'processing') return 'accent';
340
+ return 'neutral';
341
+ }
342
+
343
+ /** @param {unknown} value @returns {EntityRefContent} */
344
+ function normalizeFileContent(value) {
345
+ if (value == null) return null;
346
+ if (typeof value === 'string' || typeof value === 'number') return value;
347
+ if (value instanceof Component || (typeof value === 'object'
348
+ && typeof /** @type {{nodeType?:unknown}} */ (value).nodeType === 'number')) {
349
+ return /** @type {Node|Component} */ (value);
350
+ }
351
+ return String(value);
352
+ }
353
+
354
+ /** @param {unknown} value @returns {string|undefined} */
355
+ function normalizeLocale(value) {
356
+ if (typeof value === 'string' && value.trim()) return value;
357
+ return undefined;
358
+ }
359
+
360
+ /** @param {FileItemState} state @returns {Readonly<FileItemState>} */
361
+ function fileSnapshot(state) {
362
+ return Object.freeze({
363
+ ...state,
364
+ metadata: state.metadata.map((entry) => ({ ...entry })),
365
+ link: state.link ? { ...state.link } : null,
366
+ actions: state.actions.map((entry) => isElement(entry) ? entry : { ...entry })
367
+ });
368
+ }
369
+
370
+ /** Fired before a native file link continues. @event FileItem#activate @type {CustomEvent<{id: unknown, file: Readonly<FileItemState>, href: string, event: MouseEvent}>} */
371
+ /** Fired before a descriptor action callback runs. @event FileItem#action @type {CustomEvent<{id: string, fileId: unknown, action: object, file: Readonly<FileItemState>, event: MouseEvent}>} */
@@ -0,0 +1,51 @@
1
+ .zx-file-list {
2
+ display: grid;
3
+ gap: var(--zx-space-3);
4
+ min-inline-size: 0;
5
+ color: var(--zx-color-text);
6
+ }
7
+
8
+ .zx-file-list__items {
9
+ display: grid;
10
+ min-inline-size: 0;
11
+ margin: 0;
12
+ padding: 0;
13
+ list-style: none;
14
+ }
15
+
16
+ .zx-file-list__item {
17
+ min-inline-size: 0;
18
+ padding-block: var(--zx-space-3);
19
+ }
20
+
21
+ .zx-file-list__item:not(:last-child) {
22
+ border-block-end: 1px solid var(--zx-color-border);
23
+ }
24
+
25
+ .zx-file-list__empty,
26
+ .zx-file-list__loading {
27
+ border: 1px dashed var(--zx-color-border);
28
+ border-radius: var(--zx-radius-lg);
29
+ background: var(--zx-color-bg-muted);
30
+ padding: var(--zx-space-5);
31
+ color: var(--zx-color-text-muted);
32
+ text-align: center;
33
+ }
34
+
35
+ .zx-file-list__loading {
36
+ display: grid;
37
+ gap: var(--zx-space-4);
38
+ }
39
+
40
+ .zx-file-list__skeletons {
41
+ display: grid;
42
+ gap: var(--zx-space-3);
43
+ text-align: start;
44
+ }
45
+
46
+ .zx-file-list__skeleton {
47
+ display: grid;
48
+ grid-template-columns: auto minmax(0, 1fr);
49
+ gap: var(--zx-space-3);
50
+ align-items: center;
51
+ }