@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,602 @@
1
+ // @ts-check
2
+ import { Component } from '../../core/component.js';
3
+ import { h, restoreTarget, safeHref, snapshotTarget } from '../../core/dom.js';
4
+ import { icon } from '../../core/icons.js';
5
+ import { printf } from '../../core/i18n.js';
6
+ import { isElement, uid } from '../../core/util.js';
7
+ import { Avatar } from '../avatar/avatar.js';
8
+ import { badge } from '../badge/badge.js';
9
+ import { button } from '../button/button.js';
10
+
11
+ /** @typedef {string|number|Node|Component|null} ActivityContent */
12
+ /** @typedef {import('../avatar/avatar.js').AvatarOptions} AvatarOptions */
13
+ /** @typedef {AvatarOptions|Element|null} ActivityAvatar */
14
+
15
+ /**
16
+ * @typedef {Object} ActivityAction
17
+ * @property {string|number} id Stable action identifier.
18
+ * @property {string} [label=''] Visible label.
19
+ * @property {string|null} [icon=null] Icon name.
20
+ * @property {string|null} [href=null] Optional safe native-link destination.
21
+ * @property {string} [target] Native browsing context.
22
+ * @property {string} [rel] Native link relationship.
23
+ * @property {string} [title] Native title and icon-only accessible label.
24
+ * @property {boolean} [disabled=false] Whether the action is unavailable.
25
+ * @property {'default'|'primary'|'danger'|'ghost'} [kind='ghost'] Visual intent.
26
+ * @property {(event:MouseEvent)=>void} [onselect] Host-owned activation callback.
27
+ */
28
+
29
+ /** @typedef {Element|ActivityAction} ActivityActionSlot */
30
+
31
+ /**
32
+ * @typedef {Object} NormalizedActivityAction
33
+ * @property {string} id Stable action identifier.
34
+ * @property {string} label Visible label.
35
+ * @property {string|null} icon Icon name.
36
+ * @property {string|null} href Safe native-link destination.
37
+ * @property {string|undefined} target Native browsing context.
38
+ * @property {string|undefined} rel Native link relationship.
39
+ * @property {string|undefined} title Native title.
40
+ * @property {boolean} disabled Whether the action is unavailable.
41
+ * @property {'default'|'primary'|'danger'|'ghost'} kind Visual intent.
42
+ * @property {((event:MouseEvent)=>void)|undefined} onselect Host callback.
43
+ */
44
+
45
+ /**
46
+ * @typedef {Object} ActivityTime
47
+ * @property {string|null} dateTime Machine-readable ISO timestamp when valid.
48
+ * @property {string} label Human-readable timestamp.
49
+ */
50
+
51
+ /** @typedef {'default'|'pending'|'failed'} ActivityState Delivery state of one entry. */
52
+
53
+ /**
54
+ * @typedef {Object} ActivityStateDescriptor
55
+ * @property {ActivityState} state Resolved state.
56
+ * @property {boolean} busy Whether the entry is still waiting on its request.
57
+ * @property {string|null} key Message key for the built-in word, or null when no badge is shown.
58
+ * @property {string|null} fallback Built-in English word, or null when no badge is shown.
59
+ * @property {'neutral'|'danger'} kind Badge intent.
60
+ * @property {string|null} label Host-supplied word replacing the built-in one.
61
+ */
62
+
63
+ /**
64
+ * @typedef {Object} ActivityItemOptions
65
+ * @property {unknown} [id=null] Host-owned activity identifier.
66
+ * @property {ActivityContent} [actor=''] Actor identity text or presentation slot.
67
+ * @property {ActivityAvatar} [avatar=null] Avatar descriptor or host-owned avatar element.
68
+ * @property {ActivityContent} [title=''] Activity summary.
69
+ * @property {ActivityContent} [content=null] Optional body slot.
70
+ * @property {Date|string|number|null} [timestamp=null] Date, parseable date string, or epoch milliseconds.
71
+ * @property {string|null} [timeLabel=null] Explicit human-readable timestamp.
72
+ * @property {ActivityContent[]} [metadata=[]] Compact metadata slots.
73
+ * @property {ActivityContent[]} [attachments=[]] Attachment presentation slots, commonly FileItem elements.
74
+ * @property {ActivityActionSlot[]} [actions=[]] Native action descriptors or host-owned controls.
75
+ * @property {string} [kind='default'] Product-neutral activity kind exposed as `data-kind`.
76
+ * @property {ActivityState} [state='default'] Delivery state exposed as `data-state`.
77
+ * @property {string|null} [stateLabel=null] Word shown in the state badge instead of the built-in one.
78
+ * @property {1|2|3|4|5|6} [headingLevel=3] Semantic heading level for the activity summary.
79
+ * @property {(event:CustomEvent<{id:string,action:NormalizedActivityAction,event:MouseEvent}>)=>void} [onaction]
80
+ */
81
+
82
+ /**
83
+ * One chronological activity entry with safe content slots, timestamp semantics, and native
84
+ * actions. Posting, reactions, routing, permissions, persistence, and content sanitization remain
85
+ * host responsibilities; server HTML is never interpreted by this component.
86
+ *
87
+ * @fires ActivityItem#action
88
+ * @extends {Component<ActivityItemOptions>}
89
+ */
90
+ export class ActivityItem extends Component {
91
+ static cssName = 'activity-item';
92
+
93
+ /** @type {Readonly<ActivityItemOptions>} */
94
+ static defaults = {
95
+ id: null,
96
+ actor: '',
97
+ avatar: null,
98
+ title: '',
99
+ content: null,
100
+ timestamp: null,
101
+ timeLabel: null,
102
+ metadata: [],
103
+ attachments: [],
104
+ actions: [],
105
+ kind: 'default',
106
+ state: 'default',
107
+ stateLabel: null,
108
+ headingLevel: 3
109
+ };
110
+
111
+ /**
112
+ * Creates or enhances an activity item.
113
+ * @param {Element|string|null} [target=null] Existing list item/container, selector, or null.
114
+ * @param {ActivityItemOptions} [options={}] Activity presentation options.
115
+ */
116
+ constructor(target = null, options = {}) {
117
+ super(target, options);
118
+ }
119
+
120
+ /** @returns {HTMLElement} */
121
+ render() {
122
+ const created = !this.el;
123
+ const root = /** @type {HTMLElement} */ (this.el ?? h('li'));
124
+ this.el = root;
125
+ this._createdRoot = created;
126
+ this._original = created ? null : snapshotTarget(root);
127
+ this._cleaned = false;
128
+ this._headingId = uid('zx-activity-item-heading');
129
+ this._avatarComponent = null;
130
+ this._actionsByKey = new Map();
131
+ this._activity = normalizeActivityItem(this.options);
132
+
133
+ try {
134
+ this.listen(root, 'click', (event) => this._onClick(/** @type {MouseEvent} */ (event)));
135
+ this._renderActivity();
136
+ return root;
137
+ } catch (error) {
138
+ this._avatarComponent?.destroy();
139
+ this._avatarComponent = null;
140
+ if (!created) restoreTarget(root, this._original);
141
+ throw error;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Replaces any subset of the activity presentation.
147
+ * @param {Partial<ActivityItemOptions>} values New values.
148
+ * @returns {this}
149
+ */
150
+ setActivity(values = {}) {
151
+ if (!values || typeof values !== 'object' || Array.isArray(values)) {
152
+ throw new TypeError('ActivityItem values must be an object');
153
+ }
154
+ this._activity = normalizeActivityItem({ ...this._activity, ...values });
155
+ this._renderActivity();
156
+ return this;
157
+ }
158
+
159
+ /** Returns a shallow, host-safe copy of the current descriptor. @returns {ActivityItemOptions} */
160
+ getActivity() {
161
+ return cloneActivityItem(this._activity);
162
+ }
163
+
164
+ /** Restores an enhanced target exactly, or removes an owned item. @returns {void} */
165
+ destroy() {
166
+ if (this._cleaned) return;
167
+ this._cleaned = true;
168
+ this._avatarComponent?.destroy();
169
+ this._avatarComponent = null;
170
+ super.destroy();
171
+ if (!this._createdRoot) restoreTarget(this.el, this._original);
172
+ }
173
+
174
+ /** @private @returns {void} */
175
+ _renderActivity() {
176
+ this._avatarComponent?.destroy();
177
+ this._avatarComponent = null;
178
+ this._actionsByKey.clear();
179
+ const activity = this._activity;
180
+ const root = /** @type {HTMLElement} */ (this.el);
181
+ const serializedId = serializeActivityId(activity.id);
182
+ if (serializedId === null) delete root.dataset.activityId;
183
+ else root.dataset.activityId = serializedId;
184
+ root.dataset.kind = normalizeActivityKind(activity.kind);
185
+
186
+ const state = resolveActivityState(activity.state, activity.stateLabel);
187
+ root.dataset.state = state.state;
188
+ /*
189
+ * Only a pending entry is busy. A failed one has finished — badly, but finished — and leaving
190
+ * `aria-busy` on it would tell a screen reader the region is still updating and suppress the
191
+ * very announcement that says the send did not happen.
192
+ */
193
+ if (state.busy) root.setAttribute('aria-busy', 'true');
194
+ else root.removeAttribute('aria-busy');
195
+
196
+ const actor = activityContent(activity.actor, 'Activity actor');
197
+ const title = activityContent(activity.title, 'Activity title');
198
+ const headingChildren = [];
199
+ if (actor !== null) {
200
+ headingChildren.push(h('span', { class: 'zx-activity-item__actor' }, actor));
201
+ }
202
+ if (title !== null) {
203
+ // The flex gap separates the two on screen; the space separates them in the heading's text,
204
+ // which is what a screen reader reads ("Jonas Weber set…", not "Jonas Weberset…").
205
+ if (headingChildren.length) headingChildren.push(' ');
206
+ headingChildren.push(h('span', { class: 'zx-activity-item__title' }, title));
207
+ }
208
+ const heading = headingChildren.length ? h(`h${normalizeHeadingLevel(activity.headingLevel)}`, {
209
+ class: 'zx-activity-item__heading',
210
+ id: this._headingId
211
+ }, headingChildren) : null;
212
+ const time = createActivityTime(activity.timestamp, activity.timeLabel);
213
+ const stateBadge = this._createStateBadge(state);
214
+ const header = heading || time || stateBadge
215
+ ? h('header', { class: 'zx-activity-item__header' }, heading, stateBadge, time) : null;
216
+
217
+ const bodyContent = activityContent(activity.content, 'Activity content');
218
+ const body = bodyContent === null ? null : h('div', {
219
+ class: 'zx-activity-item__body'
220
+ }, bodyContent);
221
+ const metadata = createContentList(activity.metadata, 'zx-activity-item__metadata',
222
+ 'Activity metadata', this._message('activityItem.metadata', 'Activity metadata'));
223
+ const attachments = createContentRegion(activity.attachments, 'zx-activity-item__attachments',
224
+ 'Attachments', this._message('activityItem.attachments', 'Attachments'));
225
+ const actions = this._createActions(activity.actions);
226
+ const main = h('div', { class: 'zx-activity-item__main' }, header, body, metadata, attachments, actions);
227
+ const avatar = this._createAvatar(activity.avatar, activity.actor);
228
+ const article = h('article', {
229
+ class: 'zx-activity-item__article',
230
+ ariaLabelledby: heading ? this._headingId : undefined,
231
+ ariaLabel: heading ? undefined : this._message('activityItem.label', 'Activity')
232
+ }, avatar ? h('div', { class: 'zx-activity-item__avatar' }, avatar) : null, main);
233
+ root.replaceChildren(article);
234
+ }
235
+
236
+ /**
237
+ * Builds the delivery badge, or nothing for an ordinary entry.
238
+ * @private
239
+ * @param {ActivityStateDescriptor} state Resolved state.
240
+ * @returns {HTMLElement|null}
241
+ */
242
+ _createStateBadge(state) {
243
+ if (state.key === null || state.fallback === null) return null;
244
+ const element = badge({
245
+ label: state.label ?? this._message(state.key, state.fallback),
246
+ kind: state.kind,
247
+ size: 'sm'
248
+ });
249
+ element.classList.add('zx-activity-item__state');
250
+ return element;
251
+ }
252
+
253
+ /** @private @param {ActivityAvatar} value @param {ActivityContent} actor @returns {Element|null} */
254
+ _createAvatar(value, actor) {
255
+ if (value === null) return null;
256
+ if (isElement(value)) return value;
257
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
258
+ throw new TypeError('ActivityItem avatar must be an Element, descriptor, or null');
259
+ }
260
+ const settings = {
261
+ ...value,
262
+ name: value.name || textAlternative(actor),
263
+ label: value.label ?? null,
264
+ size: value.size ?? 'md'
265
+ };
266
+ this._avatarComponent = new Avatar(null, settings);
267
+ return this._avatarComponent.toElement();
268
+ }
269
+
270
+ /** @private @param {ActivityActionSlot[]} value @returns {HTMLElement|null} */
271
+ _createActions(value) {
272
+ const actions = resolveActivityActions(value);
273
+ if (actions.length === 0) return null;
274
+ const controls = actions.map((action, index) => {
275
+ if (isElement(action)) return action;
276
+ const key = String(index);
277
+ this._actionsByKey.set(key, action);
278
+ return createActionControl(action, key);
279
+ });
280
+ const name = activityLabel(this._activity, this._message('activityItem.untitled', 'activity'));
281
+ return h('div', {
282
+ class: 'zx-activity-item__actions',
283
+ role: 'group',
284
+ ariaLabel: this._message('activityItem.actions', 'Actions for %1', name)
285
+ }, controls);
286
+ }
287
+
288
+ /**
289
+ * Resolves a message through the host translator, falling back to the built-in English text.
290
+ * @param {string} key Message key.
291
+ * @param {string} fallback Built-in text, with `%1`-style placeholders.
292
+ * @param {...unknown} args Interpolation values.
293
+ * @returns {string}
294
+ */
295
+ _message(key, fallback, ...args) {
296
+ const message = this.msg(key, ...args);
297
+ return message === key ? printf(fallback, args) : message;
298
+ }
299
+
300
+ /** @private @param {MouseEvent} event @returns {void} */
301
+ _onClick(event) {
302
+ const origin = event.target && typeof event.target === 'object'
303
+ ? /** @type {Element} */ (event.target) : null;
304
+ const control = /** @type {HTMLElement|null} */ (origin?.closest?.('[data-activity-action]') ?? null);
305
+ if (!control || !this.el.contains(control)) return;
306
+ const action = this._actionsByKey.get(control.dataset.activityAction ?? '');
307
+ if (!action || action.disabled) {
308
+ event.preventDefault();
309
+ return;
310
+ }
311
+ const emitted = this.emit('action', { id: action.id, action, event }, {
312
+ honorDomCancellation: true
313
+ });
314
+ if (emitted.defaultPrevented) {
315
+ event.preventDefault();
316
+ return;
317
+ }
318
+ action.onselect?.(event);
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Normalizes action descriptors without mutating the caller's array or objects. Invalid entries
324
+ * and duplicate descriptor IDs are discarded; host-owned Elements pass through unchanged.
325
+ * @param {unknown} value Candidate action array.
326
+ * @returns {Array<Element|NormalizedActivityAction>}
327
+ */
328
+ export function resolveActivityActions(value) {
329
+ if (!Array.isArray(value)) return [];
330
+ const resolved = [];
331
+ const seen = new Set();
332
+ for (const entry of value) {
333
+ if (isElement(entry)) {
334
+ resolved.push(entry);
335
+ continue;
336
+ }
337
+ const action = normalizeActivityAction(entry);
338
+ if (!action || seen.has(action.id)) continue;
339
+ seen.add(action.id);
340
+ resolved.push(action);
341
+ }
342
+ return resolved;
343
+ }
344
+
345
+ /**
346
+ * Normalizes one action descriptor and rejects unsafe native destinations.
347
+ * @param {unknown} value Candidate action.
348
+ * @returns {NormalizedActivityAction|null}
349
+ */
350
+ export function normalizeActivityAction(value) {
351
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
352
+ const source = /** @type {Record<string, unknown>} */ (value);
353
+ if (source.id === null || source.id === undefined) return null;
354
+ const id = String(source.id).trim();
355
+ if (!id) return null;
356
+ const label = source.label == null ? '' : String(source.label);
357
+ const iconName = source.icon == null ? null : String(source.icon).trim() || null;
358
+ const title = source.title == null ? undefined : String(source.title);
359
+ // An icon alone has no accessible name. `title` is the supported icon-only label fallback.
360
+ if (!label && !title) return null;
361
+ const hasHref = source.href !== null && source.href !== undefined;
362
+ const href = hasHref ? safeHref(source.href) : null;
363
+ if (hasHref && href === null) return null;
364
+ if (source.onselect !== undefined && typeof source.onselect !== 'function') return null;
365
+ const target = source.target == null ? undefined : String(source.target);
366
+ return {
367
+ id,
368
+ label,
369
+ icon: iconName,
370
+ href,
371
+ target,
372
+ rel: safeRel(source.rel, target),
373
+ title,
374
+ disabled: Boolean(source.disabled),
375
+ kind: normalizeActionKind(source.kind),
376
+ onselect: /** @type {((event:MouseEvent)=>void)|undefined} */ (source.onselect)
377
+ };
378
+ }
379
+
380
+ /** Built-in delivery states. An entry in neither is an ordinary, settled entry. */
381
+ const ACTIVITY_STATES = Object.freeze({
382
+ pending: Object.freeze({
383
+ busy: true, key: 'activityItem.pending', fallback: 'Sending…', kind: 'neutral'
384
+ }),
385
+ failed: Object.freeze({
386
+ busy: false, key: 'activityItem.failed', fallback: 'Failed', kind: 'danger'
387
+ })
388
+ });
389
+
390
+ /**
391
+ * Resolves the delivery state of one entry.
392
+ *
393
+ * `failed` is a first-class state and not the absence of `pending`: an optimistic entry that
394
+ * simply disappears when its request fails tells the customer their message was sent when nobody
395
+ * was told, so the component keeps the entry and says what happened to it.
396
+ * @param {unknown} state Requested state.
397
+ * @param {unknown} stateLabel Host-supplied word replacing the built-in one.
398
+ * @returns {ActivityStateDescriptor}
399
+ */
400
+ export function resolveActivityState(state, stateLabel) {
401
+ const name = String(state ?? 'default');
402
+ const preset = Object.prototype.hasOwnProperty.call(ACTIVITY_STATES, name)
403
+ ? ACTIVITY_STATES[/** @type {'pending'|'failed'} */ (name)] : null;
404
+ if (!preset) {
405
+ return { state: 'default', busy: false, key: null, fallback: null, kind: 'neutral', label: null };
406
+ }
407
+ return {
408
+ state: /** @type {ActivityState} */ (name),
409
+ busy: preset.busy,
410
+ key: preset.key,
411
+ fallback: preset.fallback,
412
+ kind: /** @type {'neutral'|'danger'} */ (preset.kind),
413
+ label: stateLabel == null || stateLabel === '' ? null : String(stateLabel)
414
+ };
415
+ }
416
+
417
+ /**
418
+ * Resolves a timestamp for native `<time>` markup without treating invalid input as a real date.
419
+ * @param {Date|string|number|null|undefined} timestamp Date-like value.
420
+ * @param {string|null|undefined} timeLabel Explicit display label.
421
+ * @returns {ActivityTime|null}
422
+ */
423
+ export function normalizeActivityTimestamp(timestamp, timeLabel) {
424
+ const explicit = timeLabel == null ? '' : String(timeLabel).trim();
425
+ if (timestamp === null || timestamp === undefined || timestamp === '') {
426
+ return explicit ? { dateTime: null, label: explicit } : null;
427
+ }
428
+ const date = timestamp instanceof Date ? new Date(timestamp.getTime()) : new Date(timestamp);
429
+ if (Number.isNaN(date.getTime())) {
430
+ const fallback = explicit || (typeof timestamp === 'string' ? timestamp.trim() : '');
431
+ return fallback ? { dateTime: null, label: fallback } : null;
432
+ }
433
+ return {
434
+ dateTime: date.toISOString(),
435
+ label: explicit || formatActivityDate(date)
436
+ };
437
+ }
438
+
439
+ /** @param {ActivityItemOptions} source @returns {ActivityItemOptions} */
440
+ function normalizeActivityItem(source) {
441
+ if (!source || typeof source !== 'object' || Array.isArray(source)) {
442
+ throw new TypeError('ActivityItem options must be an object');
443
+ }
444
+ for (const key of ['metadata', 'attachments', 'actions']) {
445
+ if (!Array.isArray(source[key])) throw new TypeError(`ActivityItem ${key} must be an array`);
446
+ }
447
+ return cloneActivityItem(source);
448
+ }
449
+
450
+ /** @param {ActivityItemOptions} source @returns {ActivityItemOptions} */
451
+ function cloneActivityItem(source) {
452
+ const avatar = source.avatar && !isElement(source.avatar)
453
+ ? { ...source.avatar } : source.avatar ?? null;
454
+ return {
455
+ ...source,
456
+ avatar,
457
+ metadata: [...(source.metadata ?? [])],
458
+ attachments: [...(source.attachments ?? [])],
459
+ actions: [...(source.actions ?? [])]
460
+ };
461
+ }
462
+
463
+ /** @param {NormalizedActivityAction} action @param {string} key @returns {HTMLElement} */
464
+ function createActionControl(action, key) {
465
+ if (action.href && !action.disabled) {
466
+ const control = h('a', {
467
+ class: 'zx-btn zx-activity-item__action',
468
+ href: action.href,
469
+ target: action.target,
470
+ rel: action.rel,
471
+ title: action.title,
472
+ dataset: { activityAction: key, kind: action.kind, size: 'sm' }
473
+ });
474
+ if (action.icon) control.append(icon(action.icon, { size: 16 }));
475
+ if (action.label) control.append(h('span', { class: 'zx-btn__label' }, action.label));
476
+ if (!action.label && action.title) control.setAttribute('aria-label', action.title);
477
+ return control;
478
+ }
479
+ const control = button({
480
+ label: action.label,
481
+ icon: action.icon,
482
+ kind: action.kind,
483
+ size: 'sm',
484
+ disabled: action.disabled,
485
+ title: action.title
486
+ });
487
+ control.classList.add('zx-activity-item__action');
488
+ control.dataset.activityAction = key;
489
+ return control;
490
+ }
491
+
492
+ /** @param {Date|string|number|null} timestamp @param {string|null} label @returns {HTMLElement|null} */
493
+ function createActivityTime(timestamp, label) {
494
+ const resolved = normalizeActivityTimestamp(timestamp, label);
495
+ if (!resolved) return null;
496
+ if (resolved.dateTime === null) {
497
+ return h('span', { class: 'zx-activity-item__time' }, resolved.label);
498
+ }
499
+ return h('time', {
500
+ class: 'zx-activity-item__time',
501
+ dateTime: resolved.dateTime,
502
+ title: resolved.label
503
+ }, resolved.label);
504
+ }
505
+
506
+ /**
507
+ * `label` names the slot in a developer-facing TypeError and stays English; `name` is the
508
+ * accessible name a screen reader reads and is translated.
509
+ * @param {ActivityContent[]} values @param {string} className @param {string} label
510
+ * @param {string} name @returns {HTMLElement|null}
511
+ */
512
+ function createContentList(values, className, label, name) {
513
+ if (values.length === 0) return null;
514
+ return h('ul', { class: className, ariaLabel: name }, values.map((value) =>
515
+ h('li', { class: `${className}-item` }, activityContent(value, label))));
516
+ }
517
+
518
+ /**
519
+ * @param {ActivityContent[]} values @param {string} className @param {string} label
520
+ * @param {string} name @returns {HTMLElement|null}
521
+ */
522
+ function createContentRegion(values, className, label, name) {
523
+ if (values.length === 0) return null;
524
+ return h('div', { class: className, role: 'group', ariaLabel: name }, values.map((value) =>
525
+ h('div', { class: `${className}-item` }, activityContent(value, label))));
526
+ }
527
+
528
+ /** @param {ActivityContent} value @param {string} label @returns {Node|string|null} */
529
+ function activityContent(value, label) {
530
+ if (value === null || value === '') return null;
531
+ if (typeof value === 'string' || typeof value === 'number') return String(value);
532
+ if (value && typeof value === 'object'
533
+ && typeof (/** @type {{nodeType?:unknown}} */ (value)).nodeType === 'number') {
534
+ return /** @type {Node} */ (value);
535
+ }
536
+ if (value instanceof Component) return value.toElement();
537
+ throw new TypeError(`${label} must be text, a Node, a Component, or null`);
538
+ }
539
+
540
+ /** @param {ActivityContent} value @returns {string} */
541
+ function textAlternative(value) {
542
+ if (value === null) return '';
543
+ if (typeof value === 'string' || typeof value === 'number') return String(value).trim();
544
+ if (value && typeof value === 'object'
545
+ && typeof (/** @type {{nodeType?:unknown}} */ (value)).nodeType === 'number') {
546
+ return /** @type {Node} */ (value).textContent?.trim() ?? '';
547
+ }
548
+ if (value instanceof Component) return value.toElement().textContent?.trim() ?? '';
549
+ return '';
550
+ }
551
+
552
+ /** @param {ActivityItemOptions} activity @param {string} fallback @returns {string} */
553
+ function activityLabel(activity, fallback) {
554
+ return textAlternative(activity.title) || textAlternative(activity.actor) || fallback;
555
+ }
556
+
557
+ /** @param {unknown} value @returns {string|null} */
558
+ function serializeActivityId(value) {
559
+ return typeof value === 'string' || typeof value === 'number' ? String(value) : null;
560
+ }
561
+
562
+ /** @param {unknown} value @returns {string} */
563
+ function normalizeActivityKind(value) {
564
+ return String(value ?? 'default').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '-') || 'default';
565
+ }
566
+
567
+ /** @param {unknown} value @returns {'default'|'primary'|'danger'|'ghost'} */
568
+ function normalizeActionKind(value) {
569
+ return ['default', 'primary', 'danger'].includes(String(value))
570
+ ? /** @type {'default'|'primary'|'danger'} */ (value) : 'ghost';
571
+ }
572
+
573
+ /** @param {unknown} rel @param {string|undefined} target @returns {string|undefined} */
574
+ function safeRel(rel, target) {
575
+ const tokens = String(rel ?? '').split(/\s+/).filter((token) => /^[a-z-]+$/i.test(token));
576
+ if (target === '_blank' && !tokens.includes('noopener')) tokens.push('noopener');
577
+ return tokens.length ? [...new Set(tokens)].join(' ') : undefined;
578
+ }
579
+
580
+ /** @param {unknown} value @returns {1|2|3|4|5|6} */
581
+ function normalizeHeadingLevel(value) {
582
+ const level = Math.trunc(Number(value));
583
+ return /** @type {1|2|3|4|5|6} */ (level >= 1 && level <= 6 ? level : 3);
584
+ }
585
+
586
+ /** @param {Date} date @returns {string} */
587
+ function formatActivityDate(date) {
588
+ try {
589
+ return new Intl.DateTimeFormat(undefined, {
590
+ dateStyle: 'medium', timeStyle: 'short'
591
+ }).format(date);
592
+ } catch {
593
+ return date.toLocaleString();
594
+ }
595
+ }
596
+
597
+ /**
598
+ * Activity action selected before the host callback or native navigation runs. Cancellation from
599
+ * either the component event or bubbling `zx-action` event prevents both callback and navigation.
600
+ * @event ActivityItem#action
601
+ * @type {CustomEvent<{id:string,action:NormalizedActivityAction,event:MouseEvent}>}
602
+ */
@@ -0,0 +1,99 @@
1
+ .zx-activity-list {
2
+ display: grid;
3
+ gap: var(--zx-space-4);
4
+ min-inline-size: 0;
5
+ color: var(--zx-color-text);
6
+ }
7
+
8
+ .zx-activity-list__group {
9
+ display: grid;
10
+ gap: var(--zx-space-2);
11
+ min-inline-size: 0;
12
+ }
13
+
14
+ .zx-activity-list__group-heading {
15
+ margin: 0;
16
+ color: var(--zx-color-text-muted);
17
+ font-size: var(--zx-text-sm);
18
+ font-weight: var(--zx-weight-semibold);
19
+ }
20
+
21
+ .zx-activity-list__items {
22
+ display: grid;
23
+ min-inline-size: 0;
24
+ margin: 0;
25
+ padding: 0;
26
+ list-style: none;
27
+ }
28
+
29
+ .zx-activity-list__items > .zx-activity-item:not(:last-child) {
30
+ border-block-end: 1px solid var(--zx-color-border-subtle);
31
+ }
32
+
33
+ .zx-activity-list__empty,
34
+ .zx-activity-list__loading {
35
+ border: 1px dashed var(--zx-color-border);
36
+ border-radius: var(--zx-radius-lg);
37
+ background: var(--zx-color-bg-muted);
38
+ padding: var(--zx-space-5);
39
+ color: var(--zx-color-text-muted);
40
+ text-align: center;
41
+ }
42
+
43
+ .zx-activity-list__loading {
44
+ display: grid;
45
+ gap: var(--zx-space-4);
46
+ }
47
+
48
+ .zx-activity-list__skeletons {
49
+ display: grid;
50
+ gap: var(--zx-space-3);
51
+ text-align: start;
52
+ }
53
+
54
+ .zx-activity-list__skeleton {
55
+ display: grid;
56
+ grid-template-columns: auto minmax(0, 1fr);
57
+ gap: var(--zx-space-3);
58
+ align-items: start;
59
+ }
60
+
61
+ .zx-activity-list__skeleton > .zx-skeleton-text {
62
+ padding-block-start: var(--zx-space-1);
63
+ }
64
+
65
+ /* ------------------------------------------------------------------ compact -- */
66
+
67
+ /*
68
+ * `variant: 'compact'` — one line per entry at any width, for a summary card narrower than 30rem:
69
+ * who and what read as one sentence that wraps as text, the time keeps the trailing end of the
70
+ * first line instead of stacking under it, and metadata follows as a muted second line. These
71
+ * rules outrank the entry's own narrow-container layout, which would stack the header.
72
+ */
73
+ .zx-activity-list[data-variant="compact"] .zx-activity-item__article {
74
+ gap: var(--zx-space-2);
75
+ padding-block: var(--zx-space-2);
76
+ }
77
+
78
+ .zx-activity-list[data-variant="compact"] .zx-activity-item__main {
79
+ gap: calc(var(--zx-space-1) / 2);
80
+ }
81
+
82
+ .zx-activity-list[data-variant="compact"] .zx-activity-item__header {
83
+ display: flex;
84
+ flex-wrap: nowrap;
85
+ align-items: baseline;
86
+ justify-content: flex-start;
87
+ gap: var(--zx-space-2);
88
+ }
89
+
90
+ .zx-activity-list[data-variant="compact"] .zx-activity-item__heading {
91
+ display: block;
92
+ flex: 1 1 auto;
93
+ }
94
+
95
+ /* The state badge sits on the first line with the time, not centred against a wrapped sentence. */
96
+ .zx-activity-list[data-variant="compact"] .zx-activity-item__state {
97
+ align-self: baseline;
98
+ margin-inline-start: 0;
99
+ }