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.
Files changed (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/dist/css/elements/avatar.css +20 -0
  4. package/dist/css/elements/badge.css +159 -0
  5. package/dist/css/elements/button.css +171 -0
  6. package/dist/css/elements/close-button/circle.css +66 -0
  7. package/dist/css/elements/close-button/ring.css +71 -0
  8. package/dist/css/elements/close-button/square.css +70 -0
  9. package/dist/css/elements/disclosure.css +137 -0
  10. package/dist/css/elements/divider.css +75 -0
  11. package/dist/css/elements/input-otp.css +164 -0
  12. package/dist/css/elements/input-stepper/default.css +245 -0
  13. package/dist/css/elements/input-stepper/rounded.css +238 -0
  14. package/dist/css/elements/kbd.css +21 -0
  15. package/dist/css/elements/progress.css +114 -0
  16. package/dist/css/elements/select.css +71 -0
  17. package/dist/css/elements/skeleton.css +89 -0
  18. package/dist/css/elements/tabs/enclosed.css +148 -0
  19. package/dist/css/elements/tabs/line.css +138 -0
  20. package/dist/css/elements/toast.css +260 -0
  21. package/dist/css/index.css +885 -0
  22. package/dist/custom-elements.json +14424 -0
  23. package/dist/define.d.ts +9 -0
  24. package/dist/define.d.ts.map +1 -0
  25. package/dist/define.js +16 -0
  26. package/dist/elements/avatar/avatar.css +128 -0
  27. package/dist/elements/avatar/avatar.d.ts +21 -0
  28. package/dist/elements/avatar/avatar.d.ts.map +1 -0
  29. package/dist/elements/avatar/avatar.js +106 -0
  30. package/dist/elements/avatar/index.d.ts +8 -0
  31. package/dist/elements/avatar/index.d.ts.map +1 -0
  32. package/dist/elements/avatar/index.js +4 -0
  33. package/dist/elements/badge/badge.d.ts +17 -0
  34. package/dist/elements/badge/badge.d.ts.map +1 -0
  35. package/dist/elements/badge/badge.js +34 -0
  36. package/dist/elements/badge/index.d.ts +8 -0
  37. package/dist/elements/badge/index.d.ts.map +1 -0
  38. package/dist/elements/badge/index.js +4 -0
  39. package/dist/elements/carousel/carousel.css +205 -0
  40. package/dist/elements/carousel/carousel.d.ts +148 -0
  41. package/dist/elements/carousel/carousel.d.ts.map +1 -0
  42. package/dist/elements/carousel/carousel.js +473 -0
  43. package/dist/elements/carousel/index.d.ts +8 -0
  44. package/dist/elements/carousel/index.d.ts.map +1 -0
  45. package/dist/elements/carousel/index.js +4 -0
  46. package/dist/elements/carousel-item/carousel-item.css +11 -0
  47. package/dist/elements/carousel-item/carousel-item.d.ts +13 -0
  48. package/dist/elements/carousel-item/carousel-item.d.ts.map +1 -0
  49. package/dist/elements/carousel-item/carousel-item.js +20 -0
  50. package/dist/elements/carousel-item/index.d.ts +8 -0
  51. package/dist/elements/carousel-item/index.d.ts.map +1 -0
  52. package/dist/elements/carousel-item/index.js +4 -0
  53. package/dist/elements/dialog/dialog.css +92 -0
  54. package/dist/elements/dialog/dialog.d.ts +56 -0
  55. package/dist/elements/dialog/dialog.d.ts.map +1 -0
  56. package/dist/elements/dialog/dialog.js +204 -0
  57. package/dist/elements/dialog/dialog.styles.d.ts +8 -0
  58. package/dist/elements/dialog/dialog.styles.d.ts.map +1 -0
  59. package/dist/elements/dialog/dialog.styles.js +8 -0
  60. package/dist/elements/dialog/index.d.ts +8 -0
  61. package/dist/elements/dialog/index.d.ts.map +1 -0
  62. package/dist/elements/dialog/index.js +4 -0
  63. package/dist/elements/divider/divider.d.ts +23 -0
  64. package/dist/elements/divider/divider.d.ts.map +1 -0
  65. package/dist/elements/divider/divider.js +49 -0
  66. package/dist/elements/divider/index.d.ts +8 -0
  67. package/dist/elements/divider/index.d.ts.map +1 -0
  68. package/dist/elements/divider/index.js +4 -0
  69. package/dist/elements/drawer/drawer.css +66 -0
  70. package/dist/elements/drawer/drawer.d.ts +34 -0
  71. package/dist/elements/drawer/drawer.d.ts.map +1 -0
  72. package/dist/elements/drawer/drawer.js +46 -0
  73. package/dist/elements/drawer/index.d.ts +8 -0
  74. package/dist/elements/drawer/index.d.ts.map +1 -0
  75. package/dist/elements/drawer/index.js +4 -0
  76. package/dist/elements/dropdown/dropdown.css +31 -0
  77. package/dist/elements/dropdown/dropdown.d.ts +64 -0
  78. package/dist/elements/dropdown/dropdown.d.ts.map +1 -0
  79. package/dist/elements/dropdown/dropdown.js +322 -0
  80. package/dist/elements/dropdown/index.d.ts +8 -0
  81. package/dist/elements/dropdown/index.d.ts.map +1 -0
  82. package/dist/elements/dropdown/index.js +4 -0
  83. package/dist/elements/dropdown-item/dropdown-item.css +51 -0
  84. package/dist/elements/dropdown-item/dropdown-item.d.ts +25 -0
  85. package/dist/elements/dropdown-item/dropdown-item.d.ts.map +1 -0
  86. package/dist/elements/dropdown-item/dropdown-item.js +110 -0
  87. package/dist/elements/dropdown-item/index.d.ts +8 -0
  88. package/dist/elements/dropdown-item/index.d.ts.map +1 -0
  89. package/dist/elements/dropdown-item/index.js +4 -0
  90. package/dist/elements/icon/icon.css +10 -0
  91. package/dist/elements/icon/icon.d.ts +19 -0
  92. package/dist/elements/icon/icon.d.ts.map +1 -0
  93. package/dist/elements/icon/icon.js +53 -0
  94. package/dist/elements/icon/index.d.ts +8 -0
  95. package/dist/elements/icon/index.d.ts.map +1 -0
  96. package/dist/elements/icon/index.js +4 -0
  97. package/dist/elements/input-otp/index.d.ts +8 -0
  98. package/dist/elements/input-otp/index.d.ts.map +1 -0
  99. package/dist/elements/input-otp/index.js +4 -0
  100. package/dist/elements/input-otp/input-otp.d.ts +31 -0
  101. package/dist/elements/input-otp/input-otp.d.ts.map +1 -0
  102. package/dist/elements/input-otp/input-otp.js +139 -0
  103. package/dist/elements/input-stepper/index.d.ts +8 -0
  104. package/dist/elements/input-stepper/index.d.ts.map +1 -0
  105. package/dist/elements/input-stepper/index.js +4 -0
  106. package/dist/elements/input-stepper/input-stepper.d.ts +63 -0
  107. package/dist/elements/input-stepper/input-stepper.d.ts.map +1 -0
  108. package/dist/elements/input-stepper/input-stepper.js +249 -0
  109. package/dist/elements/popover/index.d.ts +8 -0
  110. package/dist/elements/popover/index.d.ts.map +1 -0
  111. package/dist/elements/popover/index.js +4 -0
  112. package/dist/elements/popover/popover.css +61 -0
  113. package/dist/elements/popover/popover.d.ts +62 -0
  114. package/dist/elements/popover/popover.d.ts.map +1 -0
  115. package/dist/elements/popover/popover.js +244 -0
  116. package/dist/elements/rating/index.d.ts +8 -0
  117. package/dist/elements/rating/index.d.ts.map +1 -0
  118. package/dist/elements/rating/index.js +4 -0
  119. package/dist/elements/rating/rating.css +102 -0
  120. package/dist/elements/rating/rating.d.ts +38 -0
  121. package/dist/elements/rating/rating.d.ts.map +1 -0
  122. package/dist/elements/rating/rating.js +193 -0
  123. package/dist/elements/skeleton/index.d.ts +8 -0
  124. package/dist/elements/skeleton/index.d.ts.map +1 -0
  125. package/dist/elements/skeleton/index.js +4 -0
  126. package/dist/elements/skeleton/skeleton.d.ts +12 -0
  127. package/dist/elements/skeleton/skeleton.d.ts.map +1 -0
  128. package/dist/elements/skeleton/skeleton.js +13 -0
  129. package/dist/elements/spinner/index.d.ts +8 -0
  130. package/dist/elements/spinner/index.d.ts.map +1 -0
  131. package/dist/elements/spinner/index.js +4 -0
  132. package/dist/elements/spinner/spinner.css +28 -0
  133. package/dist/elements/spinner/spinner.d.ts +16 -0
  134. package/dist/elements/spinner/spinner.d.ts.map +1 -0
  135. package/dist/elements/spinner/spinner.js +37 -0
  136. package/dist/elements/tabs/index.d.ts +8 -0
  137. package/dist/elements/tabs/index.d.ts.map +1 -0
  138. package/dist/elements/tabs/index.js +4 -0
  139. package/dist/elements/tabs/tabs.d.ts +48 -0
  140. package/dist/elements/tabs/tabs.d.ts.map +1 -0
  141. package/dist/elements/tabs/tabs.js +210 -0
  142. package/dist/elements/toast/index.d.ts +9 -0
  143. package/dist/elements/toast/index.d.ts.map +1 -0
  144. package/dist/elements/toast/index.js +5 -0
  145. package/dist/elements/toast/toast.d.ts +72 -0
  146. package/dist/elements/toast/toast.d.ts.map +1 -0
  147. package/dist/elements/toast/toast.js +375 -0
  148. package/dist/elements/tooltip/index.d.ts +8 -0
  149. package/dist/elements/tooltip/index.d.ts.map +1 -0
  150. package/dist/elements/tooltip/index.js +4 -0
  151. package/dist/elements/tooltip/tooltip.css +37 -0
  152. package/dist/elements/tooltip/tooltip.d.ts +59 -0
  153. package/dist/elements/tooltip/tooltip.d.ts.map +1 -0
  154. package/dist/elements/tooltip/tooltip.js +231 -0
  155. package/dist/elements/tree/index.d.ts +8 -0
  156. package/dist/elements/tree/index.d.ts.map +1 -0
  157. package/dist/elements/tree/index.js +4 -0
  158. package/dist/elements/tree/tree.css +26 -0
  159. package/dist/elements/tree/tree.d.ts +76 -0
  160. package/dist/elements/tree/tree.d.ts.map +1 -0
  161. package/dist/elements/tree/tree.js +432 -0
  162. package/dist/elements/tree-item/index.d.ts +8 -0
  163. package/dist/elements/tree-item/index.d.ts.map +1 -0
  164. package/dist/elements/tree-item/index.js +4 -0
  165. package/dist/elements/tree-item/tree-item.css +172 -0
  166. package/dist/elements/tree-item/tree-item.d.ts +74 -0
  167. package/dist/elements/tree-item/tree-item.d.ts.map +1 -0
  168. package/dist/elements/tree-item/tree-item.js +301 -0
  169. package/dist/index.d.ts +6 -0
  170. package/dist/index.d.ts.map +1 -0
  171. package/dist/index.js +4 -0
  172. package/dist/registry.d.ts +22 -0
  173. package/dist/registry.d.ts.map +1 -0
  174. package/dist/registry.js +33 -0
  175. package/dist/shared/controllers/popover.d.ts +44 -0
  176. package/dist/shared/controllers/popover.d.ts.map +1 -0
  177. package/dist/shared/controllers/popover.js +359 -0
  178. package/dist/shared/luxen-element.d.ts +20 -0
  179. package/dist/shared/luxen-element.d.ts.map +1 -0
  180. package/dist/shared/luxen-element.js +23 -0
  181. package/dist/shared/luxen-form-associated-element.d.ts +49 -0
  182. package/dist/shared/luxen-form-associated-element.d.ts.map +1 -0
  183. package/dist/shared/luxen-form-associated-element.js +123 -0
  184. package/dist/shared/styles/host.css +13 -0
  185. package/dist/shared/styles/host.styles.d.ts +9 -0
  186. package/dist/shared/styles/host.styles.d.ts.map +1 -0
  187. package/dist/shared/styles/host.styles.js +9 -0
  188. package/dist/skills/luxen-ui/SKILL.md +82 -0
  189. package/dist/skills/luxen-ui/references/avatar.md +259 -0
  190. package/dist/skills/luxen-ui/references/badge.md +289 -0
  191. package/dist/skills/luxen-ui/references/button.md +309 -0
  192. package/dist/skills/luxen-ui/references/close-button.md +104 -0
  193. package/dist/skills/luxen-ui/references/dialog.md +435 -0
  194. package/dist/skills/luxen-ui/references/drawer.md +400 -0
  195. package/dist/skills/luxen-ui/references/progress.md +133 -0
  196. package/dist/skills/luxen-ui/references/select.md +100 -0
  197. package/dist/skills/luxen-ui/references/toast.md +396 -0
  198. package/dist/skills/luxen-ui/references/tree.md +359 -0
  199. package/package.json +116 -0
  200. package/postcss-plugin-prefix.js +63 -0
  201. 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,9 @@
1
+ import { LuxenToast, LuxenToastItem } from './toast';
2
+ export * from './toast';
3
+ declare global {
4
+ interface HTMLElementTagNameMap {
5
+ 'l-toast': LuxenToast;
6
+ 'l-toast-item': LuxenToastItem;
7
+ }
8
+ }
9
+ //# sourceMappingURL=index.d.ts.map
@@ -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,5 @@
1
+ import { define } from '../../define';
2
+ import { LuxenToast, LuxenToastItem } from './toast';
3
+ export * from './toast';
4
+ define('toast', LuxenToast);
5
+ define('toast-item', LuxenToastItem);
@@ -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
+ }