@aceshooting/lyra-ui 1.0.0 → 1.1.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/README.md +8 -9
- package/custom-elements.json +10463 -4519
- package/dist/components/chart/chart.d.ts +46 -1
- package/dist/components/chart/chart.js +148 -10
- package/dist/components/chart/chart.stories.d.ts +32 -0
- package/dist/components/chart/chart.stories.js +141 -0
- package/dist/components/chart/chart.styles.js +16 -0
- package/dist/components/chart/histogram.d.ts +2 -0
- package/dist/components/chart/histogram.js +18 -2
- package/dist/components/chart/line-chart.stories.d.ts +17 -0
- package/dist/components/chart/line-chart.stories.js +75 -0
- package/dist/components/chart/lite-chart.d.ts +74 -0
- package/dist/components/chart/lite-chart.js +384 -0
- package/dist/components/chart/lite-chart.stories.d.ts +12 -0
- package/dist/components/chart/lite-chart.stories.js +98 -0
- package/dist/components/chart/lite-chart.styles.d.ts +1 -0
- package/dist/components/chart/lite-chart.styles.js +83 -0
- package/dist/components/combobox/combobox.d.ts +9 -0
- package/dist/components/combobox/combobox.js +75 -12
- package/dist/components/combobox/combobox.stories.d.ts +18 -0
- package/dist/components/combobox/combobox.stories.js +75 -0
- package/dist/components/date-picker/calendar-core.js +13 -2
- package/dist/components/date-picker/date-input.d.ts +9 -1
- package/dist/components/date-picker/date-input.js +55 -7
- package/dist/components/date-picker/date-input.stories.d.ts +5 -0
- package/dist/components/date-picker/date-input.stories.js +36 -0
- package/dist/components/date-picker/date-picker.d.ts +11 -3
- package/dist/components/date-picker/date-picker.js +87 -37
- package/dist/components/date-picker/date-picker.stories.d.ts +4 -0
- package/dist/components/date-picker/date-picker.stories.js +12 -0
- package/dist/components/date-picker/date-picker.styles.js +1 -1
- package/dist/components/empty/empty.d.ts +12 -1
- package/dist/components/empty/empty.js +20 -3
- package/dist/components/empty/empty.stories.d.ts +2 -0
- package/dist/components/empty/empty.stories.js +20 -0
- package/dist/components/empty/empty.styles.js +11 -0
- package/dist/components/export-button/csv.js +6 -2
- package/dist/components/export-button/export-button.d.ts +15 -1
- package/dist/components/export-button/export-button.js +89 -12
- package/dist/components/export-button/export-button.stories.d.ts +1 -0
- package/dist/components/export-button/export-button.stories.js +5 -0
- package/dist/components/export-button/export-button.styles.js +11 -3
- package/dist/components/file-input/accept.d.ts +8 -2
- package/dist/components/file-input/accept.js +9 -3
- package/dist/components/file-input/file-input.d.ts +5 -2
- package/dist/components/file-input/file-input.js +23 -8
- package/dist/components/file-input/file-input.stories.d.ts +2 -0
- package/dist/components/file-input/file-input.stories.js +9 -0
- package/dist/components/flag/flag.d.ts +48 -3
- package/dist/components/flag/flag.js +65 -29
- package/dist/components/flag/flag.styles.js +1 -1
- package/dist/components/flag/language-map.d.ts +11 -0
- package/dist/components/flag/language-map.js +12 -1
- package/dist/components/gauge/gauge.d.ts +2 -0
- package/dist/components/gauge/gauge.js +19 -5
- package/dist/components/gauge/gauge.stories.d.ts +2 -0
- package/dist/components/gauge/gauge.stories.js +6 -0
- package/dist/components/graph/graph-loader.d.ts +28 -0
- package/dist/components/graph/graph-loader.js +44 -0
- package/dist/components/graph/graph.d.ts +41 -15
- package/dist/components/graph/graph.js +146 -71
- package/dist/components/graph/graph.stories.d.ts +3 -0
- package/dist/components/graph/graph.stories.js +38 -0
- package/dist/components/graph/graph.styles.js +6 -1
- package/dist/components/heatmap/calendar-grid.d.ts +7 -0
- package/dist/components/heatmap/calendar-grid.js +27 -10
- package/dist/components/heatmap/heatmap.d.ts +137 -12
- package/dist/components/heatmap/heatmap.js +427 -27
- package/dist/components/heatmap/heatmap.stories.d.ts +17 -0
- package/dist/components/heatmap/heatmap.stories.js +130 -0
- package/dist/components/heatmap/heatmap.styles.js +60 -2
- package/dist/components/map/map.d.ts +17 -1
- package/dist/components/map/map.js +68 -21
- package/dist/components/map/map.stories.d.ts +10 -0
- package/dist/components/map/map.stories.js +107 -12
- package/dist/components/map/map.styles.js +2 -2
- package/dist/components/playback/playback.d.ts +6 -2
- package/dist/components/playback/playback.js +36 -12
- package/dist/components/playback/playback.stories.d.ts +2 -0
- package/dist/components/playback/playback.stories.js +6 -0
- package/dist/components/playback/playback.styles.js +10 -2
- package/dist/components/select/select.d.ts +122 -0
- package/dist/components/select/select.js +516 -0
- package/dist/components/select/select.stories.d.ts +19 -0
- package/dist/components/select/select.stories.js +75 -0
- package/dist/components/select/select.styles.d.ts +1 -0
- package/dist/components/select/select.styles.js +188 -0
- package/dist/components/skeleton/skeleton.js +4 -7
- package/dist/components/sparkline/sparkline.d.ts +1 -1
- package/dist/components/sparkline/sparkline.js +40 -13
- package/dist/components/sparkline/sparkline.stories.d.ts +4 -0
- package/dist/components/sparkline/sparkline.stories.js +22 -0
- package/dist/components/split/split.d.ts +24 -3
- package/dist/components/split/split.js +155 -31
- package/dist/components/split/split.stories.d.ts +2 -0
- package/dist/components/split/split.stories.js +20 -0
- package/dist/components/stat/stat.d.ts +27 -1
- package/dist/components/stat/stat.js +56 -3
- package/dist/components/stat/stat.stories.d.ts +3 -0
- package/dist/components/stat/stat.stories.js +49 -1
- package/dist/components/stat/stat.styles.js +44 -0
- package/dist/components/table/table.d.ts +64 -2
- package/dist/components/table/table.js +213 -17
- package/dist/components/table/table.stories.d.ts +6 -0
- package/dist/components/table/table.stories.js +45 -0
- package/dist/components/table/table.styles.js +51 -4
- package/dist/components/time-range/time-range.d.ts +34 -5
- package/dist/components/time-range/time-range.js +217 -36
- package/dist/components/time-range/time-range.stories.d.ts +2 -0
- package/dist/components/time-range/time-range.stories.js +13 -0
- package/dist/components/time-range/time-range.styles.js +44 -2
- package/dist/components/toast/toast-item.d.ts +14 -2
- package/dist/components/toast/toast-item.js +134 -17
- package/dist/components/toast/toast-item.styles.js +28 -2
- package/dist/components/toast/toast.stories.d.ts +3 -0
- package/dist/components/toast/toast.stories.js +41 -1
- package/dist/components/toast/toast.styles.js +11 -11
- package/dist/components/toast/toaster.d.ts +1 -1
- package/dist/components/toast/toaster.js +13 -6
- package/dist/components/tree/tree-node.d.ts +13 -7
- package/dist/components/tree/tree-node.js +24 -10
- package/dist/components/tree/tree.d.ts +64 -15
- package/dist/components/tree/tree.js +113 -22
- package/dist/components/tree/tree.stories.d.ts +4 -0
- package/dist/components/tree/tree.stories.js +31 -1
- package/dist/components/tree/update-cascade.d.ts +13 -0
- package/dist/components/tree/update-cascade.js +13 -0
- package/dist/components/widget/widget.d.ts +15 -1
- package/dist/components/widget/widget.js +140 -5
- package/dist/components/widget/widget.stories.d.ts +1 -0
- package/dist/components/widget/widget.stories.js +10 -0
- package/dist/components/widget/widget.styles.js +11 -1
- package/dist/components/word-cloud/word-cloud-layout.d.ts +63 -0
- package/dist/components/word-cloud/word-cloud-layout.js +145 -0
- package/dist/components/word-cloud/word-cloud.d.ts +65 -0
- package/dist/components/word-cloud/word-cloud.js +255 -0
- package/dist/components/word-cloud/word-cloud.stories.d.ts +10 -0
- package/dist/components/word-cloud/word-cloud.stories.js +55 -0
- package/dist/components/word-cloud/word-cloud.styles.d.ts +1 -0
- package/dist/components/word-cloud/word-cloud.styles.js +54 -0
- package/dist/internal/a11y.js +2 -1
- package/dist/internal/rtl.d.ts +7 -0
- package/dist/internal/rtl.js +9 -0
- package/dist/lyra.d.ts +9 -1
- package/dist/lyra.js +6 -0
- package/llms-full.txt +892 -276
- package/llms.txt +12 -9
- package/package.json +6 -4
|
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { html } from 'lit';
|
|
8
|
-
import { property } from 'lit/decorators.js';
|
|
8
|
+
import { property, state } from 'lit/decorators.js';
|
|
9
9
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
10
|
import { defineElement } from '../../internal/prefix.js';
|
|
11
11
|
import { closeIcon } from '../../internal/icons.js';
|
|
@@ -18,6 +18,16 @@ import { styles } from './toast-item.styles.js';
|
|
|
18
18
|
// parsing that back out in JS for a single setTimeout would be more fragile
|
|
19
19
|
// than this documented literal.
|
|
20
20
|
const ANIM_MS = 180;
|
|
21
|
+
function prefersReducedMotion() {
|
|
22
|
+
return typeof window !== 'undefined' && !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
23
|
+
}
|
|
24
|
+
// toast-item.styles.ts already collapses the CSS transition itself to ~0 under
|
|
25
|
+
// reduced motion; this keeps the lyra-after-show/lyra-after-hide events (and
|
|
26
|
+
// the DOM removal in hide()) from still waiting out the full duration of an
|
|
27
|
+
// animation nothing is visibly playing.
|
|
28
|
+
function animMs() {
|
|
29
|
+
return prefersReducedMotion() ? 0 : ANIM_MS;
|
|
30
|
+
}
|
|
21
31
|
/**
|
|
22
32
|
* `<lyra-toast-item>` — a single toast notification.
|
|
23
33
|
* Mirrors the Web Awesome `<wa-toast-item>` API under the `lyra-` prefix.
|
|
@@ -46,8 +56,12 @@ export class LyraToastItem extends LyraElement {
|
|
|
46
56
|
this.variant = 'neutral';
|
|
47
57
|
/** Show the icon slot. */
|
|
48
58
|
this.withIcon = false;
|
|
49
|
-
this.
|
|
59
|
+
this.elapsedMs = 0;
|
|
50
60
|
this.startedAt = 0;
|
|
61
|
+
this.timerStarted = false;
|
|
62
|
+
this.hovering = false;
|
|
63
|
+
this.focused = false;
|
|
64
|
+
this.hiding = false;
|
|
51
65
|
this.resumeTimer = () => {
|
|
52
66
|
// Guard against an interleaved pointer+focus pause/resume sequence
|
|
53
67
|
// calling resumeTimer() twice without a pauseTimer() in between --
|
|
@@ -59,28 +73,75 @@ export class LyraToastItem extends LyraElement {
|
|
|
59
73
|
clearTimeout(this.timer);
|
|
60
74
|
this.timer = undefined;
|
|
61
75
|
}
|
|
62
|
-
if (!isFinite(this.duration) || this.duration <= 0
|
|
76
|
+
if (!isFinite(this.duration) || this.duration <= 0)
|
|
77
|
+
return;
|
|
78
|
+
const remaining = this.duration - this.elapsedMs;
|
|
79
|
+
if (remaining <= 0)
|
|
63
80
|
return;
|
|
64
81
|
this.startedAt = performance.now();
|
|
65
|
-
this.timer = window.setTimeout(() => this.hide(),
|
|
82
|
+
this.timer = window.setTimeout(() => this.hide(), remaining);
|
|
66
83
|
};
|
|
67
84
|
this.pauseTimer = () => {
|
|
68
85
|
if (this.timer !== undefined) {
|
|
69
86
|
clearTimeout(this.timer);
|
|
70
87
|
this.timer = undefined;
|
|
71
|
-
this.
|
|
88
|
+
this.elapsedMs += performance.now() - this.startedAt;
|
|
72
89
|
}
|
|
73
90
|
};
|
|
91
|
+
// Hover and focus are tracked as independent pause reasons so that
|
|
92
|
+
// releasing one (e.g. Shift-Tabbing focus away) only resumes the timer
|
|
93
|
+
// once *neither* modality still holds the toast paused -- otherwise a
|
|
94
|
+
// pointer resting on the toast would see it auto-dismiss out from under
|
|
95
|
+
// it the moment focus alone moved away, or vice versa.
|
|
96
|
+
this.onPointerEnter = () => {
|
|
97
|
+
this.hovering = true;
|
|
98
|
+
this.pauseTimer();
|
|
99
|
+
};
|
|
100
|
+
this.onPointerLeave = () => {
|
|
101
|
+
this.hovering = false;
|
|
102
|
+
if (!this.focused)
|
|
103
|
+
this.resumeTimer();
|
|
104
|
+
};
|
|
105
|
+
this.onFocusIn = () => {
|
|
106
|
+
this.focused = true;
|
|
107
|
+
this.pauseTimer();
|
|
108
|
+
};
|
|
109
|
+
this.onFocusOut = () => {
|
|
110
|
+
this.focused = false;
|
|
111
|
+
if (!this.hovering)
|
|
112
|
+
this.resumeTimer();
|
|
113
|
+
};
|
|
74
114
|
}
|
|
75
115
|
static { this.styles = [LyraElement.styles, styles]; }
|
|
116
|
+
updated(changed) {
|
|
117
|
+
if (changed.has('variant')) {
|
|
118
|
+
// Assertive for actionable severities, polite otherwise. Re-evaluated
|
|
119
|
+
// on every `variant` change (not just the first render) so a toast
|
|
120
|
+
// reassigned to `danger`/`warning` after creation is announced as an
|
|
121
|
+
// interruption instead of keeping its original, now-stale role.
|
|
122
|
+
this.setAttribute('role', this.variant === 'danger' || this.variant === 'warning' ? 'alert' : 'status');
|
|
123
|
+
}
|
|
124
|
+
// `elapsedMs`/`duration` are re-read fresh every time the timer is
|
|
125
|
+
// (re)scheduled, so a `duration` change while paused (hovering/focused)
|
|
126
|
+
// or before the timer has ever started needs no action here -- the next
|
|
127
|
+
// resumeTimer()/startTimer() call already picks up the new value. Only a
|
|
128
|
+
// timer that's actively counting down right now is running against a
|
|
129
|
+
// setTimeout scheduled for the *old* duration, so that's the one case
|
|
130
|
+
// that needs an explicit reschedule.
|
|
131
|
+
if (changed.has('duration') && this.timerStarted && this.timer !== undefined) {
|
|
132
|
+
this.pauseTimer();
|
|
133
|
+
this.resumeTimer();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
76
136
|
firstUpdated() {
|
|
77
|
-
// Assertive for actionable severities, polite otherwise.
|
|
78
|
-
this.setAttribute('role', this.variant === 'danger' || this.variant === 'warning' ? 'alert' : 'status');
|
|
79
137
|
this.showRafId = requestAnimationFrame(() => {
|
|
80
138
|
this.showRafId = undefined;
|
|
81
139
|
this.setAttribute('data-visible', '');
|
|
82
140
|
this.emit('lyra-show');
|
|
83
|
-
window.setTimeout(() =>
|
|
141
|
+
this.showAnimTimer = window.setTimeout(() => {
|
|
142
|
+
this.showAnimTimer = undefined;
|
|
143
|
+
this.emit('lyra-after-show');
|
|
144
|
+
}, animMs());
|
|
84
145
|
this.startTimer();
|
|
85
146
|
});
|
|
86
147
|
}
|
|
@@ -90,12 +151,19 @@ export class LyraToastItem extends LyraElement {
|
|
|
90
151
|
cancelAnimationFrame(this.showRafId);
|
|
91
152
|
this.showRafId = undefined;
|
|
92
153
|
}
|
|
154
|
+
if (this.showAnimTimer !== undefined) {
|
|
155
|
+
clearTimeout(this.showAnimTimer);
|
|
156
|
+
this.showAnimTimer = undefined;
|
|
157
|
+
}
|
|
158
|
+
if (this.hideAnimTimer !== undefined) {
|
|
159
|
+
clearTimeout(this.hideAnimTimer);
|
|
160
|
+
this.hideAnimTimer = undefined;
|
|
161
|
+
}
|
|
93
162
|
this.clearTimer();
|
|
94
163
|
}
|
|
95
164
|
startTimer() {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this.remaining = this.duration;
|
|
165
|
+
this.timerStarted = true;
|
|
166
|
+
this.elapsedMs = 0;
|
|
99
167
|
this.resumeTimer();
|
|
100
168
|
}
|
|
101
169
|
clearTimer() {
|
|
@@ -103,12 +171,43 @@ export class LyraToastItem extends LyraElement {
|
|
|
103
171
|
clearTimeout(this.timer);
|
|
104
172
|
this.timer = undefined;
|
|
105
173
|
}
|
|
174
|
+
// A stack of several simultaneously-open toasts otherwise gives every
|
|
175
|
+
// close button the same bare "Close" label, so screen-reader/switch-access
|
|
176
|
+
// users can't tell which toast a given button dismisses without first
|
|
177
|
+
// activating it. Deriving the label from the toast's own message content
|
|
178
|
+
// mirrors combobox's per-item "Remove X" labeling for the same reason.
|
|
179
|
+
//
|
|
180
|
+
// Only direct Text-node children are considered -- the message is always
|
|
181
|
+
// assigned as plain text (`create()` sets `textContent`), whereas anything
|
|
182
|
+
// else appended to the host (an action `<button>`, a `slot="icon"` element
|
|
183
|
+
// and its own descendant text) is a sibling *element*, not a top-level text
|
|
184
|
+
// node, so it's excluded without needing to know about those features here.
|
|
185
|
+
get closeLabel() {
|
|
186
|
+
const text = Array.from(this.childNodes)
|
|
187
|
+
.filter((node) => node.nodeType === Node.TEXT_NODE)
|
|
188
|
+
.map((node) => node.textContent ?? '')
|
|
189
|
+
.join('')
|
|
190
|
+
.trim()
|
|
191
|
+
.replace(/\s+/g, ' ');
|
|
192
|
+
if (!text)
|
|
193
|
+
return 'Close';
|
|
194
|
+
const snippet = text.length > 40 ? `${text.slice(0, 40)}…` : text;
|
|
195
|
+
return `Close: ${snippet}`;
|
|
196
|
+
}
|
|
106
197
|
/** Hide with animation, then remove from the DOM. */
|
|
107
198
|
async hide() {
|
|
199
|
+
if (this.hiding)
|
|
200
|
+
return;
|
|
201
|
+
this.hiding = true;
|
|
108
202
|
this.clearTimer();
|
|
109
203
|
this.emit('lyra-hide');
|
|
110
204
|
this.removeAttribute('data-visible');
|
|
111
|
-
await new Promise((
|
|
205
|
+
await new Promise((resolve) => {
|
|
206
|
+
this.hideAnimTimer = window.setTimeout(() => {
|
|
207
|
+
this.hideAnimTimer = undefined;
|
|
208
|
+
resolve();
|
|
209
|
+
}, animMs());
|
|
210
|
+
});
|
|
112
211
|
this.emit('lyra-after-hide');
|
|
113
212
|
this.remove();
|
|
114
213
|
}
|
|
@@ -116,15 +215,30 @@ export class LyraToastItem extends LyraElement {
|
|
|
116
215
|
return html `
|
|
117
216
|
<div
|
|
118
217
|
part="toast-item"
|
|
119
|
-
@pointerenter=${this.
|
|
120
|
-
@pointerleave=${this.
|
|
121
|
-
@focusin=${this.
|
|
122
|
-
@focusout=${this.
|
|
218
|
+
@pointerenter=${this.onPointerEnter}
|
|
219
|
+
@pointerleave=${this.onPointerLeave}
|
|
220
|
+
@focusin=${this.onFocusIn}
|
|
221
|
+
@focusout=${this.onFocusOut}
|
|
123
222
|
>
|
|
124
223
|
<span part="accent" aria-hidden="true"></span>
|
|
125
224
|
${this.withIcon ? html `<span part="icon"><slot name="icon"></slot></span>` : ''}
|
|
126
225
|
<div part="content"><slot></slot></div>
|
|
127
|
-
<button
|
|
226
|
+
<button
|
|
227
|
+
part="close-button"
|
|
228
|
+
type="button"
|
|
229
|
+
aria-label=${this.closeLabel}
|
|
230
|
+
aria-disabled=${this.hiding ? 'true' : 'false'}
|
|
231
|
+
@click=${(e) => {
|
|
232
|
+
// Reflect the disabled state on the DOM node synchronously, not
|
|
233
|
+
// just via the reactive binding above -- Lit's re-render from
|
|
234
|
+
// `this.hiding = true` (inside hide()) lands on the next
|
|
235
|
+
// microtask, which is too late for a screen reader (or a second
|
|
236
|
+
// rapid click) that inspects the attribute right after this
|
|
237
|
+
// handler returns.
|
|
238
|
+
e.currentTarget.setAttribute('aria-disabled', 'true');
|
|
239
|
+
void this.hide();
|
|
240
|
+
}}
|
|
241
|
+
>
|
|
128
242
|
${closeIcon()}
|
|
129
243
|
</button>
|
|
130
244
|
</div>
|
|
@@ -143,4 +257,7 @@ __decorate([
|
|
|
143
257
|
__decorate([
|
|
144
258
|
property({ type: Boolean, attribute: 'with-icon' })
|
|
145
259
|
], LyraToastItem.prototype, "withIcon", void 0);
|
|
260
|
+
__decorate([
|
|
261
|
+
state()
|
|
262
|
+
], LyraToastItem.prototype, "hiding", void 0);
|
|
146
263
|
defineElement('toast-item', LyraToastItem);
|
|
@@ -6,6 +6,7 @@ export const styles = css `
|
|
|
6
6
|
--show-duration: var(--lyra-transition-base, 180ms ease-out);
|
|
7
7
|
--hide-duration: var(--lyra-transition-base, 180ms ease-out);
|
|
8
8
|
--padding: var(--lyra-space-m);
|
|
9
|
+
--font-size: 1rem;
|
|
9
10
|
--accent-color: var(--lyra-color-border);
|
|
10
11
|
}
|
|
11
12
|
:host([variant='brand']) {
|
|
@@ -20,6 +21,26 @@ export const styles = css `
|
|
|
20
21
|
:host([variant='danger']) {
|
|
21
22
|
--accent-color: var(--lyra-color-danger);
|
|
22
23
|
}
|
|
24
|
+
:host([size='xs']) {
|
|
25
|
+
--padding: var(--lyra-space-xs);
|
|
26
|
+
--font-size: 0.75rem;
|
|
27
|
+
}
|
|
28
|
+
:host([size='s']) {
|
|
29
|
+
--padding: var(--lyra-space-s);
|
|
30
|
+
--font-size: 0.875rem;
|
|
31
|
+
}
|
|
32
|
+
:host([size='m']) {
|
|
33
|
+
--padding: var(--lyra-space-m);
|
|
34
|
+
--font-size: 1rem;
|
|
35
|
+
}
|
|
36
|
+
:host([size='l']) {
|
|
37
|
+
--padding: var(--lyra-space-l);
|
|
38
|
+
--font-size: 1.125rem;
|
|
39
|
+
}
|
|
40
|
+
:host([size='xl']) {
|
|
41
|
+
--padding: calc(var(--lyra-space-l) * 1.5);
|
|
42
|
+
--font-size: 1.25rem;
|
|
43
|
+
}
|
|
23
44
|
|
|
24
45
|
[part='toast-item'] {
|
|
25
46
|
position: relative;
|
|
@@ -29,6 +50,7 @@ export const styles = css `
|
|
|
29
50
|
inline-size: 100%;
|
|
30
51
|
padding: var(--padding);
|
|
31
52
|
padding-inline-start: calc(var(--padding) + var(--accent-width));
|
|
53
|
+
font-size: var(--font-size);
|
|
32
54
|
background: var(--lyra-color-surface);
|
|
33
55
|
color: var(--lyra-color-text);
|
|
34
56
|
border: 1px solid var(--lyra-color-border);
|
|
@@ -75,14 +97,18 @@ export const styles = css `
|
|
|
75
97
|
border: none;
|
|
76
98
|
cursor: pointer;
|
|
77
99
|
color: var(--lyra-color-text-quiet);
|
|
78
|
-
font-size:
|
|
100
|
+
font-size: 1em;
|
|
79
101
|
line-height: 1;
|
|
80
102
|
padding: var(--lyra-space-xs);
|
|
81
103
|
border-radius: var(--lyra-radius);
|
|
82
104
|
}
|
|
83
|
-
[part='close-button']:hover {
|
|
105
|
+
[part='close-button']:hover:not([aria-disabled='true']) {
|
|
84
106
|
color: var(--lyra-color-text);
|
|
85
107
|
}
|
|
108
|
+
[part='close-button'][aria-disabled='true'] {
|
|
109
|
+
opacity: var(--lyra-opacity-disabled);
|
|
110
|
+
cursor: not-allowed;
|
|
111
|
+
}
|
|
86
112
|
[part='close-button']:focus-visible {
|
|
87
113
|
outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
|
|
88
114
|
outline-offset: var(--lyra-focus-ring-offset);
|
|
@@ -2,11 +2,12 @@ import { html } from 'lit';
|
|
|
2
2
|
import { toast } from '../../lyra.js';
|
|
3
3
|
const meta = {
|
|
4
4
|
title: 'Toast',
|
|
5
|
+
component: 'lyra-toast',
|
|
5
6
|
tags: ['autodocs'],
|
|
6
7
|
parameters: {
|
|
7
8
|
docs: {
|
|
8
9
|
description: {
|
|
9
|
-
component: 'Click a button to fire a toast via the `toast()` helper — the ergonomic entry point that lazily mounts
|
|
10
|
+
component: 'Click a button to fire a toast via the `toast()` helper — the ergonomic entry point that lazily mounts one `<lyra-toast>` region per placement on `document.body`.',
|
|
10
11
|
},
|
|
11
12
|
},
|
|
12
13
|
},
|
|
@@ -30,3 +31,42 @@ export const Triggers = {
|
|
|
30
31
|
</div>
|
|
31
32
|
`,
|
|
32
33
|
};
|
|
34
|
+
const sizes = ['xs', 's', 'm', 'l', 'xl'];
|
|
35
|
+
export const Sizes = {
|
|
36
|
+
render: () => html `
|
|
37
|
+
<div style="display:flex; gap:1rem;">
|
|
38
|
+
${sizes.map((size) => html `<button @click=${() => toast({ message: `Size "${size}"`, size })}>${size}</button>`)}
|
|
39
|
+
</div>
|
|
40
|
+
`,
|
|
41
|
+
};
|
|
42
|
+
export const WithIcon = {
|
|
43
|
+
render: () => html `
|
|
44
|
+
<div style="display:flex; gap:1rem;">
|
|
45
|
+
<button
|
|
46
|
+
@click=${() => toast({ message: 'Upload complete', variant: 'success', withIcon: true }).item.then((item) => {
|
|
47
|
+
const icon = document.createElement('span');
|
|
48
|
+
icon.slot = 'icon';
|
|
49
|
+
icon.textContent = '✓';
|
|
50
|
+
item.appendChild(icon);
|
|
51
|
+
})}
|
|
52
|
+
>
|
|
53
|
+
Success + icon
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
`,
|
|
57
|
+
};
|
|
58
|
+
const placements = [
|
|
59
|
+
'top-start',
|
|
60
|
+
'top-center',
|
|
61
|
+
'top-end',
|
|
62
|
+
'bottom-start',
|
|
63
|
+
'bottom-center',
|
|
64
|
+
'bottom-end',
|
|
65
|
+
];
|
|
66
|
+
export const Placements = {
|
|
67
|
+
render: () => html `
|
|
68
|
+
<div style="display:flex; flex-wrap:wrap; gap:1rem;">
|
|
69
|
+
${placements.map((placement) => html `<button @click=${() => toast({ message: placement, placement })}>${placement}</button>`)}
|
|
70
|
+
</div>
|
|
71
|
+
`,
|
|
72
|
+
};
|
|
@@ -13,7 +13,7 @@ export const styles = css `
|
|
|
13
13
|
flex-direction: column;
|
|
14
14
|
gap: var(--gap);
|
|
15
15
|
inline-size: var(--width);
|
|
16
|
-
max-inline-size: calc(100vw -
|
|
16
|
+
max-inline-size: calc(100vw - (var(--lyra-space-l) * 2));
|
|
17
17
|
}
|
|
18
18
|
::slotted(*) {
|
|
19
19
|
pointer-events: auto;
|
|
@@ -23,28 +23,28 @@ export const styles = css `
|
|
|
23
23
|
flex-direction: column-reverse;
|
|
24
24
|
}
|
|
25
25
|
:host([placement='top-start']) {
|
|
26
|
-
inset-block-start:
|
|
27
|
-
inset-inline-start:
|
|
26
|
+
inset-block-start: var(--lyra-space-l);
|
|
27
|
+
inset-inline-start: var(--lyra-space-l);
|
|
28
28
|
}
|
|
29
29
|
:host([placement='top-end']) {
|
|
30
|
-
inset-block-start:
|
|
31
|
-
inset-inline-end:
|
|
30
|
+
inset-block-start: var(--lyra-space-l);
|
|
31
|
+
inset-inline-end: var(--lyra-space-l);
|
|
32
32
|
}
|
|
33
33
|
:host([placement='top-center']) {
|
|
34
|
-
inset-block-start:
|
|
34
|
+
inset-block-start: var(--lyra-space-l);
|
|
35
35
|
inset-inline: 0;
|
|
36
36
|
margin-inline: auto;
|
|
37
37
|
}
|
|
38
38
|
:host([placement='bottom-start']) {
|
|
39
|
-
inset-block-end:
|
|
40
|
-
inset-inline-start:
|
|
39
|
+
inset-block-end: var(--lyra-space-l);
|
|
40
|
+
inset-inline-start: var(--lyra-space-l);
|
|
41
41
|
}
|
|
42
42
|
:host([placement='bottom-end']) {
|
|
43
|
-
inset-block-end:
|
|
44
|
-
inset-inline-end:
|
|
43
|
+
inset-block-end: var(--lyra-space-l);
|
|
44
|
+
inset-inline-end: var(--lyra-space-l);
|
|
45
45
|
}
|
|
46
46
|
:host([placement='bottom-center']) {
|
|
47
|
-
inset-block-end:
|
|
47
|
+
inset-block-end: var(--lyra-space-l);
|
|
48
48
|
inset-inline: 0;
|
|
49
49
|
margin-inline: auto;
|
|
50
50
|
}
|
|
@@ -18,7 +18,7 @@ export interface ToastHandle {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* Show a toast. Ergonomic convenience over `<lyra-toast>.create()` that mounts
|
|
21
|
-
* and reuses a
|
|
21
|
+
* and reuses a page-level region per placement — the drop-in for `react-hot-toast`.
|
|
22
22
|
*
|
|
23
23
|
* @example toast('Saved');
|
|
24
24
|
* @example toast({ message: 'Deleted', variant: 'danger', action: { label: 'Undo', onClick: undo } });
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
import { LyraToast } from './toast.js';
|
|
2
2
|
import './toast.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const DEFAULT_PLACEMENT = 'top-end';
|
|
4
|
+
const regions = new Map();
|
|
5
|
+
/**
|
|
6
|
+
* Get (or lazily mount) the singleton toast region for a given placement.
|
|
7
|
+
* Each placement gets its own region element so that a `toast()` call
|
|
8
|
+
* targeting one placement never relocates toasts already showing at another
|
|
9
|
+
* -- `placement` is a per-call option, not a global, retroactive one.
|
|
10
|
+
*/
|
|
11
|
+
function getRegion(placement = DEFAULT_PLACEMENT) {
|
|
12
|
+
let region = regions.get(placement);
|
|
6
13
|
if (!region || !region.isConnected) {
|
|
7
14
|
region = document.createElement('lyra-toast');
|
|
15
|
+
region.placement = placement;
|
|
8
16
|
document.body.appendChild(region);
|
|
17
|
+
regions.set(placement, region);
|
|
9
18
|
}
|
|
10
|
-
if (placement)
|
|
11
|
-
region.placement = placement;
|
|
12
19
|
return region;
|
|
13
20
|
}
|
|
14
21
|
/**
|
|
15
22
|
* Show a toast. Ergonomic convenience over `<lyra-toast>.create()` that mounts
|
|
16
|
-
* and reuses a
|
|
23
|
+
* and reuses a page-level region per placement — the drop-in for `react-hot-toast`.
|
|
17
24
|
*
|
|
18
25
|
* @example toast('Saved');
|
|
19
26
|
* @example toast({ message: 'Deleted', variant: 'danger', action: { label: 'Undo', onClick: undo } });
|
|
@@ -13,6 +13,8 @@ import type { TreeItem } from './tree.js';
|
|
|
13
13
|
* sibling).
|
|
14
14
|
*
|
|
15
15
|
* @customElement lyra-tree-node
|
|
16
|
+
* @event lyra-node-toggle - `detail: { id, expanded }`, fired when this node is expanded or collapsed (via `expand()`/`collapse()`, the toggle button, or ArrowRight/ArrowLeft).
|
|
17
|
+
* @event lyra-node-select - `detail: { id }`, fired when this node's primary action is activated (via `select()`, clicking its label, or Enter/Space).
|
|
16
18
|
* @csspart row, toggle, label, badge
|
|
17
19
|
*/
|
|
18
20
|
export declare class LyraTreeNode extends LyraElement {
|
|
@@ -32,15 +34,19 @@ export declare class LyraTreeNode extends LyraElement {
|
|
|
32
34
|
collapse(): void;
|
|
33
35
|
/** Fire this item's primary "select" action (Enter/Space, or clicking the label). */
|
|
34
36
|
select(): void;
|
|
37
|
+
/** See `cascadeUpdateComplete` and the matching override on `<lyra-tree>`. */
|
|
38
|
+
protected getUpdateComplete(): Promise<boolean>;
|
|
35
39
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* A `<button>` is a real focusable click target even with
|
|
41
|
+
* `tabindex="-1" aria-hidden="true"` -- only *sequential* (Tab-key)
|
|
42
|
+
* navigation honors `tabindex="-1"`, a mouse click does not. Left
|
|
43
|
+
* unguarded, clicking this toggle parks real DOM focus on this hidden,
|
|
44
|
+
* non-treeitem button instead of the host, which is both an a11y dead end
|
|
45
|
+
* and invisible to `:host(:focus-visible)`'s ring. `preventDefault()` on
|
|
46
|
+
* `mousedown` blocks that default focus move; focusing the host instead
|
|
47
|
+
* keeps focus on the actual `role="treeitem"` element the row represents.
|
|
42
48
|
*/
|
|
43
|
-
|
|
49
|
+
private onToggleMouseDown;
|
|
44
50
|
render(): TemplateResult;
|
|
45
51
|
}
|
|
46
52
|
declare global {
|
|
@@ -11,6 +11,7 @@ import { LyraElement } from '../../internal/lyra-element.js';
|
|
|
11
11
|
import { defineElement } from '../../internal/prefix.js';
|
|
12
12
|
import { chevronIcon } from '../../internal/icons.js';
|
|
13
13
|
import { css } from 'lit';
|
|
14
|
+
import { cascadeUpdateComplete } from './update-cascade.js';
|
|
14
15
|
const styles = css `
|
|
15
16
|
:host {
|
|
16
17
|
display: block;
|
|
@@ -52,7 +53,9 @@ const styles = css `
|
|
|
52
53
|
cursor: pointer;
|
|
53
54
|
flex: 0 0 auto;
|
|
54
55
|
}
|
|
55
|
-
[part='toggle']
|
|
56
|
+
[part='toggle'][hidden] {
|
|
57
|
+
/* visibility (not display) so the placeholder keeps its layout box --
|
|
58
|
+
a leaf row still lines up with sibling rows that do have a chevron. */
|
|
56
59
|
visibility: hidden;
|
|
57
60
|
}
|
|
58
61
|
:host([expanded]) [part='toggle'] {
|
|
@@ -85,6 +88,8 @@ const styles = css `
|
|
|
85
88
|
* sibling).
|
|
86
89
|
*
|
|
87
90
|
* @customElement lyra-tree-node
|
|
91
|
+
* @event lyra-node-toggle - `detail: { id, expanded }`, fired when this node is expanded or collapsed (via `expand()`/`collapse()`, the toggle button, or ArrowRight/ArrowLeft).
|
|
92
|
+
* @event lyra-node-select - `detail: { id }`, fired when this node's primary action is activated (via `select()`, clicking its label, or Enter/Space).
|
|
88
93
|
* @csspart row, toggle, label, badge
|
|
89
94
|
*/
|
|
90
95
|
export class LyraTreeNode extends LyraElement {
|
|
@@ -96,6 +101,20 @@ export class LyraTreeNode extends LyraElement {
|
|
|
96
101
|
this.activeId = null;
|
|
97
102
|
this.setSize = 1;
|
|
98
103
|
this.posInSet = 1;
|
|
104
|
+
/**
|
|
105
|
+
* A `<button>` is a real focusable click target even with
|
|
106
|
+
* `tabindex="-1" aria-hidden="true"` -- only *sequential* (Tab-key)
|
|
107
|
+
* navigation honors `tabindex="-1"`, a mouse click does not. Left
|
|
108
|
+
* unguarded, clicking this toggle parks real DOM focus on this hidden,
|
|
109
|
+
* non-treeitem button instead of the host, which is both an a11y dead end
|
|
110
|
+
* and invisible to `:host(:focus-visible)`'s ring. `preventDefault()` on
|
|
111
|
+
* `mousedown` blocks that default focus move; focusing the host instead
|
|
112
|
+
* keeps focus on the actual `role="treeitem"` element the row represents.
|
|
113
|
+
*/
|
|
114
|
+
this.onToggleMouseDown = (e) => {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
this.focus();
|
|
117
|
+
};
|
|
99
118
|
}
|
|
100
119
|
static { this.styles = [LyraElement.styles, styles]; }
|
|
101
120
|
get hasChildren() {
|
|
@@ -130,17 +149,10 @@ export class LyraTreeNode extends LyraElement {
|
|
|
130
149
|
select() {
|
|
131
150
|
this.emit('lyra-node-select', { id: this.item.id });
|
|
132
151
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Cascade `updateComplete`: a nested `<lyra-tree-node>` rendered by this
|
|
135
|
-
* one (in its own shadow root) only receives a pushed-down `activeId` (and
|
|
136
|
-
* the `tabIndex` that comes from it) once *this* node's own render has
|
|
137
|
-
* committed. Without this override, awaiting a parent's `updateComplete`
|
|
138
|
-
* doesn't guarantee an arbitrarily-nested descendant's own pending update
|
|
139
|
-
* has also settled — see the matching override on `<lyra-tree>`.
|
|
140
|
-
*/
|
|
152
|
+
/** See `cascadeUpdateComplete` and the matching override on `<lyra-tree>`. */
|
|
141
153
|
async getUpdateComplete() {
|
|
142
154
|
const result = await super.getUpdateComplete();
|
|
143
|
-
await
|
|
155
|
+
await cascadeUpdateComplete(this.shadowRoot?.querySelectorAll('lyra-tree-node') ?? []);
|
|
144
156
|
return result;
|
|
145
157
|
}
|
|
146
158
|
render() {
|
|
@@ -151,6 +163,8 @@ export class LyraTreeNode extends LyraElement {
|
|
|
151
163
|
type="button"
|
|
152
164
|
tabindex="-1"
|
|
153
165
|
aria-hidden="true"
|
|
166
|
+
?hidden=${!this.hasChildren}
|
|
167
|
+
@mousedown=${this.onToggleMouseDown}
|
|
154
168
|
@click=${(e) => {
|
|
155
169
|
e.stopPropagation();
|
|
156
170
|
this.expanded ? this.collapse() : this.expand();
|