@zeyos/zx 4.3.4 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/CHANGELOG.md +438 -0
  2. package/README.md +49 -10
  3. package/dist/types/components/activity-item/activity-item.d.ts +351 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +217 -0
  5. package/dist/types/components/app-sidebar/app-sidebar.d.ts +33 -3
  6. package/dist/types/components/avatar/avatar.d.ts +46 -7
  7. package/dist/types/components/badge/badge.d.ts +21 -1
  8. package/dist/types/components/button/button.d.ts +23 -0
  9. package/dist/types/components/calendar/calendar.d.ts +19 -0
  10. package/dist/types/components/card/card.d.ts +7 -0
  11. package/dist/types/components/card-view/card-view.d.ts +11 -3
  12. package/dist/types/components/card-view/record-card.d.ts +7 -1
  13. package/dist/types/components/chart/chart.d.ts +10 -0
  14. package/dist/types/components/checklist/checklist.d.ts +9 -0
  15. package/dist/types/components/code-editor/code-editor.d.ts +149 -0
  16. package/dist/types/components/code-editor/field-adapter.d.ts +5 -0
  17. package/dist/types/components/context-menu/context-menu.d.ts +18 -2
  18. package/dist/types/components/date-picker/date-picker.d.ts +8 -0
  19. package/dist/types/components/date-picker/time-picker.d.ts +7 -0
  20. package/dist/types/components/datebox/date-range-box.d.ts +5 -1
  21. package/dist/types/components/datebox/datebox.d.ts +7 -2
  22. package/dist/types/components/dialog/dialog.d.ts +75 -6
  23. package/dist/types/components/dropdown/dropdown.d.ts +2 -2
  24. package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
  25. package/dist/types/components/field/field.d.ts +24 -2
  26. package/dist/types/components/fieldset/fieldset.d.ts +19 -1
  27. package/dist/types/components/file-item/file-item.d.ts +262 -0
  28. package/dist/types/components/file-list/file-list.d.ts +171 -0
  29. package/dist/types/components/filter/filter.d.ts +91 -36
  30. package/dist/types/components/filter-panel/filter-panel.d.ts +449 -0
  31. package/dist/types/components/form/form.d.ts +12 -1
  32. package/dist/types/components/kanban-view/kanban-card.d.ts +146 -0
  33. package/dist/types/components/kanban-view/kanban-view.d.ts +170 -4
  34. package/dist/types/components/launcher/launcher.d.ts +278 -26
  35. package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
  36. package/dist/types/components/menu-button/menu-button.d.ts +37 -42
  37. package/dist/types/components/message/message.d.ts +126 -17
  38. package/dist/types/components/modal/modal.d.ts +61 -2
  39. package/dist/types/components/navigation-bar/navigation-bar.d.ts +105 -21
  40. package/dist/types/components/panel/panel.d.ts +7 -0
  41. package/dist/types/components/search/search.d.ts +7 -0
  42. package/dist/types/components/select/color.d.ts +12 -0
  43. package/dist/types/components/select/glyphs.d.ts +124 -0
  44. package/dist/types/components/select/select.d.ts +15 -3
  45. package/dist/types/components/sort-control/sort-control.d.ts +105 -0
  46. package/dist/types/components/stat-tile/stat-tile.d.ts +288 -0
  47. package/dist/types/components/tabbox/tabbox.d.ts +16 -0
  48. package/dist/types/components/table/table.d.ts +190 -1
  49. package/dist/types/components/table-view/table-view.d.ts +11 -5
  50. package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
  51. package/dist/types/components/timebox/timebox.d.ts +7 -0
  52. package/dist/types/components/tree/tree.d.ts +13 -0
  53. package/dist/types/components/value-list/value-list.d.ts +8 -0
  54. package/dist/types/components/view/record-view.d.ts +96 -5
  55. package/dist/types/core/breakpoint.d.ts +1 -1
  56. package/dist/types/core/icons.d.ts +37 -22
  57. package/dist/types/core/motion.d.ts +59 -0
  58. package/dist/types/core/overlay-host.d.ts +15 -1
  59. package/dist/types/index.d.ts +16 -3
  60. package/dist/types/internal/app-rail.d.ts +10 -0
  61. package/dist/types/internal/menu-items.d.ts +134 -0
  62. package/dist/types/zeyos/connect.d.ts +6 -0
  63. package/dist/types/zeyos/form.d.ts +50 -9
  64. package/dist/types/zeyos/index.d.ts +1 -1
  65. package/dist/types/zeyos/query.d.ts +79 -0
  66. package/dist/types/zeyos/schema.d.ts +24 -1
  67. package/dist/types/zeyos/table.d.ts +100 -14
  68. package/dist/types/zeyos/view.d.ts +10 -0
  69. package/dist/zx-zeyos.esm.js +2206 -489
  70. package/dist/zx-zeyos.esm.min.js +56 -1
  71. package/dist/zx.css +3322 -724
  72. package/dist/zx.esm.js +12244 -5809
  73. package/dist/zx.esm.min.js +58 -3
  74. package/dist/zx.global.js +12246 -5811
  75. package/dist/zx.global.min.js +58 -3
  76. package/dist/zx.min.css +1 -1
  77. package/docs/api.json +1683 -146
  78. package/docs/llms.md +777 -110
  79. package/docs/llms.txt +14 -8
  80. package/package.json +1 -1
  81. package/src/components/account-menu/account-menu.css +35 -10
  82. package/src/components/activity-item/activity-item.css +137 -0
  83. package/src/components/activity-item/activity-item.js +602 -0
  84. package/src/components/activity-list/activity-list.css +99 -0
  85. package/src/components/activity-list/activity-list.js +409 -0
  86. package/src/components/app-icon/app-icon.css +1 -1
  87. package/src/components/app-rail/app-rail.css +191 -22
  88. package/src/components/app-sidebar/app-sidebar.css +349 -24
  89. package/src/components/app-sidebar/app-sidebar.js +86 -11
  90. package/src/components/aurora/aurora.css +5 -3
  91. package/src/components/avatar/avatar.css +32 -7
  92. package/src/components/avatar/avatar.js +72 -8
  93. package/src/components/badge/badge.css +54 -2
  94. package/src/components/badge/badge.js +29 -2
  95. package/src/components/button/button.css +74 -8
  96. package/src/components/button/button.js +44 -1
  97. package/src/components/calendar/calendar.css +46 -46
  98. package/src/components/calendar/calendar.js +33 -3
  99. package/src/components/card/card.css +2 -2
  100. package/src/components/card/card.js +12 -1
  101. package/src/components/card-view/card-view.css +10 -7
  102. package/src/components/card-view/card-view.js +25 -9
  103. package/src/components/card-view/record-card.js +7 -3
  104. package/src/components/chart/chart.css +4 -1
  105. package/src/components/chart/chart.js +12 -0
  106. package/src/components/check-button/check-button.css +10 -2
  107. package/src/components/checklist/checklist.css +9 -2
  108. package/src/components/checklist/checklist.js +11 -5
  109. package/src/components/checklist/field-adapter.js +3 -1
  110. package/src/components/code-editor/code-editor.css +68 -0
  111. package/src/components/code-editor/code-editor.js +306 -0
  112. package/src/components/code-editor/field-adapter.js +34 -0
  113. package/src/components/context-menu/context-menu.css +87 -5
  114. package/src/components/context-menu/context-menu.js +22 -30
  115. package/src/components/copy/copy-button.css +6 -0
  116. package/src/components/copy/copy-input.css +8 -3
  117. package/src/components/data-filter/data-filter.css +5 -4
  118. package/src/components/date-picker/date-picker.css +6 -6
  119. package/src/components/date-picker/date-picker.js +22 -8
  120. package/src/components/date-picker/date-range-picker.css +3 -3
  121. package/src/components/date-picker/month-picker.js +3 -3
  122. package/src/components/date-picker/time-picker.css +1 -1
  123. package/src/components/date-picker/time-picker.js +20 -8
  124. package/src/components/datebox/date-range-box.css +79 -2
  125. package/src/components/datebox/date-range-box.js +13 -2
  126. package/src/components/datebox/datebox.css +79 -2
  127. package/src/components/datebox/datebox.js +13 -2
  128. package/src/components/dialog/dialog.css +26 -0
  129. package/src/components/dialog/dialog.js +174 -70
  130. package/src/components/dropdown/dropdown.css +1 -1
  131. package/src/components/dropdown/dropdown.js +7 -5
  132. package/src/components/empty-state/empty-state.css +2 -2
  133. package/src/components/entity-ref/entity-ref.css +182 -0
  134. package/src/components/entity-ref/entity-ref.js +496 -0
  135. package/src/components/field/field.css +28 -9
  136. package/src/components/field/field.js +68 -26
  137. package/src/components/field-upload/field-upload.css +1 -1
  138. package/src/components/fieldset/fieldset.css +5 -3
  139. package/src/components/fieldset/fieldset.js +28 -5
  140. package/src/components/file-item/file-item.css +88 -0
  141. package/src/components/file-item/file-item.js +505 -0
  142. package/src/components/file-list/file-list.css +51 -0
  143. package/src/components/file-list/file-list.js +320 -0
  144. package/src/components/filter/filter.css +27 -1
  145. package/src/components/filter/filter.js +205 -47
  146. package/src/components/filter-panel/filter-panel.css +104 -0
  147. package/src/components/filter-panel/filter-panel.js +964 -0
  148. package/src/components/finder/finder.css +15 -13
  149. package/src/components/form/form.css +5 -4
  150. package/src/components/form/form.js +11 -2
  151. package/src/components/grid/grid.css +1 -1
  152. package/src/components/groupbox/groupbox.css +1 -1
  153. package/src/components/kanban-view/kanban-card.js +247 -0
  154. package/src/components/kanban-view/kanban-view.css +226 -8
  155. package/src/components/kanban-view/kanban-view.js +255 -12
  156. package/src/components/launcher/launcher.css +107 -25
  157. package/src/components/launcher/launcher.js +557 -63
  158. package/src/components/list-toolbar/list-toolbar.css +194 -0
  159. package/src/components/list-toolbar/list-toolbar.js +851 -0
  160. package/src/components/master-panel/master-panel.css +2 -0
  161. package/src/components/menu-button/menu-button.css +87 -5
  162. package/src/components/menu-button/menu-button.js +86 -46
  163. package/src/components/message/message.css +38 -5
  164. package/src/components/message/message.js +230 -17
  165. package/src/components/modal/modal.css +15 -0
  166. package/src/components/modal/modal.js +159 -3
  167. package/src/components/navigation-bar/navigation-bar.css +95 -11
  168. package/src/components/navigation-bar/navigation-bar.js +171 -4
  169. package/src/components/number-field/field-adapter.js +1 -0
  170. package/src/components/number-field/number-field.css +70 -0
  171. package/src/components/number-field/number-field.js +2 -0
  172. package/src/components/pagination/pagination.css +8 -6
  173. package/src/components/panel/panel.css +1 -1
  174. package/src/components/panel/panel.js +13 -2
  175. package/src/components/rating/rating.css +7 -0
  176. package/src/components/search/search.js +17 -5
  177. package/src/components/select/color.js +22 -0
  178. package/src/components/select/field-adapter.js +6 -1
  179. package/src/components/select/glyphs.js +165 -0
  180. package/src/components/select/select.css +138 -45
  181. package/src/components/select/select.js +86 -47
  182. package/src/components/sheet/sheet.css +11 -8
  183. package/src/components/slider/slider.css +16 -2
  184. package/src/components/slider/slider.js +1 -1
  185. package/src/components/sort-control/sort-control.css +20 -0
  186. package/src/components/sort-control/sort-control.js +140 -0
  187. package/src/components/stat-tile/stat-tile.css +208 -0
  188. package/src/components/stat-tile/stat-tile.js +601 -0
  189. package/src/components/tabbox/tabbox.css +32 -27
  190. package/src/components/tabbox/tabbox.js +17 -2
  191. package/src/components/table/table.css +155 -31
  192. package/src/components/table/table.js +386 -33
  193. package/src/components/table-view/table-view.css +8 -2
  194. package/src/components/table-view/table-view.js +21 -6
  195. package/src/components/tag-picker/tag-picker.css +86 -6
  196. package/src/components/tag-picker/tag-picker.js +25 -8
  197. package/src/components/timebox/timebox.css +22 -2
  198. package/src/components/timebox/timebox.js +18 -5
  199. package/src/components/toggle/toggle.css +12 -1
  200. package/src/components/toolbar/toolbar.js +2 -2
  201. package/src/components/tooltip/tooltip.css +4 -0
  202. package/src/components/tooltip/tooltip.js +10 -3
  203. package/src/components/tree/tree.css +25 -16
  204. package/src/components/tree/tree.js +31 -0
  205. package/src/components/value-list/value-list.css +3 -3
  206. package/src/components/value-list/value-list.js +22 -6
  207. package/src/components/view/record-view.css +5 -0
  208. package/src/components/view/record-view.js +100 -7
  209. package/src/core/breakpoint.js +1 -1
  210. package/src/core/icons.js +259 -57
  211. package/src/core/motion.js +147 -0
  212. package/src/core/overlay-host.js +21 -2
  213. package/src/index.js +30 -3
  214. package/src/internal/app-rail.js +18 -1
  215. package/src/internal/menu-items.js +142 -0
  216. package/src/zeyos/connect.js +6 -2
  217. package/src/zeyos/form.js +67 -27
  218. package/src/zeyos/index.js +1 -1
  219. package/src/zeyos/query.js +153 -0
  220. package/src/zeyos/schema.js +144 -27
  221. package/src/zeyos/table.js +68 -5
  222. package/src/zeyos/view.js +6 -0
  223. package/styles/base.css +56 -0
  224. package/styles/icons.css +11 -1
  225. package/styles/print.css +192 -0
  226. package/styles/tokens/dark.css +113 -37
  227. package/styles/tokens/global.css +121 -27
  228. package/styles/tokens/semantic.css +100 -31
  229. package/styles/tokens/themes.css +7 -6
  230. package/styles/zx.css +11 -1
  231. package/styles/tokens/density.css +0 -9
