@zeyos/zx 2.2.1 → 3.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 (117) hide show
  1. package/CHANGELOG.md +197 -0
  2. package/DESIGN-SYSTEM.md +136 -0
  3. package/MIGRATION.md +155 -4
  4. package/README.md +51 -14
  5. package/dist/types/compat/map/data.d.ts +3 -2
  6. package/dist/types/components/account-menu/account-menu.d.ts +144 -0
  7. package/dist/types/components/app-rail/app-rail.d.ts +250 -0
  8. package/dist/types/components/app-sidebar/app-sidebar.d.ts +169 -0
  9. package/dist/types/components/avatar/avatar.d.ts +100 -0
  10. package/dist/types/components/chart/chart.d.ts +321 -0
  11. package/dist/types/components/date-picker/month-picker.d.ts +6 -0
  12. package/dist/types/components/dialog/dialog.d.ts +9 -0
  13. package/dist/types/components/dock/dock.d.ts +495 -0
  14. package/dist/types/components/dropdown/dropdown.d.ts +2 -2
  15. package/dist/types/components/field/field.d.ts +14 -0
  16. package/dist/types/components/field-upload/field-upload.d.ts +37 -4
  17. package/dist/types/components/form/form.d.ts +25 -3
  18. package/dist/types/components/grid/grid.d.ts +172 -0
  19. package/dist/types/components/launcher/launcher.d.ts +300 -0
  20. package/dist/types/components/layout/layout.d.ts +4 -4
  21. package/dist/types/components/menu-button/menu-button.d.ts +10 -2
  22. package/dist/types/components/modal/modal.d.ts +25 -0
  23. package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +74 -9
  24. package/dist/types/components/questionnaire/items.d.ts +327 -0
  25. package/dist/types/components/questionnaire/questionnaire.d.ts +449 -0
  26. package/dist/types/components/select/select.d.ts +36 -1
  27. package/dist/types/components/sheet/sheet-stack.d.ts +167 -0
  28. package/dist/types/components/sheet/sheet.d.ts +294 -0
  29. package/dist/types/components/split-view/split-view.d.ts +2 -21
  30. package/dist/types/components/tabbox/tabbox.d.ts +12 -3
  31. package/dist/types/components/table/table.d.ts +219 -12
  32. package/dist/types/components/tooltip/tooltip.d.ts +2 -2
  33. package/dist/types/components/value-list/value-list.d.ts +24 -3
  34. package/dist/types/core/dom.d.ts +114 -0
  35. package/dist/types/core/drag-axis.d.ts +111 -0
  36. package/dist/types/core/position.d.ts +4 -2
  37. package/dist/types/index.d.ts +12 -2
  38. package/dist/zx-compat.esm.js +943 -311
  39. package/dist/zx-compat.esm.min.js +1 -1
  40. package/dist/zx-compat.global.js +7893 -2252
  41. package/dist/zx-compat.global.min.js +3 -3
  42. package/dist/zx-zeyos.esm.js +826 -114
  43. package/dist/zx-zeyos.esm.min.js +1 -1
  44. package/dist/zx.css +1720 -243
  45. package/dist/zx.esm.js +8227 -2599
  46. package/dist/zx.esm.min.js +3 -3
  47. package/dist/zx.global.js +8227 -2599
  48. package/dist/zx.global.min.js +3 -3
  49. package/dist/zx.min.css +1 -1
  50. package/docs/api.json +2597 -255
  51. package/docs/llms.md +475 -37
  52. package/docs/llms.txt +21 -9
  53. package/package.json +4 -2
  54. package/src/compat/globals.js +3 -1
  55. package/src/compat/map/data.js +31 -9
  56. package/src/components/account-menu/account-menu.css +70 -0
  57. package/src/components/account-menu/account-menu.js +208 -0
  58. package/src/components/app-rail/app-rail.css +107 -0
  59. package/src/components/app-rail/app-rail.js +668 -0
  60. package/src/components/app-sidebar/app-sidebar.css +68 -0
  61. package/src/components/app-sidebar/app-sidebar.js +388 -0
  62. package/src/components/avatar/avatar.css +61 -0
  63. package/src/components/avatar/avatar.js +222 -0
  64. package/src/components/button/button.css +0 -1
  65. package/src/components/chart/chart.css +61 -0
  66. package/src/components/chart/chart.js +518 -0
  67. package/src/components/date-picker/date-range-picker.css +7 -0
  68. package/src/components/date-picker/month-picker.js +9 -0
  69. package/src/components/dialog/dialog.js +11 -4
  70. package/src/components/dock/dock.css +228 -0
  71. package/src/components/dock/dock.js +977 -0
  72. package/src/components/dropdown/dropdown.js +1 -1
  73. package/src/components/field/field.js +2 -0
  74. package/src/components/field-upload/field-upload.js +9 -4
  75. package/src/components/fieldset/fieldset.css +7 -0
  76. package/src/components/form/form.css +7 -0
  77. package/src/components/form/form.js +6 -3
  78. package/src/components/grid/grid.css +6 -0
  79. package/src/components/grid/grid.js +213 -0
  80. package/src/components/launcher/launcher.css +100 -0
  81. package/src/components/launcher/launcher.js +557 -0
  82. package/src/components/layout/layout.js +2 -2
  83. package/src/components/menu-button/menu-button.js +27 -2
  84. package/src/components/modal/modal.css +5 -1
  85. package/src/components/modal/modal.js +37 -2
  86. package/src/components/multi-value-editor/multi-value-editor.css +99 -15
  87. package/src/components/multi-value-editor/multi-value-editor.js +237 -19
  88. package/src/components/navigation-bar/navigation-bar.css +7 -0
  89. package/src/components/questionnaire/items.js +370 -0
  90. package/src/components/questionnaire/questionnaire.css +387 -0
  91. package/src/components/questionnaire/questionnaire.js +1033 -0
  92. package/src/components/search/search.css +10 -2
  93. package/src/components/select/select.css +6 -1
  94. package/src/components/select/select.js +99 -11
  95. package/src/components/sheet/sheet-stack.js +304 -0
  96. package/src/components/sheet/sheet.css +351 -0
  97. package/src/components/sheet/sheet.js +551 -0
  98. package/src/components/split-view/split-view.js +76 -161
  99. package/src/components/stepper/stepper.css +12 -2
  100. package/src/components/tabbox/tabbox.css +13 -3
  101. package/src/components/tabbox/tabbox.js +34 -10
  102. package/src/components/table/table.css +43 -0
  103. package/src/components/table/table.js +523 -45
  104. package/src/components/tooltip/tooltip.js +5 -2
  105. package/src/components/value-list/value-list.js +6 -3
  106. package/src/core/dom.js +302 -0
  107. package/src/core/drag-axis.js +203 -0
  108. package/src/core/position.js +68 -22
  109. package/src/index.js +19 -2
  110. package/styles/tokens/dark.css +15 -13
  111. package/styles/tokens/global.css +17 -5
  112. package/styles/tokens/semantic.css +14 -8
  113. package/styles/tokens/themes.css +72 -0
  114. package/styles/zx.css +11 -1
  115. package/dist/types/components/permission/permission.d.ts +0 -93
  116. package/src/components/permission/permission.css +0 -47
  117. package/src/components/permission/permission.js +0 -198
