@zeyos/zx 2.2.1 → 2.3.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 (39) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +28 -2
  3. package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +67 -8
  4. package/dist/types/components/permission/permission.d.ts +3 -0
  5. package/dist/types/components/select/select.d.ts +36 -1
  6. package/dist/types/components/tabbox/tabbox.d.ts +12 -3
  7. package/dist/zx-compat.esm.js +302 -34
  8. package/dist/zx-compat.esm.min.js +1 -1
  9. package/dist/zx-compat.global.js +302 -34
  10. package/dist/zx-compat.global.min.js +3 -3
  11. package/dist/zx-zeyos.esm.js +280 -27
  12. package/dist/zx-zeyos.esm.min.js +1 -1
  13. package/dist/zx.css +159 -33
  14. package/dist/zx.esm.js +302 -34
  15. package/dist/zx.esm.min.js +3 -3
  16. package/dist/zx.global.js +302 -34
  17. package/dist/zx.global.min.js +3 -3
  18. package/dist/zx.min.css +1 -1
  19. package/docs/api.json +420 -17
  20. package/docs/llms.md +54 -12
  21. package/package.json +1 -1
  22. package/src/components/date-picker/date-range-picker.css +7 -0
  23. package/src/components/fieldset/fieldset.css +7 -0
  24. package/src/components/form/form.css +7 -0
  25. package/src/components/multi-value-editor/multi-value-editor.css +99 -15
  26. package/src/components/multi-value-editor/multi-value-editor.js +235 -18
  27. package/src/components/navigation-bar/navigation-bar.css +7 -0
  28. package/src/components/permission/permission.js +3 -0
  29. package/src/components/search/search.css +10 -2
  30. package/src/components/select/select.css +6 -0
  31. package/src/components/select/select.js +99 -11
  32. package/src/components/stepper/stepper.css +12 -2
  33. package/src/components/tabbox/tabbox.css +13 -3
  34. package/src/components/tabbox/tabbox.js +34 -10
  35. package/styles/tokens/dark.css +13 -10
  36. package/styles/tokens/global.css +12 -0
  37. package/styles/tokens/semantic.css +9 -5
  38. package/styles/tokens/themes.css +72 -0
  39. package/styles/zx.css +1 -0
@@ -249,9 +249,19 @@
249
249
  text-align: center;
250
250
  }
251
251
 
