@zeyos/zx 3.0.0 → 4.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.
- package/CHANGELOG.md +51 -0
- package/DESIGN-SYSTEM.md +28 -68
- package/README.md +16 -37
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +106 -32
- package/dist/types/components/card/card.d.ts +128 -0
- package/dist/types/components/chart/chart.d.ts +1 -0
- package/dist/types/components/launcher/launcher.d.ts +65 -6
- package/dist/types/components/select/select.d.ts +51 -0
- package/dist/types/components/table/table.d.ts +4 -3
- package/dist/types/core/dom.d.ts +8 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/{components/app-rail → internal}/app-rail.d.ts +34 -19
- package/dist/types/zeyos/icons.d.ts +5 -0
- package/dist/types/zeyos/index.d.ts +2 -1
- package/dist/types/zeyos/launcher.d.ts +147 -0
- package/dist/types/zeyos/select.d.ts +39 -0
- package/dist/zx-zeyos.esm.js +1324 -46
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +400 -12
- package/dist/zx.esm.js +1059 -189
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +1059 -189
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +258 -145
- package/docs/llms.md +93 -112
- package/docs/llms.txt +16 -15
- package/package.json +8 -17
- package/src/components/app-sidebar/app-sidebar.css +12 -0
- package/src/components/app-sidebar/app-sidebar.js +310 -110
- package/src/components/card/card.css +149 -0
- package/src/components/card/card.js +285 -0
- package/src/components/chart/chart.js +8 -3
- package/src/components/grid/grid.js +10 -3
- package/src/components/launcher/launcher.css +113 -3
- package/src/components/launcher/launcher.js +234 -26
- package/src/components/select/field-adapter.js +9 -4
- package/src/components/select/select.css +144 -10
- package/src/components/select/select.js +259 -27
- package/src/components/table/table.js +8 -7
- package/src/core/dom.js +20 -0
- package/src/index.js +2 -1
- package/src/{components/app-rail → internal}/app-rail.js +80 -30
- package/src/zeyos/icons.js +8 -3
- package/src/zeyos/index.js +7 -1
- package/src/zeyos/launcher.js +338 -0
- package/src/zeyos/modules.js +1 -0
- package/src/zeyos/select.js +115 -1
- package/styles/tokens/global.css +1 -1
- package/styles/tokens/modules.css +1 -0
- package/styles/zx.css +1 -0
- package/MIGRATION.md +0 -749
- package/dist/types/compat/base.d.ts +0 -156
- package/dist/types/compat/globals.d.ts +0 -20
- package/dist/types/compat/index.d.ts +0 -128
- package/dist/types/compat/map/basic.d.ts +0 -175
- package/dist/types/compat/map/data.d.ts +0 -103
- package/dist/types/compat/map/date-time.d.ts +0 -80
- package/dist/types/compat/map/factory.d.ts +0 -9
- package/dist/types/compat/map/form.d.ts +0 -98
- package/dist/types/compat/map/helpers.d.ts +0 -12
- package/dist/types/compat/map/http.d.ts +0 -36
- package/dist/types/compat/map/layout.d.ts +0 -106
- package/dist/types/compat/map/options.d.ts +0 -80
- package/dist/types/compat/map/select.d.ts +0 -95
- package/dist/types/compat/map/stubs.d.ts +0 -32
- package/dist/types/compat/map/utilities.d.ts +0 -37
- package/dist/types/compat-entry.d.ts +0 -5
- package/dist/zx-compat.esm.js +0 -17848
- package/dist/zx-compat.esm.min.js +0 -1
- package/dist/zx-compat.global.js +0 -29008
- package/dist/zx-compat.global.min.js +0 -3
- package/src/compat/base.js +0 -291
- package/src/compat/globals.js +0 -167
- package/src/compat/index.js +0 -84
- package/src/compat/map/basic.js +0 -464
- package/src/compat/map/data.js +0 -254
- package/src/compat/map/date-time.js +0 -177
- package/src/compat/map/factory.js +0 -45
- package/src/compat/map/form.js +0 -330
- package/src/compat/map/helpers.js +0 -46
- package/src/compat/map/http.js +0 -117
- package/src/compat/map/layout.js +0 -256
- package/src/compat/map/options.js +0 -267
- package/src/compat/map/select.js +0 -198
- package/src/compat/map/stubs.js +0 -26
- package/src/compat/map/utilities.js +0 -113
- package/src/compat-entry.js +0 -11
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
.zx-card {
|
|
2
|
+
--zx-card-media-size: 8rem;
|
|
3
|
+
position: relative;
|
|
4
|
+
inline-size: 100%;
|
|
5
|
+
container-type: inline-size;
|
|
6
|
+
overflow: hidden;
|
|
7
|
+
border-radius: var(--zx-radius-lg);
|
|
8
|
+
color: var(--zx-color-text);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.zx-card[data-variant="outlined"] {
|
|
12
|
+
border: 1px solid var(--zx-color-border);
|
|
13
|
+
background: var(--zx-color-bg-surface);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.zx-card[data-variant="raised"] {
|
|
17
|
+
border: 1px solid transparent;
|
|
18
|
+
background: var(--zx-color-bg-surface);
|
|
19
|
+
box-shadow: var(--zx-overlay-shadow);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.zx-card[data-variant="filled"] {
|
|
23
|
+
border: 1px solid transparent;
|
|
24
|
+
background: var(--zx-color-bg-muted);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.zx-card[data-linked="true"]:hover {
|
|
28
|
+
background: var(--zx-color-bg-hover);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.zx-card:has(.zx-card__primary:focus-visible) {
|
|
32
|
+
outline: 0;
|
|
33
|
+
box-shadow: var(--zx-focus-ring);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.zx-card__layout,
|
|
37
|
+
.zx-card__inner {
|
|
38
|
+
min-inline-size: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.zx-card__inner {
|
|
42
|
+
display: grid;
|
|
43
|
+
gap: var(--zx-space-3);
|
|
44
|
+
padding: var(--zx-space-5);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.zx-card[data-orientation="horizontal"] .zx-card__layout {
|
|
48
|
+
display: grid;
|
|
49
|
+
grid-template-columns: var(--zx-card-media-size) minmax(0, 1fr);
|
|
50
|
+
align-items: stretch;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.zx-card__media {
|
|
54
|
+
min-inline-size: 0;
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.zx-card[data-orientation="horizontal"] .zx-card__media {
|
|
59
|
+
min-block-size: var(--zx-card-media-size);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.zx-card__media > :is(img, video, svg) {
|
|
63
|
+
display: block;
|
|
64
|
+
inline-size: 100%;
|
|
65
|
+
block-size: 100%;
|
|
66
|
+
object-fit: cover;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.zx-card__header {
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: start;
|
|
72
|
+
justify-content: space-between;
|
|
73
|
+
gap: var(--zx-space-3);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.zx-card__header[hidden],
|
|
77
|
+
.zx-card__media[hidden],
|
|
78
|
+
.zx-card__content[hidden],
|
|
79
|
+
.zx-card__footer[hidden],
|
|
80
|
+
.zx-card__actions[hidden] {
|
|
81
|
+
display: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.zx-card__heading {
|
|
85
|
+
min-inline-size: 0;
|
|
86
|
+
margin: 0;
|
|
87
|
+
font-size: var(--zx-text-lg);
|
|
88
|
+
font-weight: 700;
|
|
89
|
+
line-height: 1.3;
|
|
90
|
+
letter-spacing: -0.012em;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.zx-card__primary,
|
|
94
|
+
.zx-card__title {
|
|
95
|
+
color: inherit;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.zx-card__primary {
|
|
99
|
+
text-decoration: none;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.zx-card[data-linked="true"] .zx-card__primary::after {
|
|
103
|
+
content: "";
|
|
104
|
+
position: absolute;
|
|
105
|
+
inset: 0;
|
|
106
|
+
z-index: 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.zx-card__primary:focus-visible {
|
|
110
|
+
outline: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.zx-card__actions,
|
|
114
|
+
.zx-card__content :is(a, button, input, select, textarea, summary, [tabindex]:not([tabindex="-1"])),
|
|
115
|
+
.zx-card__footer :is(a, button, input, select, textarea, summary, [tabindex]:not([tabindex="-1"])) {
|
|
116
|
+
position: relative;
|
|
117
|
+
z-index: 2;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.zx-card__actions {
|
|
121
|
+
display: flex;
|
|
122
|
+
flex: none;
|
|
123
|
+
flex-wrap: wrap;
|
|
124
|
+
align-items: center;
|
|
125
|
+
justify-content: end;
|
|
126
|
+
gap: var(--zx-space-2);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.zx-card__content {
|
|
130
|
+
color: var(--zx-color-text-muted);
|
|
131
|
+
line-height: 1.6;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.zx-card__footer {
|
|
135
|
+
padding-block-start: var(--zx-space-3);
|
|
136
|
+
border-block-start: 1px solid var(--zx-color-border);
|
|
137
|
+
color: var(--zx-color-text-muted);
|
|
138
|
+
font-size: var(--zx-text-sm);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@container (max-width: 28rem) {
|
|
142
|
+
.zx-card[data-orientation="horizontal"] .zx-card__layout {
|
|
143
|
+
grid-template-columns: minmax(0, 1fr);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.zx-card[data-orientation="horizontal"] .zx-card__media {
|
|
147
|
+
min-block-size: 0;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { Component } from '../../core/component.js';
|
|
2
|
+
import { h, restoreTarget, safeHref, snapshotTarget } from '../../core/dom.js';
|
|
3
|
+
import { isElement, uid } from '../../core/util.js';
|
|
4
|
+
import { button } from '../button/button.js';
|
|
5
|
+
|
|
6
|
+
/** @typedef {string|Node|Component|null} CardContent */
|
|
7
|
+
/** @typedef {import('../button/button.js').ButtonOptions} ButtonOptions */
|
|
8
|
+
/** @typedef {Element|ButtonOptions} CardAction */
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} CardLinkDescriptor
|
|
11
|
+
* @property {string} href Native link destination; executable/data schemes are rejected.
|
|
12
|
+
* @property {string} [target] Native browsing context.
|
|
13
|
+
* @property {string} [rel] Native link relationship.
|
|
14
|
+
* @property {(event: MouseEvent) => void} [onclick] Native click listener.
|
|
15
|
+
*/
|
|
16
|
+
/** @typedef {string|CardLinkDescriptor|null} CardLink */
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} CardOptions
|
|
20
|
+
* @property {string} [title=''] Card title.
|
|
21
|
+
* @property {CardContent} [media=null] Optional image, avatar, icon, or other media.
|
|
22
|
+
* @property {CardContent} [content=null] Main card content; enhanced children are adopted by default.
|
|
23
|
+
* @property {CardAction[]} [actions=[]] Secondary native controls beside the title.
|
|
24
|
+
* @property {CardContent} [footer=null] Optional metadata or trailing content.
|
|
25
|
+
* @property {CardLink} [link=null] Optional native primary link rendered around the title.
|
|
26
|
+
* @property {'outlined'|'raised'|'filled'} [variant='outlined'] Surface treatment.
|
|
27
|
+
* @property {'vertical'|'horizontal'} [orientation='vertical'] Media/content arrangement.
|
|
28
|
+
* @property {1|2|3|4|5|6} [headingLevel=3] Semantic title heading level.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Semantic content or record surface with optional media, native primary link, actions, and footer.
|
|
33
|
+
* The root is never interactive: linked cards keep a real title anchor, so secondary controls are
|
|
34
|
+
* siblings rather than invalid nested actions.
|
|
35
|
+
* @extends {Component<CardOptions>}
|
|
36
|
+
*/
|
|
37
|
+
export class Card extends Component {
|
|
38
|
+
static cssName = 'card';
|
|
39
|
+
|
|
40
|
+
/** @type {Readonly<CardOptions>} */
|
|
41
|
+
static defaults = {
|
|
42
|
+
title: '',
|
|
43
|
+
media: null,
|
|
44
|
+
content: null,
|
|
45
|
+
actions: [],
|
|
46
|
+
footer: null,
|
|
47
|
+
link: null,
|
|
48
|
+
variant: 'outlined',
|
|
49
|
+
orientation: 'vertical',
|
|
50
|
+
headingLevel: 3
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates or enhances one card.
|
|
55
|
+
* @param {Element|string|null} [target=null] Existing card container, selector, or null.
|
|
56
|
+
* @param {CardOptions} [options={}] Card options.
|
|
57
|
+
*/
|
|
58
|
+
constructor(target = null, options = {}) {
|
|
59
|
+
super(target, options);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** @returns {HTMLElement} */
|
|
63
|
+
render() {
|
|
64
|
+
const created = !this.el;
|
|
65
|
+
const root = /** @type {HTMLElement} */ (this.el ?? h('article'));
|
|
66
|
+
this.el = root;
|
|
67
|
+
this._createdRoot = created;
|
|
68
|
+
this._original = created ? null : snapshotTarget(root);
|
|
69
|
+
this._cleaned = false;
|
|
70
|
+
this._title = '';
|
|
71
|
+
this._link = null;
|
|
72
|
+
this._actionAbort = null;
|
|
73
|
+
this._linkAbort = null;
|
|
74
|
+
|
|
75
|
+
const originalContent = created ? [] : Array.from(root.childNodes);
|
|
76
|
+
try {
|
|
77
|
+
const headingLevel = normalizeHeadingLevel(this.options.headingLevel);
|
|
78
|
+
const headingId = uid('zx-card-heading');
|
|
79
|
+
const heading = h(`h${headingLevel}`, {
|
|
80
|
+
ref: 'heading',
|
|
81
|
+
class: 'zx-card__heading',
|
|
82
|
+
id: headingId
|
|
83
|
+
});
|
|
84
|
+
const media = h('div', { ref: 'media', class: 'zx-card__media', hidden: true });
|
|
85
|
+
const actions = h('div', {
|
|
86
|
+
ref: 'actions',
|
|
87
|
+
class: 'zx-card__actions',
|
|
88
|
+
role: 'group',
|
|
89
|
+
ariaLabel: 'Card actions',
|
|
90
|
+
hidden: true
|
|
91
|
+
});
|
|
92
|
+
const content = h('div', { ref: 'content', class: 'zx-card__content', hidden: true });
|
|
93
|
+
const footer = h('footer', { ref: 'footer', class: 'zx-card__footer', hidden: true });
|
|
94
|
+
const header = h('header', { ref: 'header', class: 'zx-card__header' }, heading, actions);
|
|
95
|
+
|
|
96
|
+
root.replaceChildren(h('div', { class: 'zx-card__layout' }, media,
|
|
97
|
+
h('div', { class: 'zx-card__inner' }, header, content, footer)));
|
|
98
|
+
root.dataset.variant = normalizeVariant(this.options.variant);
|
|
99
|
+
root.dataset.orientation = normalizeOrientation(this.options.orientation);
|
|
100
|
+
|
|
101
|
+
this.setTitle(this.options.title);
|
|
102
|
+
this.setMedia(this.options.media);
|
|
103
|
+
if (this.options.content !== null) this.setContent(this.options.content);
|
|
104
|
+
else if (originalContent.length) {
|
|
105
|
+
content.replaceChildren(...originalContent);
|
|
106
|
+
content.hidden = false;
|
|
107
|
+
}
|
|
108
|
+
this.setActions(this.options.actions);
|
|
109
|
+
this.setFooter(this.options.footer);
|
|
110
|
+
this.setLink(this.options.link);
|
|
111
|
+
return root;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
this._actionAbort?.abort();
|
|
114
|
+
this._linkAbort?.abort();
|
|
115
|
+
if (!created) restoreTarget(root, this._original);
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Replaces the card title. @param {string} title Next title. @returns {this} */
|
|
121
|
+
setTitle(title) {
|
|
122
|
+
const next = String(title ?? '');
|
|
123
|
+
if (this._link && !next.trim()) throw new TypeError('A linked Card requires a non-empty title');
|
|
124
|
+
this._title = next;
|
|
125
|
+
this._renderHeading();
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Replaces or removes the media region. @param {CardContent} content Media. @returns {this} */
|
|
130
|
+
setMedia(content) {
|
|
131
|
+
replaceCardContent(this.refs.media, content, 'Card media');
|
|
132
|
+
this.refs.media.hidden = content === null;
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Replaces or clears the main content. @param {CardContent} content Content. @returns {this} */
|
|
137
|
+
setContent(content) {
|
|
138
|
+
replaceCardContent(this.refs.content, content, 'Card content');
|
|
139
|
+
this.refs.content.hidden = content === null;
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Replaces the secondary action controls. @param {CardAction[]} actions Actions. @returns {this} */
|
|
144
|
+
setActions(actions) {
|
|
145
|
+
if (!Array.isArray(actions)) throw new TypeError('Card actions must be an array');
|
|
146
|
+
const nextAbort = new AbortController();
|
|
147
|
+
let controls;
|
|
148
|
+
try {
|
|
149
|
+
controls = actions.map((action) => {
|
|
150
|
+
if (isElement(action)) return action;
|
|
151
|
+
if (!action || typeof action !== 'object' || Array.isArray(action)) {
|
|
152
|
+
throw new TypeError('Card actions must be Elements or button descriptors');
|
|
153
|
+
}
|
|
154
|
+
const descriptor = { ...action };
|
|
155
|
+
const onclick = descriptor.onclick;
|
|
156
|
+
delete descriptor.onclick;
|
|
157
|
+
const control = button(descriptor);
|
|
158
|
+
if (onclick !== undefined && typeof onclick !== 'function') {
|
|
159
|
+
throw new TypeError('Card action onclick must be a function');
|
|
160
|
+
}
|
|
161
|
+
if (onclick) control.addEventListener('click', onclick, { signal: nextAbort.signal });
|
|
162
|
+
return control;
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
nextAbort.abort();
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
this._actionAbort?.abort();
|
|
169
|
+
this._actionAbort = nextAbort;
|
|
170
|
+
this.refs.actions.replaceChildren(...controls);
|
|
171
|
+
this.refs.actions.hidden = controls.length === 0;
|
|
172
|
+
this._syncHeader();
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Replaces or removes the footer. @param {CardContent} content Footer. @returns {this} */
|
|
177
|
+
setFooter(content) {
|
|
178
|
+
replaceCardContent(this.refs.footer, content, 'Card footer');
|
|
179
|
+
this.refs.footer.hidden = content === null;
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Sets or removes the title's native primary link. @param {CardLink} link Link. @returns {this} */
|
|
184
|
+
setLink(link) {
|
|
185
|
+
const next = normalizeCardLink(link);
|
|
186
|
+
if (next && !this._title.trim()) throw new TypeError('A linked Card requires a non-empty title');
|
|
187
|
+
this._link = next;
|
|
188
|
+
this.el.dataset.linked = next ? 'true' : 'false';
|
|
189
|
+
this._renderHeading();
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Restores an enhanced target exactly, or removes an owned card. @returns {void} */
|
|
194
|
+
destroy() {
|
|
195
|
+
if (this._cleaned) return;
|
|
196
|
+
this._cleaned = true;
|
|
197
|
+
this._actionAbort?.abort();
|
|
198
|
+
this._linkAbort?.abort();
|
|
199
|
+
super.destroy();
|
|
200
|
+
if (!this._createdRoot) restoreTarget(this.el, this._original);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** @returns {void} */
|
|
204
|
+
_renderHeading() {
|
|
205
|
+
const title = this._title;
|
|
206
|
+
const nextAbort = new AbortController();
|
|
207
|
+
const node = this._link ? h('a', {
|
|
208
|
+
class: 'zx-card__primary',
|
|
209
|
+
href: this._link.href,
|
|
210
|
+
target: this._link.target,
|
|
211
|
+
rel: this._link.rel
|
|
212
|
+
}, title) : h('span', { class: 'zx-card__title' }, title);
|
|
213
|
+
if (this._link?.onclick) node.addEventListener('click', this._link.onclick, { signal: nextAbort.signal });
|
|
214
|
+
this._linkAbort?.abort();
|
|
215
|
+
this._linkAbort = nextAbort;
|
|
216
|
+
this.refs.heading.replaceChildren(node);
|
|
217
|
+
this.refs.heading.hidden = title === '';
|
|
218
|
+
if (title) this.el.setAttribute('aria-labelledby', this.refs.heading.id);
|
|
219
|
+
else this.el.removeAttribute('aria-labelledby');
|
|
220
|
+
this._syncHeader();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** @returns {void} */
|
|
224
|
+
_syncHeader() {
|
|
225
|
+
if (!this.refs.header) return;
|
|
226
|
+
this.refs.header.hidden = this._title === '' && this.refs.actions.childElementCount === 0;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** @param {Element} host @param {CardContent} content @param {string} label */
|
|
231
|
+
function replaceCardContent(host, content, label) {
|
|
232
|
+
if (content === null) {
|
|
233
|
+
host.replaceChildren();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (typeof content === 'string') {
|
|
237
|
+
host.textContent = content;
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const node = content instanceof Component ? content.toElement() : content;
|
|
241
|
+
if (node instanceof Node) {
|
|
242
|
+
host.replaceChildren(node);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
throw new TypeError(`${label} must be a string, Node, Component, or null`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** @param {CardLink} value @returns {CardLinkDescriptor|null} */
|
|
249
|
+
function normalizeCardLink(value) {
|
|
250
|
+
if (value === null) return null;
|
|
251
|
+
const descriptor = typeof value === 'string' ? { href: value } : value;
|
|
252
|
+
if (!descriptor || typeof descriptor !== 'object' || Array.isArray(descriptor)) {
|
|
253
|
+
throw new TypeError('Card link must be a URL string, descriptor, or null');
|
|
254
|
+
}
|
|
255
|
+
const href = safeHref(descriptor.href);
|
|
256
|
+
if (href === null) {
|
|
257
|
+
throw new TypeError('Card link requires a safe non-empty href');
|
|
258
|
+
}
|
|
259
|
+
if (descriptor.onclick !== undefined && typeof descriptor.onclick !== 'function') {
|
|
260
|
+
throw new TypeError('Card link onclick must be a function');
|
|
261
|
+
}
|
|
262
|
+
const target = descriptor.target == null ? undefined : String(descriptor.target);
|
|
263
|
+
let rel = descriptor.rel == null ? '' : String(descriptor.rel).trim();
|
|
264
|
+
if (target === '_blank' && !/(^|\s)noopener(\s|$)/i.test(rel)) {
|
|
265
|
+
rel = `${rel} noopener`.trim();
|
|
266
|
+
}
|
|
267
|
+
return { href, target, rel: rel || undefined, onclick: descriptor.onclick };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** @param {unknown} value @returns {'outlined'|'raised'|'filled'} */
|
|
271
|
+
function normalizeVariant(value) {
|
|
272
|
+
return ['raised', 'filled'].includes(String(value))
|
|
273
|
+
? /** @type {'raised'|'filled'} */ (value) : 'outlined';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** @param {unknown} value @returns {'vertical'|'horizontal'} */
|
|
277
|
+
function normalizeOrientation(value) {
|
|
278
|
+
return value === 'horizontal' ? 'horizontal' : 'vertical';
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** @param {unknown} value @returns {1|2|3|4|5|6} */
|
|
282
|
+
function normalizeHeadingLevel(value) {
|
|
283
|
+
const level = Math.trunc(Number(value));
|
|
284
|
+
return /** @type {1|2|3|4|5|6} */ (level >= 1 && level <= 6 ? level : 3);
|
|
285
|
+
}
|
|
@@ -106,6 +106,7 @@ export class Chart extends Component {
|
|
|
106
106
|
this._chartOptions = cloneChartValue(this.options.chartOptions);
|
|
107
107
|
this._loading = Boolean(this.options.loading);
|
|
108
108
|
this._error = null;
|
|
109
|
+
this._hasError = false;
|
|
109
110
|
this._handle = null;
|
|
110
111
|
this._destroyed = false;
|
|
111
112
|
|
|
@@ -151,6 +152,7 @@ export class Chart extends Component {
|
|
|
151
152
|
setData(data) {
|
|
152
153
|
this._data = normalizeChartData(data);
|
|
153
154
|
this._error = null;
|
|
155
|
+
this._hasError = false;
|
|
154
156
|
this._loading = false;
|
|
155
157
|
this._renderSummary();
|
|
156
158
|
this._mountOrUpdate(true);
|
|
@@ -191,6 +193,7 @@ export class Chart extends Component {
|
|
|
191
193
|
/** Shows an error and destroys the current engine. @param {unknown} error @returns {this} */
|
|
192
194
|
setError(error) {
|
|
193
195
|
this._error = error ?? new Error(String(this.options.errorText));
|
|
196
|
+
this._hasError = true;
|
|
194
197
|
this._loading = false;
|
|
195
198
|
this._destroyHandle();
|
|
196
199
|
this._syncState();
|
|
@@ -201,6 +204,7 @@ export class Chart extends Component {
|
|
|
201
204
|
/** Clears the error and remounts when data and an adapter are available. @returns {this} */
|
|
202
205
|
clearError() {
|
|
203
206
|
this._error = null;
|
|
207
|
+
this._hasError = false;
|
|
204
208
|
this._mountOrUpdate(false);
|
|
205
209
|
return this;
|
|
206
210
|
}
|
|
@@ -240,7 +244,7 @@ export class Chart extends Component {
|
|
|
240
244
|
/** @param {boolean} emitUpdate @returns {void} */
|
|
241
245
|
_mountOrUpdate(emitUpdate) {
|
|
242
246
|
this._syncState();
|
|
243
|
-
if (this._loading || this.
|
|
247
|
+
if (this._loading || this._hasError || isEmptyChartData(this._data)) {
|
|
244
248
|
this._destroyHandle();
|
|
245
249
|
return;
|
|
246
250
|
}
|
|
@@ -288,7 +292,7 @@ export class Chart extends Component {
|
|
|
288
292
|
/** @returns {void} */
|
|
289
293
|
_syncState() {
|
|
290
294
|
const empty = isEmptyChartData(this._data);
|
|
291
|
-
const state = this._loading ? 'loading' : this.
|
|
295
|
+
const state = this._loading ? 'loading' : this._hasError ? 'error' : empty ? 'empty' : 'ready';
|
|
292
296
|
this.el.dataset.state = state;
|
|
293
297
|
this.refs.viewport.hidden = state !== 'ready';
|
|
294
298
|
let text = '';
|
|
@@ -509,7 +513,8 @@ function themeChartOptions(root) {
|
|
|
509
513
|
|
|
510
514
|
/** @param {unknown} error @param {unknown} fallback @returns {string} */
|
|
511
515
|
function errorMessage(error, fallback) {
|
|
512
|
-
|
|
516
|
+
const message = error && typeof error === 'object' && 'message' in error ? String(error.message) : '';
|
|
517
|
+
return message.trim() ? message : String(fallback);
|
|
513
518
|
}
|
|
514
519
|
|
|
515
520
|
/** Fired after the adapter creates an engine instance. @event Chart#ready @type {CustomEvent<{instance: unknown}>} */
|
|
@@ -85,9 +85,9 @@ export function billingItemsConfig(options = {}) {
|
|
|
85
85
|
} = options;
|
|
86
86
|
const fields = { ...DEFAULT_FIELDS, ...requestedFields };
|
|
87
87
|
const calculateLineTotal = typeof lineTotal === 'function' ? lineTotal : (row) => {
|
|
88
|
-
const quantity =
|
|
89
|
-
const price =
|
|
90
|
-
return
|
|
88
|
+
const quantity = billingNumber(row?.[fields.quantity]);
|
|
89
|
+
const price = billingNumber(row?.[fields.unitPrice]);
|
|
90
|
+
return quantity !== null && price !== null ? quantity * price : null;
|
|
91
91
|
};
|
|
92
92
|
const columns = Array.isArray(suppliedColumns) ? suppliedColumns.map((column) => ({ ...column }))
|
|
93
93
|
: billingColumns(fields, { units, currencies, currency, locale, decimals, columnOverrides });
|
|
@@ -211,3 +211,10 @@ export function billingColumns(fields, { units, currencies, currency, locale, de
|
|
|
211
211
|
function hasChoices(value) {
|
|
212
212
|
return Array.isArray(value) ? value.length > 0 : Boolean(value && typeof value === 'object' && Object.keys(value).length);
|
|
213
213
|
}
|
|
214
|
+
|
|
215
|
+
/** Missing billing operands stay missing; explicit numeric zero remains a valid value. @param {unknown} value @returns {number|null} */
|
|
216
|
+
function billingNumber(value) {
|
|
217
|
+
if (value == null || typeof value === 'string' && value.trim() === '') return null;
|
|
218
|
+
const number = Number(value);
|
|
219
|
+
return Number.isFinite(number) ? number : null;
|
|
220
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
.zx-launcher {
|
|
2
|
-
inline-size: min(
|
|
2
|
+
inline-size: min(760px, calc(100vw - (2 * var(--zx-space-4))));
|
|
3
3
|
max-block-size: min(720px, calc(100vh - (2 * var(--zx-space-6))));
|
|
4
4
|
border: 1px solid var(--zx-color-border);
|
|
5
5
|
border-radius: var(--zx-radius-xl);
|
|
@@ -15,19 +15,23 @@
|
|
|
15
15
|
|
|
16
16
|
.zx-launcher__surface {
|
|
17
17
|
display: grid;
|
|
18
|
-
grid-template-rows: auto auto minmax(0, 1fr);
|
|
18
|
+
grid-template-rows: auto auto minmax(0, 1fr) auto;
|
|
19
19
|
max-block-size: inherit;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
.zx-launcher__search {
|
|
23
23
|
display: grid;
|
|
24
|
-
grid-template-columns: auto 1fr auto;
|
|
24
|
+
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
|
25
25
|
align-items: center;
|
|
26
26
|
gap: var(--zx-space-3);
|
|
27
27
|
border-block-end: 1px solid var(--zx-color-border);
|
|
28
28
|
padding: var(--zx-space-3) var(--zx-space-4);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
.zx-launcher__search[data-shortcut="false"] {
|
|
32
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
33
|
+
}
|
|
34
|
+
|
|
31
35
|
.zx-launcher__input {
|
|
32
36
|
min-inline-size: 0;
|
|
33
37
|
border: 0;
|
|
@@ -43,6 +47,23 @@
|
|
|
43
47
|
|
|
44
48
|
.zx-launcher__input::-webkit-search-cancel-button { display: none; }
|
|
45
49
|
|
|
50
|
+
.zx-launcher__shortcut,
|
|
51
|
+
.zx-launcher__hints kbd {
|
|
52
|
+
border: 1px solid var(--zx-color-border-control);
|
|
53
|
+
border-radius: var(--zx-radius-sm);
|
|
54
|
+
background: var(--zx-color-bg-muted);
|
|
55
|
+
color: var(--zx-color-text-muted);
|
|
56
|
+
font-family: var(--zx-font-mono);
|
|
57
|
+
font-size: var(--zx-text-xs);
|
|
58
|
+
font-weight: 600;
|
|
59
|
+
line-height: 1.5;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.zx-launcher__shortcut {
|
|
63
|
+
padding-inline: var(--zx-space-2);
|
|
64
|
+
white-space: nowrap;
|
|
65
|
+
}
|
|
66
|
+
|
|
46
67
|
.zx-launcher__status {
|
|
47
68
|
margin: 0;
|
|
48
69
|
padding: var(--zx-space-5);
|
|
@@ -67,6 +88,18 @@
|
|
|
67
88
|
text-transform: uppercase;
|
|
68
89
|
}
|
|
69
90
|
|
|
91
|
+
.zx-launcher__group[data-layout="grid"] {
|
|
92
|
+
display: grid;
|
|
93
|
+
grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
|
|
94
|
+
gap: var(--zx-space-2);
|
|
95
|
+
padding: 0 var(--zx-space-1) var(--zx-space-2);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.zx-launcher__group[data-layout="grid"] .zx-launcher__group-label {
|
|
99
|
+
grid-column: 1 / -1;
|
|
100
|
+
padding-inline: var(--zx-space-2);
|
|
101
|
+
}
|
|
102
|
+
|
|
70
103
|
.zx-launcher__option {
|
|
71
104
|
display: flex;
|
|
72
105
|
align-items: center;
|
|
@@ -87,6 +120,42 @@
|
|
|
87
120
|
background: var(--zx-color-bg-hover);
|
|
88
121
|
}
|
|
89
122
|
|
|
123
|
+
.zx-launcher__option[aria-current="page"] {
|
|
124
|
+
box-shadow: inset 2px 0 var(--zx-color-accent);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.zx-launcher__option[data-kind="application"] {
|
|
128
|
+
display: grid;
|
|
129
|
+
grid-template-rows: auto minmax(0, auto);
|
|
130
|
+
justify-items: center;
|
|
131
|
+
align-content: center;
|
|
132
|
+
min-block-size: 92px;
|
|
133
|
+
padding: var(--zx-space-3) var(--zx-space-2);
|
|
134
|
+
text-align: center;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.zx-launcher__option[data-kind="application"][aria-current="page"] {
|
|
138
|
+
box-shadow: inset 0 -2px var(--zx-color-accent);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.zx-launcher__option[data-kind="application"] .zx-launcher__icon {
|
|
142
|
+
min-block-size: 36px;
|
|
143
|
+
align-items: center;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.zx-launcher__option[data-kind="application"] .zx-launcher__copy {
|
|
147
|
+
inline-size: 100%;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.zx-launcher__option[data-kind="application"] .zx-launcher__description,
|
|
151
|
+
.zx-launcher__option[data-kind="application"] .zx-launcher__badge {
|
|
152
|
+
display: none;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.zx-launcher__option[data-kind="application"] .zx-launcher__label {
|
|
156
|
+
font-size: var(--zx-text-sm);
|
|
157
|
+
}
|
|
158
|
+
|
|
90
159
|
.zx-launcher__option[aria-disabled="true"] {
|
|
91
160
|
color: var(--zx-color-text-muted);
|
|
92
161
|
cursor: not-allowed;
|
|
@@ -98,3 +167,44 @@
|
|
|
98
167
|
.zx-launcher__label { overflow: hidden; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
|
|
99
168
|
.zx-launcher__description { overflow: hidden; color: var(--zx-color-text-muted); font-size: var(--zx-text-sm); text-overflow: ellipsis; white-space: nowrap; }
|
|
100
169
|
.zx-launcher__badge { margin-inline-start: auto; color: var(--zx-color-text-muted); font-size: var(--zx-text-xs); }
|
|
170
|
+
|
|
171
|
+
.zx-launcher__hints {
|
|
172
|
+
display: flex;
|
|
173
|
+
flex-wrap: wrap;
|
|
174
|
+
justify-content: end;
|
|
175
|
+
gap: var(--zx-space-4);
|
|
176
|
+
padding: var(--zx-space-2) var(--zx-space-4);
|
|
177
|
+
border-block-start: 1px solid var(--zx-color-border);
|
|
178
|
+
color: var(--zx-color-text-muted);
|
|
179
|
+
font-size: var(--zx-text-xs);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.zx-launcher__hints span {
|
|
183
|
+
display: inline-flex;
|
|
184
|
+
align-items: center;
|
|
185
|
+
gap: var(--zx-space-1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.zx-launcher__hints kbd {
|
|
189
|
+
min-inline-size: 1.5rem;
|
|
190
|
+
padding-inline: 4px;
|
|
191
|
+
text-align: center;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@media (max-width: 520px) {
|
|
195
|
+
.zx-launcher__group[data-layout="grid"] {
|
|
196
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.zx-launcher__shortcut {
|
|
200
|
+
display: none;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.zx-launcher__search {
|
|
204
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.zx-launcher__hints {
|
|
208
|
+
justify-content: center;
|
|
209
|
+
}
|
|
210
|
+
}
|