@@ -0,0 +1,222 @@
1
+ import { Component } from '../../core/component.js';
2
+ import { h } from '../../core/dom.js';
3
+
4
+ /**
5
+ * @typedef {Object} AvatarOptions
6
+ * @property {string|null} [src=null] Image URL.
7
+ * @property {string} [name=''] Name used to derive fallback initials.
8
+ * @property {string|null} [initials=null] Explicit fallback initials.
9
+ * @property {string|null} [label=null] Accessible label; null makes the avatar decorative.
10
+ * @property {string|number} [size='md'] Preset (`sm`, `md`, `lg`) or pixel size.
11
+ * @property {'circle'|'rounded'|'square'} [shape='circle'] Avatar shape.
12
+ * @property {'online'|'away'|'busy'|'offline'|null} [status=null] Optional presence state.
13
+ * @property {string|null} [statusLabel=null] Text alternative for the presence state.
14
+ */
15
+
16
+ /**
17
+ * Stable-size user image with deterministic initials fallback and optional presence state.
18
+ * @extends {Component<AvatarOptions>}
19
+ */
20
+ export class Avatar extends Component {
21
+ static cssName = 'avatar';
22
+
23
+ /** @type {Readonly<AvatarOptions>} */
24
+ static defaults = {
25
+ src: null,
26
+ name: '',
27
+ initials: null,
28
+ label: null,
29
+ size: 'md',
30
+ shape: 'circle',
31
+ status: null,
32
+ statusLabel: null
33
+ };
34
+
35
+ /** @returns {HTMLElement} */
36
+ render() {
37
+ this._createdRoot = this.el === null;
38
+ const root = /** @type {HTMLElement} */ (this.el ?? h('span'));
39
+ this.el = root;
40
+ this._original = this._createdRoot ? null : {
41
+ attributes: Array.from(root.attributes, (attribute) => [attribute.name, attribute.value]),
42
+ children: Array.from(root.childNodes)
43
+ };
44
+ this._state = {
45
+ src: this.options.src,
46
+ name: String(this.options.name ?? ''),
47
+ initials: this.options.initials,
48
+ label: this.options.label,
49
+ size: this.options.size,
50
+ shape: this.options.shape,
51
+ status: this.options.status,
52
+ statusLabel: this.options.statusLabel
53
+ };
54
+
55
+ const image = /** @type {HTMLImageElement} */ (h('img', {
56
+ ref: 'image',
57
+ class: 'zx-avatar__image',
58
+ alt: '',
59
+ draggable: false
60
+ }));
61
+ const fallback = h('span', {
62
+ ref: 'fallback',
63
+ class: 'zx-avatar__fallback',
64
+ ariaHidden: 'true'
65
+ });
66
+ const status = h('span', {
67
+ ref: 'status',
68
+ class: 'zx-avatar__status',
69
+ ariaHidden: 'true',
70
+ hidden: true
71
+ });
72
+ root.replaceChildren(image, fallback, status);
73
+ this.listen(image, 'load', () => {
74
+ if (this._isCurrentImageSource()) this._showImage(true);
75
+ });
76
+ this.listen(image, 'error', () => {
77
+ if (this._isCurrentImageSource()) this._showImage(false);
78
+ });
79
+ this._sync();
80
+ return root;
81
+ }
82
+
83
+ /**
84
+ * Replaces avatar identity and presentation values.
85
+ * @param {Partial<AvatarOptions>} values New values.
86
+ * @returns {this}
87
+ */
88
+ set(values = {}) {
89
+ if (!values || typeof values !== 'object') return this;
90
+ for (const key of ['src', 'name', 'initials', 'label', 'size', 'shape', 'status', 'statusLabel']) {
91
+ if (Object.hasOwn(values, key)) this._state[key] = values[key];
92
+ }
93
+ this._sync();
94
+ return this;
95
+ }
96
+
97
+ /** Sets the presence state and its text alternative. @param {AvatarOptions['status']} status @param {string|null} [label=null] @returns {this} */
98
+ setStatus(status, label = null) {
99
+ this._state.status = status;
100
+ this._state.statusLabel = label;
101
+ this._syncLabel();
102
+ this._syncStatus();
103
+ return this;
104
+ }
105
+
106
+ /** Returns whether the image is currently visible. @returns {boolean} */
107
+ hasImage() {
108
+ return !this.refs.image.hidden;
109
+ }
110
+
111
+ /** Restores an enhanced target or removes an owned root. @returns {void} */
112
+ destroy() {
113
+ const original = this._original;
114
+ super.destroy();
115
+ if (!this._createdRoot && original) {
116
+ for (const attribute of Array.from(this.el.attributes)) this.el.removeAttribute(attribute.name);
117
+ for (const [name, value] of original.attributes) this.el.setAttribute(name, value);
118
+ this.el.replaceChildren(...original.children);
119
+ }
120
+ }
121
+
122
+ /** @returns {void} */
123
+ _sync() {
124
+ const { size, shape, label } = this._state;
125
+ this.el.dataset.size = typeof size === 'string' ? size : 'custom';
126
+ this.el.dataset.shape = ['circle', 'rounded', 'square'].includes(shape) ? shape : 'circle';
127
+ if (typeof size === 'number' && Number.isFinite(size)) {
128
+ this.el.style.setProperty('--zx-avatar-size', `${Math.max(1, size)}px`);
129
+ } else {
130
+ this.el.style.removeProperty('--zx-avatar-size');
131
+ }
132
+ this._syncLabel();
133
+ this.refs.fallback.textContent = String(this._state.initials || avatarInitials(this._state.name));
134
+ const src = this._state.src == null ? '' : String(this._state.src).trim();
135
+ if (src) {
136
+ this.refs.image.setAttribute('src', src);
137
+ // Keep the fallback visible until this exact source has loaded successfully.
138
+ this._showImage(/** @type {HTMLImageElement} */ (this.refs.image).complete
139
+ && /** @type {HTMLImageElement} */ (this.refs.image).naturalWidth > 0);
140
+ } else {
141
+ this.refs.image.removeAttribute('src');
142
+ this._showImage(false);
143
+ }
144
+ this._syncStatus();
145
+ }
146
+
147
+ /** Synchronizes the standalone accessible name, including valid presence text. @returns {void} */
148
+ _syncLabel() {
149
+ const { label, status, statusLabel } = this._state;
150
+ if (label) {
151
+ const presence = ['online', 'away', 'busy', 'offline'].includes(status) ? statusLabel : null;
152
+ this.el.setAttribute('role', 'img');
153
+ this.el.setAttribute('aria-label', [label, presence].filter(Boolean).join(', '));
154
+ this.el.removeAttribute('aria-hidden');
155
+ } else {
156
+ this.el.removeAttribute('role');
157
+ this.el.removeAttribute('aria-label');
158
+ this.el.setAttribute('aria-hidden', 'true');
159
+ }
160
+ }
161
+
162
+ /** @param {boolean} visible @returns {void} */
163
+ _showImage(visible) {
164
+ this.refs.image.hidden = !visible;
165
+ this.refs.fallback.hidden = visible;
166
+ this.el.dataset.fallback = String(!visible);
167
+ }
168
+
169
+ /** Ignores a late load/error from a source that has already been replaced. @returns {boolean} */
170
+ _isCurrentImageSource() {
171
+ const image = /** @type {HTMLImageElement} */ (this.refs.image);
172
+ const requested = image.getAttribute('src');
173
+ if (!requested) return false;
174
+ try {
175
+ return image.currentSrc === new URL(requested, document.baseURI).href;
176
+ } catch {
177
+ return image.currentSrc === requested;
178
+ }
179
+ }
180
+
181
+ /** @returns {void} */
182
+ _syncStatus() {
183
+ const allowed = ['online', 'away', 'busy', 'offline'];
184
+ const status = allowed.includes(this._state.status) ? this._state.status : null;
185
+ this.refs.status.hidden = status === null;
186
+ if (status) this.refs.status.dataset.status = status;
187
+ else delete this.refs.status.dataset.status;
188
+ const label = this._state.statusLabel;
189
+ if (label) {
190
+ this.refs.status.setAttribute('title', String(label));
191
+ } else {
192
+ this.refs.status.removeAttribute('title');
193
+ }
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Derives at most two uppercase initials from the first and last words of a name.
199
+ * @param {unknown} name Name-like value.
200
+ * @returns {string}
201
+ */
202
+ export function avatarInitials(name) {
203
+ const words = String(name ?? '').trim().split(/\s+/u).filter(Boolean);
204
+ if (words.length === 0) return '?';
205
+ const first = uppercaseInitial(words[0]);
206
+ const last = words.length > 1 ? uppercaseInitial(words[words.length - 1]) : '';
207
+ return `${first}${last}`;
208
+ }
209
+
210
+ /** Returns one uppercased grapheme even when Unicode case expansion produces multiple letters. @param {string} word @returns {string} */
211
+ function uppercaseInitial(word) {
212
+ return firstGrapheme(firstGrapheme(word).toUpperCase());
213
+ }
214
+
215
+ /** @param {string} value @returns {string} */
216
+ function firstGrapheme(value) {
217
+ if (typeof Intl.Segmenter === 'function') {
218
+ const iterator = new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(value)[Symbol.iterator]();
219
+ return iterator.next().value?.segment ?? '';
220
+ }
221
+ return Array.from(value)[0] ?? '';
222
+ }
@@ -79,7 +79,6 @@
79
79
  font-size: var(--zx-text-sm);
80
80
  }
81
81
 
82
- .zx-btn > svg,
83
82
  .zx-btn > .zx-icon {
84
83
  flex: none;
85
84
  }
@@ -0,0 +1,61 @@
1
+ .zx-chart {
2
+ display: grid;
3
+ gap: var(--zx-space-4);
4
+ min-inline-size: 0;
5
+ color: var(--zx-color-text);
6
+ }
7
+
8
+ .zx-chart__viewport {
9
+ position: relative;
10
+ inline-size: 100%;
11
+ min-block-size: 220px;
12
+ aspect-ratio: var(--zx-chart-aspect-ratio, 16 / 9);
13
+ }
14
+
15
+ .zx-chart__viewport[hidden],
16
+ .zx-chart__status[hidden] { display: none; }
17
+
18
+ .zx-chart__canvas {
19
+ position: absolute;
20
+ inset: 0;
21
+ inline-size: 100% !important;
22
+ block-size: 100% !important;
23
+ }
24
+
25
+ .zx-chart__description {
26
+ margin: 0;
27
+ color: var(--zx-color-text-muted);
28
+ font-size: var(--zx-text-sm);
29
+ }
30
+
31
+ .zx-chart__description:empty { display: none; }
32
+
33
+ .zx-chart__status {
34
+ display: grid;
35
+ min-block-size: 180px;
36
+ place-items: center;
37
+ border: 1px dashed var(--zx-color-border);
38
+ border-radius: var(--zx-radius-lg);
39
+ color: var(--zx-color-text-muted);
40
+ text-align: center;
41
+ }
42
+
43
+ .zx-chart__summary[data-summary="hidden"] {
44
+ position: absolute;
45
+ inline-size: 1px;
46
+ block-size: 1px;
47
+ overflow: hidden;
48
+ clip-path: inset(50%);
49
+ white-space: nowrap;
50
+ }
51
+
52
+ .zx-chart__table {
53
+ inline-size: 100%;
54
+ border-collapse: collapse;
55
+ }
56
+
57
+ .zx-chart__table caption { margin-block-end: var(--zx-space-2); font-weight: 700; text-align: start; }
58
+ .zx-chart__table th,
59
+ .zx-chart__table td { border-block-end: 1px solid var(--zx-color-border); padding: var(--zx-space-2); text-align: end; }
60
+ .zx-chart__table th:first-child,
61
+ .zx-chart__table td:first-child { text-align: start; }