luxen-ui 0.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/LICENSE +21 -0
- package/README.md +98 -0
- package/dist/css/elements/avatar.css +20 -0
- package/dist/css/elements/badge.css +159 -0
- package/dist/css/elements/button.css +171 -0
- package/dist/css/elements/close-button/circle.css +66 -0
- package/dist/css/elements/close-button/ring.css +71 -0
- package/dist/css/elements/close-button/square.css +70 -0
- package/dist/css/elements/disclosure.css +137 -0
- package/dist/css/elements/divider.css +75 -0
- package/dist/css/elements/input-otp.css +164 -0
- package/dist/css/elements/input-stepper/default.css +245 -0
- package/dist/css/elements/input-stepper/rounded.css +238 -0
- package/dist/css/elements/kbd.css +21 -0
- package/dist/css/elements/progress.css +114 -0
- package/dist/css/elements/select.css +71 -0
- package/dist/css/elements/skeleton.css +89 -0
- package/dist/css/elements/tabs/enclosed.css +148 -0
- package/dist/css/elements/tabs/line.css +138 -0
- package/dist/css/elements/toast.css +260 -0
- package/dist/css/index.css +885 -0
- package/dist/custom-elements.json +14424 -0
- package/dist/define.d.ts +9 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +16 -0
- package/dist/elements/avatar/avatar.css +128 -0
- package/dist/elements/avatar/avatar.d.ts +21 -0
- package/dist/elements/avatar/avatar.d.ts.map +1 -0
- package/dist/elements/avatar/avatar.js +106 -0
- package/dist/elements/avatar/index.d.ts +8 -0
- package/dist/elements/avatar/index.d.ts.map +1 -0
- package/dist/elements/avatar/index.js +4 -0
- package/dist/elements/badge/badge.d.ts +17 -0
- package/dist/elements/badge/badge.d.ts.map +1 -0
- package/dist/elements/badge/badge.js +34 -0
- package/dist/elements/badge/index.d.ts +8 -0
- package/dist/elements/badge/index.d.ts.map +1 -0
- package/dist/elements/badge/index.js +4 -0
- package/dist/elements/carousel/carousel.css +205 -0
- package/dist/elements/carousel/carousel.d.ts +148 -0
- package/dist/elements/carousel/carousel.d.ts.map +1 -0
- package/dist/elements/carousel/carousel.js +473 -0
- package/dist/elements/carousel/index.d.ts +8 -0
- package/dist/elements/carousel/index.d.ts.map +1 -0
- package/dist/elements/carousel/index.js +4 -0
- package/dist/elements/carousel-item/carousel-item.css +11 -0
- package/dist/elements/carousel-item/carousel-item.d.ts +13 -0
- package/dist/elements/carousel-item/carousel-item.d.ts.map +1 -0
- package/dist/elements/carousel-item/carousel-item.js +20 -0
- package/dist/elements/carousel-item/index.d.ts +8 -0
- package/dist/elements/carousel-item/index.d.ts.map +1 -0
- package/dist/elements/carousel-item/index.js +4 -0
- package/dist/elements/dialog/dialog.css +92 -0
- package/dist/elements/dialog/dialog.d.ts +56 -0
- package/dist/elements/dialog/dialog.d.ts.map +1 -0
- package/dist/elements/dialog/dialog.js +204 -0
- package/dist/elements/dialog/dialog.styles.d.ts +8 -0
- package/dist/elements/dialog/dialog.styles.d.ts.map +1 -0
- package/dist/elements/dialog/dialog.styles.js +8 -0
- package/dist/elements/dialog/index.d.ts +8 -0
- package/dist/elements/dialog/index.d.ts.map +1 -0
- package/dist/elements/dialog/index.js +4 -0
- package/dist/elements/divider/divider.d.ts +23 -0
- package/dist/elements/divider/divider.d.ts.map +1 -0
- package/dist/elements/divider/divider.js +49 -0
- package/dist/elements/divider/index.d.ts +8 -0
- package/dist/elements/divider/index.d.ts.map +1 -0
- package/dist/elements/divider/index.js +4 -0
- package/dist/elements/drawer/drawer.css +66 -0
- package/dist/elements/drawer/drawer.d.ts +34 -0
- package/dist/elements/drawer/drawer.d.ts.map +1 -0
- package/dist/elements/drawer/drawer.js +46 -0
- package/dist/elements/drawer/index.d.ts +8 -0
- package/dist/elements/drawer/index.d.ts.map +1 -0
- package/dist/elements/drawer/index.js +4 -0
- package/dist/elements/dropdown/dropdown.css +31 -0
- package/dist/elements/dropdown/dropdown.d.ts +64 -0
- package/dist/elements/dropdown/dropdown.d.ts.map +1 -0
- package/dist/elements/dropdown/dropdown.js +322 -0
- package/dist/elements/dropdown/index.d.ts +8 -0
- package/dist/elements/dropdown/index.d.ts.map +1 -0
- package/dist/elements/dropdown/index.js +4 -0
- package/dist/elements/dropdown-item/dropdown-item.css +51 -0
- package/dist/elements/dropdown-item/dropdown-item.d.ts +25 -0
- package/dist/elements/dropdown-item/dropdown-item.d.ts.map +1 -0
- package/dist/elements/dropdown-item/dropdown-item.js +110 -0
- package/dist/elements/dropdown-item/index.d.ts +8 -0
- package/dist/elements/dropdown-item/index.d.ts.map +1 -0
- package/dist/elements/dropdown-item/index.js +4 -0
- package/dist/elements/icon/icon.css +10 -0
- package/dist/elements/icon/icon.d.ts +19 -0
- package/dist/elements/icon/icon.d.ts.map +1 -0
- package/dist/elements/icon/icon.js +53 -0
- package/dist/elements/icon/index.d.ts +8 -0
- package/dist/elements/icon/index.d.ts.map +1 -0
- package/dist/elements/icon/index.js +4 -0
- package/dist/elements/input-otp/index.d.ts +8 -0
- package/dist/elements/input-otp/index.d.ts.map +1 -0
- package/dist/elements/input-otp/index.js +4 -0
- package/dist/elements/input-otp/input-otp.d.ts +31 -0
- package/dist/elements/input-otp/input-otp.d.ts.map +1 -0
- package/dist/elements/input-otp/input-otp.js +139 -0
- package/dist/elements/input-stepper/index.d.ts +8 -0
- package/dist/elements/input-stepper/index.d.ts.map +1 -0
- package/dist/elements/input-stepper/index.js +4 -0
- package/dist/elements/input-stepper/input-stepper.d.ts +63 -0
- package/dist/elements/input-stepper/input-stepper.d.ts.map +1 -0
- package/dist/elements/input-stepper/input-stepper.js +249 -0
- package/dist/elements/popover/index.d.ts +8 -0
- package/dist/elements/popover/index.d.ts.map +1 -0
- package/dist/elements/popover/index.js +4 -0
- package/dist/elements/popover/popover.css +61 -0
- package/dist/elements/popover/popover.d.ts +62 -0
- package/dist/elements/popover/popover.d.ts.map +1 -0
- package/dist/elements/popover/popover.js +244 -0
- package/dist/elements/rating/index.d.ts +8 -0
- package/dist/elements/rating/index.d.ts.map +1 -0
- package/dist/elements/rating/index.js +4 -0
- package/dist/elements/rating/rating.css +102 -0
- package/dist/elements/rating/rating.d.ts +38 -0
- package/dist/elements/rating/rating.d.ts.map +1 -0
- package/dist/elements/rating/rating.js +193 -0
- package/dist/elements/skeleton/index.d.ts +8 -0
- package/dist/elements/skeleton/index.d.ts.map +1 -0
- package/dist/elements/skeleton/index.js +4 -0
- package/dist/elements/skeleton/skeleton.d.ts +12 -0
- package/dist/elements/skeleton/skeleton.d.ts.map +1 -0
- package/dist/elements/skeleton/skeleton.js +13 -0
- package/dist/elements/spinner/index.d.ts +8 -0
- package/dist/elements/spinner/index.d.ts.map +1 -0
- package/dist/elements/spinner/index.js +4 -0
- package/dist/elements/spinner/spinner.css +28 -0
- package/dist/elements/spinner/spinner.d.ts +16 -0
- package/dist/elements/spinner/spinner.d.ts.map +1 -0
- package/dist/elements/spinner/spinner.js +37 -0
- package/dist/elements/tabs/index.d.ts +8 -0
- package/dist/elements/tabs/index.d.ts.map +1 -0
- package/dist/elements/tabs/index.js +4 -0
- package/dist/elements/tabs/tabs.d.ts +48 -0
- package/dist/elements/tabs/tabs.d.ts.map +1 -0
- package/dist/elements/tabs/tabs.js +210 -0
- package/dist/elements/toast/index.d.ts +9 -0
- package/dist/elements/toast/index.d.ts.map +1 -0
- package/dist/elements/toast/index.js +5 -0
- package/dist/elements/toast/toast.d.ts +72 -0
- package/dist/elements/toast/toast.d.ts.map +1 -0
- package/dist/elements/toast/toast.js +375 -0
- package/dist/elements/tooltip/index.d.ts +8 -0
- package/dist/elements/tooltip/index.d.ts.map +1 -0
- package/dist/elements/tooltip/index.js +4 -0
- package/dist/elements/tooltip/tooltip.css +37 -0
- package/dist/elements/tooltip/tooltip.d.ts +59 -0
- package/dist/elements/tooltip/tooltip.d.ts.map +1 -0
- package/dist/elements/tooltip/tooltip.js +231 -0
- package/dist/elements/tree/index.d.ts +8 -0
- package/dist/elements/tree/index.d.ts.map +1 -0
- package/dist/elements/tree/index.js +4 -0
- package/dist/elements/tree/tree.css +26 -0
- package/dist/elements/tree/tree.d.ts +76 -0
- package/dist/elements/tree/tree.d.ts.map +1 -0
- package/dist/elements/tree/tree.js +432 -0
- package/dist/elements/tree-item/index.d.ts +8 -0
- package/dist/elements/tree-item/index.d.ts.map +1 -0
- package/dist/elements/tree-item/index.js +4 -0
- package/dist/elements/tree-item/tree-item.css +172 -0
- package/dist/elements/tree-item/tree-item.d.ts +74 -0
- package/dist/elements/tree-item/tree-item.d.ts.map +1 -0
- package/dist/elements/tree-item/tree-item.js +301 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/registry.d.ts +22 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +33 -0
- package/dist/shared/controllers/popover.d.ts +44 -0
- package/dist/shared/controllers/popover.d.ts.map +1 -0
- package/dist/shared/controllers/popover.js +359 -0
- package/dist/shared/luxen-element.d.ts +20 -0
- package/dist/shared/luxen-element.d.ts.map +1 -0
- package/dist/shared/luxen-element.js +23 -0
- package/dist/shared/luxen-form-associated-element.d.ts +49 -0
- package/dist/shared/luxen-form-associated-element.d.ts.map +1 -0
- package/dist/shared/luxen-form-associated-element.js +123 -0
- package/dist/shared/styles/host.css +13 -0
- package/dist/shared/styles/host.styles.d.ts +9 -0
- package/dist/shared/styles/host.styles.d.ts.map +1 -0
- package/dist/shared/styles/host.styles.js +9 -0
- package/dist/skills/luxen-ui/SKILL.md +82 -0
- package/dist/skills/luxen-ui/references/avatar.md +259 -0
- package/dist/skills/luxen-ui/references/badge.md +289 -0
- package/dist/skills/luxen-ui/references/button.md +309 -0
- package/dist/skills/luxen-ui/references/close-button.md +104 -0
- package/dist/skills/luxen-ui/references/dialog.md +435 -0
- package/dist/skills/luxen-ui/references/drawer.md +400 -0
- package/dist/skills/luxen-ui/references/progress.md +133 -0
- package/dist/skills/luxen-ui/references/select.md +100 -0
- package/dist/skills/luxen-ui/references/toast.md +396 -0
- package/dist/skills/luxen-ui/references/tree.md +359 -0
- package/package.json +116 -0
- package/postcss-plugin-prefix.js +63 -0
- package/vite-plugin.ts +29 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { property } from 'lit/decorators.js';
|
|
8
|
+
import { LuxenElement } from '../../shared/luxen-element';
|
|
9
|
+
import { uniqueId } from '../../registry';
|
|
10
|
+
/**
|
|
11
|
+
* @summary A tabs component that progressively enhances light DOM markup
|
|
12
|
+
* with ARIA roles, keyboard navigation, and animated indicators.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```html
|
|
16
|
+
* <l-tabs variant="enclosed">
|
|
17
|
+
* <div>
|
|
18
|
+
* <button>Tab 1</button>
|
|
19
|
+
* <button>Tab 2</button>
|
|
20
|
+
* </div>
|
|
21
|
+
* <div>Content 1</div>
|
|
22
|
+
* <div>Content 2</div>
|
|
23
|
+
* </l-tabs>
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @event change - Fired when the active tab changes. Detail: `{ index: number, name: string | null }`.
|
|
27
|
+
*
|
|
28
|
+
* @customElement l-tabs
|
|
29
|
+
*/
|
|
30
|
+
export class LuxenTabs extends LuxenElement {
|
|
31
|
+
constructor() {
|
|
32
|
+
super(...arguments);
|
|
33
|
+
this._instanceId = uniqueId('tabs');
|
|
34
|
+
this._tablistEl = null;
|
|
35
|
+
this._tabs = [];
|
|
36
|
+
this._panels = [];
|
|
37
|
+
/** Visual variant. */
|
|
38
|
+
this.variant = 'line';
|
|
39
|
+
/** Index of the active tab (0-based). */
|
|
40
|
+
this.value = '0';
|
|
41
|
+
/** Stretch tabs to fill container width. */
|
|
42
|
+
this.fullWidth = false;
|
|
43
|
+
/** Tab orientation. */
|
|
44
|
+
this.orientation = 'horizontal';
|
|
45
|
+
// --- Event handlers ---
|
|
46
|
+
this._onClick = (e) => {
|
|
47
|
+
const tab = e.target.closest('[role="tab"]');
|
|
48
|
+
if (!tab)
|
|
49
|
+
return;
|
|
50
|
+
const index = this._tabs.indexOf(tab);
|
|
51
|
+
if (index >= 0) {
|
|
52
|
+
this._selectTab(index);
|
|
53
|
+
tab.focus();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
this._onKeyDown = (e) => {
|
|
57
|
+
const target = e.target;
|
|
58
|
+
if (target.getAttribute('role') !== 'tab')
|
|
59
|
+
return;
|
|
60
|
+
const isHorizontal = this.orientation === 'horizontal';
|
|
61
|
+
const prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp';
|
|
62
|
+
const nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown';
|
|
63
|
+
let index = this._tabs.indexOf(target);
|
|
64
|
+
switch (e.key) {
|
|
65
|
+
case nextKey:
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
index = (index + 1) % this._tabs.length;
|
|
68
|
+
this._selectTab(index);
|
|
69
|
+
this._tabs[index].focus();
|
|
70
|
+
break;
|
|
71
|
+
case prevKey:
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
index = (index - 1 + this._tabs.length) % this._tabs.length;
|
|
74
|
+
this._selectTab(index);
|
|
75
|
+
this._tabs[index].focus();
|
|
76
|
+
break;
|
|
77
|
+
case 'Home':
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
this._selectTab(0);
|
|
80
|
+
this._tabs[0].focus();
|
|
81
|
+
break;
|
|
82
|
+
case 'End':
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
this._selectTab(this._tabs.length - 1);
|
|
85
|
+
this._tabs[this._tabs.length - 1].focus();
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
createRenderRoot() {
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
// --- Lifecycle ---
|
|
94
|
+
connectedCallback() {
|
|
95
|
+
super.connectedCallback();
|
|
96
|
+
// Defer setup to let light DOM children parse
|
|
97
|
+
requestAnimationFrame(() => this._setup());
|
|
98
|
+
}
|
|
99
|
+
disconnectedCallback() {
|
|
100
|
+
super.disconnectedCallback();
|
|
101
|
+
this._teardown();
|
|
102
|
+
}
|
|
103
|
+
updated(changed) {
|
|
104
|
+
if (changed.has('value') && this._tabs.length) {
|
|
105
|
+
this._selectTab(Number(this.value), false);
|
|
106
|
+
}
|
|
107
|
+
if (changed.has('orientation') && this._tablistEl) {
|
|
108
|
+
this._tablistEl.setAttribute('aria-orientation', this.orientation);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// --- Setup / Teardown ---
|
|
112
|
+
_setup() {
|
|
113
|
+
const children = Array.from(this.children);
|
|
114
|
+
if (children.length < 2)
|
|
115
|
+
return;
|
|
116
|
+
// First child is the tablist container
|
|
117
|
+
this._tablistEl = children[0];
|
|
118
|
+
this._tablistEl.setAttribute('role', 'tablist');
|
|
119
|
+
this._tablistEl.setAttribute('aria-orientation', this.orientation);
|
|
120
|
+
// Buttons inside tablist become tabs
|
|
121
|
+
this._tabs = Array.from(this._tablistEl.querySelectorAll('button'));
|
|
122
|
+
// Remaining children become panels
|
|
123
|
+
this._panels = children.slice(1);
|
|
124
|
+
const activeIndex = Number(this.value) || 0;
|
|
125
|
+
// Enhance tabs
|
|
126
|
+
for (let i = 0; i < this._tabs.length; i++) {
|
|
127
|
+
const tab = this._tabs[i];
|
|
128
|
+
const panel = this._panels[i];
|
|
129
|
+
const name = tab.getAttribute('name') ?? String(i);
|
|
130
|
+
const tabId = `${this._instanceId}-tab-${name}`;
|
|
131
|
+
const panelId = `${this._instanceId}-panel-${name}`;
|
|
132
|
+
tab.setAttribute('role', 'tab');
|
|
133
|
+
tab.setAttribute('id', tabId);
|
|
134
|
+
tab.setAttribute('aria-selected', String(i === activeIndex));
|
|
135
|
+
tab.setAttribute('tabindex', i === activeIndex ? '0' : '-1');
|
|
136
|
+
if (panel) {
|
|
137
|
+
tab.setAttribute('aria-controls', panelId);
|
|
138
|
+
panel.setAttribute('role', 'tabpanel');
|
|
139
|
+
panel.setAttribute('id', panelId);
|
|
140
|
+
panel.setAttribute('aria-labelledby', tabId);
|
|
141
|
+
panel.setAttribute('tabindex', '0');
|
|
142
|
+
if (i !== activeIndex) {
|
|
143
|
+
panel.hidden = true;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
panel.hidden = false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Attach listeners
|
|
151
|
+
this._tablistEl.addEventListener('click', this._onClick);
|
|
152
|
+
this._tablistEl.addEventListener('keydown', this._onKeyDown);
|
|
153
|
+
// Initial indicator position
|
|
154
|
+
requestAnimationFrame(() => this._updateIndicator());
|
|
155
|
+
}
|
|
156
|
+
_teardown() {
|
|
157
|
+
this._tablistEl?.removeEventListener('click', this._onClick);
|
|
158
|
+
this._tablistEl?.removeEventListener('keydown', this._onKeyDown);
|
|
159
|
+
}
|
|
160
|
+
// --- Tab selection ---
|
|
161
|
+
_selectTab(index, emitEvent = true) {
|
|
162
|
+
if (index < 0 || index >= this._tabs.length)
|
|
163
|
+
return;
|
|
164
|
+
for (let i = 0; i < this._tabs.length; i++) {
|
|
165
|
+
const tab = this._tabs[i];
|
|
166
|
+
const panel = this._panels[i];
|
|
167
|
+
const isActive = i === index;
|
|
168
|
+
tab.setAttribute('aria-selected', String(isActive));
|
|
169
|
+
tab.setAttribute('tabindex', isActive ? '0' : '-1');
|
|
170
|
+
if (panel) {
|
|
171
|
+
panel.hidden = !isActive;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
this.value = String(index);
|
|
175
|
+
this._updateIndicator();
|
|
176
|
+
if (emitEvent) {
|
|
177
|
+
const name = this._tabs[index]?.getAttribute('name') ?? null;
|
|
178
|
+
this.emit('change', { detail: { index, name } });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// --- Indicator ---
|
|
182
|
+
_updateIndicator() {
|
|
183
|
+
if (!this._tablistEl)
|
|
184
|
+
return;
|
|
185
|
+
const activeTab = this._tabs[Number(this.value)];
|
|
186
|
+
if (!activeTab)
|
|
187
|
+
return;
|
|
188
|
+
const isVertical = this.orientation === 'vertical';
|
|
189
|
+
if (isVertical) {
|
|
190
|
+
this._tablistEl.style.setProperty('--_indicator-top', `${activeTab.offsetTop}px`);
|
|
191
|
+
this._tablistEl.style.setProperty('--_indicator-height', `${activeTab.offsetHeight}px`);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
this._tablistEl.style.setProperty('--_indicator-left', `${activeTab.offsetLeft}px`);
|
|
195
|
+
this._tablistEl.style.setProperty('--_indicator-width', `${activeTab.offsetWidth}px`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
__decorate([
|
|
200
|
+
property({ reflect: true })
|
|
201
|
+
], LuxenTabs.prototype, "variant", void 0);
|
|
202
|
+
__decorate([
|
|
203
|
+
property({ reflect: true })
|
|
204
|
+
], LuxenTabs.prototype, "value", void 0);
|
|
205
|
+
__decorate([
|
|
206
|
+
property({ type: Boolean, reflect: true, attribute: 'full-width' })
|
|
207
|
+
], LuxenTabs.prototype, "fullWidth", void 0);
|
|
208
|
+
__decorate([
|
|
209
|
+
property({ reflect: true })
|
|
210
|
+
], LuxenTabs.prototype, "orientation", void 0);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/html/elements/toast/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACrD,cAAc,SAAS,CAAC;AAIxB,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,SAAS,EAAE,UAAU,CAAC;QACtB,cAAc,EAAE,cAAc,CAAC;KAChC;CACF"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { LuxenElement } from '../../shared/luxen-element';
|
|
2
|
+
declare global {
|
|
3
|
+
interface Element {
|
|
4
|
+
/** @see https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML */
|
|
5
|
+
setHTML(input: string, options?: Record<string, unknown>): void;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/** Minimal custom element for toast items — no render, no shadow DOM. */
|
|
9
|
+
export declare class LuxenToastItem extends HTMLElement {
|
|
10
|
+
}
|
|
11
|
+
interface ToastOptionsBase {
|
|
12
|
+
/** Optional heading text displayed above the message. */
|
|
13
|
+
heading?: string;
|
|
14
|
+
/** Override the element's variant for this toast. */
|
|
15
|
+
variant?: string;
|
|
16
|
+
/** Override auto-dismiss duration (ms) for this toast. 0 = no auto-dismiss. */
|
|
17
|
+
duration?: number;
|
|
18
|
+
/** Iconify icon name (e.g. 'lucide:check'). Replaces the accent bar. */
|
|
19
|
+
icon?: string;
|
|
20
|
+
/** Show a countdown progress bar at the bottom. Default `false`. */
|
|
21
|
+
timer?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export type ToastOptions = (ToastOptionsBase & {
|
|
24
|
+
text: string;
|
|
25
|
+
html?: never;
|
|
26
|
+
}) | (ToastOptionsBase & {
|
|
27
|
+
text?: never;
|
|
28
|
+
/** HTML content (sanitized via the Sanitizer API). */ html: string;
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* @summary A toast notification container that generates toast items internally.
|
|
32
|
+
* @customElement l-toast
|
|
33
|
+
*
|
|
34
|
+
* @event show - Emitted when a toast begins to show. Cancelable.
|
|
35
|
+
* @event after-show - Emitted after the show animation completes.
|
|
36
|
+
* @event hide - Emitted when a toast begins to hide. Cancelable.
|
|
37
|
+
* @event after-hide - Emitted after the hide animation completes and toast is removed.
|
|
38
|
+
*
|
|
39
|
+
* @cssproperty --gap - Gap between stacked toast items.
|
|
40
|
+
* @cssproperty --width - Width of the toast stack.
|
|
41
|
+
* @cssproperty --show-duration - Duration of the show animation.
|
|
42
|
+
* @cssproperty --hide-duration - Duration of the hide animation.
|
|
43
|
+
*/
|
|
44
|
+
export declare class LuxenToast extends LuxenElement {
|
|
45
|
+
/** Use light DOM — no Shadow DOM. */
|
|
46
|
+
createRenderRoot(): this;
|
|
47
|
+
/** Position of the toast stack on the screen. */
|
|
48
|
+
placement: 'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end';
|
|
49
|
+
/** Default auto-dismiss delay in milliseconds. 0 disables auto-dismiss. */
|
|
50
|
+
duration: number;
|
|
51
|
+
/** Default variant for toast items: info, success, warning, danger. */
|
|
52
|
+
variant: string;
|
|
53
|
+
private _timers;
|
|
54
|
+
private _positionCache;
|
|
55
|
+
private _documentHidden;
|
|
56
|
+
connectedCallback(): void;
|
|
57
|
+
disconnectedCallback(): void;
|
|
58
|
+
/** Create and show a toast notification. */
|
|
59
|
+
toast(options: ToastOptions): HTMLElement;
|
|
60
|
+
private _startTimer;
|
|
61
|
+
private _pauseTimer;
|
|
62
|
+
private _resumeTimer;
|
|
63
|
+
private _hideToast;
|
|
64
|
+
private _capturePositions;
|
|
65
|
+
private _animatePositions;
|
|
66
|
+
private _onVisibilityChange;
|
|
67
|
+
private _onKeyDown;
|
|
68
|
+
}
|
|
69
|
+
/** Show a toast notification. Auto-creates `<l-toast>` if none exists in the DOM. */
|
|
70
|
+
export declare function toast(options: ToastOptions): HTMLElement;
|
|
71
|
+
export {};
|
|
72
|
+
//# sourceMappingURL=toast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../../src/html/elements/toast/toast.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAI1D,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO;QACf,4EAA4E;QAC5E,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;KACjE;CACF;AAED,yEAAyE;AACzE,qBAAa,cAAe,SAAQ,WAAW;CAAG;AAElD,UAAU,gBAAgB;IACxB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,YAAY,GACpB,CAAC,gBAAgB,GAAG;IAAsC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,GACvF,CAAC,gBAAgB,GAAG;IAClB,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,sDAAsD,CAAC,IAAI,EAAE,MAAM,CAAC;CACrE,CAAC,CAAC;AASP;;;;;;;;;;;;;GAaG;AACH,qBAAa,UAAW,SAAQ,YAAY;IAC1C,qCAAqC;IAC5B,gBAAgB;IAIzB,iDAAiD;IACpB,SAAS,EAClC,WAAW,GACX,YAAY,GACZ,SAAS,GACT,cAAc,GACd,eAAe,GACf,YAAY,CAAa;IAE7B,2EAA2E;IAChC,QAAQ,SAAQ;IAE3D,uEAAuE;IAC1C,OAAO,SAAM;IAE1C,OAAO,CAAC,OAAO,CAA0C;IACzD,OAAO,CAAC,cAAc,CAAmC;IACzD,OAAO,CAAC,eAAe,CAAS;IAEvB,iBAAiB;IAWjB,oBAAoB;IAM7B,4CAA4C;IAC5C,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,WAAW;IAqLzC,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,UAAU;IAgDlB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,mBAAmB,CAQzB;IAIF,OAAO,CAAC,UAAU,CAShB;CACH;AAkBD,qFAAqF;AACrF,wBAAgB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,WAAW,CAExD"}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
/* eslint-disable no-shadow -- the local `toast` HTMLElement intentionally
|
|
8
|
+
shadows the module-level `toast()` export; renaming would touch ~50 lines
|
|
9
|
+
without behavioral benefit. */
|
|
10
|
+
import { LuxenElement } from '../../shared/luxen-element';
|
|
11
|
+
import { property } from 'lit/decorators.js';
|
|
12
|
+
import { tagName, cls, uniqueId } from '../../registry';
|
|
13
|
+
/** Minimal custom element for toast items — no render, no shadow DOM. */
|
|
14
|
+
export class LuxenToastItem extends HTMLElement {
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* @summary A toast notification container that generates toast items internally.
|
|
18
|
+
* @customElement l-toast
|
|
19
|
+
*
|
|
20
|
+
* @event show - Emitted when a toast begins to show. Cancelable.
|
|
21
|
+
* @event after-show - Emitted after the show animation completes.
|
|
22
|
+
* @event hide - Emitted when a toast begins to hide. Cancelable.
|
|
23
|
+
* @event after-hide - Emitted after the hide animation completes and toast is removed.
|
|
24
|
+
*
|
|
25
|
+
* @cssproperty --gap - Gap between stacked toast items.
|
|
26
|
+
* @cssproperty --width - Width of the toast stack.
|
|
27
|
+
* @cssproperty --show-duration - Duration of the show animation.
|
|
28
|
+
* @cssproperty --hide-duration - Duration of the hide animation.
|
|
29
|
+
*/
|
|
30
|
+
export class LuxenToast extends LuxenElement {
|
|
31
|
+
constructor() {
|
|
32
|
+
super(...arguments);
|
|
33
|
+
/** Position of the toast stack on the screen. */
|
|
34
|
+
this.placement = 'top-end';
|
|
35
|
+
/** Default auto-dismiss delay in milliseconds. 0 disables auto-dismiss. */
|
|
36
|
+
this.duration = 5000;
|
|
37
|
+
/** Default variant for toast items: info, success, warning, danger. */
|
|
38
|
+
this.variant = '';
|
|
39
|
+
this._timers = new WeakMap();
|
|
40
|
+
this._positionCache = new WeakMap();
|
|
41
|
+
this._documentHidden = false;
|
|
42
|
+
// ── Document visibility ──────────────────────────────────────────────────────
|
|
43
|
+
this._onVisibilityChange = () => {
|
|
44
|
+
this._documentHidden = document.hidden;
|
|
45
|
+
const items = this.querySelectorAll(`:scope > ${tagName('toast-item')}`);
|
|
46
|
+
if (document.hidden) {
|
|
47
|
+
for (const item of items)
|
|
48
|
+
this._pauseTimer(item);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
for (const item of items)
|
|
52
|
+
this._resumeTimer(item);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
// ── Keyboard ──────────────────────────────────────────────────────────────────
|
|
56
|
+
this._onKeyDown = (e) => {
|
|
57
|
+
if (e.key !== 'Escape')
|
|
58
|
+
return;
|
|
59
|
+
if (!this.matches(':popover-open'))
|
|
60
|
+
return;
|
|
61
|
+
const last = this.querySelector(`:scope > ${tagName('toast-item')}:last-of-type`);
|
|
62
|
+
if (last) {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
this._hideToast(last);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Use light DOM — no Shadow DOM. */
|
|
69
|
+
createRenderRoot() {
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
connectedCallback() {
|
|
73
|
+
super.connectedCallback();
|
|
74
|
+
this.popover = 'manual';
|
|
75
|
+
this.setAttribute('role', 'log');
|
|
76
|
+
this.setAttribute('aria-live', 'polite');
|
|
77
|
+
this.setAttribute('aria-relevant', 'additions');
|
|
78
|
+
document.addEventListener('keydown', this._onKeyDown);
|
|
79
|
+
document.addEventListener('visibilitychange', this._onVisibilityChange);
|
|
80
|
+
}
|
|
81
|
+
disconnectedCallback() {
|
|
82
|
+
super.disconnectedCallback();
|
|
83
|
+
document.removeEventListener('keydown', this._onKeyDown);
|
|
84
|
+
document.removeEventListener('visibilitychange', this._onVisibilityChange);
|
|
85
|
+
}
|
|
86
|
+
/** Create and show a toast notification. */
|
|
87
|
+
toast(options) {
|
|
88
|
+
const { heading, variant = this.variant, duration = this.duration } = options;
|
|
89
|
+
// Build toast DOM
|
|
90
|
+
const uid = uniqueId('toast');
|
|
91
|
+
const toast = document.createElement(tagName('toast-item'));
|
|
92
|
+
if (variant)
|
|
93
|
+
toast.setAttribute('variant', variant);
|
|
94
|
+
toast.setAttribute('role', variant === 'danger' ? 'alert' : 'status');
|
|
95
|
+
toast.setAttribute('aria-atomic', 'true');
|
|
96
|
+
if (options.icon) {
|
|
97
|
+
const icon = document.createElement('iconify-icon');
|
|
98
|
+
icon.setAttribute('icon', options.icon);
|
|
99
|
+
icon.setAttribute('width', '20');
|
|
100
|
+
icon.setAttribute('height', '20');
|
|
101
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
102
|
+
icon.className = cls('toast-icon');
|
|
103
|
+
toast.appendChild(icon);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const accent = document.createElement('div');
|
|
107
|
+
accent.className = cls('toast-accent');
|
|
108
|
+
accent.setAttribute('aria-hidden', 'true');
|
|
109
|
+
toast.appendChild(accent);
|
|
110
|
+
}
|
|
111
|
+
const content = document.createElement('div');
|
|
112
|
+
content.className = cls('toast-content');
|
|
113
|
+
const textWrap = document.createElement('div');
|
|
114
|
+
if (heading) {
|
|
115
|
+
const headingEl = document.createElement('div');
|
|
116
|
+
headingEl.id = `${uid}-heading`;
|
|
117
|
+
headingEl.className = `${cls('toast-heading')} font-medium`;
|
|
118
|
+
headingEl.textContent = heading;
|
|
119
|
+
textWrap.appendChild(headingEl);
|
|
120
|
+
toast.setAttribute('aria-labelledby', `${uid}-heading`);
|
|
121
|
+
}
|
|
122
|
+
const messageEl = document.createElement('div');
|
|
123
|
+
messageEl.id = `${uid}-message`;
|
|
124
|
+
messageEl.className = cls('toast-message');
|
|
125
|
+
if ('html' in options && options.html) {
|
|
126
|
+
messageEl.setHTML(options.html);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
messageEl.textContent = options.text ?? '';
|
|
130
|
+
}
|
|
131
|
+
textWrap.appendChild(messageEl);
|
|
132
|
+
toast.setAttribute(heading ? 'aria-describedby' : 'aria-labelledby', `${uid}-message`);
|
|
133
|
+
content.appendChild(textWrap);
|
|
134
|
+
toast.appendChild(content);
|
|
135
|
+
const closeBtn = document.createElement('button');
|
|
136
|
+
closeBtn.type = 'button';
|
|
137
|
+
closeBtn.className = cls('close');
|
|
138
|
+
closeBtn.setAttribute('data-appearance', 'ring');
|
|
139
|
+
closeBtn.setAttribute('aria-label', 'Close');
|
|
140
|
+
closeBtn.addEventListener('click', () => this._hideToast(toast));
|
|
141
|
+
toast.appendChild(closeBtn);
|
|
142
|
+
// Timer bar (opt-in)
|
|
143
|
+
if (options.timer && duration > 0 && isFinite(duration)) {
|
|
144
|
+
const timerBar = document.createElement('div');
|
|
145
|
+
timerBar.className = cls('toast-timer');
|
|
146
|
+
timerBar.setAttribute('aria-hidden', 'true');
|
|
147
|
+
timerBar.style.setProperty('--_timer-duration', `${duration}ms`);
|
|
148
|
+
toast.appendChild(timerBar);
|
|
149
|
+
}
|
|
150
|
+
// FLIP: capture existing positions
|
|
151
|
+
this._capturePositions();
|
|
152
|
+
// Insert into container
|
|
153
|
+
if (this.placement.includes('bottom')) {
|
|
154
|
+
this.appendChild(toast);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
this.prepend(toast);
|
|
158
|
+
}
|
|
159
|
+
// Show popover if not already open
|
|
160
|
+
if (!this.matches(':popover-open')) {
|
|
161
|
+
this.showPopover();
|
|
162
|
+
}
|
|
163
|
+
// Dispatch show
|
|
164
|
+
if (!this.emit('show', { cancelable: true, detail: { toast } })) {
|
|
165
|
+
toast.remove();
|
|
166
|
+
return toast;
|
|
167
|
+
}
|
|
168
|
+
// Trigger show animation on next frame
|
|
169
|
+
requestAnimationFrame(() => {
|
|
170
|
+
toast.setAttribute('showing', '');
|
|
171
|
+
this._animatePositions();
|
|
172
|
+
const onShown = (e) => {
|
|
173
|
+
if (e.target !== toast)
|
|
174
|
+
return;
|
|
175
|
+
toast.removeEventListener('transitionend', onShown);
|
|
176
|
+
this.emit('after-show', { detail: { toast } });
|
|
177
|
+
};
|
|
178
|
+
toast.addEventListener('transitionend', onShown);
|
|
179
|
+
});
|
|
180
|
+
// Auto-dismiss timer (0 or Infinity = persistent)
|
|
181
|
+
if (duration > 0 && isFinite(duration)) {
|
|
182
|
+
this._startTimer(toast, duration);
|
|
183
|
+
}
|
|
184
|
+
// Pause/resume timer on hover and focus
|
|
185
|
+
toast.addEventListener('pointerenter', () => this._pauseTimer(toast));
|
|
186
|
+
toast.addEventListener('pointerleave', () => this._resumeTimer(toast));
|
|
187
|
+
toast.addEventListener('focusin', () => this._pauseTimer(toast));
|
|
188
|
+
toast.addEventListener('focusout', () => this._resumeTimer(toast));
|
|
189
|
+
// ── Swipe-to-dismiss ──────────────────────────────────────────────────────
|
|
190
|
+
let swipeStart = null;
|
|
191
|
+
let swiping = false;
|
|
192
|
+
toast.addEventListener('pointerdown', (e) => {
|
|
193
|
+
if (e.button !== 0)
|
|
194
|
+
return;
|
|
195
|
+
if (e.target.closest('button, a, [role="button"]'))
|
|
196
|
+
return;
|
|
197
|
+
swipeStart = { x: e.clientX, y: e.clientY };
|
|
198
|
+
swiping = false;
|
|
199
|
+
toast.setPointerCapture(e.pointerId);
|
|
200
|
+
toast.style.transition = 'none';
|
|
201
|
+
});
|
|
202
|
+
// Only allow swiping away from the screen edge, never inward
|
|
203
|
+
const clampDelta = (dx) => this.placement.endsWith('-start')
|
|
204
|
+
? Math.min(dx, 0)
|
|
205
|
+
: this.placement.endsWith('-end')
|
|
206
|
+
? Math.max(dx, 0)
|
|
207
|
+
: dx;
|
|
208
|
+
toast.addEventListener('pointermove', (e) => {
|
|
209
|
+
if (!swipeStart)
|
|
210
|
+
return;
|
|
211
|
+
const deltaX = clampDelta(e.clientX - swipeStart.x);
|
|
212
|
+
const deltaY = e.clientY - swipeStart.y;
|
|
213
|
+
const buffer = e.pointerType === 'touch' ? 10 : 2;
|
|
214
|
+
if (!swiping && Math.abs(deltaX) < buffer)
|
|
215
|
+
return;
|
|
216
|
+
// If vertical movement dominates, cancel swipe tracking
|
|
217
|
+
if (!swiping && Math.abs(deltaY) > Math.abs(deltaX)) {
|
|
218
|
+
swipeStart = null;
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
swiping = true;
|
|
222
|
+
this._pauseTimer(toast);
|
|
223
|
+
toast.style.transform = `translateX(${deltaX}px)`;
|
|
224
|
+
toast.style.opacity = `${1 - Math.abs(deltaX) / 300}`;
|
|
225
|
+
});
|
|
226
|
+
toast.addEventListener('pointerup', (e) => {
|
|
227
|
+
if (!swipeStart)
|
|
228
|
+
return;
|
|
229
|
+
const deltaX = clampDelta(e.clientX - swipeStart.x);
|
|
230
|
+
swipeStart = null;
|
|
231
|
+
toast.style.transition = '';
|
|
232
|
+
if (swiping && Math.abs(deltaX) > 50) {
|
|
233
|
+
const dir = deltaX > 0 ? 1 : -1;
|
|
234
|
+
toast.style.transform = `translateX(${dir * 120}%)`;
|
|
235
|
+
toast.style.opacity = '0';
|
|
236
|
+
toast.addEventListener('transitionend', () => this._hideToast(toast), { once: true });
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
toast.style.transform = '';
|
|
240
|
+
toast.style.opacity = '';
|
|
241
|
+
if (swiping)
|
|
242
|
+
this._resumeTimer(toast);
|
|
243
|
+
}
|
|
244
|
+
swiping = false;
|
|
245
|
+
});
|
|
246
|
+
return toast;
|
|
247
|
+
}
|
|
248
|
+
// ── Timer management ──────────────────────────────────────────────────────────
|
|
249
|
+
_startTimer(toast, duration) {
|
|
250
|
+
const timeoutId = window.setTimeout(() => this._hideToast(toast), duration);
|
|
251
|
+
this._timers.set(toast, {
|
|
252
|
+
remaining: duration,
|
|
253
|
+
startTime: performance.now(),
|
|
254
|
+
timeoutId,
|
|
255
|
+
paused: false,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
_pauseTimer(toast) {
|
|
259
|
+
const timer = this._timers.get(toast);
|
|
260
|
+
if (!timer || timer.paused)
|
|
261
|
+
return;
|
|
262
|
+
clearTimeout(timer.timeoutId);
|
|
263
|
+
timer.remaining -= performance.now() - timer.startTime;
|
|
264
|
+
timer.paused = true;
|
|
265
|
+
// Use the Web Animations API to pause the countdown bar. CSS `animation-play-state`
|
|
266
|
+
// cannot be used here because browsers defer style recalculations while the tab is
|
|
267
|
+
// hidden, so the animation runs to completion before the style change takes effect.
|
|
268
|
+
const timerBar = toast.querySelector(`.${cls('toast-timer')}`);
|
|
269
|
+
if (timerBar)
|
|
270
|
+
for (const anim of timerBar.getAnimations())
|
|
271
|
+
anim.pause();
|
|
272
|
+
}
|
|
273
|
+
_resumeTimer(toast) {
|
|
274
|
+
const timer = this._timers.get(toast);
|
|
275
|
+
if (!timer || !timer.paused || timer.remaining <= 0 || this._documentHidden)
|
|
276
|
+
return;
|
|
277
|
+
timer.startTime = performance.now();
|
|
278
|
+
timer.timeoutId = window.setTimeout(() => this._hideToast(toast), timer.remaining);
|
|
279
|
+
timer.paused = false;
|
|
280
|
+
const timerBar = toast.querySelector(`.${cls('toast-timer')}`);
|
|
281
|
+
if (timerBar)
|
|
282
|
+
for (const anim of timerBar.getAnimations())
|
|
283
|
+
anim.play();
|
|
284
|
+
}
|
|
285
|
+
// ── Hide a toast ──────────────────────────────────────────────────────────────
|
|
286
|
+
_hideToast(toast) {
|
|
287
|
+
// Clear timer
|
|
288
|
+
const timer = this._timers.get(toast);
|
|
289
|
+
if (timer) {
|
|
290
|
+
clearTimeout(timer.timeoutId);
|
|
291
|
+
this._timers.delete(toast);
|
|
292
|
+
}
|
|
293
|
+
// Prevent double-hide
|
|
294
|
+
if (!toast.hasAttribute('showing'))
|
|
295
|
+
return;
|
|
296
|
+
// Dispatch hide
|
|
297
|
+
if (!this.emit('hide', { cancelable: true, detail: { toast } }))
|
|
298
|
+
return;
|
|
299
|
+
this._capturePositions();
|
|
300
|
+
// Remove showing class to trigger exit transition
|
|
301
|
+
toast.removeAttribute('showing');
|
|
302
|
+
const remove = () => {
|
|
303
|
+
toast.remove();
|
|
304
|
+
this._animatePositions();
|
|
305
|
+
this.emit('after-hide', { detail: { toast } });
|
|
306
|
+
// Close popover if no toasts remain
|
|
307
|
+
const remaining = this.querySelectorAll(`:scope > ${tagName('toast-item')}`);
|
|
308
|
+
if (remaining.length === 0 && this.matches(':popover-open')) {
|
|
309
|
+
this.hidePopover();
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
// Handle zero-duration transitions (reduced motion)
|
|
313
|
+
const duration = parseFloat(getComputedStyle(toast).transitionDuration);
|
|
314
|
+
if (duration === 0) {
|
|
315
|
+
remove();
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
const onEnd = (e) => {
|
|
319
|
+
if (e.target !== toast)
|
|
320
|
+
return;
|
|
321
|
+
toast.removeEventListener('transitionend', onEnd);
|
|
322
|
+
remove();
|
|
323
|
+
};
|
|
324
|
+
toast.addEventListener('transitionend', onEnd);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// ── FLIP animation for reordering ─────────────────────────────────────────────
|
|
328
|
+
_capturePositions() {
|
|
329
|
+
const items = this.querySelectorAll(`:scope > ${tagName('toast-item')}`);
|
|
330
|
+
for (const child of items) {
|
|
331
|
+
this._positionCache.set(child, child.getBoundingClientRect());
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
_animatePositions() {
|
|
335
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
336
|
+
return;
|
|
337
|
+
const items = this.querySelectorAll(`:scope > ${tagName('toast-item')}`);
|
|
338
|
+
for (const child of items) {
|
|
339
|
+
const oldRect = this._positionCache.get(child);
|
|
340
|
+
if (!oldRect)
|
|
341
|
+
continue;
|
|
342
|
+
const newRect = child.getBoundingClientRect();
|
|
343
|
+
const deltaY = oldRect.top - newRect.top;
|
|
344
|
+
if (Math.abs(deltaY) < 1)
|
|
345
|
+
continue;
|
|
346
|
+
child.animate([{ transform: `translateY(${deltaY}px)` }, { transform: 'translateY(0)' }], { duration: 200, easing: 'cubic-bezier(0.2, 0, 0, 1)' });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
__decorate([
|
|
351
|
+
property({ reflect: true })
|
|
352
|
+
], LuxenToast.prototype, "placement", void 0);
|
|
353
|
+
__decorate([
|
|
354
|
+
property({ type: Number, reflect: true })
|
|
355
|
+
], LuxenToast.prototype, "duration", void 0);
|
|
356
|
+
__decorate([
|
|
357
|
+
property({ reflect: true })
|
|
358
|
+
], LuxenToast.prototype, "variant", void 0);
|
|
359
|
+
// ── Standalone function ───────────────────────────────────────────────────────
|
|
360
|
+
let _defaultInstance = null;
|
|
361
|
+
function _getDefault() {
|
|
362
|
+
if (!_defaultInstance || !_defaultInstance.isConnected) {
|
|
363
|
+
_defaultInstance =
|
|
364
|
+
document.querySelector(tagName('toast')) ??
|
|
365
|
+
document.createElement(tagName('toast'));
|
|
366
|
+
if (!_defaultInstance.isConnected) {
|
|
367
|
+
document.body.appendChild(_defaultInstance);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return _defaultInstance;
|
|
371
|
+
}
|
|
372
|
+
/** Show a toast notification. Auto-creates `<l-toast>` if none exists in the DOM. */
|
|
373
|
+
export function toast(options) {
|
|
374
|
+
return _getDefault().toast(options);
|
|
375
|
+
}
|