252
- .zx-tabbox__close {
253
- margin-inline-start: calc(-1 * var(--zx-space-1));
254
- color: var(--zx-color-text-muted);
252
+ /* Chrome comes from the shared .zx-icon-btn ghost utility; only the box is reset here, because
253
+ a full control-height square would widen every closable tab. The extra .zx-tabbox raises the
254
+ specificity to match `.zx-scope .zx-icon-btn` in base.css, which sets the size. */
255
+ .zx-tabbox .zx-tabbox__close {
256
+ inline-size: 1.5rem;
257
+ block-size: 1.5rem;
258
+ margin-inline: calc(-1 * var(--zx-space-1));
259
+ }
260
+
261
+ /* The tab under the pointer is already filled with bg-hover, so the close control needs a tint
262
+ that still reads on top of it — in both themes, over any variant's tab background. */
263
+ .zx-tabbox .zx-tabbox__close:hover {
264
+ background: color-mix(in srgb, var(--zx-color-text) 12%, transparent);
255
265
  }
256
266
 
257
267
  /* ----------------------------------------------------------------------- panels -- */
@@ -11,7 +11,7 @@ import { uid } from '../../core/util.js';
11
11
  * @property {string} title Visible tab label.
12
12
  * @property {TabContent} content Panel content or a lazy content factory.
13
13
  * @property {string} [icon] Icon name passed to `icon()`, shown before the title.
14
- * @property {boolean} [closable=false] Whether Delete can close the tab.
14
+ * @property {boolean} [closable=false] Whether the tab shows a close control and Delete closes it.
15
15
  * @property {boolean} [disabled=false] Whether the tab is unavailable.
16
16
  */
17
17
 
@@ -38,7 +38,8 @@ const VARIANTS = new Set(['divided', 'bracket', 'line', 'segmented']);
38
38
  * control, for a toolbar or card header.
39
39
  * @property {boolean} [keepAlive=true] Keep inactive panel elements mounted.
40
40
  * @property {(event: CustomEvent<{name: string, previous: string|null}>) => void} [onchange] Change callback.
41
- * @property {(event: CustomEvent<{name: string}>) => void} [onclose] Close callback.
41
+ * @property {(event: CustomEvent<{name: string}>) => void} [onclose] Close callback, fired when the
42
+ * user dismisses a closable tab.
42
43
  */
43
44
 
44
45
  /**
@@ -121,6 +122,10 @@ export class Tabbox extends Component {
121
122
  if (!tab || !tablist.contains(tab)) return;
122
123
  const record = this._recordForTab(tab);
123
124
  if (!record || record.definition.disabled) return;
125
+ if (event.target.closest?.('.zx-tabbox__close')) {
126
+ this._closeTab(record);
127
+ return;
128
+ }
124
129
  this._setFocused(record);
125
130
  this.openTab(record.definition.name);
126
131
  });
@@ -168,9 +173,13 @@ export class Tabbox extends Component {
168
173
  }
169
174
  children.push(title, badge);
170
175
  if (normalized.closable) {
176
+ // Deliberately a span and not a nested <button>: the tab itself is a button, and buttons
177
+ // cannot nest. The tablist click handler routes clicks that land in here to _closeTab(),
178
+ // and it stays out of the accessibility tree because Delete is the keyboard equivalent.
171
179
  children.push(h('span', {
172
- class: 'zx-tabbox__close',
173
- ariaHidden: 'true'
180
+ class: 'zx-tabbox__close zx-icon-btn',
181
+ ariaHidden: 'true',
182
+ title: 'Close'
174
183
  }, icon('x', { size: 14 })));
175
184
  }
176
185
  const tab = /** @type {HTMLButtonElement} */ (h('button', {
@@ -353,14 +362,28 @@ export class Tabbox extends Component {
353
362
  }
354
363
  if (event.key === 'Delete' && current.definition.closable) {
355
364
  event.preventDefault();
356
- const index = this._tabs.indexOf(current);
357
- if (!this._removeTab(current.definition.name, true)) return;
358
- const remaining = this._tabs[Math.min(index, this._tabs.length - 1)] ?? this._tabs[index - 1];
359
- const focusTarget = remaining && !remaining.definition.disabled ? remaining : this._active;
360
- if (focusTarget) this._setFocused(focusTarget);
365
+ this._closeTab(current);
361
366
  }
362
367
  }
363
368
 
369
+ /**
370
+ * Closes a tab on a user gesture, keeping roving focus inside the tablist.
371
+ * @param {TabRecord} record Tab to close.
372
+ * @returns {void}
373
+ * @fires Tabbox#close
374
+ */
375
+ _closeTab(record) {
376
+ const index = this._tabs.indexOf(record);
377
+ // Clicking the close control focuses its tab first in most browsers, so the element about to
378
+ // be removed holds the focus; left alone, focus would fall back to <body>.
379
+ const held = record.tab.contains(document.activeElement);
380
+ if (!this._removeTab(record.definition.name, true)) return;
381
+ const remaining = this._tabs[Math.min(index, this._tabs.length - 1)] ?? this._tabs[index - 1];
382
+ const focusTarget = remaining && !remaining.definition.disabled ? remaining : this._active;
383
+ // Roving tabindex has to move even for a mouse close, or Tab has no entry point left.
384
+ if (focusTarget) this._setFocused(focusTarget, held);
385
+ }
386
+
364
387
  /** @param {string} name @param {boolean} emitClose @returns {boolean} */
365
388
  _removeTab(name, emitClose) {
366
389
  const record = this._find(name);
@@ -453,7 +476,8 @@ export class Tabbox extends Component {
453
476
  */
454
477
 
455
478
  /**
456
- * Fired after a closable tab is removed through the keyboard.
479
+ * Fired after the user removes a closable tab, through its close control or Delete.
480
+ * `removeTab()` is silent.
457
481
  * @event Tabbox#close
458
482
  * @type {CustomEvent<{name: string}>}
459
483
  */
@@ -3,6 +3,9 @@
3
3
  * Zinc-950 page, Zinc-900 cards and popovers, Zinc-800 muted fills, translucent white borders
4
4
  * (10%) and inputs (15%), Zinc-400 muted text.
5
5
  *
6
+ * The accent roles take the light end of the `--zx-accent-*` ramp (300/400/500) where the light
7
+ * theme takes the dark end, so a preset that repoints the ramp lands correctly in both.
8
+ *
6
9
  * The `[data-zx-theme="auto"]` block below repeats these assignments under a
7
10
  * `prefers-color-scheme` query; CSS has no way to share a declaration block across a media
8
11
  * boundary, so the two lists must be kept in sync.
@@ -15,7 +18,7 @@
15
18
  --zx-color-bg-control: color-mix(in srgb, var(--zx-gray-0) 5%, transparent);
16
19
  --zx-color-bg-muted: var(--zx-gray-800);
17
20
  --zx-color-bg-hover: color-mix(in srgb, var(--zx-gray-0) 8%, transparent);
18
- --zx-color-bg-selected: color-mix(in srgb, var(--zx-green-400) 18%, var(--zx-gray-900));
21
+ --zx-color-bg-selected: color-mix(in srgb, var(--zx-accent-400) 18%, var(--zx-gray-900));
19
22
  --zx-color-bg-backdrop: color-mix(in srgb, var(--zx-gray-950) 75%, transparent);
20
23
  --zx-color-border: color-mix(in srgb, var(--zx-gray-0) 10%, transparent);
21
24
  --zx-color-border-strong: color-mix(in srgb, var(--zx-gray-0) 28%, transparent);
@@ -24,10 +27,10 @@
24
27
  --zx-color-text-muted: var(--zx-gray-400);
25
28
  --zx-color-text-placeholder: var(--zx-gray-500);
26
29
  --zx-color-text-invert: var(--zx-gray-950);
27
- --zx-color-accent: var(--zx-green-400);
28
- --zx-color-accent-hover: var(--zx-green-300);
29
- --zx-color-accent-active: var(--zx-green-500);
30
- --zx-color-accent-subtle: color-mix(in srgb, var(--zx-green-400) 16%, var(--zx-gray-900));
30
+ --zx-color-accent: var(--zx-accent-400);
31
+ --zx-color-accent-hover: var(--zx-accent-300);
32
+ --zx-color-accent-active: var(--zx-accent-500);
33
+ --zx-color-accent-subtle: color-mix(in srgb, var(--zx-accent-400) 16%, var(--zx-gray-900));
31
34
  --zx-color-on-accent: var(--zx-gray-950);
32
35
  --zx-color-danger: var(--zx-red-400);
33
36
  --zx-color-danger-bg: color-mix(in srgb, var(--zx-red-400) 16%, var(--zx-gray-900));
@@ -53,7 +56,7 @@
53
56
  --zx-color-bg-control: color-mix(in srgb, var(--zx-gray-0) 5%, transparent);
54
57
  --zx-color-bg-muted: var(--zx-gray-800);
55
58
  --zx-color-bg-hover: color-mix(in srgb, var(--zx-gray-0) 8%, transparent);
56
- --zx-color-bg-selected: color-mix(in srgb, var(--zx-green-400) 18%, var(--zx-gray-900));
59
+ --zx-color-bg-selected: color-mix(in srgb, var(--zx-accent-400) 18%, var(--zx-gray-900));
57
60
  --zx-color-bg-backdrop: color-mix(in srgb, var(--zx-gray-950) 75%, transparent);
58
61
  --zx-color-border: color-mix(in srgb, var(--zx-gray-0) 10%, transparent);
59
62
  --zx-color-border-strong: color-mix(in srgb, var(--zx-gray-0) 28%, transparent);
@@ -62,10 +65,10 @@
62
65
  --zx-color-text-muted: var(--zx-gray-400);
63
66
  --zx-color-text-placeholder: var(--zx-gray-500);
64
67
  --zx-color-text-invert: var(--zx-gray-950);
65
- --zx-color-accent: var(--zx-green-400);
66
- --zx-color-accent-hover: var(--zx-green-300);
67
- --zx-color-accent-active: var(--zx-green-500);
68
- --zx-color-accent-subtle: color-mix(in srgb, var(--zx-green-400) 16%, var(--zx-gray-900));
68
+ --zx-color-accent: var(--zx-accent-400);
69
+ --zx-color-accent-hover: var(--zx-accent-300);
70
+ --zx-color-accent-active: var(--zx-accent-500);
71
+ --zx-color-accent-subtle: color-mix(in srgb, var(--zx-accent-400) 16%, var(--zx-gray-900));
69
72
  --zx-color-on-accent: var(--zx-gray-950);
70
73
  --zx-color-danger: var(--zx-red-400);
71
74
  --zx-color-danger-bg: color-mix(in srgb, var(--zx-red-400) 16%, var(--zx-gray-900));
@@ -31,6 +31,18 @@
31
31
  --zx-green-600: #007a55;
32
32
  --zx-green-700: #006045;
33
33
 
34
+ /*
35
+ * Accent ramp — the indirection the semantic tier reads. It defaults to the Emerald ramp
36
+ * above, which is what makes a theme preset five declarations rather than a second copy of
37
+ * every semantic mapping: `tokens/themes.css` repoints these, and light, dark, and auto all
38
+ * follow because each of them picks its own stop off the ramp.
39
+ */
40
+ --zx-accent-300: var(--zx-green-300);
41
+ --zx-accent-400: var(--zx-green-400);
42
+ --zx-accent-500: var(--zx-green-500);
43
+ --zx-accent-600: var(--zx-green-600);
44
+ --zx-accent-700: var(--zx-green-700);
45
+
34
46
  /* Status */
35
47
  --zx-red-400: #ff6467;
36
48
  --zx-red-500: #e7000b;
@@ -4,6 +4,10 @@
4
4
  * The mapping follows the shadcn/ui preset `b2BVl11e4`: white surfaces, Zinc-100 hover/muted
5
5
  * fills, Zinc-200 borders and inputs, Zinc-500 muted text, Zinc-950 body text, and the Nova
6
6
  * control geometry (32px controls, `--radius` corners, a 3px translucent focus ring).
7
+ *
8
+ * The accent roles read `--zx-accent-*` rather than the green ramp directly, which is what lets
9
+ * `tokens/themes.css` retheme them. Status colours keep their own ramps on purpose — see the note
10
+ * in that file.
7
11
  */
8
12
  :root,
9
13
  [data-zx-theme="light"] {
@@ -14,7 +18,7 @@
14
18
  --zx-color-bg-control: var(--zx-gray-0);
15
19
  --zx-color-bg-muted: var(--zx-gray-100);
16
20
  --zx-color-bg-hover: var(--zx-gray-100);
17
- --zx-color-bg-selected: color-mix(in srgb, var(--zx-green-600) 10%, var(--zx-gray-0));
21
+ --zx-color-bg-selected: color-mix(in srgb, var(--zx-accent-600) 10%, var(--zx-gray-0));
18
22
  --zx-color-bg-backdrop: color-mix(in srgb, var(--zx-gray-950) 50%, transparent);
19
23
  --zx-color-border: var(--zx-gray-200);
20
24
  --zx-color-border-strong: var(--zx-gray-300);
@@ -23,10 +27,10 @@
23
27
  --zx-color-text-muted: var(--zx-gray-500);
24
28
  --zx-color-text-placeholder: var(--zx-gray-400);
25
29
  --zx-color-text-invert: var(--zx-gray-50);
26
- --zx-color-accent: var(--zx-green-600);
27
- --zx-color-accent-hover: var(--zx-green-700);
28
- --zx-color-accent-active: var(--zx-green-700);
29
- --zx-color-accent-subtle: color-mix(in srgb, var(--zx-green-600) 10%, var(--zx-gray-0));
30
+ --zx-color-accent: var(--zx-accent-600);
31
+ --zx-color-accent-hover: var(--zx-accent-700);
32
+ --zx-color-accent-active: var(--zx-accent-700);
33
+ --zx-color-accent-subtle: color-mix(in srgb, var(--zx-accent-600) 10%, var(--zx-gray-0));
30
34
  --zx-color-on-accent: var(--zx-gray-0);
31
35
  --zx-color-danger: var(--zx-red-500);
32
36
  --zx-color-danger-bg: color-mix(in srgb, var(--zx-red-500) 10%, var(--zx-gray-0));
@@ -0,0 +1,72 @@
1
+ /*
2
+ * Standard theme presets.
3
+ *
4
+ * A preset repoints the tier-1 accent ramp — nothing else. The semantic tier picks its own stop
5
+ * off that ramp per polarity (600/700 on light surfaces, 300/400/500 on dark ones), so five
6
+ * declarations retheme light, dark, and auto at once, and every component follows without a
7
+ * second mapping to keep in sync.
8
+ *
9
+ * Set the preset alongside the theme: `<html data-zx-theme="dark" data-zx-preset="ocean">`.
10
+ * `zx` is the default and never needs stating; the attribute exists so an application can pin it.
11
+ *
12
+ * Status colours are deliberately not part of a preset. Danger, warning, and success mean the
13
+ * same thing whatever a product's brand colour is, and a preset that repainted them would leave
14
+ * an application with no reliable way to say "this failed".
15
+ *
16
+ * Every 600 clears 4.5:1 against white and against the light page, and every 400 clears it
17
+ * against the dark page, which is what keeps `--zx-color-on-accent` correct in both polarities.
18
+ */
19
+
20
+ /* Zx — the ZeyOS brand green on the shadcn Emerald ramp. The default. */
21
+ [data-zx-preset="zx"] {
22
+ --zx-accent-300: #5ee9b5;
23
+ --zx-accent-400: #21cc75;
24
+ --zx-accent-500: #009966;
25
+ --zx-accent-600: #007a55;
26
+ --zx-accent-700: #006045;
27
+ }
28
+
29
+ /* ZeyOS — the gold of the ZeyOS product marque, anchored at 400. */
30
+ [data-zx-preset="zeyos"] {
31
+ --zx-accent-300: #ffd9a8;
32
+ --zx-accent-400: #f7bc60;
33
+ --zx-accent-500: #d0952f;
34
+ --zx-accent-600: #96690f;
35
+ --zx-accent-700: #6f4e0b;
36
+ }
37
+
38
+ /* Ocean — the palette's own blue ramp, for products that read as utilities rather than brands. */
39
+ [data-zx-preset="ocean"] {
40
+ --zx-accent-300: #8ec5ff;
41
+ --zx-accent-400: #51a2ff;
42
+ --zx-accent-500: #2b7fff;
43
+ --zx-accent-600: #155dfc;
44
+ --zx-accent-700: #1447e6;
45
+ }
46
+
47
+ /* Violet */
48
+ [data-zx-preset="violet"] {
49
+ --zx-accent-300: #c4b4ff;
50
+ --zx-accent-400: #a684ff;
51
+ --zx-accent-500: #8e51ff;
52
+ --zx-accent-600: #7f22fe;
53
+ --zx-accent-700: #7008e7;
54
+ }
55
+
56
+ /* Rose */
57
+ [data-zx-preset="rose"] {
58
+ --zx-accent-300: #ffa1ad;
59
+ --zx-accent-400: #ff637e;
60
+ --zx-accent-500: #ff2056;
61
+ --zx-accent-600: #dc0039;
62
+ --zx-accent-700: #a80030;
63
+ }
64
+
65
+ /* Slate — a near-neutral accent, for screens where the data should carry the colour. */
66
+ [data-zx-preset="slate"] {
67
+ --zx-accent-300: #cad5e2;
68
+ --zx-accent-400: #90a1b9;
69
+ --zx-accent-500: #62748e;
70
+ --zx-accent-600: #45556c;
71
+ --zx-accent-700: #314158;
72
+ }
package/styles/zx.css CHANGED
@@ -1,4 +1,5 @@
1
1
  @import url("./tokens/global.css");
2
+ @import url("./tokens/themes.css");
2
3
  @import url("./tokens/semantic.css");
3
4
  @import url("./tokens/dark.css");
4
5
  @import url("./tokens/density.css");