@zeyos/zx 4.0.0 → 4.1.1
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.
- package/CHANGELOG.md +61 -0
- package/DESIGN-SYSTEM.md +59 -3
- package/dist/types/components/app-icon/app-icon.d.ts +88 -0
- package/dist/types/components/filter/filter-model.d.ts +87 -0
- package/dist/types/components/filter/filter.d.ts +331 -0
- package/dist/types/components/select/select.d.ts +46 -4
- package/dist/types/components/table/table.d.ts +103 -0
- package/dist/types/components/tag-picker/tag-picker.d.ts +16 -0
- package/dist/types/core/dom.d.ts +1 -1
- package/dist/types/core/util.d.ts +9 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/types/zeyos/icons.d.ts +21 -4
- package/dist/types/zeyos/index.d.ts +1 -1
- package/dist/types/zeyos/select.d.ts +2 -0
- package/dist/zx-zeyos.esm.js +703 -75
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +564 -9
- package/dist/zx.esm.js +1569 -115
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +1569 -115
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +379 -0
- package/docs/llms.md +97 -18
- package/docs/llms.txt +11 -9
- package/package.json +1 -1
- package/src/components/app-icon/app-icon.css +189 -0
- package/src/components/app-icon/app-icon.js +137 -0
- package/src/components/button/button.css +34 -2
- package/src/components/card/card.css +25 -3
- package/src/components/filter/filter-model.js +226 -0
- package/src/components/filter/filter.css +147 -0
- package/src/components/filter/filter.js +554 -0
- package/src/components/grid/grid.css +1 -1
- package/src/components/grid/grid.js +4 -1
- package/src/components/message/message.css +40 -3
- package/src/components/select/select.css +17 -0
- package/src/components/select/select.js +105 -24
- package/src/components/table/table.css +85 -0
- package/src/components/table/table.js +418 -24
- package/src/components/tag-picker/tag-picker.css +19 -0
- package/src/components/tag-picker/tag-picker.js +42 -5
- package/src/core/dom.js +1 -1
- package/src/core/util.js +20 -0
- package/src/index.js +7 -1
- package/src/zeyos/icons.js +37 -24
- package/src/zeyos/index.js +1 -1
- package/src/zeyos/select.js +6 -5
- package/styles/tokens/semantic.css +17 -0
- package/styles/tokens/themes.css +2 -2
- package/styles/zx.css +2 -0
package/docs/llms.txt
CHANGED
|
@@ -22,30 +22,32 @@ exports and as `window.zx.*`.
|
|
|
22
22
|
|
|
23
23
|
## Components
|
|
24
24
|
- Inputs and identity: `button()`, `buttonGroup()`, `badge()`/`badgeGroup()`, `CheckButton`,
|
|
25
|
-
`Toggle`, `Search`, `
|
|
26
|
-
`filter: false|'local'|async`; priority, status, permission, and
|
|
27
|
-
`Checklist`, `TagPicker
|
|
25
|
+
`Toggle`, `Search`, `AppIcon` (+ `zeyosAppIcon()` presets), `Avatar`,
|
|
26
|
+
`Select` (APG combobox; `filter: false|'local'|async`; priority, status, permission, and
|
|
27
|
+
`Select.entity()` presets), `Checklist`, `TagPicker` (icon/color identity), `NumberField`, `Rating`, `Slider`,
|
|
28
28
|
`copyButton()`/`CopyInput`, `DatePicker`, `MonthPicker`, `TimePicker`,
|
|
29
29
|
`Datebox`/`DateTimeBox`, `DateRangePicker`/`DateRangeBox`, `Timebox`.
|
|
30
30
|
- Overlays: `Message` (toasts/inline/progress), `Modal`, `Dialog` (+ `alert`/`confirm`/`prompt` Promises), `Sheet` (edge-anchored Dialog; `side`, `modal: true|'trap-focus'|false`, `backdrop`, `snap` detents, swipe-to-dismiss), `SheetStack` (drill-down; `stack`/`cascade`), `Dropdown`, `MenuButton`, `ContextMenu` (right-click; `selector` for per-row menus), `Tooltip`/`tooltip()`/`describe()`.
|
|
31
|
-
- Data: `Table` (sortable, selectable, sticky header, opt-in inline cell/row editing
|
|
32
|
-
|
|
33
|
-
`
|
|
31
|
+
- Data: `Table` (sortable, selectable, sticky header, opt-in inline cell/row editing, row reorder,
|
|
32
|
+
column visibility), `Grid` with single-click editing and `Grid.BillingItems()`, engine-neutral
|
|
33
|
+
`Chart` with injected `ChartJsAdapter`, backend-neutral nested `Filter`, simple client-side
|
|
34
|
+
`DataFilter`, `Pagination` (+ pure `paginationRange()`), `TreeView` (APG tree),
|
|
34
35
|
`Finder` (Miller columns).
|
|
35
36
|
- Forms: `Form`, `Fieldset`, `Questionnaire` (guided one-question-at-a-time flow with branching), `Field` (type registry), `ValueList`, `MultiValueEditor`, `FieldUpload`.
|
|
36
37
|
- Layout: `stack()`/`grid()`/`aspect()` (spacing, an intrinsically reflowing grid, a fixed-ratio
|
|
37
38
|
box), `Groupbox`, `Card` (media/title link/actions/footer), `Panel` (header/footer action
|
|
38
39
|
buttons), `MasterPanel`, `Tabbox`
|
|
39
40
|
(`variant: 'divided'|'bracket'|'line'|'segmented'`), `NavigationBar`, `AppSidebar` (expanded and
|
|
40
|
-
minimized vertical states plus horizontal rail layouts), `
|
|
41
|
+
minimized vertical states plus horizontal rail layouts), `AccountMenu`, `Launcher`,
|
|
42
|
+
`spinner()`/`ProgressBar`/`InlineLoading`, `skeleton()`/`skeletonText()`/`skeletonTable()`,
|
|
43
|
+
`Toolbar` (APG toolbar with overflow menu), `SplitView`, `Dock`
|
|
41
44
|
(collapsible resizable pane stack; tab groups, regions, adopts a `Sheet`), `Stepper`,
|
|
42
45
|
`Breadcrumb`, `emptyState()`.
|
|
43
|
-
- Feedback: `spinner()` (unknown duration), `ProgressBar` (known share; determinate or indeterminate), `InlineLoading` (a wait resolving into its outcome in place), `skeleton()`/`skeletonText()`/`skeletonTable()` (placeholders shaped like the content; `aria-hidden`, set `aria-busy` on the region).
|
|
44
46
|
- Core: `Component`, `h`/`__`/`icon`, `position`, `breakpoints`/`breakpointOf`/`matchBreakpoint`/`onBreakpoint` (named width bands, observed on an element or the window), `Http`/`zeyosService`, i18n, date utils, `defineElements` (declarative `<zx-*>` custom elements).
|
|
45
47
|
- Helpers: `formatNumber`/`formatCurrency`/`formatPercent`/`formatFileSize`/`formatRelativeTime` (locale-aware, cached `Intl`), `debounce`/`throttle`, `groupBy`/`sortBy`/`uniqueBy`, `escapeRegExp`/`highlightMatch`, `storage()` (namespaced JSON, degrades to memory), `toCsv`/`downloadBlob`/`copyToClipboard`, `truncate()`/`isTruncated()` (line clamp with the full value on hover).
|
|
46
48
|
|
|
47
49
|
## Theming
|
|
48
|
-
Load `zx.css` once; set `data-zx-theme` (`light`|`dark`|`auto`) and `data-zx-density` (`cozy`|`compact`) on any ancestor. Define a product theme by overriding semantic `--zx-color-*` / `--zx-control-*` tokens under `[data-zx-theme="name"]`. Never use tier-1 palette tokens or raw color literals in application CSS.
|
|
50
|
+
Load `zx.css` once; set `data-zx-theme` (`light`|`dark`|`auto`) and `data-zx-density` (`cozy`|`compact`) on any ancestor. Define a product theme by overriding semantic `--zx-color-*` / `--zx-control-*` tokens under `[data-zx-theme="name"]`. The theme studio controls accent, neutral tint, density, font, type size, radius, control height, and CSS-first glass material. Never use tier-1 palette tokens or raw color literals in application CSS.
|
|
49
51
|
|
|
50
52
|
## Talking to ZeyOS
|
|
51
53
|
Use the dedicated ZeyOS client library **`@zeyos/client`** (`npm install @zeyos/client`) for ZeyOS business data: `createZeyosClient({ platform, auth })`, typed `client.api.list/get/create/update<Resource>` over the full OpenAPI surface, and `normalizeListResult`. Zx accepts the client by injection and does not bundle it. See the `website/layouts/zeyos-invoices.layout.js` layout for a Zx-components-on-@zeyos/client reference. Zx also ships a minimal `Http`/`zeyosService` for ad-hoc in-app `remotecall`, but `@zeyos/client` is the recommended default.
|
package/package.json
CHANGED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
.zx-app-icon {
|
|
2
|
+
--zx-app-icon-size: 36px;
|
|
3
|
+
--zx-app-icon-glyph-size: 52%;
|
|
4
|
+
--zx-app-icon-color: var(--zx-color-accent);
|
|
5
|
+
position: relative;
|
|
6
|
+
isolation: isolate;
|
|
7
|
+
display: inline-grid;
|
|
8
|
+
place-items: center;
|
|
9
|
+
flex: none;
|
|
10
|
+
inline-size: var(--zx-app-icon-size);
|
|
11
|
+
block-size: var(--zx-app-icon-size);
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
border: 1px solid var(--zx-color-glass-border);
|
|
14
|
+
border-radius: max(var(--zx-radius-sm), calc(var(--zx-app-icon-size) * .12));
|
|
15
|
+
background: var(--zx-app-icon-color);
|
|
16
|
+
box-shadow: var(--zx-app-icon-shadow);
|
|
17
|
+
color: var(--zx-app-icon-glyph, var(--zx-color-app-icon-glyph));
|
|
18
|
+
font-size: var(--zx-app-icon-size);
|
|
19
|
+
vertical-align: middle;
|
|
20
|
+
transform: translateZ(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.zx-app-icon[data-glass="subtle"],
|
|
24
|
+
.zx-app-icon[data-glass="strong"] {
|
|
25
|
+
background:
|
|
26
|
+
linear-gradient(145deg, var(--zx-color-glass-highlight-strong), transparent 38%),
|
|
27
|
+
linear-gradient(to bottom right, transparent 42%, var(--zx-color-app-icon-depth)),
|
|
28
|
+
color-mix(in srgb, var(--zx-app-icon-color) var(--zx-glass-color-strength), transparent);
|
|
29
|
+
-webkit-backdrop-filter: blur(calc(var(--zx-glass-blur) * .55)) saturate(var(--zx-glass-saturation));
|
|
30
|
+
backdrop-filter: blur(calc(var(--zx-glass-blur) * .55)) saturate(var(--zx-glass-saturation));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.zx-app-icon[data-glass="strong"] {
|
|
34
|
+
-webkit-backdrop-filter: blur(var(--zx-glass-blur)) saturate(var(--zx-glass-saturation));
|
|
35
|
+
backdrop-filter: blur(var(--zx-glass-blur)) saturate(var(--zx-glass-saturation));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.zx-app-icon[data-glass="none"] {
|
|
39
|
+
border-color: var(--zx-color-border);
|
|
40
|
+
box-shadow: none;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.zx-app-icon[data-glass="none"] .zx-app-icon__shine { display: none; }
|
|
44
|
+
.zx-app-icon[data-glass="none"]::after { display: none; }
|
|
45
|
+
|
|
46
|
+
.zx-app-icon[data-selected] {
|
|
47
|
+
outline: 2px solid var(--zx-color-bg-page);
|
|
48
|
+
box-shadow: 0 0 0 3px var(--zx-color-ring), var(--zx-overlay-shadow);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.zx-app-icon__shine {
|
|
52
|
+
position: absolute;
|
|
53
|
+
z-index: 1;
|
|
54
|
+
inset: 1px 13% auto;
|
|
55
|
+
block-size: 38%;
|
|
56
|
+
border-radius: 999px;
|
|
57
|
+
background: linear-gradient(to bottom, var(--zx-color-glass-highlight-strong), transparent);
|
|
58
|
+
filter: blur(1px);
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.zx-app-icon::after {
|
|
63
|
+
content: "";
|
|
64
|
+
position: absolute;
|
|
65
|
+
z-index: 1;
|
|
66
|
+
inset: 0;
|
|
67
|
+
border-radius: inherit;
|
|
68
|
+
box-shadow: inset 0 0 0 1px var(--zx-color-glass-highlight);
|
|
69
|
+
pointer-events: none;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.zx-app-icon__glyph {
|
|
73
|
+
position: absolute;
|
|
74
|
+
z-index: 2;
|
|
75
|
+
inset-block-start: 50%;
|
|
76
|
+
inset-inline-start: 50%;
|
|
77
|
+
display: grid;
|
|
78
|
+
place-items: center;
|
|
79
|
+
inline-size: var(--zx-app-icon-glyph-size);
|
|
80
|
+
block-size: var(--zx-app-icon-glyph-size);
|
|
81
|
+
margin: 0;
|
|
82
|
+
padding: 0;
|
|
83
|
+
font-size: var(--zx-app-icon-glyph-size);
|
|
84
|
+
line-height: 1;
|
|
85
|
+
filter: drop-shadow(var(--zx-glass-glyph-shadow));
|
|
86
|
+
transform: translate(-50%, -50%);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.zx-app-icon__glyph > .zx-icon,
|
|
90
|
+
.zx-app-icon__glyph > svg,
|
|
91
|
+
.zx-app-icon__glyph > i {
|
|
92
|
+
display: grid;
|
|
93
|
+
place-items: center;
|
|
94
|
+
inline-size: 1em;
|
|
95
|
+
block-size: 1em;
|
|
96
|
+
margin: 0;
|
|
97
|
+
color: inherit;
|
|
98
|
+
font-size: 1em;
|
|
99
|
+
line-height: 1;
|
|
100
|
+
text-align: center;
|
|
101
|
+
vertical-align: middle;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.zx-app-icon__glyph > i::before {
|
|
105
|
+
display: block;
|
|
106
|
+
line-height: 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.zx-app-icon__glyph svg [fill]:not([fill="none"]) {
|
|
110
|
+
fill: currentColor;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.zx-app-icon__glyph svg [stroke]:not([stroke="none"]) {
|
|
114
|
+
stroke: currentColor;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.zx-app-icon__badge {
|
|
118
|
+
position: absolute;
|
|
119
|
+
z-index: 3;
|
|
120
|
+
inset-block-end: 1px;
|
|
121
|
+
inset-inline-end: 1px;
|
|
122
|
+
display: grid;
|
|
123
|
+
place-items: center;
|
|
124
|
+
min-inline-size: 14px;
|
|
125
|
+
block-size: 14px;
|
|
126
|
+
padding-inline: 3px;
|
|
127
|
+
border: 1px solid var(--zx-color-bg-raised);
|
|
128
|
+
border-radius: var(--zx-radius-full);
|
|
129
|
+
background: var(--zx-color-danger);
|
|
130
|
+
color: var(--zx-color-text-invert);
|
|
131
|
+
font-size: 9px;
|
|
132
|
+
font-weight: 700;
|
|
133
|
+
line-height: 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.zx-app-icon:hover {
|
|
137
|
+
border-color: var(--zx-color-glass-highlight-strong);
|
|
138
|
+
box-shadow: var(--zx-app-icon-shadow-hover);
|
|
139
|
+
filter: brightness(1.06) saturate(1.08);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.zx-app-icon:focus-visible,
|
|
143
|
+
:is(a, button, [role="button"]):focus-visible > .zx-app-icon {
|
|
144
|
+
outline: none;
|
|
145
|
+
box-shadow: var(--zx-focus-ring), var(--zx-app-icon-shadow-hover);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
149
|
+
.zx-app-icon {
|
|
150
|
+
transition: border-color var(--zx-dur-fast) var(--zx-ease),
|
|
151
|
+
box-shadow var(--zx-dur-fast) var(--zx-ease),
|
|
152
|
+
filter var(--zx-dur-fast) var(--zx-ease),
|
|
153
|
+
transform var(--zx-dur-fast) var(--zx-ease);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.zx-app-icon:hover {
|
|
157
|
+
transform: translateY(-2px) scale(1.035);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
162
|
+
.zx-app-icon[data-glass="strong"] {
|
|
163
|
+
background: var(--zx-app-icon-color);
|
|
164
|
+
-webkit-backdrop-filter: none;
|
|
165
|
+
backdrop-filter: none;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.zx-app-icon[data-glass="subtle"] {
|
|
169
|
+
background: var(--zx-app-icon-color);
|
|
170
|
+
-webkit-backdrop-filter: none;
|
|
171
|
+
backdrop-filter: none;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@media (forced-colors: active) {
|
|
176
|
+
.zx-app-icon {
|
|
177
|
+
border-color: ButtonText;
|
|
178
|
+
background: ButtonFace;
|
|
179
|
+
color: ButtonText;
|
|
180
|
+
box-shadow: none;
|
|
181
|
+
filter: none;
|
|
182
|
+
transform: none;
|
|
183
|
+
-webkit-backdrop-filter: none;
|
|
184
|
+
backdrop-filter: none;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.zx-app-icon::after,
|
|
188
|
+
.zx-app-icon__shine { display: none; }
|
|
189
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Component } from '../../core/component.js';
|
|
2
|
+
import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
3
|
+
import { icon as renderIcon } from '../../core/icons.js';
|
|
4
|
+
import { isCssColor } from '../../core/util.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} AppIconOptions
|
|
8
|
+
* @property {string|Node|(()=>string|Node)|null} [icon=null] Icon name, node, or lazy renderer.
|
|
9
|
+
* @property {string|null} [color=null] Identity colour; falls back to the accent colour.
|
|
10
|
+
* @property {number|string} [size=36] Edge length in pixels or any CSS length.
|
|
11
|
+
* @property {number|string} [iconSize='52%'] Glyph size.
|
|
12
|
+
* @property {string|null} [label=null] Accessible image label; null makes the icon decorative.
|
|
13
|
+
* @property {string|number|null} [badge=null] Optional corner badge.
|
|
14
|
+
* @property {boolean} [selected=false] Whether the icon represents the active application.
|
|
15
|
+
* @property {false|'subtle'|'strong'} [glass='subtle'] CSS-first glass treatment.
|
|
16
|
+
* @property {string|string[]} [class] Additional class names.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Stable application-identity tile with progressive CSS glass treatment.
|
|
21
|
+
* @extends {Component<AppIconOptions>}
|
|
22
|
+
*/
|
|
23
|
+
export class AppIcon extends Component {
|
|
24
|
+
static cssName = 'app-icon';
|
|
25
|
+
|
|
26
|
+
/** @type {Readonly<AppIconOptions>} */
|
|
27
|
+
static defaults = {
|
|
28
|
+
icon: null,
|
|
29
|
+
color: null,
|
|
30
|
+
size: 36,
|
|
31
|
+
iconSize: '52%',
|
|
32
|
+
label: null,
|
|
33
|
+
badge: null,
|
|
34
|
+
selected: false,
|
|
35
|
+
glass: 'subtle',
|
|
36
|
+
class: []
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** @returns {HTMLElement} */
|
|
40
|
+
render() {
|
|
41
|
+
this._createdRoot = this.el === null;
|
|
42
|
+
const root = /** @type {HTMLElement} */ (this.el ?? h('span'));
|
|
43
|
+
this.el = root;
|
|
44
|
+
this._snapshot = this._createdRoot ? null : snapshotTarget(root);
|
|
45
|
+
this._state = { ...this.options };
|
|
46
|
+
this._appliedClasses = [];
|
|
47
|
+
this._sync();
|
|
48
|
+
return root;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Updates identity or presentation without replacing the root. @param {Partial<AppIconOptions>} values @returns {this} */
|
|
52
|
+
set(values = {}) {
|
|
53
|
+
if (!values || typeof values !== 'object') return this;
|
|
54
|
+
Object.assign(this._state, values);
|
|
55
|
+
this._sync();
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Restores an enhanced target. @returns {void} */
|
|
60
|
+
destroy() {
|
|
61
|
+
const root = this.el;
|
|
62
|
+
const snapshot = this._snapshot;
|
|
63
|
+
super.destroy();
|
|
64
|
+
if (!this._createdRoot && root) restoreTarget(root, snapshot);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @returns {void} */
|
|
68
|
+
_sync() {
|
|
69
|
+
const state = this._state;
|
|
70
|
+
this.el.classList.remove(...this._appliedClasses);
|
|
71
|
+
this._appliedClasses = [state.class].flat()
|
|
72
|
+
.flatMap((value) => String(value ?? '').split(/\s+/)).filter(Boolean);
|
|
73
|
+
this.el.classList.add(...this._appliedClasses);
|
|
74
|
+
const size = cssLength(state.size, '36px');
|
|
75
|
+
const glyphSize = cssLength(state.iconSize, '52%');
|
|
76
|
+
this.el.style.setProperty('--zx-app-icon-size', size);
|
|
77
|
+
this.el.style.setProperty('--zx-app-icon-glyph-size', glyphSize);
|
|
78
|
+
if (isCssColor(state.color)) this.el.style.setProperty('--zx-app-icon-color', String(state.color));
|
|
79
|
+
else this.el.style.removeProperty('--zx-app-icon-color');
|
|
80
|
+
this.el.dataset.glass = normalizeGlass(state.glass);
|
|
81
|
+
this.el.toggleAttribute('data-selected', Boolean(state.selected));
|
|
82
|
+
|
|
83
|
+
const label = state.label == null ? '' : String(state.label).trim();
|
|
84
|
+
if (label) {
|
|
85
|
+
this.el.setAttribute('role', 'img');
|
|
86
|
+
this.el.setAttribute('aria-label', label);
|
|
87
|
+
this.el.removeAttribute('aria-hidden');
|
|
88
|
+
} else {
|
|
89
|
+
this.el.removeAttribute('role');
|
|
90
|
+
this.el.removeAttribute('aria-label');
|
|
91
|
+
this.el.setAttribute('aria-hidden', 'true');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const visual = resolveVisual(state.icon);
|
|
95
|
+
const badge = state.badge == null || state.badge === '' ? null : h('span', {
|
|
96
|
+
class: 'zx-app-icon__badge', ariaHidden: 'true'
|
|
97
|
+
}, String(state.badge));
|
|
98
|
+
const children = [
|
|
99
|
+
h('span', { class: 'zx-app-icon__shine', ariaHidden: 'true' }),
|
|
100
|
+
h('span', { class: 'zx-app-icon__glyph', ariaHidden: 'true' }, visual)
|
|
101
|
+
];
|
|
102
|
+
// Native replaceChildren() stringifies nullish arguments. Only append a badge when it exists,
|
|
103
|
+
// otherwise the icon gets a visible "null" text node that can also disturb optical centring.
|
|
104
|
+
if (badge) children.push(badge);
|
|
105
|
+
this.el.replaceChildren(...children);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Creates an AppIcon element without retaining a component lifecycle handle.
|
|
111
|
+
* @param {AppIconOptions} [options={}] Icon options.
|
|
112
|
+
* @returns {HTMLElement}
|
|
113
|
+
*/
|
|
114
|
+
export function appIcon(options = {}) {
|
|
115
|
+
return new AppIcon(null, options).toElement();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** @param {AppIconOptions['icon']} value @returns {Node|string|null} */
|
|
119
|
+
function resolveVisual(value) {
|
|
120
|
+
const resolved = typeof value === 'function' ? value() : value;
|
|
121
|
+
if (typeof resolved === 'string') return renderIcon(resolved, { size: '1em' });
|
|
122
|
+
return resolved && typeof resolved === 'object' && typeof resolved.nodeType === 'number'
|
|
123
|
+
? /** @type {Node} */ (resolved) : null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** @param {unknown} value @param {string} fallback @returns {string} */
|
|
127
|
+
function cssLength(value, fallback) {
|
|
128
|
+
if (typeof value === 'number' && Number.isFinite(value)) return `${Math.max(1, value)}px`;
|
|
129
|
+
const text = String(value ?? '').trim();
|
|
130
|
+
return text && !/[;{}]/.test(text) ? text : fallback;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** @param {unknown} value @returns {'none'|'subtle'|'strong'} */
|
|
134
|
+
function normalizeGlass(value) {
|
|
135
|
+
if (value === false || value === 'none') return 'none';
|
|
136
|
+
return value === 'strong' ? 'strong' : 'subtle';
|
|
137
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
min-block-size: var(--zx-control-height);
|
|
7
7
|
border: 1px solid var(--zx-color-border-control);
|
|
8
8
|
border-radius: var(--zx-button-radius, var(--zx-control-radius));
|
|
9
|
-
background: var(--zx-color-
|
|
9
|
+
background: var(--zx-color-glass-control);
|
|
10
10
|
color: var(--zx-color-text);
|
|
11
11
|
padding-inline: var(--zx-control-pad-x);
|
|
12
12
|
font: inherit;
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
font-weight: var(--zx-control-font-weight);
|
|
15
15
|
line-height: 1;
|
|
16
16
|
cursor: pointer;
|
|
17
|
+
-webkit-backdrop-filter: blur(var(--zx-glass-blur)) saturate(var(--zx-glass-saturation));
|
|
18
|
+
backdrop-filter: blur(var(--zx-glass-blur)) saturate(var(--zx-glass-saturation));
|
|
19
|
+
box-shadow: var(--zx-glass-control-shadow);
|
|
17
20
|
transition: color var(--zx-dur-fast) var(--zx-ease),
|
|
18
21
|
background-color var(--zx-dur-fast) var(--zx-ease),
|
|
19
22
|
border-color var(--zx-dur-fast) var(--zx-ease),
|
|
@@ -21,7 +24,7 @@
|
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
.zx-btn:hover:not(:disabled) {
|
|
24
|
-
background: var(--zx-color-
|
|
27
|
+
background: var(--zx-color-glass-control-hover);
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
.zx-btn:active:not(:disabled) {
|
|
@@ -66,6 +69,9 @@
|
|
|
66
69
|
.zx-btn[data-kind="ghost"] {
|
|
67
70
|
border-color: transparent;
|
|
68
71
|
background: none;
|
|
72
|
+
-webkit-backdrop-filter: none;
|
|
73
|
+
backdrop-filter: none;
|
|
74
|
+
box-shadow: none;
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
.zx-btn[data-kind="ghost"]:hover:not(:disabled) {
|
|
@@ -101,3 +107,29 @@
|
|
|
101
107
|
.zx-btn-group > .zx-btn:focus-visible {
|
|
102
108
|
z-index: 1;
|
|
103
109
|
}
|
|
110
|
+
|
|
111
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
112
|
+
.zx-btn {
|
|
113
|
+
background: var(--zx-color-bg-control);
|
|
114
|
+
-webkit-backdrop-filter: none;
|
|
115
|
+
backdrop-filter: none;
|
|
116
|
+
box-shadow: none;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@media (forced-colors: active) {
|
|
121
|
+
.zx-btn {
|
|
122
|
+
border-color: ButtonText;
|
|
123
|
+
background: ButtonFace;
|
|
124
|
+
color: ButtonText;
|
|
125
|
+
box-shadow: none;
|
|
126
|
+
-webkit-backdrop-filter: none;
|
|
127
|
+
backdrop-filter: none;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.zx-btn[data-kind="primary"] {
|
|
131
|
+
border-color: Highlight;
|
|
132
|
+
background: Highlight;
|
|
133
|
+
color: HighlightText;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -14,9 +14,11 @@
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
.zx-card[data-variant="raised"] {
|
|
17
|
-
border: 1px solid
|
|
18
|
-
background: var(--zx-color-
|
|
19
|
-
|
|
17
|
+
border: 1px solid var(--zx-color-glass-border);
|
|
18
|
+
background: var(--zx-color-glass-surface);
|
|
19
|
+
-webkit-backdrop-filter: blur(var(--zx-glass-blur)) saturate(var(--zx-glass-saturation));
|
|
20
|
+
backdrop-filter: blur(var(--zx-glass-blur)) saturate(var(--zx-glass-saturation));
|
|
21
|
+
box-shadow: var(--zx-glass-raised-shadow);
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
.zx-card[data-variant="filled"] {
|
|
@@ -24,6 +26,26 @@
|
|
|
24
26
|
background: var(--zx-color-bg-muted);
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
30
|
+
.zx-card[data-variant="raised"] {
|
|
31
|
+
border-color: var(--zx-color-border);
|
|
32
|
+
background: var(--zx-color-bg-surface);
|
|
33
|
+
-webkit-backdrop-filter: none;
|
|
34
|
+
backdrop-filter: none;
|
|
35
|
+
box-shadow: var(--zx-overlay-shadow);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@media (forced-colors: active) {
|
|
40
|
+
.zx-card[data-variant="raised"] {
|
|
41
|
+
border-color: ButtonText;
|
|
42
|
+
background: Canvas;
|
|
43
|
+
box-shadow: none;
|
|
44
|
+
-webkit-backdrop-filter: none;
|
|
45
|
+
backdrop-filter: none;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
27
49
|
.zx-card[data-linked="true"]:hover {
|
|
28
50
|
background: var(--zx-color-bg-hover);
|
|
29
51
|
}
|