@@ -0,0 +1,505 @@
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 { getIconConfig, iconNames } from '../../core/icons.js';
6
+ import { isElement } from '../../core/util.js';
7
+ import { badge } from '../badge/badge.js';
8
+ import {
9
+ EntityRef, normalizeEntityRefActions, normalizeEntityRefLink, normalizeEntityRefMetadata
10
+ } from '../entity-ref/entity-ref.js';
11
+
12
+ /** @typedef {import('../entity-ref/entity-ref.js').EntityRefContent} EntityRefContent */
13
+ /** @typedef {import('../entity-ref/entity-ref.js').EntityRefIcon} EntityRefIcon */
14
+ /** @typedef {import('../entity-ref/entity-ref.js').EntityRefMetadata} EntityRefMetadata */
15
+ /** @typedef {import('../entity-ref/entity-ref.js').EntityRefLinkValue} EntityRefLinkValue */
16
+ /** @typedef {import('../entity-ref/entity-ref.js').EntityRefActionEntry} EntityRefActionEntry */
17
+ /** @typedef {'ready'|'temporary'|'waiting'|'uploading'|'processing'|'success'|'error'} FileStatus */
18
+
19
+ /**
20
+ * @typedef {Object} FileItemOptions
21
+ * @property {unknown} [id=null] Application-owned file identifier included in events.
22
+ * @property {string} [name=''] File name.
23
+ * @property {number|string|null} [size=null] Byte count; invalid and negative values are omitted.
24
+ * @property {string} [mime=''] MIME type. Shown as a readable type ("PDF", "Image"), never as
25
+ * the raw string, and used to pick the icon.
26
+ * @property {string|null} [typeLabel=null] Type shown beside the size; null derives it from the
27
+ * MIME type (or the name's extension when the MIME type is unfamiliar), an empty string hides it.
28
+ * @property {EntityRefContent} [subtitle=null] Optional supporting text before generated metadata.
29
+ * @property {EntityRefIcon} [icon='file'] File icon name, node, component, or lazy renderer. The
30
+ * generic `'file'` glyph gives way to one for the MIME type when the active icon set has it.
31
+ * @property {EntityRefMetadata[]} [metadata=[]] Additional labelled metadata.
32
+ * @property {EntityRefLinkValue} [link=null] Optional safe native file/download link.
33
+ * @property {EntityRefActionEntry[]} [actions=[]] File actions; application policy stays external.
34
+ * @property {FileStatus} [status='ready'] Durable or transient presentation state.
35
+ * @property {string|null} [statusLabel=null] Overrides the generated state label; an empty string hides it.
36
+ * @property {number|null} [progress=null] Completed percentage, clamped to 0–100.
37
+ * @property {boolean} [indeterminate=false] Whether active progress has no known amount.
38
+ * @property {'sm'|'md'|'lg'} [identitySize='md'] Size of the file's icon and name.
39
+ * @property {boolean} [wrap=false] Whether long identity text may wrap.
40
+ * @property {string} [actionsLabel='File actions'] Accessible name for secondary actions.
41
+ * @property {string} [locale] Locale passed to `formatFileSize`.
42
+ * @property {(event: CustomEvent<{id: unknown, file: Readonly<FileItemState>, href: string, event: MouseEvent}>) => void} [onactivate]
43
+ * Preventable native-link listener.
44
+ * @property {(event: CustomEvent<{id: string, fileId: unknown, action: object, file: Readonly<FileItemState>, event: MouseEvent}>) => void} [onaction]
45
+ * Preventable file-action listener.
46
+ */
47
+
48
+ /**
49
+ * @typedef {Object} FileItemState
50
+ * @property {unknown} id File identifier.
51
+ * @property {string} name File name.
52
+ * @property {number|null} size Byte count.
53
+ * @property {string} mime MIME type.
54
+ * @property {string|null} typeLabel Explicit type label, or null for the one derived from `mime`.
55
+ * @property {EntityRefContent} subtitle Supporting content.
56
+ * @property {EntityRefIcon} icon File visual.
57
+ * @property {EntityRefMetadata[]} metadata Additional metadata.
58
+ * @property {import('../entity-ref/entity-ref.js').EntityRefLink|null} link Safe native link.
59
+ * @property {EntityRefActionEntry[]} actions Normalized actions.
60
+ * @property {FileStatus} status Presentation state.
61
+ * @property {string|null} statusLabel Explicit state label, or null for the generated label.
62
+ * @property {number|null} progress Completed percentage.
63
+ * @property {boolean} indeterminate Unknown-progress state.
64
+ * @property {'sm'|'md'|'lg'} identitySize Size of the file's icon and name.
65
+ * @property {boolean} wrap Wrapping state.
66
+ * @property {string} actionsLabel Action-group label.
67
+ * @property {string|undefined} locale File-size locale.
68
+ */
69
+
70
+ const FILE_STATUSES = new Set([
71
+ 'ready', 'temporary', 'waiting', 'uploading', 'processing', 'success', 'error'
72
+ ]);
73
+
74
+ const STATUS_LABELS = Object.freeze({
75
+ ready: '',
76
+ temporary: 'Temporary',
77
+ waiting: 'Waiting',
78
+ uploading: 'Uploading',
79
+ processing: 'Processing',
80
+ success: 'Complete',
81
+ error: 'Failed'
82
+ });
83
+
84
+ /**
85
+ * Durable or transient file presentation. The host owns upload/download transport, authorization,
86
+ * preview behavior, retry policy, and persistence.
87
+ * @fires FileItem#activate
88
+ * @fires FileItem#action
89
+ * @extends {Component<FileItemOptions>}
90
+ */
91
+ export class FileItem extends Component {
92
+ static cssName = 'file-item';
93
+
94
+ /** @type {Readonly<FileItemOptions>} */
95
+ static defaults = {
96
+ id: null,
97
+ name: '',
98
+ size: null,
99
+ mime: '',
100
+ typeLabel: null,
101
+ subtitle: null,
102
+ icon: 'file',
103
+ metadata: [],
104
+ link: null,
105
+ actions: [],
106
+ status: 'ready',
107
+ statusLabel: null,
108
+ progress: null,
109
+ indeterminate: false,
110
+ identitySize: 'md',
111
+ wrap: false,
112
+ actionsLabel: 'File actions'
113
+ };
114
+
115
+ /**
116
+ * Creates or enhances a file row.
117
+ * @param {Element|string|null} [target=null] Existing host, selector, or null.
118
+ * @param {FileItemOptions} [options={}] File presentation.
119
+ */
120
+ constructor(target = null, options = {}) {
121
+ super(target, options);
122
+ }
123
+
124
+ /** @returns {HTMLElement} */
125
+ render() {
126
+ this._createdRoot = this.el === null;
127
+ const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
128
+ this.el = root;
129
+ this._snapshot = this._createdRoot ? null : snapshotTarget(root);
130
+ this._destroyed = false;
131
+ this._state = normalizeFileItem(this.options);
132
+
133
+ const entityHost = h('div', { ref: 'entity', class: 'zx-file-item__entity' });
134
+ root.replaceChildren(
135
+ entityHost,
136
+ h('div', { ref: 'status', class: 'zx-file-item__status', role: 'status' }),
137
+ h('div', { ref: 'progress', class: 'zx-file-item__progress' },
138
+ h('div', { ref: 'progressHead', class: 'zx-file-item__progress-head' }),
139
+ h('div', {
140
+ ref: 'progressTrack',
141
+ class: 'zx-file-item__progress-track',
142
+ role: 'progressbar',
143
+ ariaValuemin: '0',
144
+ ariaValuemax: '100'
145
+ }, h('div', { ref: 'progressFill', class: 'zx-file-item__progress-fill' })))
146
+ );
147
+
148
+ this._entity = new EntityRef(entityHost, entityOptions(this._state));
149
+ this.listen(this._entity.el, 'zx-activate', (event) => this._forwardActivate(/** @type {CustomEvent} */ (event)));
150
+ this.listen(this._entity.el, 'zx-action', (event) => this._forwardAction(/** @type {CustomEvent} */ (event)));
151
+ this._sync();
152
+ return root;
153
+ }
154
+
155
+ /**
156
+ * Updates any subset of the file without replacing the row root.
157
+ * @param {Partial<FileItemOptions>} values Next values.
158
+ * @returns {this}
159
+ */
160
+ set(values = {}) {
161
+ if (!values || typeof values !== 'object' || Array.isArray(values)) return this;
162
+ this._state = normalizeFileItem({ ...this._state, ...values });
163
+ this._sync();
164
+ return this;
165
+ }
166
+
167
+ /**
168
+ * Updates the file lifecycle state and, optionally, its visible label.
169
+ * @param {FileStatus} status State.
170
+ * @param {string|null} [statusLabel] Explicit label.
171
+ * @returns {this}
172
+ */
173
+ setStatus(status, statusLabel) {
174
+ return this.set({ status, ...(statusLabel === undefined ? {} : { statusLabel }) });
175
+ }
176
+
177
+ /**
178
+ * Updates the progress meter.
179
+ * @param {number|null} progress Completed percentage, or null.
180
+ * @param {{indeterminate?: boolean}} [options={}] Unknown-progress override.
181
+ * @returns {this}
182
+ */
183
+ setProgress(progress, { indeterminate = false } = {}) {
184
+ return this.set({ progress, indeterminate });
185
+ }
186
+
187
+ /** Returns a defensive file snapshot. @returns {Readonly<FileItemState>} */
188
+ getFile() {
189
+ return fileSnapshot(this._state);
190
+ }
191
+
192
+ /** Focuses the native destination or first enabled action. @returns {this} */
193
+ focus() {
194
+ this._entity.focus();
195
+ return this;
196
+ }
197
+
198
+ /** Restores an enhanced target exactly, or removes an owned row. @returns {void} */
199
+ destroy() {
200
+ if (this._destroyed) return;
201
+ this._destroyed = true;
202
+ const root = this.el;
203
+ this._entity?.destroy();
204
+ super.destroy();
205
+ if (!this._createdRoot && root) restoreTarget(root, this._snapshot);
206
+ }
207
+
208
+ /** @returns {void} */
209
+ _sync() {
210
+ const state = this._state;
211
+ const root = /** @type {HTMLElement} */ (this.el);
212
+ const status = /** @type {HTMLElement} */ (this.refs.status);
213
+ const progress = /** @type {HTMLElement} */ (this.refs.progress);
214
+ const track = /** @type {HTMLElement} */ (this.refs.progressTrack);
215
+ const fill = /** @type {HTMLElement} */ (this.refs.progressFill);
216
+ root.dataset.status = state.status;
217
+ root.dataset.identitySize = state.identitySize;
218
+ this._entity.set(entityOptions(state));
219
+
220
+ const label = fileStatusLabel(state);
221
+ status.replaceChildren(...(label ? [badge({
222
+ label,
223
+ kind: statusKind(state.status),
224
+ size: 'sm',
225
+ dot: state.status !== 'ready'
226
+ })] : []));
227
+ status.hidden = label === '';
228
+
229
+ const visible = state.indeterminate || state.progress !== null;
230
+ progress.hidden = !visible;
231
+ progress.toggleAttribute('data-indeterminate', state.indeterminate);
232
+ if (!visible) return;
233
+
234
+ const progressLabel = `${label || 'Progress'}${state.indeterminate || state.progress === null
235
+ ? '' : ` — ${Math.round(state.progress)}%`}`;
236
+ this.refs.progressHead.textContent = progressLabel;
237
+ track.setAttribute('aria-label', `${state.name || 'File'}: ${label || 'Progress'}`);
238
+ if (state.indeterminate) {
239
+ track.removeAttribute('aria-valuenow');
240
+ track.removeAttribute('aria-valuetext');
241
+ fill.style.removeProperty('--zx-file-progress');
242
+ } else {
243
+ const value = state.progress ?? 0;
244
+ track.setAttribute('aria-valuenow', String(value));
245
+ track.setAttribute('aria-valuetext', `${Math.round(value)}%`);
246
+ fill.style.setProperty('--zx-file-progress', `${value}%`);
247
+ }
248
+ }
249
+
250
+ /** @param {CustomEvent} event @returns {void} */
251
+ _forwardActivate(event) {
252
+ const detail = /** @type {{href?:string,event?:MouseEvent}} */ (event.detail ?? {});
253
+ const forwarded = this.emit('activate', {
254
+ id: this._state.id,
255
+ file: this.getFile(),
256
+ href: String(detail.href ?? ''),
257
+ event: detail.event
258
+ }, { honorDomCancellation: true });
259
+ if (forwarded.defaultPrevented) event.preventDefault();
260
+ }
261
+
262
+ /** @param {CustomEvent} event @returns {void} */
263
+ _forwardAction(event) {
264
+ const detail = /** @type {{id?:string,action?:object,event?:MouseEvent}} */ (event.detail ?? {});
265
+ const forwarded = this.emit('action', {
266
+ id: String(detail.id ?? ''),
267
+ fileId: this._state.id,
268
+ action: detail.action ?? {},
269
+ file: this.getFile(),
270
+ event: detail.event
271
+ }, { honorDomCancellation: true });
272
+ if (forwarded.defaultPrevented) event.preventDefault();
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Normalizes a file presentation without touching the DOM.
278
+ * @param {Partial<FileItemOptions>|null|undefined} values Candidate values.
279
+ * @returns {FileItemState}
280
+ */
281
+ export function normalizeFileItem(values = {}) {
282
+ const source = values && typeof values === 'object' && !Array.isArray(values) ? values : {};
283
+ const rawSize = source.size == null || source.size === '' ? null : Number(source.size);
284
+ const rawProgress = source.progress == null ? null : Number(source.progress);
285
+ const status = FILE_STATUSES.has(String(source.status))
286
+ ? /** @type {FileStatus} */ (String(source.status)) : 'ready';
287
+ return {
288
+ id: source.id ?? null,
289
+ name: String(source.name ?? ''),
290
+ size: rawSize !== null && Number.isFinite(rawSize) && rawSize >= 0 ? rawSize : null,
291
+ mime: String(source.mime ?? ''),
292
+ typeLabel: source.typeLabel == null ? null : String(source.typeLabel),
293
+ subtitle: normalizeFileContent(source.subtitle),
294
+ icon: source.icon === undefined ? 'file' : source.icon,
295
+ metadata: normalizeEntityRefMetadata(source.metadata),
296
+ link: normalizeEntityRefLink(source.link),
297
+ actions: normalizeEntityRefActions(source.actions),
298
+ status,
299
+ statusLabel: source.statusLabel == null ? null : String(source.statusLabel),
300
+ progress: rawProgress === null || !Number.isFinite(rawProgress)
301
+ ? null : Math.min(100, Math.max(0, rawProgress)),
302
+ indeterminate: Boolean(source.indeterminate),
303
+ identitySize: source.identitySize === 'sm' || source.identitySize === 'lg' ? source.identitySize : 'md',
304
+ wrap: Boolean(source.wrap),
305
+ actionsLabel: String(source.actionsLabel || 'File actions'),
306
+ locale: normalizeLocale(source.locale)
307
+ };
308
+ }
309
+
310
+ /** @param {FileItemState} state @returns {import('../entity-ref/entity-ref.js').EntityRefOptions} */
311
+ function entityOptions(state) {
312
+ const metadata = [];
313
+ const type = state.typeLabel ?? fileTypeLabel(state.mime, state.name);
314
+ if (type) metadata.push({ label: 'Type', value: type, showLabel: false });
315
+ if (state.size !== null) {
316
+ metadata.push({
317
+ label: 'Size',
318
+ value: formatFileSize(state.size, { locale: state.locale }),
319
+ showLabel: false
320
+ });
321
+ }
322
+ metadata.push(...state.metadata);
323
+ return {
324
+ id: state.id,
325
+ title: state.name,
326
+ subtitle: state.subtitle,
327
+ icon: state.icon === 'file' ? fileTypeIcon(state.mime) : state.icon,
328
+ metadata,
329
+ link: state.link,
330
+ actions: state.actions,
331
+ size: state.identitySize,
332
+ wrap: state.wrap,
333
+ actionsLabel: state.actionsLabel
334
+ };
335
+ }
336
+
337
+ /**
338
+ * What a MIME type is called and drawn as. Labels are what a reader recognises — a format's own
339
+ * acronym where it has a well-known one, the family otherwise — and icons are Font Awesome names
340
+ * in order of preference, used only when the active icon set can draw them.
341
+ * @type {Readonly<Record<string, {label: string, icons: string[]}>>}
342
+ */
343
+ const FILE_KINDS = Object.freeze({
344
+ pdf: { label: 'PDF', icons: ['file-pdf'] },
345
+ csv: { label: 'CSV', icons: ['file-csv', 'file-excel'] },
346
+ tsv: { label: 'TSV', icons: ['file-csv', 'file-excel'] },
347
+ spreadsheet: { label: 'Spreadsheet', icons: ['file-excel'] },
348
+ document: { label: 'Document', icons: ['file-word', 'file-lines'] },
349
+ presentation: { label: 'Presentation', icons: ['file-powerpoint'] },
350
+ text: { label: 'Text', icons: ['file-lines'] },
351
+ markdown: { label: 'Markdown', icons: ['file-lines'] },
352
+ html: { label: 'HTML', icons: ['file-code', 'code'] },
353
+ css: { label: 'CSS', icons: ['file-code', 'code'] },
354
+ javascript: { label: 'JavaScript', icons: ['file-code', 'code'] },
355
+ json: { label: 'JSON', icons: ['file-code', 'code'] },
356
+ xml: { label: 'XML', icons: ['file-code', 'code'] },
357
+ zip: { label: 'ZIP', icons: ['file-zipper'] },
358
+ gzip: { label: 'GZIP', icons: ['file-zipper'] },
359
+ tar: { label: 'TAR', icons: ['file-zipper'] },
360
+ rar: { label: 'RAR', icons: ['file-zipper'] },
361
+ sevenZip: { label: '7Z', icons: ['file-zipper'] },
362
+ email: { label: 'Email', icons: ['envelope'] },
363
+ calendar: { label: 'Calendar', icons: ['calendar'] },
364
+ contact: { label: 'Contact', icons: ['address-card'] },
365
+ image: { label: 'Image', icons: ['file-image', 'image'] },
366
+ video: { label: 'Video', icons: ['file-video', 'video'] },
367
+ audio: { label: 'Audio', icons: ['file-audio'] },
368
+ font: { label: 'Font', icons: ['font'] }
369
+ });
370
+
371
+ /** Exact MIME types, lower case and without parameters, mapped to their kind. */
372
+ const MIME_KINDS = new Map(Object.entries({
373
+ 'application/pdf': 'pdf',
374
+ 'text/csv': 'csv',
375
+ 'application/csv': 'csv',
376
+ 'text/comma-separated-values': 'csv',
377
+ 'text/tab-separated-values': 'tsv',
378
+ 'application/vnd.ms-excel': 'spreadsheet',
379
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'spreadsheet',
380
+ 'application/vnd.oasis.opendocument.spreadsheet': 'spreadsheet',
381
+ 'application/msword': 'document',
382
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'document',
383
+ 'application/vnd.oasis.opendocument.text': 'document',
384
+ 'application/rtf': 'document',
385
+ 'text/rtf': 'document',
386
+ 'application/vnd.ms-powerpoint': 'presentation',
387
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'presentation',
388
+ 'application/vnd.oasis.opendocument.presentation': 'presentation',
389
+ 'text/plain': 'text',
390
+ 'text/markdown': 'markdown',
391
+ 'text/x-markdown': 'markdown',
392
+ 'text/html': 'html',
393
+ 'application/xhtml+xml': 'html',
394
+ 'text/css': 'css',
395
+ 'text/javascript': 'javascript',
396
+ 'application/javascript': 'javascript',
397
+ 'application/json': 'json',
398
+ 'application/ld+json': 'json',
399
+ 'application/xml': 'xml',
400
+ 'text/xml': 'xml',
401
+ 'application/zip': 'zip',
402
+ 'application/x-zip-compressed': 'zip',
403
+ 'application/gzip': 'gzip',
404
+ 'application/x-gzip': 'gzip',
405
+ 'application/x-tar': 'tar',
406
+ 'application/vnd.rar': 'rar',
407
+ 'application/x-rar-compressed': 'rar',
408
+ 'application/x-7z-compressed': 'sevenZip',
409
+ 'message/rfc822': 'email',
410
+ 'text/calendar': 'calendar',
411
+ 'text/vcard': 'contact',
412
+ 'text/x-vcard': 'contact'
413
+ }));
414
+
415
+ /** Top-level MIME types that name a whole family. */
416
+ const MIME_FAMILIES = new Map([
417
+ ['image', 'image'], ['video', 'video'], ['audio', 'audio'], ['font', 'font'], ['text', 'text']
418
+ ]);
419
+
420
+ /**
421
+ * @param {string} mime MIME type, possibly with parameters.
422
+ * @returns {{label: string, icons: string[]}|null}
423
+ */
424
+ function fileKind(mime) {
425
+ const essence = String(mime ?? '').split(';')[0].trim().toLowerCase();
426
+ if (!essence) return null;
427
+ const kind = MIME_KINDS.get(essence) ?? MIME_FAMILIES.get(essence.split('/')[0]);
428
+ return kind ? FILE_KINDS[kind] : null;
429
+ }
430
+
431
+ /**
432
+ * A readable name for a file's type: "PDF", "CSV", "Image", "Spreadsheet". An unfamiliar MIME type
433
+ * falls back to the name's extension in capitals ("EXE"); without a MIME type, or with neither,
434
+ * the result is empty — a raw MIME string is never shown.
435
+ * @param {string} mime MIME type, possibly with parameters (`text/csv; charset=utf-8`).
436
+ * @param {string} [name=''] File name, consulted only when the MIME type is unfamiliar.
437
+ * @returns {string}
438
+ */
439
+ export function fileTypeLabel(mime, name = '') {
440
+ if (!String(mime ?? '').trim()) return '';
441
+ const kind = fileKind(mime);
442
+ if (kind) return kind.label;
443
+ const extension = /\.([a-z0-9]{1,5})$/i.exec(String(name ?? ''))?.[1];
444
+ return extension ? extension.toUpperCase() : '';
445
+ }
446
+
447
+ /**
448
+ * The icon name for a MIME type: a type-specific glyph when the active icon set can draw one,
449
+ * `'file'` otherwise. Font Awesome resolves every name it is given; the bundled inline set only
450
+ * those it carries or that `registerIcons()` added, so a missing glyph never shows a placeholder.
451
+ * @param {string} mime MIME type, possibly with parameters.
452
+ * @returns {string}
453
+ */
454
+ export function fileTypeIcon(mime) {
455
+ const candidates = fileKind(mime)?.icons ?? [];
456
+ if (!candidates.length) return 'file';
457
+ if (getIconConfig().provider === 'fontawesome') return candidates[0];
458
+ const available = new Set(iconNames());
459
+ return candidates.find((name) => available.has(name)) ?? 'file';
460
+ }
461
+
462
+ /** @param {FileItemState} state @returns {string} */
463
+ function fileStatusLabel(state) {
464
+ return state.statusLabel === null ? STATUS_LABELS[state.status] : state.statusLabel;
465
+ }
466
+
467
+ /** @param {FileStatus} status @returns {'neutral'|'accent'|'success'|'warning'|'danger'|'info'} */
468
+ function statusKind(status) {
469
+ if (status === 'error') return 'danger';
470
+ if (status === 'success') return 'success';
471
+ if (status === 'temporary' || status === 'waiting') return 'warning';
472
+ if (status === 'uploading') return 'info';
473
+ if (status === 'processing') return 'accent';
474
+ return 'neutral';
475
+ }
476
+
477
+ /** @param {unknown} value @returns {EntityRefContent} */
478
+ function normalizeFileContent(value) {
479
+ if (value == null) return null;
480
+ if (typeof value === 'string' || typeof value === 'number') return value;
481
+ if (value instanceof Component || (typeof value === 'object'
482
+ && typeof /** @type {{nodeType?:unknown}} */ (value).nodeType === 'number')) {
483
+ return /** @type {Node|Component} */ (value);
484
+ }
485
+ return String(value);
486
+ }
487
+
488
+ /** @param {unknown} value @returns {string|undefined} */
489
+ function normalizeLocale(value) {
490
+ if (typeof value === 'string' && value.trim()) return value;
491
+ return undefined;
492
+ }
493
+
494
+ /** @param {FileItemState} state @returns {Readonly<FileItemState>} */
495
+ function fileSnapshot(state) {
496
+ return Object.freeze({
497
+ ...state,
498
+ metadata: state.metadata.map((entry) => ({ ...entry })),
499
+ link: state.link ? { ...state.link } : null,
500
+ actions: state.actions.map((entry) => isElement(entry) ? entry : { ...entry })
501
+ });
502
+ }
503
+
504
+ /** Fired before a native file link continues. @event FileItem#activate @type {CustomEvent<{id: unknown, file: Readonly<FileItemState>, href: string, event: MouseEvent}>} */
505
+ /** 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
+ }