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,359 @@
1
+ import { computePosition, autoUpdate, offset, flip, shift, arrow, } from '@floating-ui/dom';
2
+ /** Check if a point is inside a polygon using ray-casting. */
3
+ function isPointInPolygon([px, py], polygon) {
4
+ let inside = false;
5
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
6
+ const [xi, yi] = polygon[i];
7
+ const [xj, yj] = polygon[j];
8
+ if (yi >= py !== yj >= py && px <= ((xj - xi) * (py - yi)) / (yj - yi) + xi) {
9
+ inside = !inside;
10
+ }
11
+ }
12
+ return inside;
13
+ }
14
+ /** Check if a point is inside a rect. */
15
+ function isPointInRect([px, py], rect) {
16
+ return px >= rect.left && px <= rect.right && py >= rect.top && py <= rect.bottom;
17
+ }
18
+ /**
19
+ * Build a safe polygon (triangle/quadrilateral) from cursor to floating element edges.
20
+ * Adapted from floating-ui/safePolygon.
21
+ * @see https://github.com/floating-ui/floating-ui/blob/master/packages/react/src/safePolygon.ts
22
+ */
23
+ function getSafePolygon(cursorX, cursorY, rect, refRect, placement) {
24
+ const side = placement.split('-')[0];
25
+ const buffer = 2;
26
+ const isFloatingWider = rect.width > refRect.width;
27
+ const isFloatingTaller = rect.height > refRect.height;
28
+ const cursorLeaveFromRight = cursorX > rect.right - rect.width / 2;
29
+ const cursorLeaveFromBottom = cursorY > rect.bottom - rect.height / 2;
30
+ switch (side) {
31
+ case 'top': {
32
+ const c1 = [
33
+ isFloatingWider
34
+ ? cursorX + buffer / 2
35
+ : cursorLeaveFromRight
36
+ ? cursorX + buffer * 4
37
+ : cursorX - buffer * 4,
38
+ cursorY + buffer + 1,
39
+ ];
40
+ const c2 = [
41
+ isFloatingWider
42
+ ? cursorX - buffer / 2
43
+ : cursorLeaveFromRight
44
+ ? cursorX + buffer * 4
45
+ : cursorX - buffer * 4,
46
+ cursorY + buffer + 1,
47
+ ];
48
+ return [
49
+ c1,
50
+ c2,
51
+ [
52
+ rect.left,
53
+ cursorLeaveFromRight
54
+ ? rect.bottom - buffer
55
+ : isFloatingWider
56
+ ? rect.bottom - buffer
57
+ : rect.top,
58
+ ],
59
+ [
60
+ rect.right,
61
+ cursorLeaveFromRight
62
+ ? isFloatingWider
63
+ ? rect.bottom - buffer
64
+ : rect.top
65
+ : rect.bottom - buffer,
66
+ ],
67
+ ];
68
+ }
69
+ case 'bottom': {
70
+ const c1 = [
71
+ isFloatingWider
72
+ ? cursorX + buffer / 2
73
+ : cursorLeaveFromRight
74
+ ? cursorX + buffer * 4
75
+ : cursorX - buffer * 4,
76
+ cursorY - buffer,
77
+ ];
78
+ const c2 = [
79
+ isFloatingWider
80
+ ? cursorX - buffer / 2
81
+ : cursorLeaveFromRight
82
+ ? cursorX + buffer * 4
83
+ : cursorX - buffer * 4,
84
+ cursorY - buffer,
85
+ ];
86
+ return [
87
+ c1,
88
+ c2,
89
+ [
90
+ rect.left,
91
+ cursorLeaveFromRight
92
+ ? rect.top + buffer
93
+ : isFloatingWider
94
+ ? rect.top + buffer
95
+ : rect.bottom,
96
+ ],
97
+ [
98
+ rect.right,
99
+ cursorLeaveFromRight
100
+ ? isFloatingWider
101
+ ? rect.top + buffer
102
+ : rect.bottom
103
+ : rect.top + buffer,
104
+ ],
105
+ ];
106
+ }
107
+ case 'left': {
108
+ const c1 = [
109
+ cursorX + buffer + 1,
110
+ isFloatingTaller
111
+ ? cursorY + buffer / 2
112
+ : cursorLeaveFromBottom
113
+ ? cursorY + buffer * 4
114
+ : cursorY - buffer * 4,
115
+ ];
116
+ const c2 = [
117
+ cursorX + buffer + 1,
118
+ isFloatingTaller
119
+ ? cursorY - buffer / 2
120
+ : cursorLeaveFromBottom
121
+ ? cursorY + buffer * 4
122
+ : cursorY - buffer * 4,
123
+ ];
124
+ return [
125
+ [
126
+ cursorLeaveFromBottom
127
+ ? rect.right - buffer
128
+ : isFloatingTaller
129
+ ? rect.right - buffer
130
+ : rect.left,
131
+ rect.top,
132
+ ],
133
+ [
134
+ cursorLeaveFromBottom
135
+ ? isFloatingTaller
136
+ ? rect.right - buffer
137
+ : rect.left
138
+ : rect.right - buffer,
139
+ rect.bottom,
140
+ ],
141
+ c1,
142
+ c2,
143
+ ];
144
+ }
145
+ case 'right': {
146
+ const c1 = [
147
+ cursorX - buffer,
148
+ isFloatingTaller
149
+ ? cursorY + buffer / 2
150
+ : cursorLeaveFromBottom
151
+ ? cursorY + buffer * 4
152
+ : cursorY - buffer * 4,
153
+ ];
154
+ const c2 = [
155
+ cursorX - buffer,
156
+ isFloatingTaller
157
+ ? cursorY - buffer / 2
158
+ : cursorLeaveFromBottom
159
+ ? cursorY + buffer * 4
160
+ : cursorY - buffer * 4,
161
+ ];
162
+ return [
163
+ [
164
+ cursorLeaveFromBottom
165
+ ? rect.left + buffer
166
+ : isFloatingTaller
167
+ ? rect.left + buffer
168
+ : rect.right,
169
+ rect.top,
170
+ ],
171
+ [
172
+ cursorLeaveFromBottom
173
+ ? isFloatingTaller
174
+ ? rect.left + buffer
175
+ : rect.right
176
+ : rect.left + buffer,
177
+ rect.bottom,
178
+ ],
179
+ c1,
180
+ c2,
181
+ ];
182
+ }
183
+ default:
184
+ return [];
185
+ }
186
+ }
187
+ // --- Constants ---
188
+ const STATIC_SIDE = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' };
189
+ const reducedMotion = typeof window !== 'undefined' ? window.matchMedia('(prefers-reduced-motion: reduce)') : undefined;
190
+ export class PopoverController {
191
+ constructor(host, config) {
192
+ this._currentPlacement = '';
193
+ this._host = host;
194
+ this._config = config;
195
+ host.addController(this);
196
+ }
197
+ hostConnected() { }
198
+ hostDisconnected() {
199
+ this.stopPositioning();
200
+ this.cleanupSafePolygon();
201
+ this.removeTriggerListeners();
202
+ }
203
+ get currentPlacement() {
204
+ return this._currentPlacement;
205
+ }
206
+ // --- Positioning ---
207
+ async updatePosition(options) {
208
+ const trigger = this._config.getTriggerElement();
209
+ const floating = this._config.getFloatingElement();
210
+ const arrowEl = this._config.getArrowElement();
211
+ if (!trigger || !floating)
212
+ return;
213
+ const middleware = [
214
+ offset(options.distance),
215
+ flip(),
216
+ shift(),
217
+ ...(arrowEl ? [arrow({ element: arrowEl, padding: 8 })] : []),
218
+ ];
219
+ const { x, y, placement, middlewareData } = await computePosition(trigger, floating, {
220
+ placement: options.placement,
221
+ strategy: 'fixed',
222
+ middleware,
223
+ });
224
+ Object.assign(floating.style, {
225
+ position: 'fixed',
226
+ left: `${x}px`,
227
+ top: `${y}px`,
228
+ });
229
+ if (placement !== this._currentPlacement) {
230
+ this._currentPlacement = placement;
231
+ this._config.onPlacementChange?.(placement);
232
+ }
233
+ if (middlewareData.arrow && arrowEl) {
234
+ const { x: ax, y: ay } = middlewareData.arrow;
235
+ const side = STATIC_SIDE[placement.split('-')[0]];
236
+ Object.assign(arrowEl.style, {
237
+ left: ax != null ? `${ax}px` : '',
238
+ top: ay != null ? `${ay}px` : '',
239
+ [side]: `${-arrowEl.offsetWidth / 2}px`,
240
+ });
241
+ }
242
+ }
243
+ startPositioning(options) {
244
+ const trigger = this._config.getTriggerElement();
245
+ const floating = this._config.getFloatingElement();
246
+ if (!trigger || !floating)
247
+ return;
248
+ this._cleanupAutoUpdate?.();
249
+ this._cleanupAutoUpdate = autoUpdate(trigger, floating, () => this.updatePosition(options));
250
+ }
251
+ stopPositioning() {
252
+ this._cleanupAutoUpdate?.();
253
+ this._cleanupAutoUpdate = undefined;
254
+ }
255
+ // --- Safe polygon ---
256
+ handlePointerLeave(e, onHide) {
257
+ const floating = this._config.getFloatingElement();
258
+ const trigger = this._config.getTriggerElement();
259
+ if (!floating || !trigger) {
260
+ onHide();
261
+ return;
262
+ }
263
+ const cursorX = e.clientX;
264
+ const cursorY = e.clientY;
265
+ const onMove = (moveEvent) => {
266
+ const { clientX: mx, clientY: my } = moveEvent;
267
+ const cursor = [mx, my];
268
+ const rect = floating.getBoundingClientRect();
269
+ const refRect = trigger.getBoundingClientRect();
270
+ const side = this._currentPlacement.split('-')[0];
271
+ if (isPointInRect(cursor, rect))
272
+ return;
273
+ if (isPointInRect(cursor, refRect))
274
+ return;
275
+ if ((side === 'top' && cursorY >= refRect.bottom - 1) ||
276
+ (side === 'bottom' && cursorY <= refRect.top + 1) ||
277
+ (side === 'left' && cursorX >= refRect.right - 1) ||
278
+ (side === 'right' && cursorX <= refRect.left + 1)) {
279
+ cleanup();
280
+ onHide();
281
+ return;
282
+ }
283
+ const polygon = getSafePolygon(cursorX, cursorY, rect, refRect, this._currentPlacement);
284
+ if (isPointInPolygon(cursor, polygon))
285
+ return;
286
+ cleanup();
287
+ onHide();
288
+ };
289
+ const cleanup = () => {
290
+ document.removeEventListener('pointermove', onMove);
291
+ };
292
+ document.addEventListener('pointermove', onMove);
293
+ this._cleanupSafePolygon?.();
294
+ this._cleanupSafePolygon = cleanup;
295
+ }
296
+ cleanupSafePolygon() {
297
+ this._cleanupSafePolygon?.();
298
+ this._cleanupSafePolygon = undefined;
299
+ }
300
+ // --- Trigger listener management ---
301
+ addTriggerListeners(handlers) {
302
+ const trigger = this._config.getTriggerElement();
303
+ if (!trigger)
304
+ return;
305
+ this._handlers = handlers;
306
+ this._triggerElement = trigger;
307
+ if (handlers.onPointerEnter)
308
+ trigger.addEventListener('pointerenter', handlers.onPointerEnter);
309
+ if (handlers.onPointerLeave)
310
+ trigger.addEventListener('pointerleave', handlers.onPointerLeave);
311
+ if (handlers.onFocusIn)
312
+ trigger.addEventListener('focusin', handlers.onFocusIn);
313
+ if (handlers.onFocusOut)
314
+ trigger.addEventListener('focusout', handlers.onFocusOut);
315
+ if (handlers.onClick)
316
+ trigger.addEventListener('click', handlers.onClick);
317
+ if (handlers.onKeyDown)
318
+ trigger.addEventListener('keydown', handlers.onKeyDown);
319
+ }
320
+ removeTriggerListeners(triggerOverride) {
321
+ const trigger = triggerOverride ?? this._triggerElement;
322
+ const handlers = this._handlers;
323
+ if (!trigger || !handlers)
324
+ return;
325
+ if (handlers.onPointerEnter)
326
+ trigger.removeEventListener('pointerenter', handlers.onPointerEnter);
327
+ if (handlers.onPointerLeave)
328
+ trigger.removeEventListener('pointerleave', handlers.onPointerLeave);
329
+ if (handlers.onFocusIn)
330
+ trigger.removeEventListener('focusin', handlers.onFocusIn);
331
+ if (handlers.onFocusOut)
332
+ trigger.removeEventListener('focusout', handlers.onFocusOut);
333
+ if (handlers.onClick)
334
+ trigger.removeEventListener('click', handlers.onClick);
335
+ if (handlers.onKeyDown)
336
+ trigger.removeEventListener('keydown', handlers.onKeyDown);
337
+ if (!triggerOverride) {
338
+ this._handlers = undefined;
339
+ this._triggerElement = undefined;
340
+ }
341
+ }
342
+ // --- Animation ---
343
+ async animateShow(el, duration, keyframes) {
344
+ const d = reducedMotion?.matches ? 0 : duration;
345
+ el.getAnimations().forEach((a) => a.cancel());
346
+ el.animate(keyframes ?? [
347
+ { opacity: 0, scale: 0.96 },
348
+ { opacity: 1, scale: 1 },
349
+ ], { duration: d, fill: 'forwards' });
350
+ }
351
+ async animateHide(el, duration, keyframes) {
352
+ const d = reducedMotion?.matches ? 0 : duration;
353
+ const anim = el.animate(keyframes ?? [
354
+ { opacity: 1, scale: 1 },
355
+ { opacity: 0, scale: 0.96 },
356
+ ], { duration: d, fill: 'forwards' });
357
+ await anim.finished;
358
+ }
359
+ }
@@ -0,0 +1,20 @@
1
+ import { LitElement } from 'lit';
2
+ /**
3
+ * Base class for all Luxen custom elements.
4
+ * Extends LitElement with shared utilities.
5
+ */
6
+ export declare class LuxenElement extends LitElement {
7
+ /**
8
+ * Dispatch a custom event. Returns `true` if not cancelled.
9
+ *
10
+ * @param name - Event name (unprefixed, e.g. `'show'`).
11
+ * @param options - CustomEvent options. Defaults: bubbles + composed.
12
+ */
13
+ emit<T = unknown>(name: string, options?: {
14
+ detail?: T;
15
+ bubbles?: boolean;
16
+ composed?: boolean;
17
+ cancelable?: boolean;
18
+ }): boolean;
19
+ }
20
+ //# sourceMappingURL=luxen-element.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"luxen-element.d.ts","sourceRoot":"","sources":["../../src/html/shared/luxen-element.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAEjC;;;GAGG;AACH,qBAAa,YAAa,SAAQ,UAAU;IAC1C;;;;;OAKG;IACH,IAAI,CAAC,CAAC,GAAG,OAAO,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,CAAC,CAAC;QACX,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,GACA,OAAO;CAUX"}
@@ -0,0 +1,23 @@
1
+ import { LitElement } from 'lit';
2
+ /**
3
+ * Base class for all Luxen custom elements.
4
+ * Extends LitElement with shared utilities.
5
+ */
6
+ export class LuxenElement extends LitElement {
7
+ /**
8
+ * Dispatch a custom event. Returns `true` if not cancelled.
9
+ *
10
+ * @param name - Event name (unprefixed, e.g. `'show'`).
11
+ * @param options - CustomEvent options. Defaults: bubbles + composed.
12
+ */
13
+ emit(name, options) {
14
+ const event = new CustomEvent(name, {
15
+ bubbles: options?.bubbles ?? true,
16
+ composed: options?.composed ?? true,
17
+ cancelable: options?.cancelable ?? false,
18
+ detail: options?.detail,
19
+ });
20
+ this.dispatchEvent(event);
21
+ return !event.defaultPrevented;
22
+ }
23
+ }
@@ -0,0 +1,49 @@
1
+ import { LuxenElement } from './luxen-element';
2
+ /**
3
+ * Base class for form-associated Luxen custom elements.
4
+ * Implements the Form Associated Custom Elements API via ElementInternals.
5
+ *
6
+ * Subclasses own their typed `value` property and call `_syncFormValue()`
7
+ * whenever it changes.
8
+ */
9
+ export declare class LuxenFormAssociatedElement extends LuxenElement {
10
+ static formAssociated: boolean;
11
+ /** @internal */
12
+ readonly _internals: ElementInternals;
13
+ name: string;
14
+ disabled: boolean;
15
+ required: boolean;
16
+ /** The serialised value the element resets to on form reset. */
17
+ protected _defaultFormValue: string;
18
+ /** Whether the user has interacted with this control. */
19
+ hasInteracted: boolean;
20
+ constructor();
21
+ /** The form this control is associated with. */
22
+ get form(): HTMLFormElement | null;
23
+ /** `<label>` elements associated with this control via `for` attribute. */
24
+ get formLabels(): NodeList;
25
+ /**
26
+ * Override to return the inner element where validation popups anchor.
27
+ * Passed as the 3rd arg to `ElementInternals.setValidity()`.
28
+ */
29
+ get validationTarget(): HTMLElement | undefined;
30
+ /** Update the form submission value. */
31
+ protected _syncFormValue(value: string): void;
32
+ checkValidity(): boolean;
33
+ reportValidity(): boolean;
34
+ /** Set a custom validity message. Pass `''` to clear. */
35
+ setCustomValidity(message: string): void;
36
+ /** Directly set validity flags. With no args, clears all flags. */
37
+ protected setValidity(flags?: ValidityStateFlags, message?: string, anchor?: HTMLElement): void;
38
+ get validity(): ValidityState;
39
+ get validationMessage(): string;
40
+ get willValidate(): boolean;
41
+ /** Update `:state()` custom states based on current validity and interaction. */
42
+ private _updateCustomStates;
43
+ private _toggleState;
44
+ formAssociatedCallback(_form: HTMLFormElement | null): void;
45
+ formDisabledCallback(disabled: boolean): void;
46
+ formResetCallback(): void;
47
+ formStateRestoreCallback(state: string, _mode: 'restore' | 'autocomplete'): void;
48
+ }
49
+ //# sourceMappingURL=luxen-form-associated-element.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"luxen-form-associated-element.d.ts","sourceRoot":"","sources":["../../src/html/shared/luxen-form-associated-element.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C;;;;;;GAMG;AACH,qBAAa,0BAA2B,SAAQ,YAAY;IAC1D,MAAM,CAAC,cAAc,UAAQ;IAE7B,gBAAgB;IAChB,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IAGtC,IAAI,SAAM;IAGV,QAAQ,UAAS;IAGjB,QAAQ,UAAS;IAEjB,gEAAgE;IAChE,SAAS,CAAC,iBAAiB,SAAM;IAEjC,yDAAyD;IACzD,aAAa,UAAS;;IAOtB,gDAAgD;IAChD,IAAI,IAAI,IAAI,eAAe,GAAG,IAAI,CAEjC;IAED,2EAA2E;IAC3E,IAAI,UAAU,IAAI,QAAQ,CAEzB;IAED;;;OAGG;IACH,IAAI,gBAAgB,IAAI,WAAW,GAAG,SAAS,CAE9C;IAID,wCAAwC;IACxC,SAAS,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM7C,aAAa,IAAI,OAAO;IAIxB,cAAc,IAAI,OAAO;IAMzB,yDAAyD;IACzD,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IASxC,mEAAmE;IACnE,SAAS,CAAC,WAAW,CAAC,KAAK,GAAE,kBAAuB,EAAE,OAAO,SAAK,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI;IAK/F,IAAI,QAAQ,IAAI,aAAa,CAE5B;IAED,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,iFAAiF;IACjF,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,YAAY;IAUpB,sBAAsB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,GAAG,IAAI;IAE3D,oBAAoB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAI7C,iBAAiB,IAAI,IAAI;IAMzB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,cAAc,GAAG,IAAI;CAGjF"}
@@ -0,0 +1,123 @@
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 './luxen-element';
9
+ /**
10
+ * Base class for form-associated Luxen custom elements.
11
+ * Implements the Form Associated Custom Elements API via ElementInternals.
12
+ *
13
+ * Subclasses own their typed `value` property and call `_syncFormValue()`
14
+ * whenever it changes.
15
+ */
16
+ export class LuxenFormAssociatedElement extends LuxenElement {
17
+ constructor() {
18
+ super();
19
+ this.name = '';
20
+ this.disabled = false;
21
+ this.required = false;
22
+ /** The serialised value the element resets to on form reset. */
23
+ this._defaultFormValue = '';
24
+ /** Whether the user has interacted with this control. */
25
+ this.hasInteracted = false;
26
+ this._internals = this.attachInternals();
27
+ }
28
+ /** The form this control is associated with. */
29
+ get form() {
30
+ return this._internals.form;
31
+ }
32
+ /** `<label>` elements associated with this control via `for` attribute. */
33
+ get formLabels() {
34
+ return this._internals.labels;
35
+ }
36
+ /**
37
+ * Override to return the inner element where validation popups anchor.
38
+ * Passed as the 3rd arg to `ElementInternals.setValidity()`.
39
+ */
40
+ get validationTarget() {
41
+ return undefined;
42
+ }
43
+ // --- Form value ---
44
+ /** Update the form submission value. */
45
+ _syncFormValue(value) {
46
+ this._internals.setFormValue(value);
47
+ }
48
+ // --- Validity ---
49
+ checkValidity() {
50
+ return this._internals.checkValidity();
51
+ }
52
+ reportValidity() {
53
+ this.hasInteracted = true;
54
+ this._updateCustomStates();
55
+ return this._internals.reportValidity();
56
+ }
57
+ /** Set a custom validity message. Pass `''` to clear. */
58
+ setCustomValidity(message) {
59
+ if (message) {
60
+ this._internals.setValidity({ customError: true }, message, this.validationTarget);
61
+ }
62
+ else {
63
+ this._internals.setValidity({});
64
+ }
65
+ this._updateCustomStates();
66
+ }
67
+ /** Directly set validity flags. With no args, clears all flags. */
68
+ setValidity(flags = {}, message = '', anchor) {
69
+ this._internals.setValidity(flags, message, anchor ?? this.validationTarget);
70
+ this._updateCustomStates();
71
+ }
72
+ get validity() {
73
+ return this._internals.validity;
74
+ }
75
+ get validationMessage() {
76
+ return this._internals.validationMessage;
77
+ }
78
+ get willValidate() {
79
+ return this._internals.willValidate;
80
+ }
81
+ /** Update `:state()` custom states based on current validity and interaction. */
82
+ _updateCustomStates() {
83
+ const states = this._internals.states;
84
+ const isValid = this._internals.validity.valid;
85
+ this._toggleState(states, 'required', this.required);
86
+ this._toggleState(states, 'optional', !this.required);
87
+ this._toggleState(states, 'valid', isValid);
88
+ this._toggleState(states, 'invalid', !isValid);
89
+ this._toggleState(states, 'user-valid', isValid && this.hasInteracted);
90
+ this._toggleState(states, 'user-invalid', !isValid && this.hasInteracted);
91
+ }
92
+ _toggleState(states, name, force) {
93
+ if (force) {
94
+ states.add(name);
95
+ }
96
+ else {
97
+ states.delete(name);
98
+ }
99
+ }
100
+ // --- Form lifecycle callbacks ---
101
+ formAssociatedCallback(_form) { }
102
+ formDisabledCallback(disabled) {
103
+ this.disabled = disabled;
104
+ }
105
+ formResetCallback() {
106
+ this.hasInteracted = false;
107
+ this._syncFormValue(this._defaultFormValue);
108
+ this._updateCustomStates();
109
+ }
110
+ formStateRestoreCallback(state, _mode) {
111
+ this._syncFormValue(state);
112
+ }
113
+ }
114
+ LuxenFormAssociatedElement.formAssociated = true;
115
+ __decorate([
116
+ property({ reflect: true })
117
+ ], LuxenFormAssociatedElement.prototype, "name", void 0);
118
+ __decorate([
119
+ property({ type: Boolean, reflect: true })
120
+ ], LuxenFormAssociatedElement.prototype, "disabled", void 0);
121
+ __decorate([
122
+ property({ type: Boolean, reflect: true })
123
+ ], LuxenFormAssociatedElement.prototype, "required", void 0);
@@ -0,0 +1,13 @@
1
+ :host {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ :host *,
6
+ :host *::before,
7
+ :host *::after {
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ [hidden] {
12
+ display: none !important;
13
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Styles applied to every shadow-DOM element via `static styles`.
3
+ * Wrapper module: `unsafeCSS()` is called once here so all 12+ importers
4
+ * share the same `CSSResult` instance (one constructed `CSSStyleSheet`,
5
+ * not one per importing element).
6
+ */
7
+ declare const _default: import("lit").CSSResult;
8
+ export default _default;
9
+ //# sourceMappingURL=host.styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.styles.d.ts","sourceRoot":"","sources":["../../../src/html/shared/styles/host.styles.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;;AACH,wBAA8B"}
@@ -0,0 +1,9 @@
1
+ import { unsafeCSS } from 'lit';
2
+ import raw from './host.css?inline';
3
+ /**
4
+ * Styles applied to every shadow-DOM element via `static styles`.
5
+ * Wrapper module: `unsafeCSS()` is called once here so all 12+ importers
6
+ * share the same `CSSResult` instance (one constructed `CSSStyleSheet`,
7
+ * not one per importing element).
8
+ */
9
+ export default unsafeCSS(raw);