@santi020k/lumen-elements 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 +141 -0
- package/dist/define.d.ts +153 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +3890 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/package.json +72 -0
- package/styles.css +5 -0
package/dist/define.js
ADDED
|
@@ -0,0 +1,3890 @@
|
|
|
1
|
+
/* eslint-disable complexity, @typescript-eslint/no-non-null-assertion */
|
|
2
|
+
import { coerceThemeBuilderExportFormat, coerceThemeBuilderMode, coerceThemeBuilderScheme, composeClassName, createThemeBuilderTokens, exportThemeBuilderValue, getLumenRichTextShortcut, getVirtualRange, isLumenRichTextToggleCommand, lumenComponentNames, normalizeThemeBuilderHex, parseThemeCss, renderLumenIconSvg, scoreThemeContrast, tuneThemeContrast } from '@santi020k/lumen-core';
|
|
3
|
+
const registry = new Set();
|
|
4
|
+
const appliedClassNames = new WeakMap();
|
|
5
|
+
const focusableSelector = [
|
|
6
|
+
'a[href]',
|
|
7
|
+
'button:not([disabled])',
|
|
8
|
+
'input:not([disabled])',
|
|
9
|
+
'select:not([disabled])',
|
|
10
|
+
'textarea:not([disabled])',
|
|
11
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
12
|
+
].join(',');
|
|
13
|
+
// cspell:ignore spinbutton
|
|
14
|
+
const fieldControlSelector = [
|
|
15
|
+
'input:not([type="hidden"])',
|
|
16
|
+
'select',
|
|
17
|
+
'textarea',
|
|
18
|
+
'[contenteditable="true"]',
|
|
19
|
+
'[role="combobox"]',
|
|
20
|
+
'[role="listbox"]',
|
|
21
|
+
'[role="spinbutton"]',
|
|
22
|
+
'[role="textbox"]'
|
|
23
|
+
].join(',');
|
|
24
|
+
const fieldDescriptionSelector = '[data-ui-field-hint], [data-ui-field-error], .ui-field__hint, .ui-field__error';
|
|
25
|
+
const fieldErrorSelector = '[data-ui-field-error], .ui-field__error';
|
|
26
|
+
const richTextEditorSelector = '[data-ui-rich-text-editor]';
|
|
27
|
+
const richTextEditorCommandSelector = '[data-ui-editor-command]';
|
|
28
|
+
const richTextEditorContentSelector = '[data-ui-rich-text-editable], [contenteditable="true"]';
|
|
29
|
+
const scheduleSelector = '[data-ui-schedule]';
|
|
30
|
+
const scheduleEventSelector = '[data-ui-schedule-event]';
|
|
31
|
+
const scheduleSlotSelector = '[data-ui-schedule-slot]';
|
|
32
|
+
const resizableSelector = '[data-ui-resizable]';
|
|
33
|
+
const resizableHandleSelector = '[data-ui-resizable-handle]';
|
|
34
|
+
const resizableHandleTemplateSelector = '[data-ui-resizable-handle-template]';
|
|
35
|
+
const contextMenuSelector = '[data-ui-context-menu]';
|
|
36
|
+
const contextMenuTriggerSelector = '[data-ui-context-menu-trigger]';
|
|
37
|
+
const dateRangePickerSelector = '[data-ui-date-range-picker]';
|
|
38
|
+
const dateRangePickerInputSelector = 'input[type="date"]';
|
|
39
|
+
const inputOtpSelector = '[data-ui-input-otp], lumen-input-otp';
|
|
40
|
+
const inputOtpNativeSelector = '[data-ui-input-otp-native]';
|
|
41
|
+
const inputOtpSegmentSelector = '[data-ui-input-otp-segment]';
|
|
42
|
+
const inputOtpSegmentsSelector = '[data-ui-input-otp-segments]';
|
|
43
|
+
const calendarSelector = '[data-ui-calendar], lumen-calendar';
|
|
44
|
+
const calendarInputSelector = '[data-ui-calendar-input]';
|
|
45
|
+
const calendarLabelSelector = '[data-ui-calendar-label]';
|
|
46
|
+
const calendarGridSelector = '[data-ui-calendar-grid]';
|
|
47
|
+
const calendarDaySelector = '[data-ui-calendar-day]';
|
|
48
|
+
const datePickerSelector = '[data-ui-date-picker], lumen-date-picker';
|
|
49
|
+
const datePickerNativeSelector = '[data-ui-date-picker-native]';
|
|
50
|
+
const datePickerControlSelector = '[data-ui-date-picker-control]';
|
|
51
|
+
const datePickerTriggerSelector = '[data-ui-date-picker-trigger]';
|
|
52
|
+
const datePickerValueSelector = '[data-ui-date-picker-value]';
|
|
53
|
+
const datePickerPopoverSelector = '[data-ui-date-picker-popover]';
|
|
54
|
+
const formControlSelector = [
|
|
55
|
+
'input:not([type="hidden"])',
|
|
56
|
+
'select',
|
|
57
|
+
'textarea'
|
|
58
|
+
].join(',');
|
|
59
|
+
const selectOptionSelector = '[data-ui-select-option]';
|
|
60
|
+
const defaultToastDuration = 5000;
|
|
61
|
+
const defaultToastMax = 5;
|
|
62
|
+
const toastCloseDelay = 240;
|
|
63
|
+
const validityMessageAttributes = [
|
|
64
|
+
['valueMissing', 'data-error-required'],
|
|
65
|
+
['typeMismatch', 'data-error-type'],
|
|
66
|
+
['patternMismatch', 'data-error-pattern'],
|
|
67
|
+
['tooShort', 'data-error-too-short'],
|
|
68
|
+
['tooLong', 'data-error-too-long'],
|
|
69
|
+
['rangeUnderflow', 'data-error-min'],
|
|
70
|
+
['rangeOverflow', 'data-error-max'],
|
|
71
|
+
['stepMismatch', 'data-error-step'],
|
|
72
|
+
['badInput', 'data-error-bad-input'],
|
|
73
|
+
['customError', 'data-error-custom']
|
|
74
|
+
];
|
|
75
|
+
const toastTimers = new WeakMap();
|
|
76
|
+
const mergeClassNames = (...classNames) => composeClassName(...classNames).split(/\s+/).filter(Boolean);
|
|
77
|
+
const glassAttributeClasses = (className) => ({
|
|
78
|
+
glass: {
|
|
79
|
+
strong: `${className} ui-glass-strong`,
|
|
80
|
+
subtle: `${className} ui-glass-subtle`,
|
|
81
|
+
true: className
|
|
82
|
+
},
|
|
83
|
+
surface: { glass: className }
|
|
84
|
+
});
|
|
85
|
+
const elementConfigs = {
|
|
86
|
+
Accordion: { baseClassName: 'ui-accordion', tagName: 'lumen-accordion' },
|
|
87
|
+
Alert: {
|
|
88
|
+
attributeClasses: {
|
|
89
|
+
glass: { strong: 'ui-alert--glass ui-glass-strong', subtle: 'ui-alert--glass ui-glass-subtle', true: 'ui-alert--glass' },
|
|
90
|
+
variant: {
|
|
91
|
+
destructive: 'ui-alert--destructive',
|
|
92
|
+
success: 'ui-alert--success',
|
|
93
|
+
warning: 'ui-alert--warning'
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
baseClassName: 'ui-alert',
|
|
97
|
+
defaults: { variant: 'default' },
|
|
98
|
+
tagName: 'lumen-alert'
|
|
99
|
+
},
|
|
100
|
+
AlertDialog: {
|
|
101
|
+
attributeClasses: {
|
|
102
|
+
glass: { strong: 'ui-dialog--glass ui-glass-strong', subtle: 'ui-dialog--glass ui-glass-subtle', true: 'ui-dialog--glass' },
|
|
103
|
+
surface: { glass: 'ui-dialog--glass' }
|
|
104
|
+
},
|
|
105
|
+
baseClassName: 'ui-dialog ui-alert-dialog',
|
|
106
|
+
defaults: { 'data-ui-alert-dialog': '', surface: 'default' },
|
|
107
|
+
tagName: 'lumen-alert-dialog'
|
|
108
|
+
},
|
|
109
|
+
Agenda: { attributeClasses: glassAttributeClasses('ui-agenda--glass'), baseClassName: 'ui-agenda', tagName: 'lumen-agenda' },
|
|
110
|
+
AspectRatio: { baseClassName: 'ui-aspect-ratio', tagName: 'lumen-aspect-ratio' },
|
|
111
|
+
Attachment: { attributeClasses: glassAttributeClasses('ui-attachment--glass'), baseClassName: 'ui-attachment', tagName: 'lumen-attachment' },
|
|
112
|
+
Autocomplete: { baseClassName: 'ui-input ui-autocomplete', defaults: { role: 'combobox', type: 'search' }, tagName: 'lumen-autocomplete' },
|
|
113
|
+
Avatar: { baseClassName: 'ui-avatar', tagName: 'lumen-avatar' },
|
|
114
|
+
BackToTop: { baseClassName: 'ui-back-to-top', tagName: 'lumen-back-to-top' },
|
|
115
|
+
Badge: {
|
|
116
|
+
attributeClasses: {
|
|
117
|
+
variant: {
|
|
118
|
+
default: 'ui-badge--default',
|
|
119
|
+
destructive: 'ui-badge--destructive',
|
|
120
|
+
outline: 'ui-badge--outline',
|
|
121
|
+
secondary: 'ui-badge--secondary',
|
|
122
|
+
success: 'ui-badge--success',
|
|
123
|
+
warning: 'ui-badge--warning'
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
baseClassName: 'ui-badge',
|
|
127
|
+
defaults: { variant: 'default' },
|
|
128
|
+
tagName: 'lumen-badge'
|
|
129
|
+
},
|
|
130
|
+
Breadcrumb: { baseClassName: 'ui-breadcrumb', defaults: { 'aria-label': 'Breadcrumb' }, tagName: 'lumen-breadcrumb' },
|
|
131
|
+
Bubble: {
|
|
132
|
+
attributeClasses: { from: { user: 'ui-bubble--user' }, ...glassAttributeClasses('ui-bubble--glass') },
|
|
133
|
+
baseClassName: 'ui-bubble',
|
|
134
|
+
defaults: { from: 'assistant' },
|
|
135
|
+
tagName: 'lumen-bubble'
|
|
136
|
+
},
|
|
137
|
+
Button: {
|
|
138
|
+
attributeClasses: {
|
|
139
|
+
variant: {
|
|
140
|
+
default: 'ui-button--default',
|
|
141
|
+
destructive: 'ui-button--destructive',
|
|
142
|
+
ghost: 'ui-button--ghost',
|
|
143
|
+
link: 'ui-button--link',
|
|
144
|
+
outline: 'ui-button--outline',
|
|
145
|
+
secondary: 'ui-button--secondary'
|
|
146
|
+
},
|
|
147
|
+
size: {
|
|
148
|
+
default: 'ui-button--default-size',
|
|
149
|
+
icon: 'ui-button--icon',
|
|
150
|
+
lg: 'ui-button--lg',
|
|
151
|
+
sm: 'ui-button--sm'
|
|
152
|
+
},
|
|
153
|
+
disabled: { true: 'ui-button--disabled' },
|
|
154
|
+
loading: { true: 'ui-button--loading' }
|
|
155
|
+
},
|
|
156
|
+
baseClassName: 'ui-button',
|
|
157
|
+
defaults: { role: 'button', size: 'default', tabindex: '0', variant: 'default' },
|
|
158
|
+
role: 'button',
|
|
159
|
+
tagName: 'lumen-button'
|
|
160
|
+
},
|
|
161
|
+
ButtonGroup: { baseClassName: 'ui-button-group', tagName: 'lumen-button-group' },
|
|
162
|
+
Calendar: { attributeClasses: glassAttributeClasses('ui-calendar--glass'), baseClassName: 'ui-calendar', defaults: { 'data-ui-calendar': '' }, tagName: 'lumen-calendar' },
|
|
163
|
+
Callout: { baseClassName: 'ui-callout', tagName: 'lumen-callout' },
|
|
164
|
+
Card: {
|
|
165
|
+
attributeClasses: {
|
|
166
|
+
glass: {
|
|
167
|
+
strong: 'ui-card--glass ui-glass-strong',
|
|
168
|
+
subtle: 'ui-card--glass ui-glass-subtle',
|
|
169
|
+
true: 'ui-card--glass'
|
|
170
|
+
},
|
|
171
|
+
variant: {
|
|
172
|
+
glass: 'ui-card--glass',
|
|
173
|
+
interactive: 'ui-card--interactive',
|
|
174
|
+
muted: 'ui-card--muted'
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
baseClassName: 'ui-card',
|
|
178
|
+
defaults: { variant: 'default' },
|
|
179
|
+
tagName: 'lumen-card'
|
|
180
|
+
},
|
|
181
|
+
Carousel: { attributeClasses: glassAttributeClasses('ui-carousel--glass'), baseClassName: 'ui-carousel', defaults: { 'data-ui-carousel': '' }, tagName: 'lumen-carousel' },
|
|
182
|
+
Chart: { attributeClasses: glassAttributeClasses('ui-chart--glass'), baseClassName: 'ui-chart', tagName: 'lumen-chart' },
|
|
183
|
+
Checkbox: { baseClassName: 'ui-checkbox', defaults: { type: 'checkbox' }, tagName: 'lumen-checkbox' },
|
|
184
|
+
Collapsible: { baseClassName: 'ui-collapsible', defaults: { 'data-ui-collapsible': '' }, tagName: 'lumen-collapsible' },
|
|
185
|
+
Code: {
|
|
186
|
+
attributeClasses: {
|
|
187
|
+
variant: {
|
|
188
|
+
block: 'ui-code--block',
|
|
189
|
+
inline: 'ui-code--inline'
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
baseClassName: 'ui-code',
|
|
193
|
+
defaults: { 'data-code-theme': 'auto', variant: 'inline' },
|
|
194
|
+
tagName: 'lumen-code'
|
|
195
|
+
},
|
|
196
|
+
Combobox: { baseClassName: 'ui-combobox', defaults: { 'data-ui-combobox': '' }, tagName: 'lumen-combobox' },
|
|
197
|
+
Command: { attributeClasses: glassAttributeClasses('ui-command--glass'), baseClassName: 'ui-command', defaults: { 'data-ui-command': '' }, tagName: 'lumen-command' },
|
|
198
|
+
ColorPicker: { baseClassName: 'ui-color-picker', defaults: { type: 'color' }, tagName: 'lumen-color-picker' },
|
|
199
|
+
ContextMenu: {
|
|
200
|
+
attributeClasses: {
|
|
201
|
+
glass: { strong: 'ui-menu--glass ui-glass-strong', subtle: 'ui-menu--glass ui-glass-subtle', true: 'ui-menu--glass' },
|
|
202
|
+
surface: { glass: 'ui-menu--glass' }
|
|
203
|
+
},
|
|
204
|
+
baseClassName: 'ui-menu',
|
|
205
|
+
defaults: { 'data-ui-context-menu': '', role: 'menu', surface: 'default' },
|
|
206
|
+
tagName: 'lumen-context-menu'
|
|
207
|
+
},
|
|
208
|
+
DataTable: { attributeClasses: glassAttributeClasses('ui-data-table--glass'), baseClassName: 'ui-data-table', defaults: { 'data-ui-datatable': '' }, tagName: 'lumen-data-table' },
|
|
209
|
+
DatePicker: {
|
|
210
|
+
attributeClasses: glassAttributeClasses('ui-date-picker-field--glass'),
|
|
211
|
+
baseClassName: 'ui-input ui-date-picker',
|
|
212
|
+
defaults: { type: 'date' },
|
|
213
|
+
tagName: 'lumen-date-picker'
|
|
214
|
+
},
|
|
215
|
+
DateRangePicker: { baseClassName: 'ui-date-range-picker', defaults: { 'data-ui-date-range-picker': '' }, tagName: 'lumen-date-range-picker' },
|
|
216
|
+
Dialog: {
|
|
217
|
+
attributeClasses: {
|
|
218
|
+
glass: { strong: 'ui-dialog--glass ui-glass-strong', subtle: 'ui-dialog--glass ui-glass-subtle', true: 'ui-dialog--glass' },
|
|
219
|
+
surface: { glass: 'ui-dialog--glass' }
|
|
220
|
+
},
|
|
221
|
+
baseClassName: 'ui-dialog',
|
|
222
|
+
defaults: { 'data-ui-dialog': '', surface: 'default' },
|
|
223
|
+
tagName: 'lumen-dialog'
|
|
224
|
+
},
|
|
225
|
+
Direction: { baseClassName: 'ui-direction', defaults: { dir: 'ltr' }, tagName: 'lumen-direction' },
|
|
226
|
+
Drawer: {
|
|
227
|
+
attributeClasses: {
|
|
228
|
+
glass: { strong: 'ui-drawer--glass ui-glass-strong', subtle: 'ui-drawer--glass ui-glass-subtle', true: 'ui-drawer--glass' },
|
|
229
|
+
surface: { glass: 'ui-drawer--glass' }
|
|
230
|
+
},
|
|
231
|
+
baseClassName: 'ui-drawer',
|
|
232
|
+
defaults: { 'data-ui-drawer': '', surface: 'default' },
|
|
233
|
+
tagName: 'lumen-drawer'
|
|
234
|
+
},
|
|
235
|
+
DropdownMenu: {
|
|
236
|
+
attributeClasses: {
|
|
237
|
+
glass: { strong: 'ui-menu--glass ui-glass-strong', subtle: 'ui-menu--glass ui-glass-subtle', true: 'ui-menu--glass' },
|
|
238
|
+
surface: { glass: 'ui-menu--glass' }
|
|
239
|
+
},
|
|
240
|
+
baseClassName: 'ui-menu',
|
|
241
|
+
defaults: { 'data-ui-dropdown-menu': '', surface: 'default' },
|
|
242
|
+
tagName: 'lumen-dropdown-menu'
|
|
243
|
+
},
|
|
244
|
+
Empty: { attributeClasses: glassAttributeClasses('ui-empty--glass'), baseClassName: 'ui-empty', tagName: 'lumen-empty' },
|
|
245
|
+
Eyebrow: { baseClassName: 'ui-eyebrow', tagName: 'lumen-eyebrow' },
|
|
246
|
+
Field: { attributeClasses: glassAttributeClasses('ui-field--glass'), baseClassName: 'ui-field', defaults: { 'data-ui-field': '' }, tagName: 'lumen-field' },
|
|
247
|
+
FloatingBadge: { baseClassName: 'ui-floating-badge', tagName: 'lumen-floating-badge' },
|
|
248
|
+
FormattedDate: { baseClassName: 'ui-formatted-date', tagName: 'lumen-formatted-date' },
|
|
249
|
+
HoverCard: {
|
|
250
|
+
attributeClasses: {
|
|
251
|
+
glass: { strong: 'ui-hover-card--glass ui-glass-strong', subtle: 'ui-hover-card--glass ui-glass-subtle', true: 'ui-hover-card--glass' },
|
|
252
|
+
surface: { glass: 'ui-hover-card--glass' }
|
|
253
|
+
},
|
|
254
|
+
baseClassName: 'ui-hover-card',
|
|
255
|
+
defaults: { 'data-ui-hover-card': '', surface: 'default' },
|
|
256
|
+
tagName: 'lumen-hover-card'
|
|
257
|
+
},
|
|
258
|
+
Icon: {
|
|
259
|
+
attributeClasses: {
|
|
260
|
+
size: {
|
|
261
|
+
lg: 'ui-icon--lg',
|
|
262
|
+
sm: 'ui-icon--sm',
|
|
263
|
+
xl: 'ui-icon--xl'
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
baseClassName: 'ui-icon',
|
|
267
|
+
defaults: { size: 'default' },
|
|
268
|
+
tagName: 'lumen-icon'
|
|
269
|
+
},
|
|
270
|
+
Image: {
|
|
271
|
+
attributeClasses: {
|
|
272
|
+
'invert-on-dark': { true: 'ui-image--invert-dark' }
|
|
273
|
+
},
|
|
274
|
+
baseClassName: 'ui-image',
|
|
275
|
+
tagName: 'lumen-image'
|
|
276
|
+
},
|
|
277
|
+
Input: { attributeClasses: { size: { lg: 'ui-input--lg', sm: 'ui-input--sm' } }, baseClassName: 'ui-input', defaults: { type: 'text' }, tagName: 'lumen-input' },
|
|
278
|
+
InputGroup: { baseClassName: 'ui-input-group', tagName: 'lumen-input-group' },
|
|
279
|
+
InputOTP: { baseClassName: 'ui-input-otp-field', defaults: { 'data-ui-input-otp': '', 'data-ui-input-otp-length': '6' }, tagName: 'lumen-input-otp' },
|
|
280
|
+
NumberField: { baseClassName: 'ui-input ui-number-field', defaults: { type: 'number' }, tagName: 'lumen-number-field' },
|
|
281
|
+
Item: { attributeClasses: glassAttributeClasses('ui-item--glass'), baseClassName: 'ui-item', tagName: 'lumen-item' },
|
|
282
|
+
Kbd: { baseClassName: 'ui-kbd', tagName: 'lumen-kbd' },
|
|
283
|
+
Label: { baseClassName: 'ui-label', tagName: 'lumen-label' },
|
|
284
|
+
Link: { baseClassName: 'ui-link', tagName: 'lumen-link' },
|
|
285
|
+
Marker: {
|
|
286
|
+
attributeClasses: {
|
|
287
|
+
variant: {
|
|
288
|
+
danger: 'ui-marker--danger',
|
|
289
|
+
success: 'ui-marker--success',
|
|
290
|
+
warning: 'ui-marker--warning'
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
baseClassName: 'ui-marker',
|
|
294
|
+
defaults: { variant: 'default' },
|
|
295
|
+
tagName: 'lumen-marker'
|
|
296
|
+
},
|
|
297
|
+
Menubar: {
|
|
298
|
+
attributeClasses: {
|
|
299
|
+
glass: { strong: 'ui-menubar--glass ui-glass-strong', subtle: 'ui-menubar--glass ui-glass-subtle', true: 'ui-menubar--glass' },
|
|
300
|
+
surface: { glass: 'ui-menubar--glass' }
|
|
301
|
+
},
|
|
302
|
+
baseClassName: 'ui-menubar',
|
|
303
|
+
defaults: { 'data-ui-menubar': '', surface: 'default' },
|
|
304
|
+
tagName: 'lumen-menubar'
|
|
305
|
+
},
|
|
306
|
+
Message: {
|
|
307
|
+
attributeClasses: {
|
|
308
|
+
from: {
|
|
309
|
+
assistant: 'ui-message--assistant',
|
|
310
|
+
user: 'ui-message--user'
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
baseClassName: 'ui-message',
|
|
314
|
+
defaults: { from: 'assistant' },
|
|
315
|
+
tagName: 'lumen-message'
|
|
316
|
+
},
|
|
317
|
+
MessageScroller: { attributeClasses: glassAttributeClasses('ui-message-scroller--glass'), baseClassName: 'ui-message-scroller', tagName: 'lumen-message-scroller' },
|
|
318
|
+
NativeSelect: { attributeClasses: { size: { lg: 'ui-select--lg', sm: 'ui-select--sm' } }, baseClassName: 'ui-select', tagName: 'lumen-native-select' },
|
|
319
|
+
NavigationMenu: {
|
|
320
|
+
attributeClasses: {
|
|
321
|
+
glass: { strong: 'ui-navigation-menu--glass ui-glass-strong', subtle: 'ui-navigation-menu--glass ui-glass-subtle', true: 'ui-navigation-menu--glass' },
|
|
322
|
+
surface: { glass: 'ui-navigation-menu--glass' }
|
|
323
|
+
},
|
|
324
|
+
baseClassName: 'ui-navigation-menu',
|
|
325
|
+
defaults: { 'data-ui-navigation-menu': '', surface: 'default' },
|
|
326
|
+
tagName: 'lumen-navigation-menu'
|
|
327
|
+
},
|
|
328
|
+
Pagination: { baseClassName: 'ui-pagination', defaults: { 'aria-label': 'Pagination' }, tagName: 'lumen-pagination' },
|
|
329
|
+
PhoneInput: { baseClassName: 'ui-phone-input ui-input-group', tagName: 'lumen-phone-input' },
|
|
330
|
+
Pill: { baseClassName: 'ui-pill', tagName: 'lumen-pill' },
|
|
331
|
+
Popover: {
|
|
332
|
+
attributeClasses: {
|
|
333
|
+
glass: { strong: 'ui-popover--glass ui-glass-strong', subtle: 'ui-popover--glass ui-glass-subtle', true: 'ui-popover--glass' },
|
|
334
|
+
surface: { glass: 'ui-popover--glass' }
|
|
335
|
+
},
|
|
336
|
+
baseClassName: 'ui-popover',
|
|
337
|
+
defaults: { 'data-ui-popover': '', surface: 'default' },
|
|
338
|
+
tagName: 'lumen-popover'
|
|
339
|
+
},
|
|
340
|
+
Progress: { baseClassName: 'ui-progress', defaults: { role: 'progressbar' }, tagName: 'lumen-progress' },
|
|
341
|
+
Prose: { baseClassName: 'ui-prose', tagName: 'lumen-prose' },
|
|
342
|
+
RadioGroup: { baseClassName: 'ui-radio-group', defaults: { 'data-ui-radio-group': '' }, tagName: 'lumen-radio-group' },
|
|
343
|
+
Resizable: {
|
|
344
|
+
attributeClasses: { 'data-orientation': { vertical: 'ui-resizable--vertical' } },
|
|
345
|
+
baseClassName: 'ui-resizable',
|
|
346
|
+
defaults: { 'data-orientation': 'horizontal', 'data-ui-resizable': '', 'data-ui-resizable-reset': 'true' },
|
|
347
|
+
tagName: 'lumen-resizable'
|
|
348
|
+
},
|
|
349
|
+
RichTextEditor: { attributeClasses: glassAttributeClasses('ui-rich-text-editor--glass'), baseClassName: 'ui-rich-text-editor', defaults: { 'data-ui-rich-text-editor': '' }, tagName: 'lumen-rich-text-editor' },
|
|
350
|
+
ScrollArea: { attributeClasses: glassAttributeClasses('ui-scroll-area--glass'), baseClassName: 'ui-scroll-area', tagName: 'lumen-scroll-area' },
|
|
351
|
+
Schedule: { attributeClasses: glassAttributeClasses('ui-schedule--glass'), baseClassName: 'ui-schedule', defaults: { 'data-ui-schedule': '' }, tagName: 'lumen-schedule' },
|
|
352
|
+
SearchField: { baseClassName: 'ui-input ui-search-field', defaults: { type: 'search' }, tagName: 'lumen-search-field' },
|
|
353
|
+
Select: {
|
|
354
|
+
attributeClasses: {
|
|
355
|
+
...glassAttributeClasses('ui-select-field--glass'),
|
|
356
|
+
size: { lg: 'ui-select--lg', sm: 'ui-select--sm' }
|
|
357
|
+
},
|
|
358
|
+
baseClassName: 'ui-select',
|
|
359
|
+
tagName: 'lumen-select'
|
|
360
|
+
},
|
|
361
|
+
Separator: {
|
|
362
|
+
attributeClasses: {
|
|
363
|
+
orientation: {
|
|
364
|
+
horizontal: 'ui-separator--horizontal',
|
|
365
|
+
vertical: 'ui-separator--vertical'
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
baseClassName: 'ui-separator',
|
|
369
|
+
defaults: { orientation: 'horizontal' },
|
|
370
|
+
tagName: 'lumen-separator'
|
|
371
|
+
},
|
|
372
|
+
Sheet: {
|
|
373
|
+
attributeClasses: {
|
|
374
|
+
glass: { strong: 'ui-sheet--glass ui-glass-strong', subtle: 'ui-sheet--glass ui-glass-subtle', true: 'ui-sheet--glass' },
|
|
375
|
+
surface: { glass: 'ui-sheet--glass' }
|
|
376
|
+
},
|
|
377
|
+
baseClassName: 'ui-sheet',
|
|
378
|
+
defaults: { 'data-ui-sheet': '', surface: 'default' },
|
|
379
|
+
tagName: 'lumen-sheet'
|
|
380
|
+
},
|
|
381
|
+
Sidebar: {
|
|
382
|
+
attributeClasses: {
|
|
383
|
+
glass: { strong: 'ui-sidebar--glass ui-glass-strong', subtle: 'ui-sidebar--glass ui-glass-subtle', true: 'ui-sidebar--glass' },
|
|
384
|
+
surface: { glass: 'ui-sidebar--glass' }
|
|
385
|
+
},
|
|
386
|
+
baseClassName: 'ui-sidebar',
|
|
387
|
+
defaults: { surface: 'default' },
|
|
388
|
+
tagName: 'lumen-sidebar'
|
|
389
|
+
},
|
|
390
|
+
Skeleton: { baseClassName: 'ui-skeleton', tagName: 'lumen-skeleton' },
|
|
391
|
+
SkipLink: { baseClassName: 'ui-skip-link', tagName: 'lumen-skip-link' },
|
|
392
|
+
Slider: { baseClassName: 'ui-slider', defaults: { type: 'range' }, tagName: 'lumen-slider' },
|
|
393
|
+
Sonner: { baseClassName: 'ui-sonner', defaults: { 'data-ui-sonner': '' }, tagName: 'lumen-sonner' },
|
|
394
|
+
Spinner: { baseClassName: 'ui-spinner', tagName: 'lumen-spinner' },
|
|
395
|
+
Switch: { baseClassName: 'ui-switch', defaults: { role: 'switch', type: 'checkbox' }, tagName: 'lumen-switch' },
|
|
396
|
+
Table: { attributeClasses: glassAttributeClasses('ui-table-wrap--glass'), baseClassName: 'ui-table-wrap', tagName: 'lumen-table' },
|
|
397
|
+
Tabs: { attributeClasses: glassAttributeClasses('ui-tabs--glass'), baseClassName: 'ui-tabs', defaults: { 'data-ui-tabs': '' }, tagName: 'lumen-tabs' },
|
|
398
|
+
TagGroup: { baseClassName: 'ui-tag-group', defaults: { role: 'list' }, tagName: 'lumen-tag-group' },
|
|
399
|
+
Textarea: { baseClassName: 'ui-textarea', defaults: { rows: '4' }, tagName: 'lumen-textarea' },
|
|
400
|
+
ThemeBuilder: { attributeClasses: glassAttributeClasses('ui-theme-builder--glass'), baseClassName: 'ui-theme-builder', defaults: { 'data-ui-theme-builder': '' }, tagName: 'lumen-theme-builder' },
|
|
401
|
+
ThemeToggle: { baseClassName: 'ui-theme-toggle', tagName: 'lumen-theme-toggle' },
|
|
402
|
+
TimeField: { baseClassName: 'ui-input ui-time-field', defaults: { type: 'time' }, tagName: 'lumen-time-field' },
|
|
403
|
+
Toast: {
|
|
404
|
+
attributeClasses: {
|
|
405
|
+
glass: {
|
|
406
|
+
true: 'ui-toast--glass'
|
|
407
|
+
},
|
|
408
|
+
surface: {
|
|
409
|
+
glass: 'ui-toast--glass'
|
|
410
|
+
},
|
|
411
|
+
variant: {
|
|
412
|
+
destructive: 'ui-toast--destructive',
|
|
413
|
+
success: 'ui-toast--success',
|
|
414
|
+
warning: 'ui-toast--warning'
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
baseClassName: 'ui-toast',
|
|
418
|
+
defaults: { surface: 'default', variant: 'default' },
|
|
419
|
+
tagName: 'lumen-toast'
|
|
420
|
+
},
|
|
421
|
+
Toggle: {
|
|
422
|
+
attributeClasses: { pressed: { true: 'ui-toggle--pressed' } },
|
|
423
|
+
baseClassName: 'ui-toggle',
|
|
424
|
+
defaults: { 'aria-pressed': 'false', 'data-ui-toggle': '', role: 'button', tabindex: '0' },
|
|
425
|
+
role: 'button',
|
|
426
|
+
tagName: 'lumen-toggle'
|
|
427
|
+
},
|
|
428
|
+
ToggleGroup: { baseClassName: 'ui-toggle-group', defaults: { 'data-ui-toggle-group': '' }, tagName: 'lumen-toggle-group' },
|
|
429
|
+
Tooltip: { baseClassName: 'ui-tooltip', tagName: 'lumen-tooltip' },
|
|
430
|
+
Tree: { attributeClasses: glassAttributeClasses('ui-tree--glass'), baseClassName: 'ui-tree', defaults: { role: 'tree' }, tagName: 'lumen-tree' },
|
|
431
|
+
TreeGrid: { attributeClasses: glassAttributeClasses('ui-tree-grid--glass'), baseClassName: 'ui-tree-grid', defaults: { role: 'treegrid' }, tagName: 'lumen-tree-grid' },
|
|
432
|
+
Typography: { baseClassName: 'ui-typography', tagName: 'lumen-typography' },
|
|
433
|
+
VirtualList: { attributeClasses: glassAttributeClasses('ui-virtual-list--glass'), baseClassName: 'ui-virtual-list', defaults: { 'data-ui-virtual-list': '' }, tagName: 'lumen-virtual-list' },
|
|
434
|
+
LanguageToggle: { baseClassName: 'ui-language-toggle', tagName: 'lumen-language-toggle' },
|
|
435
|
+
Particles: { baseClassName: 'ui-particles', tagName: 'lumen-particles' },
|
|
436
|
+
AnimatedNumber: {
|
|
437
|
+
attributeClasses: {
|
|
438
|
+
duration: {
|
|
439
|
+
fast: 'ui-motion-duration-fast',
|
|
440
|
+
slow: 'ui-motion-duration-slow',
|
|
441
|
+
standard: 'ui-motion-duration-standard'
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
baseClassName: 'ui-animated-number',
|
|
445
|
+
defaults: { duration: 'slow', from: '0' },
|
|
446
|
+
tagName: 'lumen-animated-number'
|
|
447
|
+
},
|
|
448
|
+
RevealGroup: {
|
|
449
|
+
attributeClasses: {
|
|
450
|
+
animation: {
|
|
451
|
+
fade: 'ui-reveal-group-fade',
|
|
452
|
+
scale: 'ui-reveal-group-scale',
|
|
453
|
+
'slide-up': 'ui-reveal-group-slide-up'
|
|
454
|
+
},
|
|
455
|
+
duration: {
|
|
456
|
+
fast: 'ui-motion-duration-fast',
|
|
457
|
+
slow: 'ui-motion-duration-slow',
|
|
458
|
+
standard: 'ui-motion-duration-standard'
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
baseClassName: 'ui-reveal-group',
|
|
462
|
+
defaults: { animation: 'slide-up', duration: 'slow', once: 'true', threshold: '0.15' },
|
|
463
|
+
tagName: 'lumen-reveal-group'
|
|
464
|
+
},
|
|
465
|
+
ScrollReveal: {
|
|
466
|
+
attributeClasses: {
|
|
467
|
+
animation: {
|
|
468
|
+
fade: 'ui-scroll-reveal-fade',
|
|
469
|
+
scale: 'ui-scroll-reveal-scale',
|
|
470
|
+
'slide-up': 'ui-scroll-reveal-slide-up'
|
|
471
|
+
},
|
|
472
|
+
duration: {
|
|
473
|
+
fast: 'ui-motion-duration-fast',
|
|
474
|
+
slow: 'ui-motion-duration-slow',
|
|
475
|
+
standard: 'ui-motion-duration-standard'
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
baseClassName: 'ui-scroll-reveal',
|
|
479
|
+
defaults: { animation: 'fade', duration: 'slow', once: 'true', threshold: '0.15' },
|
|
480
|
+
tagName: 'lumen-scroll-reveal'
|
|
481
|
+
},
|
|
482
|
+
Stat: { baseClassName: 'ui-stat', tagName: 'lumen-stat' },
|
|
483
|
+
Meter: { baseClassName: 'ui-meter', tagName: 'lumen-meter' },
|
|
484
|
+
Note: {
|
|
485
|
+
attributeClasses: {
|
|
486
|
+
'border-position': {
|
|
487
|
+
left: 'ui-note--border-left',
|
|
488
|
+
none: 'ui-note--border-none',
|
|
489
|
+
right: 'ui-note--border-right'
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
baseClassName: 'ui-note',
|
|
493
|
+
defaults: { 'border-position': 'left' },
|
|
494
|
+
tagName: 'lumen-note'
|
|
495
|
+
},
|
|
496
|
+
Rating: { baseClassName: 'ui-rating', tagName: 'lumen-rating' },
|
|
497
|
+
Timeline: { baseClassName: 'ui-timeline', tagName: 'lumen-timeline' },
|
|
498
|
+
AnimatedLogo: { baseClassName: 'ui-animated-logo', tagName: 'lumen-animated-logo' },
|
|
499
|
+
AnimatedPortrait: { baseClassName: 'ui-animated-portrait', tagName: 'lumen-animated-portrait' },
|
|
500
|
+
ButtonLink: {
|
|
501
|
+
attributeClasses: { shape: { icon: 'ui-button-link--icon' } },
|
|
502
|
+
baseClassName: 'ui-button-link',
|
|
503
|
+
tagName: 'lumen-button-link'
|
|
504
|
+
},
|
|
505
|
+
CoverImage: {
|
|
506
|
+
attributeClasses: { hover: { true: 'ui-cover-image--hover' } },
|
|
507
|
+
baseClassName: 'ui-cover-image',
|
|
508
|
+
tagName: 'lumen-cover-image'
|
|
509
|
+
},
|
|
510
|
+
GradientDivider: { baseClassName: 'ui-gradient-divider', tagName: 'lumen-gradient-divider' },
|
|
511
|
+
Stepper: {
|
|
512
|
+
attributeClasses: { orientation: { vertical: 'ui-stepper--vertical' } },
|
|
513
|
+
baseClassName: 'ui-stepper',
|
|
514
|
+
defaults: { orientation: 'horizontal' },
|
|
515
|
+
tagName: 'lumen-stepper'
|
|
516
|
+
},
|
|
517
|
+
FileUpload: { baseClassName: 'ui-file-upload', defaults: { 'data-ui-file-upload': '' }, tagName: 'lumen-file-upload' },
|
|
518
|
+
Tour: { baseClassName: 'ui-tour', defaults: { 'data-ui-tour': '' }, tagName: 'lumen-tour' },
|
|
519
|
+
Anchor: { baseClassName: 'ui-anchor', defaults: { 'aria-label': 'On this page', 'data-ui-anchor': '' }, tagName: 'lumen-anchor' },
|
|
520
|
+
Segmented: {
|
|
521
|
+
attributeClasses: { size: { lg: 'ui-segmented--lg', sm: 'ui-segmented--sm' } },
|
|
522
|
+
baseClassName: 'ui-segmented',
|
|
523
|
+
defaults: { role: 'group' },
|
|
524
|
+
tagName: 'lumen-segmented'
|
|
525
|
+
},
|
|
526
|
+
Toolbar: {
|
|
527
|
+
attributeClasses: { orientation: { vertical: 'ui-toolbar--vertical' } },
|
|
528
|
+
baseClassName: 'ui-toolbar',
|
|
529
|
+
defaults: { 'aria-label': 'Toolbar', 'data-ui-toolbar': '', orientation: 'horizontal', role: 'toolbar' },
|
|
530
|
+
tagName: 'lumen-toolbar'
|
|
531
|
+
},
|
|
532
|
+
Descriptions: { baseClassName: 'ui-descriptions', tagName: 'lumen-descriptions' },
|
|
533
|
+
Popconfirm: { baseClassName: 'ui-popover ui-popconfirm', defaults: { 'data-ui-popconfirm': '', 'data-ui-popover': '' }, tagName: 'lumen-popconfirm' },
|
|
534
|
+
Transfer: { baseClassName: 'ui-transfer', defaults: { 'data-ui-transfer': '' }, tagName: 'lumen-transfer' },
|
|
535
|
+
Cascader: { baseClassName: 'ui-cascader', defaults: { 'data-ui-cascader': '' }, tagName: 'lumen-cascader' },
|
|
536
|
+
TreeSelect: { baseClassName: 'ui-tree-select', defaults: { 'data-ui-tree-select': '' }, tagName: 'lumen-tree-select' },
|
|
537
|
+
Mentions: { baseClassName: 'ui-mentions', defaults: { 'data-ui-mentions': '' }, tagName: 'lumen-mentions' },
|
|
538
|
+
QRCode: { baseClassName: 'ui-qr-code', tagName: 'lumen-qr-code' },
|
|
539
|
+
Watermark: { baseClassName: 'ui-watermark', defaults: { 'data-ui-watermark': '' }, tagName: 'lumen-watermark' },
|
|
540
|
+
Affix: {
|
|
541
|
+
attributeClasses: { position: { bottom: 'ui-affix--bottom' } },
|
|
542
|
+
baseClassName: 'ui-affix',
|
|
543
|
+
defaults: { position: 'top' },
|
|
544
|
+
tagName: 'lumen-affix'
|
|
545
|
+
},
|
|
546
|
+
SpeedDial: {
|
|
547
|
+
attributeClasses: {
|
|
548
|
+
direction: {
|
|
549
|
+
down: 'ui-speed-dial--down',
|
|
550
|
+
left: 'ui-speed-dial--left',
|
|
551
|
+
right: 'ui-speed-dial--right',
|
|
552
|
+
up: 'ui-speed-dial--up'
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
baseClassName: 'ui-speed-dial',
|
|
556
|
+
defaults: { 'data-ui-speed-dial': '', direction: 'up' },
|
|
557
|
+
tagName: 'lumen-speed-dial'
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
const observedAttributeNames = [
|
|
561
|
+
'animation',
|
|
562
|
+
'border-position',
|
|
563
|
+
'decimals',
|
|
564
|
+
'delay',
|
|
565
|
+
'decorative',
|
|
566
|
+
'direction',
|
|
567
|
+
'disabled',
|
|
568
|
+
'duration',
|
|
569
|
+
'from',
|
|
570
|
+
'glass',
|
|
571
|
+
'hover',
|
|
572
|
+
'label',
|
|
573
|
+
'locale',
|
|
574
|
+
'name',
|
|
575
|
+
'orientation',
|
|
576
|
+
'position',
|
|
577
|
+
'prefix',
|
|
578
|
+
'pressed',
|
|
579
|
+
'shape',
|
|
580
|
+
'size',
|
|
581
|
+
'surface',
|
|
582
|
+
'stagger',
|
|
583
|
+
'suffix',
|
|
584
|
+
'threshold',
|
|
585
|
+
'value',
|
|
586
|
+
'variant'
|
|
587
|
+
];
|
|
588
|
+
const hasDocument = () => typeof document !== 'undefined';
|
|
589
|
+
const parseMotionDuration = (value) => {
|
|
590
|
+
const parsedDuration = Number.parseFloat(value);
|
|
591
|
+
if (!Number.isFinite(parsedDuration))
|
|
592
|
+
return 300;
|
|
593
|
+
return value.endsWith('ms') ? parsedDuration : parsedDuration * 1000;
|
|
594
|
+
};
|
|
595
|
+
const createId = (prefix) => {
|
|
596
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
597
|
+
return `${prefix}-${crypto.randomUUID()}`;
|
|
598
|
+
}
|
|
599
|
+
return `${prefix}-${Math.random().toString(36).slice(2)}`;
|
|
600
|
+
};
|
|
601
|
+
const isElementVisible = (element) => {
|
|
602
|
+
if (typeof element.checkVisibility === 'function') {
|
|
603
|
+
return element.checkVisibility();
|
|
604
|
+
}
|
|
605
|
+
return element.offsetParent !== null || element.getClientRects().length > 0;
|
|
606
|
+
};
|
|
607
|
+
const getFocusable = (root) => {
|
|
608
|
+
if (!root)
|
|
609
|
+
return [];
|
|
610
|
+
return [...root.querySelectorAll(focusableSelector)]
|
|
611
|
+
.filter(element => !element.hasAttribute('hidden') && isElementVisible(element));
|
|
612
|
+
};
|
|
613
|
+
const getOwnedTarget = (event) => event.target instanceof Node ? event.target : null;
|
|
614
|
+
const getScopedElements = (scope, selector) => {
|
|
615
|
+
const elements = [...scope.querySelectorAll(selector)];
|
|
616
|
+
if (scope instanceof Element && scope.matches(selector)) {
|
|
617
|
+
elements.unshift(scope);
|
|
618
|
+
}
|
|
619
|
+
return elements;
|
|
620
|
+
};
|
|
621
|
+
const getClosestScopedElement = (scope, selector) => scope instanceof Element ? scope.closest(selector) : null;
|
|
622
|
+
const isNativeFormControl = (element) => element instanceof HTMLInputElement ||
|
|
623
|
+
element instanceof HTMLSelectElement ||
|
|
624
|
+
element instanceof HTMLTextAreaElement;
|
|
625
|
+
const getFieldControl = (root) => {
|
|
626
|
+
const controlId = root.dataset.uiFieldControl;
|
|
627
|
+
if (controlId && hasDocument()) {
|
|
628
|
+
const control = document.getElementById(controlId);
|
|
629
|
+
if (control instanceof HTMLElement)
|
|
630
|
+
return control;
|
|
631
|
+
}
|
|
632
|
+
return root.querySelector(fieldControlSelector);
|
|
633
|
+
};
|
|
634
|
+
const getFieldDescriptionId = (element) => {
|
|
635
|
+
if (!element.id) {
|
|
636
|
+
element.id = createId('ui-field-description');
|
|
637
|
+
}
|
|
638
|
+
return element.id;
|
|
639
|
+
};
|
|
640
|
+
const getFieldRoot = (control) => control.closest('.ui-field, [data-ui-field]');
|
|
641
|
+
const getValidationMessage = (control, field) => {
|
|
642
|
+
for (const [validityKey, attributeName] of validityMessageAttributes) {
|
|
643
|
+
if (!control.validity[validityKey])
|
|
644
|
+
continue;
|
|
645
|
+
return control.getAttribute(attributeName) ??
|
|
646
|
+
field?.getAttribute(attributeName) ??
|
|
647
|
+
control.validationMessage;
|
|
648
|
+
}
|
|
649
|
+
return control.validationMessage;
|
|
650
|
+
};
|
|
651
|
+
const setFieldValidity = (control, invalid, message = '') => {
|
|
652
|
+
const field = getFieldRoot(control);
|
|
653
|
+
if (invalid) {
|
|
654
|
+
control.setAttribute('aria-invalid', 'true');
|
|
655
|
+
control.dataset.uiValidationInvalid = 'true';
|
|
656
|
+
}
|
|
657
|
+
else if (control.dataset.uiValidationInvalid === 'true') {
|
|
658
|
+
control.removeAttribute('aria-invalid');
|
|
659
|
+
delete control.dataset.uiValidationInvalid;
|
|
660
|
+
}
|
|
661
|
+
if (!field)
|
|
662
|
+
return;
|
|
663
|
+
field.dataset.invalid = String(invalid);
|
|
664
|
+
for (const error of field.querySelectorAll(fieldErrorSelector)) {
|
|
665
|
+
error.dataset.uiValidationErrorBound = 'true';
|
|
666
|
+
error.hidden = !invalid;
|
|
667
|
+
error.textContent = invalid ? message : '';
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
const validateControl = (control, form) => {
|
|
671
|
+
form.dispatchEvent(new CustomEvent('ui:validate', {
|
|
672
|
+
bubbles: true,
|
|
673
|
+
detail: { control, form, value: control.value }
|
|
674
|
+
}));
|
|
675
|
+
const invalid = !control.validity.valid;
|
|
676
|
+
const message = invalid ? getValidationMessage(control, getFieldRoot(control)) : '';
|
|
677
|
+
setFieldValidity(control, invalid, message);
|
|
678
|
+
return !invalid;
|
|
679
|
+
};
|
|
680
|
+
const getFormControls = (form) => [...form.querySelectorAll(formControlSelector)]
|
|
681
|
+
.filter(control => control.form === form && !control.disabled);
|
|
682
|
+
const validateForm = (form) => getFormControls(form).filter(control => !validateControl(control, form));
|
|
683
|
+
const initLumenFields = (scope) => {
|
|
684
|
+
for (const root of getScopedElements(scope, '.ui-field, [data-ui-field]')) {
|
|
685
|
+
if (root.dataset.uiFieldBound === 'true')
|
|
686
|
+
continue;
|
|
687
|
+
const control = getFieldControl(root);
|
|
688
|
+
if (!control)
|
|
689
|
+
continue;
|
|
690
|
+
root.dataset.uiFieldBound = 'true';
|
|
691
|
+
const existingIds = control.getAttribute('aria-describedby')?.trim().split(/\s+/) ?? [];
|
|
692
|
+
const configuredIds = root.dataset.uiFieldDescribedby?.trim().split(/\s+/) ?? [];
|
|
693
|
+
const descriptionIds = [...root.querySelectorAll(fieldDescriptionSelector)]
|
|
694
|
+
.map(element => getFieldDescriptionId(element));
|
|
695
|
+
const describedBy = [...new Set([...existingIds, ...configuredIds, ...descriptionIds].filter(Boolean))];
|
|
696
|
+
if (describedBy.length) {
|
|
697
|
+
control.setAttribute('aria-describedby', describedBy.join(' '));
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
const initLumenForms = (scope) => {
|
|
702
|
+
for (const form of getScopedElements(scope, 'form[data-ui-form]')) {
|
|
703
|
+
if (form.dataset.uiFormBound === 'true')
|
|
704
|
+
continue;
|
|
705
|
+
form.dataset.uiFormBound = 'true';
|
|
706
|
+
form.noValidate = true;
|
|
707
|
+
form.addEventListener('focusout', event => {
|
|
708
|
+
if (!isNativeFormControl(event.target) || event.target.form !== form)
|
|
709
|
+
return;
|
|
710
|
+
const valid = validateControl(event.target, form);
|
|
711
|
+
form.dispatchEvent(new CustomEvent(valid ? 'ui:valid' : 'ui:invalid', {
|
|
712
|
+
bubbles: true,
|
|
713
|
+
detail: valid
|
|
714
|
+
? { control: event.target, form }
|
|
715
|
+
: { control: event.target, controls: [event.target], form }
|
|
716
|
+
}));
|
|
717
|
+
});
|
|
718
|
+
form.addEventListener('submit', event => {
|
|
719
|
+
const invalidControls = validateForm(form);
|
|
720
|
+
if (invalidControls.length) {
|
|
721
|
+
event.preventDefault();
|
|
722
|
+
const firstInvalid = invalidControls[0];
|
|
723
|
+
if (!firstInvalid)
|
|
724
|
+
return;
|
|
725
|
+
form.dispatchEvent(new CustomEvent('ui:invalid', {
|
|
726
|
+
bubbles: true,
|
|
727
|
+
detail: { control: firstInvalid, controls: invalidControls, form }
|
|
728
|
+
}));
|
|
729
|
+
firstInvalid.focus({ preventScroll: true });
|
|
730
|
+
firstInvalid.reportValidity();
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
form.dispatchEvent(new CustomEvent('ui:valid', {
|
|
734
|
+
bubbles: true,
|
|
735
|
+
detail: { controls: getFormControls(form), form }
|
|
736
|
+
}));
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
export const enhanceLumenForms = (scope = document) => {
|
|
741
|
+
initLumenFields(scope);
|
|
742
|
+
initLumenForms(scope);
|
|
743
|
+
};
|
|
744
|
+
const installFormController = () => {
|
|
745
|
+
if (!hasDocument() || document.documentElement.dataset.uiElementsFormsBound === 'true')
|
|
746
|
+
return;
|
|
747
|
+
document.documentElement.dataset.uiElementsFormsBound = 'true';
|
|
748
|
+
enhanceLumenForms(document);
|
|
749
|
+
if (typeof MutationObserver === 'undefined')
|
|
750
|
+
return;
|
|
751
|
+
const observer = new MutationObserver(mutations => {
|
|
752
|
+
for (const mutation of mutations) {
|
|
753
|
+
for (const node of mutation.addedNodes) {
|
|
754
|
+
if (node instanceof Element || node instanceof DocumentFragment) {
|
|
755
|
+
enhanceLumenForms(node);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
761
|
+
};
|
|
762
|
+
const getRichTextCommandValue = (control) => {
|
|
763
|
+
if (control.dataset.uiEditorValue !== undefined)
|
|
764
|
+
return control.dataset.uiEditorValue;
|
|
765
|
+
if (control instanceof HTMLInputElement
|
|
766
|
+
|| control instanceof HTMLSelectElement
|
|
767
|
+
|| control instanceof HTMLTextAreaElement)
|
|
768
|
+
return control.value;
|
|
769
|
+
return undefined;
|
|
770
|
+
};
|
|
771
|
+
const syncRichTextCommandStates = (root) => {
|
|
772
|
+
const commandDocument = document;
|
|
773
|
+
for (const control of root.querySelectorAll(richTextEditorCommandSelector)) {
|
|
774
|
+
const command = control.dataset.uiEditorCommand ?? '';
|
|
775
|
+
const value = getRichTextCommandValue(control);
|
|
776
|
+
let active = false;
|
|
777
|
+
if (isLumenRichTextToggleCommand(command)) {
|
|
778
|
+
active = commandDocument.queryCommandState?.(command) ?? false;
|
|
779
|
+
control.setAttribute('aria-pressed', String(active));
|
|
780
|
+
}
|
|
781
|
+
else if (command === 'formatBlock' && value) {
|
|
782
|
+
const currentValue = commandDocument.queryCommandValue?.(command)
|
|
783
|
+
.replaceAll(/[<>]/g, '')
|
|
784
|
+
.toLowerCase();
|
|
785
|
+
active = currentValue === value.replaceAll(/[<>]/g, '').toLowerCase();
|
|
786
|
+
}
|
|
787
|
+
control.dataset.state = active ? 'on' : 'off';
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
const dispatchRichTextChange = (root) => {
|
|
791
|
+
const editable = root.querySelector(richTextEditorContentSelector);
|
|
792
|
+
if (!editable)
|
|
793
|
+
return;
|
|
794
|
+
const detail = {
|
|
795
|
+
html: editable.innerHTML,
|
|
796
|
+
text: editable.textContent
|
|
797
|
+
};
|
|
798
|
+
root.dispatchEvent(new CustomEvent('ui:editor-change', {
|
|
799
|
+
bubbles: true,
|
|
800
|
+
detail
|
|
801
|
+
}));
|
|
802
|
+
};
|
|
803
|
+
const executeRichTextCommand = (root, command, value) => {
|
|
804
|
+
if (!command)
|
|
805
|
+
return false;
|
|
806
|
+
const commandDocument = document;
|
|
807
|
+
let executed = false;
|
|
808
|
+
if (typeof commandDocument.execCommand === 'function') {
|
|
809
|
+
executed = value === undefined
|
|
810
|
+
? commandDocument.execCommand(command)
|
|
811
|
+
: commandDocument.execCommand(command, false, value);
|
|
812
|
+
}
|
|
813
|
+
const detail = {
|
|
814
|
+
command,
|
|
815
|
+
executed,
|
|
816
|
+
...(value === undefined ? {} : { value })
|
|
817
|
+
};
|
|
818
|
+
root.dispatchEvent(new CustomEvent('ui:editor-command', {
|
|
819
|
+
bubbles: true,
|
|
820
|
+
detail
|
|
821
|
+
}));
|
|
822
|
+
syncRichTextCommandStates(root);
|
|
823
|
+
dispatchRichTextChange(root);
|
|
824
|
+
return executed;
|
|
825
|
+
};
|
|
826
|
+
const initRichTextEditors = (scope) => {
|
|
827
|
+
const closestRoot = getClosestScopedElement(scope, richTextEditorSelector);
|
|
828
|
+
const roots = getScopedElements(scope, richTextEditorSelector);
|
|
829
|
+
if (closestRoot instanceof HTMLElement && !roots.includes(closestRoot)) {
|
|
830
|
+
roots.unshift(closestRoot);
|
|
831
|
+
}
|
|
832
|
+
for (const root of roots) {
|
|
833
|
+
if (root.dataset.uiEditorBound === 'true')
|
|
834
|
+
continue;
|
|
835
|
+
root.dataset.uiEditorBound = 'true';
|
|
836
|
+
for (const control of root.querySelectorAll(richTextEditorCommandSelector)) {
|
|
837
|
+
control.dataset.uiEditorCommandBound = 'true';
|
|
838
|
+
const execute = () => {
|
|
839
|
+
executeRichTextCommand(root, control.dataset.uiEditorCommand ?? '', getRichTextCommandValue(control));
|
|
840
|
+
};
|
|
841
|
+
if (control instanceof HTMLSelectElement)
|
|
842
|
+
control.addEventListener('change', execute);
|
|
843
|
+
else
|
|
844
|
+
control.addEventListener('click', execute);
|
|
845
|
+
}
|
|
846
|
+
root.addEventListener('keydown', event => {
|
|
847
|
+
if (!(event.target instanceof HTMLElement)
|
|
848
|
+
|| !event.target.closest(richTextEditorContentSelector))
|
|
849
|
+
return;
|
|
850
|
+
const command = getLumenRichTextShortcut(event);
|
|
851
|
+
if (!command)
|
|
852
|
+
return;
|
|
853
|
+
event.preventDefault();
|
|
854
|
+
executeRichTextCommand(root, command);
|
|
855
|
+
});
|
|
856
|
+
root.addEventListener('input', event => {
|
|
857
|
+
if (!(event.target instanceof HTMLElement)
|
|
858
|
+
|| !event.target.closest(richTextEditorContentSelector))
|
|
859
|
+
return;
|
|
860
|
+
syncRichTextCommandStates(root);
|
|
861
|
+
dispatchRichTextChange(root);
|
|
862
|
+
});
|
|
863
|
+
root.addEventListener('keyup', () => { syncRichTextCommandStates(root); });
|
|
864
|
+
root.addEventListener('mouseup', () => { syncRichTextCommandStates(root); });
|
|
865
|
+
syncRichTextCommandStates(root);
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
export const enhanceLumenRichTextEditors = (scope = document) => {
|
|
869
|
+
initRichTextEditors(scope);
|
|
870
|
+
};
|
|
871
|
+
const installRichTextEditorController = () => {
|
|
872
|
+
if (!hasDocument() || document.documentElement.dataset.uiElementsRichTextEditorsBound === 'true')
|
|
873
|
+
return;
|
|
874
|
+
document.documentElement.dataset.uiElementsRichTextEditorsBound = 'true';
|
|
875
|
+
enhanceLumenRichTextEditors(document);
|
|
876
|
+
if (typeof MutationObserver === 'undefined')
|
|
877
|
+
return;
|
|
878
|
+
const observer = new MutationObserver(mutations => {
|
|
879
|
+
for (const mutation of mutations) {
|
|
880
|
+
for (const node of mutation.addedNodes) {
|
|
881
|
+
if (node instanceof Element || node instanceof DocumentFragment) {
|
|
882
|
+
enhanceLumenRichTextEditors(node);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
888
|
+
};
|
|
889
|
+
const getScheduleTransferValue = (event) => event.id || event.textContent.trim();
|
|
890
|
+
const initSchedules = (scope) => {
|
|
891
|
+
const closestRoot = getClosestScopedElement(scope, scheduleSelector);
|
|
892
|
+
const roots = getScopedElements(scope, scheduleSelector);
|
|
893
|
+
if (closestRoot instanceof HTMLElement && !roots.includes(closestRoot)) {
|
|
894
|
+
roots.unshift(closestRoot);
|
|
895
|
+
}
|
|
896
|
+
for (const root of roots) {
|
|
897
|
+
root.dataset.uiScheduleBound = 'true';
|
|
898
|
+
for (const scheduleEvent of root.querySelectorAll(scheduleEventSelector)) {
|
|
899
|
+
if (scheduleEvent.dataset.uiScheduleEventBound === 'true')
|
|
900
|
+
continue;
|
|
901
|
+
scheduleEvent.dataset.uiScheduleEventBound = 'true';
|
|
902
|
+
scheduleEvent.draggable = scheduleEvent.draggable || scheduleEvent.dataset.uiDraggable === 'true';
|
|
903
|
+
scheduleEvent.addEventListener('dragstart', event => {
|
|
904
|
+
event.dataTransfer?.setData('text/plain', getScheduleTransferValue(scheduleEvent));
|
|
905
|
+
root.dataset.uiDragging = 'true';
|
|
906
|
+
});
|
|
907
|
+
scheduleEvent.addEventListener('dragend', () => {
|
|
908
|
+
delete root.dataset.uiDragging;
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
for (const slot of root.querySelectorAll(scheduleSlotSelector)) {
|
|
912
|
+
if (slot.dataset.uiScheduleSlotBound === 'true')
|
|
913
|
+
continue;
|
|
914
|
+
slot.dataset.uiScheduleSlotBound = 'true';
|
|
915
|
+
slot.addEventListener('dragover', event => {
|
|
916
|
+
event.preventDefault();
|
|
917
|
+
slot.dataset.state = 'drag-over';
|
|
918
|
+
});
|
|
919
|
+
slot.addEventListener('dragleave', () => {
|
|
920
|
+
delete slot.dataset.state;
|
|
921
|
+
});
|
|
922
|
+
slot.addEventListener('drop', event => {
|
|
923
|
+
event.preventDefault();
|
|
924
|
+
delete slot.dataset.state;
|
|
925
|
+
const draggedId = event.dataTransfer?.getData('text/plain');
|
|
926
|
+
const dragged = draggedId ? document.getElementById(draggedId) : null;
|
|
927
|
+
if (dragged instanceof HTMLElement) {
|
|
928
|
+
slot.append(dragged);
|
|
929
|
+
}
|
|
930
|
+
root.dispatchEvent(new CustomEvent('ui:schedule-change', {
|
|
931
|
+
bubbles: true,
|
|
932
|
+
detail: {
|
|
933
|
+
...(draggedId ? { eventId: draggedId } : {}),
|
|
934
|
+
...(slot.dataset.uiScheduleSlot ? { slot: slot.dataset.uiScheduleSlot } : {})
|
|
935
|
+
}
|
|
936
|
+
}));
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
export const enhanceLumenSchedules = (scope = document) => {
|
|
942
|
+
initSchedules(scope);
|
|
943
|
+
};
|
|
944
|
+
const installScheduleController = () => {
|
|
945
|
+
if (!hasDocument() || document.documentElement.dataset.uiElementsSchedulesBound === 'true')
|
|
946
|
+
return;
|
|
947
|
+
document.documentElement.dataset.uiElementsSchedulesBound = 'true';
|
|
948
|
+
enhanceLumenSchedules(document);
|
|
949
|
+
if (typeof MutationObserver === 'undefined')
|
|
950
|
+
return;
|
|
951
|
+
const observer = new MutationObserver(mutations => {
|
|
952
|
+
for (const mutation of mutations) {
|
|
953
|
+
for (const node of mutation.addedNodes) {
|
|
954
|
+
if (node instanceof Element || node instanceof DocumentFragment) {
|
|
955
|
+
enhanceLumenSchedules(node);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
961
|
+
};
|
|
962
|
+
/* cspell:ignore valuenow */
|
|
963
|
+
/* eslint-disable @stylistic/padding-line-between-statements -- Resizable mirrors Astro's compact pane sizing runtime. */
|
|
964
|
+
const parseResizableNumberList = (value, count, fallback) => {
|
|
965
|
+
const values = (value ?? '')
|
|
966
|
+
.split(',')
|
|
967
|
+
.map(item => Number.parseFloat(item.trim()))
|
|
968
|
+
.filter(Number.isFinite);
|
|
969
|
+
return Array.from({ length: count }, (_, index) => values[index] ?? values[0] ?? fallback);
|
|
970
|
+
};
|
|
971
|
+
const normalizeResizableSizes = (sizes, count) => {
|
|
972
|
+
const fallbackSize = 100 / Math.max(1, count);
|
|
973
|
+
const usableSizes = Array.from({ length: count }, (_, index) => {
|
|
974
|
+
const size = sizes[index] ?? fallbackSize;
|
|
975
|
+
return Number.isFinite(size) && size > 0 ? size : fallbackSize;
|
|
976
|
+
});
|
|
977
|
+
const total = usableSizes.reduce((sum, size) => sum + size, 0);
|
|
978
|
+
if (total <= 0)
|
|
979
|
+
return Array.from({ length: count }, () => fallbackSize);
|
|
980
|
+
return usableSizes.map(size => (size / total) * 100);
|
|
981
|
+
};
|
|
982
|
+
const getResizablePanes = (root) => [...root.children].filter((child) => child instanceof HTMLElement &&
|
|
983
|
+
!child.hasAttribute('data-ui-resizable-handle') &&
|
|
984
|
+
!child.hasAttribute('data-ui-resizable-handle-template'));
|
|
985
|
+
const applyResizableSizes = (root, panes, handles, sizes, minSizes, maxSizes) => {
|
|
986
|
+
for (const [index, pane] of panes.entries()) {
|
|
987
|
+
pane.dataset.uiResizablePanel = '';
|
|
988
|
+
pane.style.setProperty('--ui-resizable-size', `${sizes[index] ?? 0}%`);
|
|
989
|
+
}
|
|
990
|
+
for (const handle of handles) {
|
|
991
|
+
const index = Number.parseInt(handle.dataset.index ?? '0', 10);
|
|
992
|
+
handle.setAttribute('aria-valuemin', String(Math.round(minSizes[index] ?? 0)));
|
|
993
|
+
handle.setAttribute('aria-valuemax', String(Math.round(maxSizes[index] ?? 100)));
|
|
994
|
+
handle.setAttribute('aria-valuenow', String(Math.round(sizes[index] ?? 0)));
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
const resizeResizablePair = (sizes, minSizes, maxSizes, index, nextSize) => {
|
|
998
|
+
const nextIndex = index + 1;
|
|
999
|
+
const total = (sizes[index] ?? 0) + (sizes[nextIndex] ?? 0);
|
|
1000
|
+
const min = Math.max(minSizes[index] ?? 0, total - (maxSizes[nextIndex] ?? 100));
|
|
1001
|
+
const max = Math.min(maxSizes[index] ?? 100, total - (minSizes[nextIndex] ?? 0));
|
|
1002
|
+
const paneSize = Math.min(max, Math.max(min, nextSize));
|
|
1003
|
+
sizes[index] = paneSize;
|
|
1004
|
+
sizes[nextIndex] = total - paneSize;
|
|
1005
|
+
};
|
|
1006
|
+
const createResizableHandle = (root, index, separatorOrientation) => {
|
|
1007
|
+
const template = root.querySelector(`:scope > ${resizableHandleTemplateSelector}`);
|
|
1008
|
+
const handleFromTemplate = template?.content
|
|
1009
|
+
.querySelector(resizableHandleSelector)
|
|
1010
|
+
?.cloneNode(true);
|
|
1011
|
+
const handle = handleFromTemplate instanceof HTMLButtonElement
|
|
1012
|
+
? handleFromTemplate
|
|
1013
|
+
: document.createElement('button');
|
|
1014
|
+
handle.type = 'button';
|
|
1015
|
+
handle.className = 'ui-resizable__handle';
|
|
1016
|
+
handle.dataset.uiResizableHandle = '';
|
|
1017
|
+
handle.dataset.index = String(index);
|
|
1018
|
+
handle.tabIndex = 0;
|
|
1019
|
+
handle.setAttribute('aria-label', `Resize panel ${index + 1}`);
|
|
1020
|
+
handle.setAttribute('role', 'separator');
|
|
1021
|
+
handle.setAttribute('aria-orientation', separatorOrientation);
|
|
1022
|
+
return handle;
|
|
1023
|
+
};
|
|
1024
|
+
const initResizableGroups = (scope) => {
|
|
1025
|
+
const closestRoot = getClosestScopedElement(scope, resizableSelector);
|
|
1026
|
+
const roots = getScopedElements(scope, resizableSelector);
|
|
1027
|
+
if (closestRoot instanceof HTMLElement && !roots.includes(closestRoot)) {
|
|
1028
|
+
roots.unshift(closestRoot);
|
|
1029
|
+
}
|
|
1030
|
+
for (const root of roots) {
|
|
1031
|
+
if (root.dataset.uiBound === 'true')
|
|
1032
|
+
continue;
|
|
1033
|
+
if (root.getAttribute('direction') === 'vertical') {
|
|
1034
|
+
root.dataset.orientation = 'vertical';
|
|
1035
|
+
}
|
|
1036
|
+
const panes = getResizablePanes(root);
|
|
1037
|
+
if (panes.length < 2)
|
|
1038
|
+
continue;
|
|
1039
|
+
root.dataset.uiBound = 'true';
|
|
1040
|
+
root.dataset.uiResizableEnhanced = 'true';
|
|
1041
|
+
root.classList.toggle('ui-resizable--vertical', root.dataset.orientation === 'vertical');
|
|
1042
|
+
const direction = root.dataset.orientation === 'vertical' ? 'vertical' : 'horizontal';
|
|
1043
|
+
const separatorOrientation = direction === 'horizontal' ? 'vertical' : 'horizontal';
|
|
1044
|
+
const axis = direction === 'horizontal' ? 'clientX' : 'clientY';
|
|
1045
|
+
const sizeProperty = direction === 'horizontal' ? 'width' : 'height';
|
|
1046
|
+
const minSizes = parseResizableNumberList(root.dataset.uiResizableMinSize, panes.length, 12);
|
|
1047
|
+
const maxSizes = parseResizableNumberList(root.dataset.uiResizableMaxSize, panes.length, 88);
|
|
1048
|
+
const initialSizes = normalizeResizableSizes(parseResizableNumberList(root.dataset.uiResizableDefaultSizes, panes.length, 100 / panes.length), panes.length);
|
|
1049
|
+
let sizes = [...initialSizes];
|
|
1050
|
+
const handles = [];
|
|
1051
|
+
for (const [index, pane] of panes.entries()) {
|
|
1052
|
+
pane.dataset.uiResizablePanel = '';
|
|
1053
|
+
if (index >= panes.length - 1)
|
|
1054
|
+
continue;
|
|
1055
|
+
const existingHandle = pane.nextElementSibling instanceof HTMLElement &&
|
|
1056
|
+
pane.nextElementSibling.matches(resizableHandleSelector)
|
|
1057
|
+
? pane.nextElementSibling
|
|
1058
|
+
: null;
|
|
1059
|
+
const handle = existingHandle ?? createResizableHandle(root, index, separatorOrientation);
|
|
1060
|
+
if (!existingHandle) {
|
|
1061
|
+
pane.after(handle);
|
|
1062
|
+
}
|
|
1063
|
+
handles.push(handle);
|
|
1064
|
+
let startPosition = 0;
|
|
1065
|
+
let startSize = 0;
|
|
1066
|
+
let containerSize = 1;
|
|
1067
|
+
const applySizes = () => {
|
|
1068
|
+
applyResizableSizes(root, panes, handles, sizes, minSizes, maxSizes);
|
|
1069
|
+
};
|
|
1070
|
+
const resizePair = (nextSize) => {
|
|
1071
|
+
resizeResizablePair(sizes, minSizes, maxSizes, index, nextSize);
|
|
1072
|
+
applySizes();
|
|
1073
|
+
};
|
|
1074
|
+
handle.addEventListener('pointerdown', event => {
|
|
1075
|
+
if (event.button !== 0)
|
|
1076
|
+
return;
|
|
1077
|
+
event.preventDefault();
|
|
1078
|
+
startPosition = event[axis];
|
|
1079
|
+
startSize = sizes[index] ?? 0;
|
|
1080
|
+
containerSize = Math.max(1, root.getBoundingClientRect()[sizeProperty]);
|
|
1081
|
+
root.dataset.resizing = 'true';
|
|
1082
|
+
handle.dataset.active = 'true';
|
|
1083
|
+
handle.setPointerCapture(event.pointerId);
|
|
1084
|
+
});
|
|
1085
|
+
handle.addEventListener('pointermove', event => {
|
|
1086
|
+
if (handle.dataset.active !== 'true')
|
|
1087
|
+
return;
|
|
1088
|
+
const delta = ((event[axis] - startPosition) / containerSize) * 100;
|
|
1089
|
+
resizePair(startSize + delta);
|
|
1090
|
+
});
|
|
1091
|
+
handle.addEventListener('pointerup', event => {
|
|
1092
|
+
if (handle.dataset.active !== 'true')
|
|
1093
|
+
return;
|
|
1094
|
+
delete handle.dataset.active;
|
|
1095
|
+
delete root.dataset.resizing;
|
|
1096
|
+
handle.releasePointerCapture(event.pointerId);
|
|
1097
|
+
});
|
|
1098
|
+
handle.addEventListener('dblclick', () => {
|
|
1099
|
+
if (root.dataset.uiResizableReset !== 'true')
|
|
1100
|
+
return;
|
|
1101
|
+
sizes = [...initialSizes];
|
|
1102
|
+
applySizes();
|
|
1103
|
+
});
|
|
1104
|
+
handle.addEventListener('keydown', event => {
|
|
1105
|
+
const step = event.shiftKey ? 10 : 2;
|
|
1106
|
+
const keyDeltas = direction === 'horizontal'
|
|
1107
|
+
? { ArrowLeft: -step, ArrowRight: step }
|
|
1108
|
+
: { ArrowDown: step, ArrowUp: -step };
|
|
1109
|
+
if (event.key === 'Home') {
|
|
1110
|
+
event.preventDefault();
|
|
1111
|
+
resizePair(minSizes[index] ?? 0);
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
if (event.key === 'End') {
|
|
1115
|
+
event.preventDefault();
|
|
1116
|
+
resizePair(maxSizes[index] ?? 100);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
const delta = keyDeltas[event.key];
|
|
1120
|
+
if (delta === undefined)
|
|
1121
|
+
return;
|
|
1122
|
+
event.preventDefault();
|
|
1123
|
+
resizePair((sizes[index] ?? 0) + delta);
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
applyResizableSizes(root, panes, handles, sizes, minSizes, maxSizes);
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
export const enhanceLumenResizable = (scope = document) => {
|
|
1130
|
+
initResizableGroups(scope);
|
|
1131
|
+
};
|
|
1132
|
+
const installResizableController = () => {
|
|
1133
|
+
if (!hasDocument() || document.documentElement.dataset.uiElementsResizableBound === 'true')
|
|
1134
|
+
return;
|
|
1135
|
+
document.documentElement.dataset.uiElementsResizableBound = 'true';
|
|
1136
|
+
enhanceLumenResizable(document);
|
|
1137
|
+
if (typeof MutationObserver === 'undefined')
|
|
1138
|
+
return;
|
|
1139
|
+
const observer = new MutationObserver(mutations => {
|
|
1140
|
+
for (const mutation of mutations) {
|
|
1141
|
+
for (const node of mutation.addedNodes) {
|
|
1142
|
+
if (node instanceof Element || node instanceof DocumentFragment) {
|
|
1143
|
+
enhanceLumenResizable(node);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
1149
|
+
};
|
|
1150
|
+
/* eslint-enable @stylistic/padding-line-between-statements */
|
|
1151
|
+
const getLoopedIndex = (key, currentIndex, itemCount, forwardKeys) => {
|
|
1152
|
+
const lastIndex = itemCount - 1;
|
|
1153
|
+
if (key === 'Home')
|
|
1154
|
+
return 0;
|
|
1155
|
+
if (key === 'End')
|
|
1156
|
+
return lastIndex;
|
|
1157
|
+
if (forwardKeys.includes(key))
|
|
1158
|
+
return (currentIndex + 1) % itemCount;
|
|
1159
|
+
return (currentIndex - 1 + itemCount) % itemCount;
|
|
1160
|
+
};
|
|
1161
|
+
const syncDateRangePicker = (root) => {
|
|
1162
|
+
const inputs = [...root.querySelectorAll(dateRangePickerInputSelector)];
|
|
1163
|
+
const start = inputs[0];
|
|
1164
|
+
const end = inputs.at(-1);
|
|
1165
|
+
if (!start || !end || start === end)
|
|
1166
|
+
return;
|
|
1167
|
+
const datePickers = root.querySelectorAll(datePickerSelector);
|
|
1168
|
+
datePickers[0]?.setAttribute('data-range-part', 'start');
|
|
1169
|
+
datePickers[datePickers.length - 1]?.setAttribute('data-range-part', 'end');
|
|
1170
|
+
if (start.value) {
|
|
1171
|
+
end.min = start.value;
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
end.removeAttribute('min');
|
|
1175
|
+
}
|
|
1176
|
+
if (end.value) {
|
|
1177
|
+
start.max = end.value;
|
|
1178
|
+
}
|
|
1179
|
+
else {
|
|
1180
|
+
start.removeAttribute('max');
|
|
1181
|
+
}
|
|
1182
|
+
if (start.value && end.value && end.value < start.value) {
|
|
1183
|
+
end.value = start.value;
|
|
1184
|
+
end.dispatchEvent(new Event('change', { bubbles: true }));
|
|
1185
|
+
}
|
|
1186
|
+
let rangeState = 'empty';
|
|
1187
|
+
if (start.value && end.value) {
|
|
1188
|
+
rangeState = 'complete';
|
|
1189
|
+
}
|
|
1190
|
+
else if (start.value) {
|
|
1191
|
+
rangeState = 'selecting-end';
|
|
1192
|
+
}
|
|
1193
|
+
root.dataset.rangeState = rangeState;
|
|
1194
|
+
};
|
|
1195
|
+
const initDateRangePickers = (scope) => {
|
|
1196
|
+
const closestRoot = getClosestScopedElement(scope, dateRangePickerSelector);
|
|
1197
|
+
const roots = getScopedElements(scope, dateRangePickerSelector);
|
|
1198
|
+
if (closestRoot instanceof HTMLElement && !roots.includes(closestRoot)) {
|
|
1199
|
+
roots.unshift(closestRoot);
|
|
1200
|
+
}
|
|
1201
|
+
for (const root of roots) {
|
|
1202
|
+
for (const input of root.querySelectorAll(dateRangePickerInputSelector)) {
|
|
1203
|
+
if (input.dataset.uiDateRangeInputBound === 'true')
|
|
1204
|
+
continue;
|
|
1205
|
+
input.dataset.uiDateRangeInputBound = 'true';
|
|
1206
|
+
input.addEventListener('change', () => {
|
|
1207
|
+
syncDateRangePicker(root);
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
syncDateRangePicker(root);
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
/* eslint-disable unicorn/consistent-function-scoping -- DatePicker callbacks intentionally close over each picker instance. */
|
|
1214
|
+
const initDatePickers = (scope) => {
|
|
1215
|
+
const closestRoot = getClosestScopedElement(scope, datePickerSelector);
|
|
1216
|
+
const roots = getScopedElements(scope, datePickerSelector);
|
|
1217
|
+
if (closestRoot instanceof HTMLElement && !roots.includes(closestRoot)) {
|
|
1218
|
+
roots.unshift(closestRoot);
|
|
1219
|
+
}
|
|
1220
|
+
for (const root of roots) {
|
|
1221
|
+
if (root.dataset.uiBound === 'true')
|
|
1222
|
+
continue;
|
|
1223
|
+
const native = root.querySelector(datePickerNativeSelector);
|
|
1224
|
+
const control = root.querySelector(datePickerControlSelector);
|
|
1225
|
+
const trigger = root.querySelector(datePickerTriggerSelector);
|
|
1226
|
+
const valueEl = root.querySelector(datePickerValueSelector);
|
|
1227
|
+
const popover = root.querySelector(datePickerPopoverSelector);
|
|
1228
|
+
const calendar = root.querySelector(calendarSelector);
|
|
1229
|
+
if (!native || !control || !trigger || !popover)
|
|
1230
|
+
continue;
|
|
1231
|
+
root.dataset.uiBound = 'true';
|
|
1232
|
+
native.dataset.uiEnhanced = 'true';
|
|
1233
|
+
control.hidden = false;
|
|
1234
|
+
const closePopover = () => {
|
|
1235
|
+
popover.hidden = true;
|
|
1236
|
+
popover.dataset.state = 'closed';
|
|
1237
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
1238
|
+
// eslint-disable-next-line no-use-before-define -- the paired callbacks reference each other.
|
|
1239
|
+
document.removeEventListener('click', handleOutsideClick);
|
|
1240
|
+
};
|
|
1241
|
+
const handleOutsideClick = (event) => {
|
|
1242
|
+
if (!(event.target instanceof Node))
|
|
1243
|
+
return;
|
|
1244
|
+
if (!popover.contains(event.target) && !trigger.contains(event.target)) {
|
|
1245
|
+
closePopover();
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
const openPopover = () => {
|
|
1249
|
+
if (calendar) {
|
|
1250
|
+
if (native.min)
|
|
1251
|
+
calendar.dataset.uiCalendarMin = native.min;
|
|
1252
|
+
else
|
|
1253
|
+
delete calendar.dataset.uiCalendarMin;
|
|
1254
|
+
if (native.max)
|
|
1255
|
+
calendar.dataset.uiCalendarMax = native.max;
|
|
1256
|
+
else
|
|
1257
|
+
delete calendar.dataset.uiCalendarMax;
|
|
1258
|
+
}
|
|
1259
|
+
popover.hidden = false;
|
|
1260
|
+
popover.dataset.state = 'open';
|
|
1261
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
1262
|
+
globalThis.setTimeout(() => {
|
|
1263
|
+
document.addEventListener('click', handleOutsideClick);
|
|
1264
|
+
});
|
|
1265
|
+
};
|
|
1266
|
+
trigger.addEventListener('click', () => {
|
|
1267
|
+
if (popover.hidden) {
|
|
1268
|
+
openPopover();
|
|
1269
|
+
}
|
|
1270
|
+
else {
|
|
1271
|
+
closePopover();
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
popover.addEventListener('change', event => {
|
|
1275
|
+
const target = event.target;
|
|
1276
|
+
if (target instanceof HTMLInputElement && target.hasAttribute('data-ui-calendar-input')) {
|
|
1277
|
+
const newDate = target.value;
|
|
1278
|
+
native.value = newDate;
|
|
1279
|
+
if (valueEl)
|
|
1280
|
+
valueEl.textContent = newDate || 'mm/dd/yyyy';
|
|
1281
|
+
native.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
|
|
1282
|
+
closePopover();
|
|
1283
|
+
}
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
/* eslint-enable unicorn/consistent-function-scoping */
|
|
1288
|
+
export const enhanceLumenDateRangePickers = (scope = document) => {
|
|
1289
|
+
initDateRangePickers(scope);
|
|
1290
|
+
};
|
|
1291
|
+
const installDateRangePickerController = () => {
|
|
1292
|
+
if (!hasDocument() || document.documentElement.dataset.uiElementsDateRangePickersBound === 'true')
|
|
1293
|
+
return;
|
|
1294
|
+
document.documentElement.dataset.uiElementsDateRangePickersBound = 'true';
|
|
1295
|
+
enhanceLumenDateRangePickers(document);
|
|
1296
|
+
if (typeof MutationObserver === 'undefined')
|
|
1297
|
+
return;
|
|
1298
|
+
const observer = new MutationObserver(mutations => {
|
|
1299
|
+
for (const mutation of mutations) {
|
|
1300
|
+
for (const node of mutation.addedNodes) {
|
|
1301
|
+
if (node instanceof Element || node instanceof DocumentFragment) {
|
|
1302
|
+
enhanceLumenDateRangePickers(node);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
1308
|
+
};
|
|
1309
|
+
export const enhanceLumenDatePickers = (scope = document) => {
|
|
1310
|
+
initDatePickers(scope);
|
|
1311
|
+
};
|
|
1312
|
+
const installDatePickerController = () => {
|
|
1313
|
+
if (!hasDocument() || document.documentElement.dataset.uiElementsDatePickersBound === 'true')
|
|
1314
|
+
return;
|
|
1315
|
+
document.documentElement.dataset.uiElementsDatePickersBound = 'true';
|
|
1316
|
+
enhanceLumenDatePickers(document);
|
|
1317
|
+
if (typeof MutationObserver === 'undefined')
|
|
1318
|
+
return;
|
|
1319
|
+
const observer = new MutationObserver(mutations => {
|
|
1320
|
+
for (const mutation of mutations) {
|
|
1321
|
+
for (const node of mutation.addedNodes) {
|
|
1322
|
+
if (node instanceof Element || node instanceof DocumentFragment) {
|
|
1323
|
+
enhanceLumenDatePickers(node);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
1329
|
+
};
|
|
1330
|
+
/* eslint-disable @stylistic/padding-line-between-statements, unicorn/consistent-function-scoping -- InputOTP mirrors Astro's compact DOM synchronization runtime. */
|
|
1331
|
+
const defaultInputOtpLength = 6;
|
|
1332
|
+
const defaultInputOtpPattern = '[0-9]*';
|
|
1333
|
+
const normalizeInputOtpLength = (value) => Math.max(1, Number.parseInt(value ?? '', 10) || defaultInputOtpLength);
|
|
1334
|
+
const getInputOtpLength = (root) => normalizeInputOtpLength(root.getAttribute('length') ?? root.getAttribute('maxlength') ?? root.dataset.uiInputOtpLength);
|
|
1335
|
+
const sanitizeInputOtpValue = (input, value, length) => {
|
|
1336
|
+
const numericOnly = input.inputMode === 'numeric' || input.getAttribute('pattern') === defaultInputOtpPattern;
|
|
1337
|
+
const normalized = numericOnly ? value.replaceAll(/\D/g, '') : value.replaceAll(/\s/g, '');
|
|
1338
|
+
return normalized.slice(0, length);
|
|
1339
|
+
};
|
|
1340
|
+
const createInputOtpSegment = (index) => {
|
|
1341
|
+
const segment = document.createElement('button');
|
|
1342
|
+
const char = document.createElement('span');
|
|
1343
|
+
segment.ariaHidden = 'true';
|
|
1344
|
+
segment.className = 'ui-input-otp__segment';
|
|
1345
|
+
segment.dataset.index = String(index);
|
|
1346
|
+
segment.dataset.uiInputOtpSegment = '';
|
|
1347
|
+
segment.tabIndex = -1;
|
|
1348
|
+
segment.type = 'button';
|
|
1349
|
+
char.dataset.uiInputOtpChar = '';
|
|
1350
|
+
char.textContent = '\u00a0';
|
|
1351
|
+
segment.append(char);
|
|
1352
|
+
return segment;
|
|
1353
|
+
};
|
|
1354
|
+
const copyInputOtpAttribute = (root, input, name) => {
|
|
1355
|
+
if (!root.hasAttribute(name) || input.hasAttribute(name))
|
|
1356
|
+
return;
|
|
1357
|
+
input.setAttribute(name, root.getAttribute(name) ?? '');
|
|
1358
|
+
};
|
|
1359
|
+
const ensureInputOtpNativeInput = (root, length) => {
|
|
1360
|
+
let input = root.querySelector(inputOtpNativeSelector);
|
|
1361
|
+
const created = !input;
|
|
1362
|
+
if (!input) {
|
|
1363
|
+
input = document.createElement('input');
|
|
1364
|
+
root.prepend(input);
|
|
1365
|
+
}
|
|
1366
|
+
input.classList.add('ui-input-otp', 'ui-input-otp__native');
|
|
1367
|
+
input.dataset.uiInputOtpNative = '';
|
|
1368
|
+
if (!input.hasAttribute('autocomplete')) {
|
|
1369
|
+
input.setAttribute('autocomplete', root.getAttribute('autocomplete') ?? 'one-time-code');
|
|
1370
|
+
}
|
|
1371
|
+
if (!input.hasAttribute('inputmode')) {
|
|
1372
|
+
input.setAttribute('inputmode', root.getAttribute('inputmode') ?? 'numeric');
|
|
1373
|
+
}
|
|
1374
|
+
if (!input.hasAttribute('maxlength')) {
|
|
1375
|
+
input.setAttribute('maxlength', root.getAttribute('maxlength') ?? root.getAttribute('length') ?? String(length));
|
|
1376
|
+
}
|
|
1377
|
+
if (!input.hasAttribute('pattern')) {
|
|
1378
|
+
input.setAttribute('pattern', root.getAttribute('pattern') ?? defaultInputOtpPattern);
|
|
1379
|
+
}
|
|
1380
|
+
if (!input.hasAttribute('type')) {
|
|
1381
|
+
input.setAttribute('type', 'text');
|
|
1382
|
+
}
|
|
1383
|
+
for (const name of ['aria-describedby', 'aria-label', 'name', 'placeholder']) {
|
|
1384
|
+
copyInputOtpAttribute(root, input, name);
|
|
1385
|
+
}
|
|
1386
|
+
input.disabled = root.hasAttribute('disabled') || input.disabled;
|
|
1387
|
+
input.required = root.hasAttribute('required') || input.required;
|
|
1388
|
+
if (root.hasAttribute('aria-invalid') && !input.hasAttribute('aria-invalid')) {
|
|
1389
|
+
input.setAttribute('aria-invalid', root.getAttribute('aria-invalid') ?? 'true');
|
|
1390
|
+
}
|
|
1391
|
+
if (created && root.hasAttribute('value')) {
|
|
1392
|
+
input.value = root.getAttribute('value') ?? '';
|
|
1393
|
+
}
|
|
1394
|
+
return input;
|
|
1395
|
+
};
|
|
1396
|
+
const ensureInputOtpSegmentsRoot = (root) => {
|
|
1397
|
+
let segmentsRoot = root.querySelector(inputOtpSegmentsSelector);
|
|
1398
|
+
if (!segmentsRoot) {
|
|
1399
|
+
segmentsRoot = document.createElement('div');
|
|
1400
|
+
root.append(segmentsRoot);
|
|
1401
|
+
}
|
|
1402
|
+
segmentsRoot.classList.add('ui-input-otp__segments');
|
|
1403
|
+
segmentsRoot.dataset.uiInputOtpSegments = '';
|
|
1404
|
+
return segmentsRoot;
|
|
1405
|
+
};
|
|
1406
|
+
const ensureInputOtpSegments = (segmentsRoot, length) => {
|
|
1407
|
+
const segments = [...segmentsRoot.querySelectorAll(inputOtpSegmentSelector)];
|
|
1408
|
+
for (let index = segments.length; index < length; index += 1) {
|
|
1409
|
+
const segment = createInputOtpSegment(index);
|
|
1410
|
+
segmentsRoot.append(segment);
|
|
1411
|
+
segments.push(segment);
|
|
1412
|
+
}
|
|
1413
|
+
for (const segment of segments.splice(length)) {
|
|
1414
|
+
segment.remove();
|
|
1415
|
+
}
|
|
1416
|
+
for (const [index, segment] of segments.entries()) {
|
|
1417
|
+
segment.classList.add('ui-input-otp__segment');
|
|
1418
|
+
segment.dataset.index = String(index);
|
|
1419
|
+
segment.dataset.uiInputOtpSegment = '';
|
|
1420
|
+
segment.setAttribute('aria-hidden', 'true');
|
|
1421
|
+
segment.tabIndex = -1;
|
|
1422
|
+
segment.type = 'button';
|
|
1423
|
+
if (!segment.querySelector('[data-ui-input-otp-char]')) {
|
|
1424
|
+
const char = document.createElement('span');
|
|
1425
|
+
char.dataset.uiInputOtpChar = '';
|
|
1426
|
+
segment.append(char);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
return segments;
|
|
1430
|
+
};
|
|
1431
|
+
const syncInputOtpSegments = (root, input, segments, length) => {
|
|
1432
|
+
const value = sanitizeInputOtpValue(input, input.value, length);
|
|
1433
|
+
if (input.value !== value) {
|
|
1434
|
+
input.value = value;
|
|
1435
|
+
}
|
|
1436
|
+
const activeIndex = Math.min(length - 1, Math.max(0, input.selectionStart ?? value.length));
|
|
1437
|
+
root.dataset.disabled = input.disabled ? 'true' : 'false';
|
|
1438
|
+
root.dataset.invalid = input.getAttribute('aria-invalid') === 'true' ? 'true' : 'false';
|
|
1439
|
+
for (const [index, segment] of segments.entries()) {
|
|
1440
|
+
const char = segment.querySelector('[data-ui-input-otp-char]');
|
|
1441
|
+
segment.disabled = input.disabled;
|
|
1442
|
+
segment.dataset.active = String(document.activeElement === input && index === activeIndex);
|
|
1443
|
+
if (char) {
|
|
1444
|
+
char.textContent = value[index] ?? '\u00a0';
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
const initInputOtpFields = (scope) => {
|
|
1449
|
+
const closestRoot = getClosestScopedElement(scope, inputOtpSelector);
|
|
1450
|
+
const roots = getScopedElements(scope, inputOtpSelector);
|
|
1451
|
+
if (closestRoot instanceof HTMLElement && !roots.includes(closestRoot)) {
|
|
1452
|
+
roots.unshift(closestRoot);
|
|
1453
|
+
}
|
|
1454
|
+
for (const root of roots) {
|
|
1455
|
+
if (root.dataset.uiBound === 'true')
|
|
1456
|
+
continue;
|
|
1457
|
+
const length = getInputOtpLength(root);
|
|
1458
|
+
const input = ensureInputOtpNativeInput(root, length);
|
|
1459
|
+
const segmentsRoot = ensureInputOtpSegmentsRoot(root);
|
|
1460
|
+
const segments = ensureInputOtpSegments(segmentsRoot, length);
|
|
1461
|
+
root.dataset.uiBound = 'true';
|
|
1462
|
+
root.dataset.uiInputOtpLength = String(length);
|
|
1463
|
+
input.dataset.uiEnhanced = 'true';
|
|
1464
|
+
segmentsRoot.hidden = false;
|
|
1465
|
+
const syncSegments = () => {
|
|
1466
|
+
syncInputOtpSegments(root, input, segments, length);
|
|
1467
|
+
};
|
|
1468
|
+
const setSelection = (index) => {
|
|
1469
|
+
const position = Math.min(input.value.length, Math.max(0, index));
|
|
1470
|
+
input.focus({ preventScroll: true });
|
|
1471
|
+
input.setSelectionRange(position, position);
|
|
1472
|
+
syncSegments();
|
|
1473
|
+
};
|
|
1474
|
+
const setValue = (value) => {
|
|
1475
|
+
input.value = sanitizeInputOtpValue(input, value, length);
|
|
1476
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
1477
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1478
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
1479
|
+
syncSegments();
|
|
1480
|
+
};
|
|
1481
|
+
syncSegments();
|
|
1482
|
+
input.addEventListener('input', syncSegments);
|
|
1483
|
+
input.addEventListener('change', syncSegments);
|
|
1484
|
+
input.addEventListener('focus', syncSegments);
|
|
1485
|
+
input.addEventListener('blur', syncSegments);
|
|
1486
|
+
input.addEventListener('click', syncSegments);
|
|
1487
|
+
input.addEventListener('keyup', syncSegments);
|
|
1488
|
+
input.addEventListener('keydown', event => {
|
|
1489
|
+
if (event.key === 'ArrowLeft') {
|
|
1490
|
+
event.preventDefault();
|
|
1491
|
+
setSelection((input.selectionStart ?? input.value.length) - 1);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
if (event.key === 'ArrowRight') {
|
|
1495
|
+
event.preventDefault();
|
|
1496
|
+
setSelection((input.selectionStart ?? input.value.length) + 1);
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
if (event.key === 'Home') {
|
|
1500
|
+
event.preventDefault();
|
|
1501
|
+
setSelection(0);
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
if (event.key === 'End') {
|
|
1505
|
+
event.preventDefault();
|
|
1506
|
+
setSelection(input.value.length);
|
|
1507
|
+
}
|
|
1508
|
+
});
|
|
1509
|
+
input.addEventListener('paste', event => {
|
|
1510
|
+
const pasted = event.clipboardData?.getData('text') ?? '';
|
|
1511
|
+
if (!pasted)
|
|
1512
|
+
return;
|
|
1513
|
+
event.preventDefault();
|
|
1514
|
+
setValue(pasted);
|
|
1515
|
+
});
|
|
1516
|
+
input.form?.addEventListener('reset', () => {
|
|
1517
|
+
globalThis.setTimeout(syncSegments);
|
|
1518
|
+
});
|
|
1519
|
+
for (const [index, segment] of segments.entries()) {
|
|
1520
|
+
segment.addEventListener('click', () => {
|
|
1521
|
+
setSelection(index);
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
export const enhanceLumenInputOTPs = (scope = document) => {
|
|
1527
|
+
initInputOtpFields(scope);
|
|
1528
|
+
};
|
|
1529
|
+
const installInputOtpController = () => {
|
|
1530
|
+
if (!hasDocument() || document.documentElement.dataset.uiElementsInputOtpBound === 'true')
|
|
1531
|
+
return;
|
|
1532
|
+
document.documentElement.dataset.uiElementsInputOtpBound = 'true';
|
|
1533
|
+
enhanceLumenInputOTPs(document);
|
|
1534
|
+
if (typeof MutationObserver === 'undefined')
|
|
1535
|
+
return;
|
|
1536
|
+
const observer = new MutationObserver(mutations => {
|
|
1537
|
+
for (const mutation of mutations) {
|
|
1538
|
+
for (const node of mutation.addedNodes) {
|
|
1539
|
+
if (node instanceof Element || node instanceof DocumentFragment) {
|
|
1540
|
+
enhanceLumenInputOTPs(node);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
1546
|
+
};
|
|
1547
|
+
/* eslint-enable @stylistic/padding-line-between-statements, unicorn/consistent-function-scoping */
|
|
1548
|
+
/* cspell:ignore lsaquo rsaquo */
|
|
1549
|
+
/* eslint-disable @stylistic/padding-line-between-statements -- Calendar mirrors Astro's UTC date grid runtime. */
|
|
1550
|
+
const calendarDatePattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
1551
|
+
const calendarMonthPattern = /^\d{4}-\d{2}$/;
|
|
1552
|
+
const parseCalendarDate = (value) => {
|
|
1553
|
+
if (!value || !calendarDatePattern.test(value))
|
|
1554
|
+
return null;
|
|
1555
|
+
const [year = Number.NaN, month = Number.NaN, day = Number.NaN] = value.split('-').map(Number);
|
|
1556
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
1557
|
+
return date.getUTCFullYear() === year &&
|
|
1558
|
+
date.getUTCMonth() === month - 1 &&
|
|
1559
|
+
date.getUTCDate() === day
|
|
1560
|
+
? date
|
|
1561
|
+
: null;
|
|
1562
|
+
};
|
|
1563
|
+
const parseCalendarMonth = (value) => {
|
|
1564
|
+
if (!value || !calendarMonthPattern.test(value))
|
|
1565
|
+
return null;
|
|
1566
|
+
const [year = Number.NaN, month = Number.NaN] = value.split('-').map(Number);
|
|
1567
|
+
const date = new Date(Date.UTC(year, month - 1, 1));
|
|
1568
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 ? date : null;
|
|
1569
|
+
};
|
|
1570
|
+
const formatCalendarDate = (date) => date.toISOString().slice(0, 10);
|
|
1571
|
+
const formatCalendarMonth = (date) => date.toISOString().slice(0, 7);
|
|
1572
|
+
const addCalendarDays = (date, days) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + days));
|
|
1573
|
+
const getCalendarDaysInMonth = (date) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0)).getUTCDate();
|
|
1574
|
+
const addCalendarMonths = (date, months) => {
|
|
1575
|
+
const targetMonth = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + months, 1));
|
|
1576
|
+
return new Date(Date.UTC(targetMonth.getUTCFullYear(), targetMonth.getUTCMonth(), Math.min(date.getUTCDate(), getCalendarDaysInMonth(targetMonth))));
|
|
1577
|
+
};
|
|
1578
|
+
const compareCalendarDates = (date, other) => other ? formatCalendarDate(date).localeCompare(formatCalendarDate(other)) : 0;
|
|
1579
|
+
const getCalendarToday = () => {
|
|
1580
|
+
const today = new Date();
|
|
1581
|
+
return new Date(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate()));
|
|
1582
|
+
};
|
|
1583
|
+
const getCalendarGridStart = (month) => addCalendarDays(month, -((month.getUTCDay() + 6) % 7));
|
|
1584
|
+
const getCalendarLocale = () => document.documentElement.lang || navigator.language || 'en';
|
|
1585
|
+
const isCalendarDateDisabled = (root, date, min, max) => root.dataset.disabled === 'true' ||
|
|
1586
|
+
compareCalendarDates(date, min) < 0 ||
|
|
1587
|
+
compareCalendarDates(date, max) > 0;
|
|
1588
|
+
const clampCalendarDate = (date, min, max) => {
|
|
1589
|
+
if (min && compareCalendarDates(date, min) < 0)
|
|
1590
|
+
return min;
|
|
1591
|
+
if (max && compareCalendarDates(date, max) > 0)
|
|
1592
|
+
return max;
|
|
1593
|
+
return date;
|
|
1594
|
+
};
|
|
1595
|
+
const getCalendarFocusDate = (root, month, min, max, requestedDate) => {
|
|
1596
|
+
const selectedDate = parseCalendarDate(root.dataset.uiCalendarValue);
|
|
1597
|
+
const today = getCalendarToday();
|
|
1598
|
+
const candidates = [requestedDate, selectedDate, today, month];
|
|
1599
|
+
for (const candidate of candidates) {
|
|
1600
|
+
if (candidate &&
|
|
1601
|
+
formatCalendarMonth(candidate) === formatCalendarMonth(month) &&
|
|
1602
|
+
!isCalendarDateDisabled(root, candidate, min, max)) {
|
|
1603
|
+
return candidate;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
for (let index = 0; index < getCalendarDaysInMonth(month); index += 1) {
|
|
1607
|
+
const date = addCalendarDays(month, index);
|
|
1608
|
+
if (!isCalendarDateDisabled(root, date, min, max))
|
|
1609
|
+
return date;
|
|
1610
|
+
}
|
|
1611
|
+
return month;
|
|
1612
|
+
};
|
|
1613
|
+
const ensureCalendarStructure = (root) => {
|
|
1614
|
+
root.id ||= createId('ui-calendar');
|
|
1615
|
+
const labelId = `${root.id}-label`;
|
|
1616
|
+
const selectedDate = parseCalendarDate(root.getAttribute('value') ?? root.dataset.uiCalendarValue);
|
|
1617
|
+
const minDate = parseCalendarDate(root.getAttribute('min') ?? root.dataset.uiCalendarMin);
|
|
1618
|
+
const maxDate = parseCalendarDate(root.getAttribute('max') ?? root.dataset.uiCalendarMax);
|
|
1619
|
+
const monthDate = parseCalendarMonth(root.getAttribute('month') ?? root.dataset.uiCalendarMonth) ??
|
|
1620
|
+
(selectedDate ? new Date(Date.UTC(selectedDate.getUTCFullYear(), selectedDate.getUTCMonth(), 1)) : null) ??
|
|
1621
|
+
new Date(Date.UTC(getCalendarToday().getUTCFullYear(), getCalendarToday().getUTCMonth(), 1));
|
|
1622
|
+
let input = root.querySelector(calendarInputSelector);
|
|
1623
|
+
root.dataset.uiCalendarInitialMonth ??= formatCalendarMonth(monthDate);
|
|
1624
|
+
root.dataset.uiCalendarMonth ||= formatCalendarMonth(monthDate);
|
|
1625
|
+
if (minDate)
|
|
1626
|
+
root.dataset.uiCalendarMin = formatCalendarDate(minDate);
|
|
1627
|
+
if (maxDate)
|
|
1628
|
+
root.dataset.uiCalendarMax = formatCalendarDate(maxDate);
|
|
1629
|
+
if (selectedDate)
|
|
1630
|
+
root.dataset.uiCalendarValue = formatCalendarDate(selectedDate);
|
|
1631
|
+
if (root.hasAttribute('disabled'))
|
|
1632
|
+
root.dataset.disabled = 'true';
|
|
1633
|
+
if (!input) {
|
|
1634
|
+
input = document.createElement('input');
|
|
1635
|
+
input.type = 'hidden';
|
|
1636
|
+
input.dataset.uiCalendarInput = '';
|
|
1637
|
+
root.prepend(input);
|
|
1638
|
+
}
|
|
1639
|
+
input.disabled = root.dataset.disabled === 'true';
|
|
1640
|
+
input.value = root.dataset.uiCalendarValue ?? '';
|
|
1641
|
+
if (root.hasAttribute('name') && !input.name) {
|
|
1642
|
+
input.name = root.getAttribute('name') ?? '';
|
|
1643
|
+
}
|
|
1644
|
+
if (!root.querySelector('[data-ui-calendar-prev]')) {
|
|
1645
|
+
const header = document.createElement('div');
|
|
1646
|
+
const previous = document.createElement('button');
|
|
1647
|
+
const label = document.createElement('strong');
|
|
1648
|
+
const next = document.createElement('button');
|
|
1649
|
+
header.className = 'ui-calendar__header';
|
|
1650
|
+
previous.type = 'button';
|
|
1651
|
+
previous.className = 'ui-calendar__nav';
|
|
1652
|
+
previous.dataset.uiCalendarPrev = '';
|
|
1653
|
+
previous.setAttribute('aria-label', 'Previous month');
|
|
1654
|
+
previous.innerHTML = '<span aria-hidden="true">‹</span>';
|
|
1655
|
+
label.className = 'ui-calendar__label';
|
|
1656
|
+
label.dataset.uiCalendarLabel = '';
|
|
1657
|
+
label.id = labelId;
|
|
1658
|
+
next.type = 'button';
|
|
1659
|
+
next.className = 'ui-calendar__nav';
|
|
1660
|
+
next.dataset.uiCalendarNext = '';
|
|
1661
|
+
next.setAttribute('aria-label', 'Next month');
|
|
1662
|
+
next.innerHTML = '<span aria-hidden="true">›</span>';
|
|
1663
|
+
header.append(previous, label, next);
|
|
1664
|
+
input.after(header);
|
|
1665
|
+
}
|
|
1666
|
+
const label = root.querySelector(calendarLabelSelector);
|
|
1667
|
+
if (label) {
|
|
1668
|
+
label.id ||= labelId;
|
|
1669
|
+
}
|
|
1670
|
+
if (!root.querySelector(calendarGridSelector)) {
|
|
1671
|
+
const table = document.createElement('table');
|
|
1672
|
+
table.className = 'ui-calendar__grid';
|
|
1673
|
+
table.dataset.uiCalendarGrid = '';
|
|
1674
|
+
table.role = 'grid';
|
|
1675
|
+
table.setAttribute('aria-labelledby', label?.id ?? labelId);
|
|
1676
|
+
root.append(table);
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1679
|
+
const syncCalendarNavigation = (root, month, min, max) => {
|
|
1680
|
+
const previous = root.querySelector('[data-ui-calendar-prev]');
|
|
1681
|
+
const next = root.querySelector('[data-ui-calendar-next]');
|
|
1682
|
+
const disabled = root.dataset.disabled === 'true';
|
|
1683
|
+
const previousMonthLastDay = addCalendarDays(month, -1);
|
|
1684
|
+
const nextMonthFirstDay = addCalendarMonths(month, 1);
|
|
1685
|
+
if (previous)
|
|
1686
|
+
previous.disabled = disabled || compareCalendarDates(previousMonthLastDay, min) < 0;
|
|
1687
|
+
if (next)
|
|
1688
|
+
next.disabled = disabled || compareCalendarDates(nextMonthFirstDay, max) > 0;
|
|
1689
|
+
};
|
|
1690
|
+
const renderCalendar = (root, requestedFocusDate, shouldFocus = false) => {
|
|
1691
|
+
const label = root.querySelector(calendarLabelSelector);
|
|
1692
|
+
const grid = root.querySelector(calendarGridSelector);
|
|
1693
|
+
if (!label || !grid)
|
|
1694
|
+
return;
|
|
1695
|
+
const locale = getCalendarLocale();
|
|
1696
|
+
const min = parseCalendarDate(root.dataset.uiCalendarMin);
|
|
1697
|
+
const max = parseCalendarDate(root.dataset.uiCalendarMax);
|
|
1698
|
+
const selectedDate = parseCalendarDate(root.dataset.uiCalendarValue);
|
|
1699
|
+
const todayIso = formatCalendarDate(getCalendarToday());
|
|
1700
|
+
const month = parseCalendarMonth(root.dataset.uiCalendarMonth) ??
|
|
1701
|
+
(selectedDate ? new Date(Date.UTC(selectedDate.getUTCFullYear(), selectedDate.getUTCMonth(), 1)) : null) ??
|
|
1702
|
+
getCalendarToday();
|
|
1703
|
+
const visibleMonth = new Date(Date.UTC(month.getUTCFullYear(), month.getUTCMonth(), 1));
|
|
1704
|
+
const focusDate = getCalendarFocusDate(root, visibleMonth, min, max, requestedFocusDate);
|
|
1705
|
+
const focusIso = formatCalendarDate(focusDate);
|
|
1706
|
+
const selectedIso = selectedDate ? formatCalendarDate(selectedDate) : '';
|
|
1707
|
+
const monthLabel = new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC', year: 'numeric' }).format(visibleMonth);
|
|
1708
|
+
const dayLabel = new Intl.DateTimeFormat(locale, { day: 'numeric', month: 'long', timeZone: 'UTC', weekday: 'long', year: 'numeric' });
|
|
1709
|
+
const weekdayLabel = new Intl.DateTimeFormat(locale, { timeZone: 'UTC', weekday: 'short' });
|
|
1710
|
+
const header = document.createElement('thead');
|
|
1711
|
+
const headerRow = document.createElement('tr');
|
|
1712
|
+
const body = document.createElement('tbody');
|
|
1713
|
+
const firstCell = getCalendarGridStart(visibleMonth);
|
|
1714
|
+
root.dataset.uiCalendarMonth = formatCalendarMonth(visibleMonth);
|
|
1715
|
+
label.textContent = monthLabel;
|
|
1716
|
+
syncCalendarNavigation(root, visibleMonth, min, max);
|
|
1717
|
+
headerRow.role = 'row';
|
|
1718
|
+
for (let index = 0; index < 7; index += 1) {
|
|
1719
|
+
const cell = document.createElement('th');
|
|
1720
|
+
cell.scope = 'col';
|
|
1721
|
+
cell.role = 'columnheader';
|
|
1722
|
+
cell.textContent = weekdayLabel.format(new Date(Date.UTC(2026, 0, 5 + index)));
|
|
1723
|
+
headerRow.append(cell);
|
|
1724
|
+
}
|
|
1725
|
+
header.append(headerRow);
|
|
1726
|
+
for (let rowIndex = 0; rowIndex < 6; rowIndex += 1) {
|
|
1727
|
+
const row = document.createElement('tr');
|
|
1728
|
+
row.role = 'row';
|
|
1729
|
+
for (let columnIndex = 0; columnIndex < 7; columnIndex += 1) {
|
|
1730
|
+
const date = addCalendarDays(firstCell, rowIndex * 7 + columnIndex);
|
|
1731
|
+
const dateIso = formatCalendarDate(date);
|
|
1732
|
+
const cell = document.createElement('td');
|
|
1733
|
+
const unavailable = isCalendarDateDisabled(root, date, min, max);
|
|
1734
|
+
cell.role = 'gridcell';
|
|
1735
|
+
cell.tabIndex = !unavailable && dateIso === focusIso ? 0 : -1;
|
|
1736
|
+
cell.textContent = String(date.getUTCDate());
|
|
1737
|
+
cell.dataset.date = dateIso;
|
|
1738
|
+
cell.dataset.uiCalendarDay = '';
|
|
1739
|
+
cell.setAttribute('aria-label', dayLabel.format(date));
|
|
1740
|
+
cell.setAttribute('aria-selected', String(selectedIso === dateIso));
|
|
1741
|
+
if (unavailable)
|
|
1742
|
+
cell.setAttribute('aria-disabled', 'true');
|
|
1743
|
+
if (formatCalendarMonth(date) !== formatCalendarMonth(visibleMonth))
|
|
1744
|
+
cell.dataset.outside = 'true';
|
|
1745
|
+
if (dateIso === todayIso)
|
|
1746
|
+
cell.dataset.today = 'true';
|
|
1747
|
+
if (selectedIso === dateIso)
|
|
1748
|
+
cell.dataset.selected = 'true';
|
|
1749
|
+
row.append(cell);
|
|
1750
|
+
}
|
|
1751
|
+
body.append(row);
|
|
1752
|
+
}
|
|
1753
|
+
grid.replaceChildren(header, body);
|
|
1754
|
+
if (shouldFocus) {
|
|
1755
|
+
root.querySelector(`${calendarDaySelector}[data-date="${focusIso}"]`)?.focus({ preventScroll: true });
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
const selectCalendarDate = (root, date) => {
|
|
1759
|
+
const input = root.querySelector(calendarInputSelector);
|
|
1760
|
+
if (!input || root.dataset.disabled === 'true')
|
|
1761
|
+
return;
|
|
1762
|
+
const min = parseCalendarDate(root.dataset.uiCalendarMin);
|
|
1763
|
+
const max = parseCalendarDate(root.dataset.uiCalendarMax);
|
|
1764
|
+
const nextDate = clampCalendarDate(date, min, max);
|
|
1765
|
+
if (isCalendarDateDisabled(root, nextDate, min, max))
|
|
1766
|
+
return;
|
|
1767
|
+
input.value = formatCalendarDate(nextDate);
|
|
1768
|
+
root.dataset.uiCalendarValue = input.value;
|
|
1769
|
+
root.dataset.uiCalendarMonth = formatCalendarMonth(nextDate);
|
|
1770
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1771
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
1772
|
+
renderCalendar(root, nextDate, true);
|
|
1773
|
+
};
|
|
1774
|
+
const focusCalendarDate = (root, date) => {
|
|
1775
|
+
const min = parseCalendarDate(root.dataset.uiCalendarMin);
|
|
1776
|
+
const max = parseCalendarDate(root.dataset.uiCalendarMax);
|
|
1777
|
+
const nextDate = clampCalendarDate(date, min, max);
|
|
1778
|
+
root.dataset.uiCalendarMonth = formatCalendarMonth(nextDate);
|
|
1779
|
+
renderCalendar(root, nextDate, true);
|
|
1780
|
+
};
|
|
1781
|
+
const moveCalendarFocus = (root, currentDate, key) => {
|
|
1782
|
+
const column = (currentDate.getUTCDay() + 6) % 7;
|
|
1783
|
+
const keyOffsets = {
|
|
1784
|
+
ArrowDown: 7,
|
|
1785
|
+
ArrowLeft: -1,
|
|
1786
|
+
ArrowRight: 1,
|
|
1787
|
+
ArrowUp: -7,
|
|
1788
|
+
End: 6 - column,
|
|
1789
|
+
Home: -column
|
|
1790
|
+
};
|
|
1791
|
+
if (key === 'PageDown' || key === 'PageUp') {
|
|
1792
|
+
focusCalendarDate(root, addCalendarMonths(currentDate, key === 'PageDown' ? 1 : -1));
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
const offset = keyOffsets[key];
|
|
1796
|
+
if (offset !== undefined) {
|
|
1797
|
+
focusCalendarDate(root, addCalendarDays(currentDate, offset));
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
const initCalendars = (scope) => {
|
|
1801
|
+
const closestRoot = getClosestScopedElement(scope, calendarSelector);
|
|
1802
|
+
const roots = getScopedElements(scope, calendarSelector);
|
|
1803
|
+
if (closestRoot instanceof HTMLElement && !roots.includes(closestRoot)) {
|
|
1804
|
+
roots.unshift(closestRoot);
|
|
1805
|
+
}
|
|
1806
|
+
for (const root of roots) {
|
|
1807
|
+
if (root.dataset.uiBound === 'true')
|
|
1808
|
+
continue;
|
|
1809
|
+
ensureCalendarStructure(root);
|
|
1810
|
+
const input = root.querySelector(calendarInputSelector);
|
|
1811
|
+
if (!input)
|
|
1812
|
+
continue;
|
|
1813
|
+
root.dataset.uiBound = 'true';
|
|
1814
|
+
renderCalendar(root);
|
|
1815
|
+
root.querySelector('[data-ui-calendar-prev]')?.addEventListener('click', () => {
|
|
1816
|
+
const month = parseCalendarMonth(root.dataset.uiCalendarMonth);
|
|
1817
|
+
if (!month)
|
|
1818
|
+
return;
|
|
1819
|
+
const nextMonth = addCalendarMonths(month, -1);
|
|
1820
|
+
root.dataset.uiCalendarMonth = formatCalendarMonth(nextMonth);
|
|
1821
|
+
renderCalendar(root, nextMonth, true);
|
|
1822
|
+
});
|
|
1823
|
+
root.querySelector('[data-ui-calendar-next]')?.addEventListener('click', () => {
|
|
1824
|
+
const month = parseCalendarMonth(root.dataset.uiCalendarMonth);
|
|
1825
|
+
if (!month)
|
|
1826
|
+
return;
|
|
1827
|
+
const nextMonth = addCalendarMonths(month, 1);
|
|
1828
|
+
root.dataset.uiCalendarMonth = formatCalendarMonth(nextMonth);
|
|
1829
|
+
renderCalendar(root, nextMonth, true);
|
|
1830
|
+
});
|
|
1831
|
+
root.addEventListener('click', event => {
|
|
1832
|
+
const target = event.target;
|
|
1833
|
+
if (!(target instanceof Element))
|
|
1834
|
+
return;
|
|
1835
|
+
const cell = target.closest(calendarDaySelector);
|
|
1836
|
+
if (!cell || !root.contains(cell) || cell.getAttribute('aria-disabled') === 'true')
|
|
1837
|
+
return;
|
|
1838
|
+
const date = parseCalendarDate(cell.dataset.date);
|
|
1839
|
+
if (date)
|
|
1840
|
+
selectCalendarDate(root, date);
|
|
1841
|
+
});
|
|
1842
|
+
root.addEventListener('keydown', event => {
|
|
1843
|
+
const target = event.target;
|
|
1844
|
+
if (!(target instanceof HTMLElement) || !target.hasAttribute('data-ui-calendar-day'))
|
|
1845
|
+
return;
|
|
1846
|
+
const date = parseCalendarDate(target.dataset.date);
|
|
1847
|
+
if (!date)
|
|
1848
|
+
return;
|
|
1849
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
1850
|
+
event.preventDefault();
|
|
1851
|
+
selectCalendarDate(root, date);
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
if (event.key !== 'ArrowDown' &&
|
|
1855
|
+
event.key !== 'ArrowLeft' &&
|
|
1856
|
+
event.key !== 'ArrowRight' &&
|
|
1857
|
+
event.key !== 'ArrowUp' &&
|
|
1858
|
+
event.key !== 'End' &&
|
|
1859
|
+
event.key !== 'Home' &&
|
|
1860
|
+
event.key !== 'PageDown' &&
|
|
1861
|
+
event.key !== 'PageUp') {
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
event.preventDefault();
|
|
1865
|
+
moveCalendarFocus(root, date, event.key);
|
|
1866
|
+
});
|
|
1867
|
+
input.form?.addEventListener('reset', () => {
|
|
1868
|
+
globalThis.setTimeout(() => {
|
|
1869
|
+
root.dataset.uiCalendarValue = input.value;
|
|
1870
|
+
root.dataset.uiCalendarMonth = root.dataset.uiCalendarInitialMonth ?? root.dataset.uiCalendarMonth;
|
|
1871
|
+
renderCalendar(root);
|
|
1872
|
+
});
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
};
|
|
1876
|
+
export const enhanceLumenCalendars = (scope = document) => {
|
|
1877
|
+
initCalendars(scope);
|
|
1878
|
+
};
|
|
1879
|
+
const installCalendarController = () => {
|
|
1880
|
+
if (!hasDocument() || document.documentElement.dataset.uiElementsCalendarsBound === 'true')
|
|
1881
|
+
return;
|
|
1882
|
+
document.documentElement.dataset.uiElementsCalendarsBound = 'true';
|
|
1883
|
+
enhanceLumenCalendars(document);
|
|
1884
|
+
if (typeof MutationObserver === 'undefined')
|
|
1885
|
+
return;
|
|
1886
|
+
const observer = new MutationObserver(mutations => {
|
|
1887
|
+
for (const mutation of mutations) {
|
|
1888
|
+
for (const node of mutation.addedNodes) {
|
|
1889
|
+
if (node instanceof Element || node instanceof DocumentFragment) {
|
|
1890
|
+
enhanceLumenCalendars(node);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
1896
|
+
};
|
|
1897
|
+
/* eslint-enable @stylistic/padding-line-between-statements */
|
|
1898
|
+
const closeContextMenu = (menu) => {
|
|
1899
|
+
if (menu.dataset.state !== 'open')
|
|
1900
|
+
return;
|
|
1901
|
+
menu.dataset.state = 'closed';
|
|
1902
|
+
menu.hidden = true;
|
|
1903
|
+
};
|
|
1904
|
+
const closeOpenContextMenus = (except) => {
|
|
1905
|
+
if (!hasDocument())
|
|
1906
|
+
return;
|
|
1907
|
+
for (const menu of document.querySelectorAll(`${contextMenuSelector}[data-state="open"]`)) {
|
|
1908
|
+
if (menu !== except) {
|
|
1909
|
+
closeContextMenu(menu);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
const openContextMenuAt = (menu, x, y) => {
|
|
1914
|
+
closeOpenContextMenus(menu);
|
|
1915
|
+
menu.hidden = false;
|
|
1916
|
+
menu.style.position = 'fixed';
|
|
1917
|
+
menu.style.zIndex = '60';
|
|
1918
|
+
menu.dataset.state = 'open';
|
|
1919
|
+
const rect = menu.getBoundingClientRect();
|
|
1920
|
+
const left = Math.max(8, Math.min(x, window.innerWidth - rect.width - 8));
|
|
1921
|
+
const top = Math.max(8, Math.min(y, window.innerHeight - rect.height - 8));
|
|
1922
|
+
menu.style.left = `${left}px`;
|
|
1923
|
+
menu.style.top = `${top}px`;
|
|
1924
|
+
getFocusable(menu)[0]?.focus();
|
|
1925
|
+
};
|
|
1926
|
+
const initContextMenus = (scope) => {
|
|
1927
|
+
for (const trigger of getScopedElements(scope, contextMenuTriggerSelector)) {
|
|
1928
|
+
if (trigger.dataset.uiContextMenuBound === 'true')
|
|
1929
|
+
continue;
|
|
1930
|
+
const menuId = trigger.getAttribute('data-ui-context-menu-trigger');
|
|
1931
|
+
const menu = menuId && hasDocument() ? document.getElementById(menuId) : null;
|
|
1932
|
+
if (!(menu instanceof HTMLElement))
|
|
1933
|
+
continue;
|
|
1934
|
+
trigger.dataset.uiContextMenuBound = 'true';
|
|
1935
|
+
menu.hidden = true;
|
|
1936
|
+
menu.dataset.state = 'closed';
|
|
1937
|
+
trigger.addEventListener('contextmenu', event => {
|
|
1938
|
+
event.preventDefault();
|
|
1939
|
+
openContextMenuAt(menu, event.clientX, event.clientY);
|
|
1940
|
+
});
|
|
1941
|
+
trigger.addEventListener('keydown', event => {
|
|
1942
|
+
if (event.key !== 'ContextMenu' && !(event.shiftKey && event.key === 'F10'))
|
|
1943
|
+
return;
|
|
1944
|
+
event.preventDefault();
|
|
1945
|
+
const rect = trigger.getBoundingClientRect();
|
|
1946
|
+
openContextMenuAt(menu, rect.left + 16, rect.top + 16);
|
|
1947
|
+
});
|
|
1948
|
+
if (menu.dataset.uiContextMenuMenuBound === 'true')
|
|
1949
|
+
continue;
|
|
1950
|
+
menu.dataset.uiContextMenuMenuBound = 'true';
|
|
1951
|
+
menu.addEventListener('keydown', event => {
|
|
1952
|
+
if (event.key === 'Escape') {
|
|
1953
|
+
event.preventDefault();
|
|
1954
|
+
closeContextMenu(menu);
|
|
1955
|
+
trigger.focus({ preventScroll: true });
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
if (!['ArrowDown', 'ArrowUp', 'End', 'Home'].includes(event.key))
|
|
1959
|
+
return;
|
|
1960
|
+
const items = getFocusable(menu);
|
|
1961
|
+
if (items.length === 0)
|
|
1962
|
+
return;
|
|
1963
|
+
event.preventDefault();
|
|
1964
|
+
const currentIndex = items.indexOf(document.activeElement);
|
|
1965
|
+
items[getLoopedIndex(event.key, currentIndex, items.length, ['ArrowDown'])]?.focus();
|
|
1966
|
+
});
|
|
1967
|
+
menu.addEventListener('click', event => {
|
|
1968
|
+
if (event.target instanceof HTMLElement && event.target.closest('[role="menuitem"]')) {
|
|
1969
|
+
closeContextMenu(menu);
|
|
1970
|
+
}
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
};
|
|
1974
|
+
export const enhanceLumenContextMenus = (scope = document) => {
|
|
1975
|
+
initContextMenus(scope);
|
|
1976
|
+
};
|
|
1977
|
+
const installContextMenuController = () => {
|
|
1978
|
+
if (!hasDocument() || document.documentElement.dataset.uiElementsContextMenusBound === 'true')
|
|
1979
|
+
return;
|
|
1980
|
+
document.documentElement.dataset.uiElementsContextMenusBound = 'true';
|
|
1981
|
+
enhanceLumenContextMenus(document);
|
|
1982
|
+
document.addEventListener('pointerdown', event => {
|
|
1983
|
+
const target = getOwnedTarget(event);
|
|
1984
|
+
if (!target)
|
|
1985
|
+
return;
|
|
1986
|
+
for (const menu of document.querySelectorAll(`${contextMenuSelector}[data-state="open"]`)) {
|
|
1987
|
+
if (!menu.contains(target)) {
|
|
1988
|
+
closeContextMenu(menu);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
});
|
|
1992
|
+
document.addEventListener('keydown', event => {
|
|
1993
|
+
if (event.key === 'Escape') {
|
|
1994
|
+
closeOpenContextMenus();
|
|
1995
|
+
}
|
|
1996
|
+
});
|
|
1997
|
+
if (typeof MutationObserver === 'undefined')
|
|
1998
|
+
return;
|
|
1999
|
+
const observer = new MutationObserver(mutations => {
|
|
2000
|
+
for (const mutation of mutations) {
|
|
2001
|
+
for (const node of mutation.addedNodes) {
|
|
2002
|
+
if (node instanceof Element || node instanceof DocumentFragment) {
|
|
2003
|
+
enhanceLumenContextMenus(node);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
});
|
|
2008
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
2009
|
+
};
|
|
2010
|
+
const getDataTableSortValue = (cell) => {
|
|
2011
|
+
if (!cell)
|
|
2012
|
+
return '';
|
|
2013
|
+
return cell.dataset.sortValue ?? cell.textContent.trim();
|
|
2014
|
+
};
|
|
2015
|
+
const compareDataTableValues = (value, other, sortType) => {
|
|
2016
|
+
const numericValue = Number(value.replaceAll(',', ''));
|
|
2017
|
+
const numericOther = Number(other.replaceAll(',', ''));
|
|
2018
|
+
const useNumeric = sortType === 'number' || (sortType !== 'string' &&
|
|
2019
|
+
value.trim() !== '' &&
|
|
2020
|
+
other.trim() !== '' &&
|
|
2021
|
+
Number.isFinite(numericValue) &&
|
|
2022
|
+
Number.isFinite(numericOther));
|
|
2023
|
+
if (useNumeric) {
|
|
2024
|
+
return numericValue - numericOther;
|
|
2025
|
+
}
|
|
2026
|
+
return value.localeCompare(other, undefined, { numeric: true, sensitivity: 'base' });
|
|
2027
|
+
};
|
|
2028
|
+
const getDataTableRows = (table) => [...(table.tBodies[0]?.rows ?? [])];
|
|
2029
|
+
const getDataTableRowValue = (row, index) => row.dataset.value || row.id || String(index);
|
|
2030
|
+
const getNextDataTableSortDirection = (currentColumn, columnIndex, currentDirection) => {
|
|
2031
|
+
if (currentColumn !== String(columnIndex) || currentDirection === 'none')
|
|
2032
|
+
return 'ascending';
|
|
2033
|
+
if (currentDirection === 'ascending')
|
|
2034
|
+
return 'descending';
|
|
2035
|
+
return 'none';
|
|
2036
|
+
};
|
|
2037
|
+
const getControlledPanel = (trigger) => {
|
|
2038
|
+
const controls = trigger.getAttribute('aria-controls');
|
|
2039
|
+
if (controls && hasDocument()) {
|
|
2040
|
+
return document.getElementById(controls);
|
|
2041
|
+
}
|
|
2042
|
+
return trigger.nextElementSibling instanceof HTMLElement ? trigger.nextElementSibling : null;
|
|
2043
|
+
};
|
|
2044
|
+
const setDisclosureOpen = (trigger, panel, open) => {
|
|
2045
|
+
trigger.setAttribute('aria-expanded', String(open));
|
|
2046
|
+
panel.hidden = !open;
|
|
2047
|
+
panel.dataset.state = open ? 'open' : 'closed';
|
|
2048
|
+
};
|
|
2049
|
+
const isPrintableKey = (event) => event.key.length === 1 && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
2050
|
+
const escapeCssIdentifier = (value) => {
|
|
2051
|
+
if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
|
|
2052
|
+
return CSS.escape(value);
|
|
2053
|
+
}
|
|
2054
|
+
return value.replaceAll(/["#.:?[\\\]]/g, '\\$&');
|
|
2055
|
+
};
|
|
2056
|
+
const isToastPlacement = (placement) => placement === 'top-left' ||
|
|
2057
|
+
placement === 'top-center' ||
|
|
2058
|
+
placement === 'top-right' ||
|
|
2059
|
+
placement === 'bottom-left' ||
|
|
2060
|
+
placement === 'bottom-center' ||
|
|
2061
|
+
placement === 'bottom-right';
|
|
2062
|
+
const getToastPlacement = (placement) => isToastPlacement(placement) ? placement : 'bottom-right';
|
|
2063
|
+
const getToastVariantClass = (variant) => {
|
|
2064
|
+
if (variant === 'success')
|
|
2065
|
+
return 'ui-toast--success';
|
|
2066
|
+
if (variant === 'warning')
|
|
2067
|
+
return 'ui-toast--warning';
|
|
2068
|
+
if (variant === 'destructive')
|
|
2069
|
+
return 'ui-toast--destructive';
|
|
2070
|
+
return false;
|
|
2071
|
+
};
|
|
2072
|
+
const normalizeToastViewport = (viewport, placement) => {
|
|
2073
|
+
viewport.classList.add('ui-sonner');
|
|
2074
|
+
viewport.dataset.uiSonner = '';
|
|
2075
|
+
viewport.dataset.placement = getToastPlacement(placement ?? viewport.dataset.placement);
|
|
2076
|
+
viewport.setAttribute('aria-live', viewport.getAttribute('aria-live') ?? 'polite');
|
|
2077
|
+
viewport.setAttribute('aria-atomic', viewport.getAttribute('aria-atomic') ?? 'false');
|
|
2078
|
+
viewport.setAttribute('aria-label', viewport.getAttribute('aria-label') ?? 'Notifications');
|
|
2079
|
+
};
|
|
2080
|
+
const getToastViewport = (placement) => {
|
|
2081
|
+
if (!hasDocument())
|
|
2082
|
+
return null;
|
|
2083
|
+
const resolvedPlacement = getToastPlacement(placement);
|
|
2084
|
+
const shouldUseAnyViewport = placement === undefined;
|
|
2085
|
+
const existing = document.querySelector(`[data-ui-sonner][data-placement="${resolvedPlacement}"]`) ??
|
|
2086
|
+
(shouldUseAnyViewport ? document.querySelector('[data-ui-sonner]') : null);
|
|
2087
|
+
if (existing) {
|
|
2088
|
+
normalizeToastViewport(existing, existing.dataset.placement ?? resolvedPlacement);
|
|
2089
|
+
return existing;
|
|
2090
|
+
}
|
|
2091
|
+
const tagName = typeof customElements !== 'undefined' && customElements.get('lumen-sonner')
|
|
2092
|
+
? 'lumen-sonner'
|
|
2093
|
+
: 'div';
|
|
2094
|
+
const viewport = document.createElement(tagName);
|
|
2095
|
+
normalizeToastViewport(viewport, resolvedPlacement);
|
|
2096
|
+
document.body.append(viewport);
|
|
2097
|
+
return viewport;
|
|
2098
|
+
};
|
|
2099
|
+
const clearToastTimer = (toast) => {
|
|
2100
|
+
const timer = toastTimers.get(toast);
|
|
2101
|
+
if (!timer)
|
|
2102
|
+
return;
|
|
2103
|
+
if (timer.timer) {
|
|
2104
|
+
globalThis.clearTimeout(timer.timer);
|
|
2105
|
+
}
|
|
2106
|
+
timer.cleanup?.();
|
|
2107
|
+
toastTimers.delete(toast);
|
|
2108
|
+
};
|
|
2109
|
+
const dismissToastElement = (toast) => {
|
|
2110
|
+
clearToastTimer(toast);
|
|
2111
|
+
toast.dataset.state = 'closed';
|
|
2112
|
+
globalThis.setTimeout(() => { toast.remove(); }, toastCloseDelay);
|
|
2113
|
+
};
|
|
2114
|
+
const scheduleToastDismiss = (toast, duration) => {
|
|
2115
|
+
clearToastTimer(toast);
|
|
2116
|
+
if (!Number.isFinite(duration) || duration <= 0)
|
|
2117
|
+
return;
|
|
2118
|
+
const timerState = {
|
|
2119
|
+
cleanup: undefined,
|
|
2120
|
+
remaining: duration,
|
|
2121
|
+
startedAt: Date.now(),
|
|
2122
|
+
timer: undefined
|
|
2123
|
+
};
|
|
2124
|
+
const start = () => {
|
|
2125
|
+
timerState.startedAt = Date.now();
|
|
2126
|
+
timerState.timer = globalThis.setTimeout(() => { dismissToastElement(toast); }, timerState.remaining);
|
|
2127
|
+
};
|
|
2128
|
+
const pause = () => {
|
|
2129
|
+
if (timerState.timer) {
|
|
2130
|
+
globalThis.clearTimeout(timerState.timer);
|
|
2131
|
+
}
|
|
2132
|
+
timerState.remaining = Math.max(0, timerState.remaining - (Date.now() - timerState.startedAt));
|
|
2133
|
+
};
|
|
2134
|
+
const resume = () => {
|
|
2135
|
+
if (timerState.remaining > 0)
|
|
2136
|
+
start();
|
|
2137
|
+
};
|
|
2138
|
+
timerState.cleanup = () => {
|
|
2139
|
+
toast.removeEventListener('mouseenter', pause);
|
|
2140
|
+
toast.removeEventListener('mouseleave', resume);
|
|
2141
|
+
toast.removeEventListener('focusin', pause);
|
|
2142
|
+
toast.removeEventListener('focusout', resume);
|
|
2143
|
+
};
|
|
2144
|
+
toast.addEventListener('mouseenter', pause);
|
|
2145
|
+
toast.addEventListener('mouseleave', resume);
|
|
2146
|
+
toast.addEventListener('focusin', pause);
|
|
2147
|
+
toast.addEventListener('focusout', resume);
|
|
2148
|
+
toastTimers.set(toast, timerState);
|
|
2149
|
+
start();
|
|
2150
|
+
};
|
|
2151
|
+
const setToastContent = (toast, detail) => {
|
|
2152
|
+
const title = detail.title ?? toast.dataset.title ?? 'Notification';
|
|
2153
|
+
const description = detail.description ?? toast.dataset.description ?? '';
|
|
2154
|
+
const variant = detail.variant ?? toast.dataset.variant ?? 'default';
|
|
2155
|
+
const variantClass = getToastVariantClass(variant);
|
|
2156
|
+
toast.className = variantClass ? `ui-toast ${variantClass}` : 'ui-toast';
|
|
2157
|
+
toast.dataset.uiToast = '';
|
|
2158
|
+
toast.dataset.variant = variant;
|
|
2159
|
+
toast.dataset.title = title;
|
|
2160
|
+
toast.dataset.description = description;
|
|
2161
|
+
toast.setAttribute('variant', variant);
|
|
2162
|
+
toast.setAttribute('role', variant === 'destructive' ? 'alert' : 'status');
|
|
2163
|
+
toast.setAttribute('aria-live', variant === 'destructive' ? 'assertive' : 'polite');
|
|
2164
|
+
toast.replaceChildren();
|
|
2165
|
+
const body = document.createElement('div');
|
|
2166
|
+
const heading = document.createElement('strong');
|
|
2167
|
+
body.className = 'ui-toast__body';
|
|
2168
|
+
heading.textContent = title;
|
|
2169
|
+
body.append(heading);
|
|
2170
|
+
if (description) {
|
|
2171
|
+
const text = document.createElement('p');
|
|
2172
|
+
text.textContent = description;
|
|
2173
|
+
body.append(text);
|
|
2174
|
+
}
|
|
2175
|
+
toast.append(body);
|
|
2176
|
+
if (detail.action?.label) {
|
|
2177
|
+
const action = document.createElement('button');
|
|
2178
|
+
action.className = 'ui-button ui-button--secondary ui-button--sm ui-toast__action';
|
|
2179
|
+
action.type = 'button';
|
|
2180
|
+
action.textContent = detail.action.label;
|
|
2181
|
+
action.addEventListener('click', event => {
|
|
2182
|
+
detail.action?.onClick?.(event, toast);
|
|
2183
|
+
toast.dispatchEvent(new CustomEvent(detail.action?.event ?? 'ui:toast-action', {
|
|
2184
|
+
bubbles: true,
|
|
2185
|
+
detail: { id: toast.id, value: detail.action?.value }
|
|
2186
|
+
}));
|
|
2187
|
+
dismissToastElement(toast);
|
|
2188
|
+
});
|
|
2189
|
+
toast.append(action);
|
|
2190
|
+
}
|
|
2191
|
+
const dismiss = document.createElement('button');
|
|
2192
|
+
dismiss.className = 'ui-toast__dismiss';
|
|
2193
|
+
dismiss.type = 'button';
|
|
2194
|
+
dismiss.setAttribute('aria-label', 'Dismiss notification');
|
|
2195
|
+
dismiss.textContent = 'Dismiss';
|
|
2196
|
+
dismiss.addEventListener('click', () => { dismissToastElement(toast); });
|
|
2197
|
+
toast.append(dismiss);
|
|
2198
|
+
};
|
|
2199
|
+
export const LumenToast = {
|
|
2200
|
+
create(detail) {
|
|
2201
|
+
const viewport = getToastViewport(detail.placement);
|
|
2202
|
+
if (!viewport)
|
|
2203
|
+
return detail.id ?? createId('ui-toast');
|
|
2204
|
+
const id = detail.id ?? createId('ui-toast');
|
|
2205
|
+
const duration = detail.duration ?? defaultToastDuration;
|
|
2206
|
+
const max = detail.max ?? Number(viewport.dataset.uiToastMax ?? defaultToastMax);
|
|
2207
|
+
const existingToast = document.getElementById(id);
|
|
2208
|
+
if (existingToast instanceof HTMLElement && existingToast.hasAttribute('data-ui-toast')) {
|
|
2209
|
+
existingToast.dataset.state = 'open';
|
|
2210
|
+
setToastContent(existingToast, detail);
|
|
2211
|
+
viewport.append(existingToast);
|
|
2212
|
+
scheduleToastDismiss(existingToast, duration);
|
|
2213
|
+
return id;
|
|
2214
|
+
}
|
|
2215
|
+
const tagName = typeof customElements !== 'undefined' && customElements.get('lumen-toast')
|
|
2216
|
+
? 'lumen-toast'
|
|
2217
|
+
: 'aside';
|
|
2218
|
+
const toast = document.createElement(tagName);
|
|
2219
|
+
toast.id = id;
|
|
2220
|
+
toast.dataset.state = 'open';
|
|
2221
|
+
toast.addEventListener('keydown', event => {
|
|
2222
|
+
if (event.key !== 'Escape')
|
|
2223
|
+
return;
|
|
2224
|
+
event.preventDefault();
|
|
2225
|
+
dismissToastElement(toast);
|
|
2226
|
+
});
|
|
2227
|
+
setToastContent(toast, detail);
|
|
2228
|
+
viewport.append(toast);
|
|
2229
|
+
scheduleToastDismiss(toast, duration);
|
|
2230
|
+
const toasts = [...viewport.querySelectorAll('[data-ui-toast]')];
|
|
2231
|
+
for (const staleToast of toasts.slice(0, Math.max(0, toasts.length - max))) {
|
|
2232
|
+
dismissToastElement(staleToast);
|
|
2233
|
+
}
|
|
2234
|
+
return id;
|
|
2235
|
+
},
|
|
2236
|
+
dismiss(id) {
|
|
2237
|
+
if (!hasDocument())
|
|
2238
|
+
return;
|
|
2239
|
+
const selector = id ? `#${escapeCssIdentifier(id)}` : '[data-ui-toast]';
|
|
2240
|
+
for (const toast of document.querySelectorAll(selector)) {
|
|
2241
|
+
if (toast.hasAttribute('data-ui-toast')) {
|
|
2242
|
+
dismissToastElement(toast);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
},
|
|
2246
|
+
update(id, detail) {
|
|
2247
|
+
if (!hasDocument())
|
|
2248
|
+
return;
|
|
2249
|
+
const toast = document.getElementById(id);
|
|
2250
|
+
if (!(toast instanceof HTMLElement) || !toast.hasAttribute('data-ui-toast'))
|
|
2251
|
+
return;
|
|
2252
|
+
setToastContent(toast, { ...detail, id });
|
|
2253
|
+
if (detail.duration !== undefined) {
|
|
2254
|
+
scheduleToastDismiss(toast, detail.duration);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
};
|
|
2258
|
+
const installToastController = () => {
|
|
2259
|
+
if (!hasDocument() || document.documentElement.dataset.uiElementsToastsBound === 'true')
|
|
2260
|
+
return;
|
|
2261
|
+
document.documentElement.dataset.uiElementsToastsBound = 'true';
|
|
2262
|
+
globalThis.LumenToast = LumenToast;
|
|
2263
|
+
for (const viewport of document.querySelectorAll('[data-ui-sonner]')) {
|
|
2264
|
+
normalizeToastViewport(viewport);
|
|
2265
|
+
}
|
|
2266
|
+
document.addEventListener('ui:toast', event => {
|
|
2267
|
+
LumenToast.create(event instanceof CustomEvent ? event.detail : {});
|
|
2268
|
+
});
|
|
2269
|
+
document.addEventListener('ui:toast-update', event => {
|
|
2270
|
+
const detail = event instanceof CustomEvent ? event.detail : {};
|
|
2271
|
+
if (detail.id) {
|
|
2272
|
+
LumenToast.update(detail.id, detail);
|
|
2273
|
+
}
|
|
2274
|
+
});
|
|
2275
|
+
document.addEventListener('ui:toast-dismiss', event => {
|
|
2276
|
+
const detail = event instanceof CustomEvent ? event.detail : {};
|
|
2277
|
+
LumenToast.dismiss(detail.id);
|
|
2278
|
+
});
|
|
2279
|
+
};
|
|
2280
|
+
export class LumenElement extends HTMLElement {
|
|
2281
|
+
static config;
|
|
2282
|
+
static get observedAttributes() {
|
|
2283
|
+
return observedAttributeNames;
|
|
2284
|
+
}
|
|
2285
|
+
connectedCallback() {
|
|
2286
|
+
this.applyDefaults();
|
|
2287
|
+
this.applyClassNames();
|
|
2288
|
+
}
|
|
2289
|
+
disconnectedCallback() {
|
|
2290
|
+
/* Subclasses clean up behavior listeners when needed. */
|
|
2291
|
+
}
|
|
2292
|
+
attributeChangedCallback() {
|
|
2293
|
+
this.applyClassNames();
|
|
2294
|
+
}
|
|
2295
|
+
get config() {
|
|
2296
|
+
return this.constructor.config;
|
|
2297
|
+
}
|
|
2298
|
+
applyDefaults() {
|
|
2299
|
+
for (const [name, value] of Object.entries(this.config.defaults ?? {})) {
|
|
2300
|
+
if (!this.hasAttribute(name)) {
|
|
2301
|
+
this.setAttribute(name, value);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
if (this.config.role && !this.hasAttribute('role')) {
|
|
2305
|
+
this.setAttribute('role', this.config.role);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
applyClassNames() {
|
|
2309
|
+
const previousClassNames = appliedClassNames.get(this) ?? [];
|
|
2310
|
+
for (const className of previousClassNames) {
|
|
2311
|
+
this.classList.remove(className);
|
|
2312
|
+
}
|
|
2313
|
+
const classNames = mergeClassNames(this.config.baseClassName);
|
|
2314
|
+
for (const [attributeName, classMap] of Object.entries(this.config.attributeClasses ?? {})) {
|
|
2315
|
+
const attributeValue = this.hasAttribute(attributeName)
|
|
2316
|
+
? this.getAttribute(attributeName) || 'true'
|
|
2317
|
+
: this.config.defaults?.[attributeName];
|
|
2318
|
+
const className = attributeValue ? classMap[attributeValue] : undefined;
|
|
2319
|
+
if (className) {
|
|
2320
|
+
classNames.push(...mergeClassNames(className));
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
this.classList.add(...classNames);
|
|
2324
|
+
appliedClassNames.set(this, classNames);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
class LumenIconBehaviorElement extends LumenElement {
|
|
2328
|
+
connectedCallback() {
|
|
2329
|
+
super.connectedCallback();
|
|
2330
|
+
this.renderIcon();
|
|
2331
|
+
}
|
|
2332
|
+
attributeChangedCallback() {
|
|
2333
|
+
super.attributeChangedCallback();
|
|
2334
|
+
this.renderIcon();
|
|
2335
|
+
}
|
|
2336
|
+
renderIcon() {
|
|
2337
|
+
const name = this.getAttribute('name');
|
|
2338
|
+
const label = this.getAttribute('label') ?? this.getAttribute('aria-label');
|
|
2339
|
+
const isDecorative = this.hasAttribute('decorative') || !label;
|
|
2340
|
+
if (name) {
|
|
2341
|
+
const svg = renderLumenIconSvg(name);
|
|
2342
|
+
if (svg !== this.innerHTML) {
|
|
2343
|
+
this.innerHTML = svg;
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
if (isDecorative) {
|
|
2347
|
+
this.setAttribute('aria-hidden', 'true');
|
|
2348
|
+
this.removeAttribute('aria-label');
|
|
2349
|
+
this.removeAttribute('role');
|
|
2350
|
+
}
|
|
2351
|
+
else {
|
|
2352
|
+
this.removeAttribute('aria-hidden');
|
|
2353
|
+
this.setAttribute('aria-label', label);
|
|
2354
|
+
this.setAttribute('role', 'img');
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
class LumenDialogBehaviorElement extends LumenElement {
|
|
2359
|
+
abortController;
|
|
2360
|
+
lastTrigger;
|
|
2361
|
+
connectedCallback() {
|
|
2362
|
+
super.connectedCallback();
|
|
2363
|
+
if (!hasDocument())
|
|
2364
|
+
return;
|
|
2365
|
+
this.abortController?.abort();
|
|
2366
|
+
this.abortController = new AbortController();
|
|
2367
|
+
this.setupDialog(this.abortController.signal);
|
|
2368
|
+
}
|
|
2369
|
+
disconnectedCallback() {
|
|
2370
|
+
this.abortController?.abort();
|
|
2371
|
+
this.abortController = undefined;
|
|
2372
|
+
}
|
|
2373
|
+
show(trigger) {
|
|
2374
|
+
this.openDialog(trigger);
|
|
2375
|
+
}
|
|
2376
|
+
close() {
|
|
2377
|
+
this.closeDialog();
|
|
2378
|
+
}
|
|
2379
|
+
get isAlertDialog() {
|
|
2380
|
+
return this.hasAttribute('data-ui-alert-dialog') || this.tagName.toLowerCase() === 'lumen-alert-dialog';
|
|
2381
|
+
}
|
|
2382
|
+
get nativeDialog() {
|
|
2383
|
+
const dialog = this.querySelector('dialog');
|
|
2384
|
+
return dialog instanceof HTMLDialogElement ? dialog : null;
|
|
2385
|
+
}
|
|
2386
|
+
get dialogRoot() {
|
|
2387
|
+
return this.nativeDialog ?? this;
|
|
2388
|
+
}
|
|
2389
|
+
setupDialog(signal) {
|
|
2390
|
+
const dialog = this.dialogRoot;
|
|
2391
|
+
const initiallyOpen = this.hasAttribute('open') || this.nativeDialog?.open === true;
|
|
2392
|
+
dialog.setAttribute('aria-modal', 'true');
|
|
2393
|
+
dialog.setAttribute('role', this.isAlertDialog ? 'alertdialog' : 'dialog');
|
|
2394
|
+
if (!this.nativeDialog) {
|
|
2395
|
+
this.hidden = !initiallyOpen;
|
|
2396
|
+
this.dataset.state = initiallyOpen ? 'open' : 'closed';
|
|
2397
|
+
}
|
|
2398
|
+
this.nativeDialog?.addEventListener('click', event => {
|
|
2399
|
+
if (event.target === this.nativeDialog && !this.isAlertDialog) {
|
|
2400
|
+
this.closeDialog();
|
|
2401
|
+
}
|
|
2402
|
+
}, { signal });
|
|
2403
|
+
this.nativeDialog?.addEventListener('close', () => { this.returnFocus(); }, { signal });
|
|
2404
|
+
this.addEventListener('click', event => {
|
|
2405
|
+
const target = getOwnedTarget(event);
|
|
2406
|
+
const closeButton = target instanceof Element
|
|
2407
|
+
? target.closest('[data-ui-dialog-close], [data-ui-alert-dialog-close]')
|
|
2408
|
+
: null;
|
|
2409
|
+
if (closeButton) {
|
|
2410
|
+
this.closeDialog();
|
|
2411
|
+
}
|
|
2412
|
+
}, { signal });
|
|
2413
|
+
this.addEventListener('keydown', event => {
|
|
2414
|
+
if (event.key === 'Escape') {
|
|
2415
|
+
event.preventDefault();
|
|
2416
|
+
this.closeDialog();
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
if (event.key !== 'Tab')
|
|
2420
|
+
return;
|
|
2421
|
+
this.trapFocus(event);
|
|
2422
|
+
}, { signal });
|
|
2423
|
+
document.addEventListener('click', event => {
|
|
2424
|
+
const target = getOwnedTarget(event);
|
|
2425
|
+
const trigger = target instanceof Element
|
|
2426
|
+
? target.closest('[data-ui-dialog-trigger], [data-ui-alert-dialog-trigger]')
|
|
2427
|
+
: null;
|
|
2428
|
+
const targetId = trigger?.dataset.uiDialogTrigger ?? trigger?.dataset.uiAlertDialogTrigger;
|
|
2429
|
+
if (!trigger || targetId !== this.id)
|
|
2430
|
+
return;
|
|
2431
|
+
this.openDialog(trigger);
|
|
2432
|
+
}, { signal });
|
|
2433
|
+
document.addEventListener('pointerdown', event => {
|
|
2434
|
+
if (!this.isOpen() || this.isAlertDialog)
|
|
2435
|
+
return;
|
|
2436
|
+
const target = getOwnedTarget(event);
|
|
2437
|
+
if (!target || this.contains(target))
|
|
2438
|
+
return;
|
|
2439
|
+
this.closeDialog();
|
|
2440
|
+
}, { signal });
|
|
2441
|
+
}
|
|
2442
|
+
isOpen() {
|
|
2443
|
+
return this.nativeDialog?.open === true || this.hasAttribute('open') || this.dataset.state === 'open';
|
|
2444
|
+
}
|
|
2445
|
+
openDialog(trigger) {
|
|
2446
|
+
if (trigger) {
|
|
2447
|
+
if (!trigger.id) {
|
|
2448
|
+
trigger.id = createId('ui-trigger');
|
|
2449
|
+
}
|
|
2450
|
+
this.lastTrigger = trigger;
|
|
2451
|
+
}
|
|
2452
|
+
const dialog = this.nativeDialog;
|
|
2453
|
+
if (dialog) {
|
|
2454
|
+
if (typeof dialog.showModal === 'function' && !dialog.open) {
|
|
2455
|
+
dialog.showModal();
|
|
2456
|
+
}
|
|
2457
|
+
else {
|
|
2458
|
+
dialog.setAttribute('open', '');
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
else {
|
|
2462
|
+
this.hidden = false;
|
|
2463
|
+
this.setAttribute('open', '');
|
|
2464
|
+
this.dataset.state = 'open';
|
|
2465
|
+
}
|
|
2466
|
+
getFocusable(this.dialogRoot)[0]?.focus({ preventScroll: true });
|
|
2467
|
+
}
|
|
2468
|
+
closeDialog() {
|
|
2469
|
+
const dialog = this.nativeDialog;
|
|
2470
|
+
if (dialog?.open) {
|
|
2471
|
+
dialog.close();
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
if (!dialog) {
|
|
2475
|
+
this.hidden = true;
|
|
2476
|
+
this.removeAttribute('open');
|
|
2477
|
+
this.dataset.state = 'closed';
|
|
2478
|
+
this.returnFocus();
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
returnFocus() {
|
|
2482
|
+
this.lastTrigger?.focus({ preventScroll: true });
|
|
2483
|
+
}
|
|
2484
|
+
trapFocus(event) {
|
|
2485
|
+
const focusable = getFocusable(this.dialogRoot);
|
|
2486
|
+
if (!focusable.length)
|
|
2487
|
+
return;
|
|
2488
|
+
const first = focusable[0];
|
|
2489
|
+
const last = focusable.at(-1);
|
|
2490
|
+
if (!first || !last)
|
|
2491
|
+
return;
|
|
2492
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
2493
|
+
event.preventDefault();
|
|
2494
|
+
last.focus();
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
if (!event.shiftKey && document.activeElement === last) {
|
|
2498
|
+
event.preventDefault();
|
|
2499
|
+
first.focus();
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
class LumenDisclosureBehaviorElement extends LumenElement {
|
|
2504
|
+
abortController;
|
|
2505
|
+
connectedCallback() {
|
|
2506
|
+
super.connectedCallback();
|
|
2507
|
+
if (!hasDocument())
|
|
2508
|
+
return;
|
|
2509
|
+
this.abortController?.abort();
|
|
2510
|
+
this.abortController = new AbortController();
|
|
2511
|
+
this.setupDisclosure(this.abortController.signal);
|
|
2512
|
+
}
|
|
2513
|
+
disconnectedCallback() {
|
|
2514
|
+
this.abortController?.abort();
|
|
2515
|
+
this.abortController = undefined;
|
|
2516
|
+
}
|
|
2517
|
+
setupDisclosure(signal) {
|
|
2518
|
+
const trigger = this.querySelector('[data-ui-trigger]');
|
|
2519
|
+
if (!trigger)
|
|
2520
|
+
return;
|
|
2521
|
+
const panel = getControlledPanel(trigger);
|
|
2522
|
+
if (!panel)
|
|
2523
|
+
return;
|
|
2524
|
+
trigger.setAttribute('aria-haspopup', trigger.getAttribute('aria-haspopup') ?? 'menu');
|
|
2525
|
+
setDisclosureOpen(trigger, panel, trigger.getAttribute('aria-expanded') === 'true');
|
|
2526
|
+
const close = () => { setDisclosureOpen(trigger, panel, false); };
|
|
2527
|
+
const open = () => { setDisclosureOpen(trigger, panel, true); };
|
|
2528
|
+
trigger.addEventListener('click', () => {
|
|
2529
|
+
setDisclosureOpen(trigger, panel, trigger.getAttribute('aria-expanded') !== 'true');
|
|
2530
|
+
}, { signal });
|
|
2531
|
+
trigger.addEventListener('keydown', event => {
|
|
2532
|
+
if (event.key !== 'ArrowDown' && event.key !== 'Enter' && event.key !== ' ')
|
|
2533
|
+
return;
|
|
2534
|
+
event.preventDefault();
|
|
2535
|
+
open();
|
|
2536
|
+
getFocusable(panel)[0]?.focus();
|
|
2537
|
+
}, { signal });
|
|
2538
|
+
panel.addEventListener('keydown', event => {
|
|
2539
|
+
if (event.key === 'Escape') {
|
|
2540
|
+
event.preventDefault();
|
|
2541
|
+
close();
|
|
2542
|
+
trigger.focus({ preventScroll: true });
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
2545
|
+
const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
|
|
2546
|
+
if (!keys.includes(event.key))
|
|
2547
|
+
return;
|
|
2548
|
+
const items = getFocusable(panel);
|
|
2549
|
+
if (!items.length)
|
|
2550
|
+
return;
|
|
2551
|
+
event.preventDefault();
|
|
2552
|
+
const currentIndex = items.indexOf(document.activeElement);
|
|
2553
|
+
const nextItem = items[getLoopedIndex(event.key, Math.max(0, currentIndex), items.length, ['ArrowDown'])];
|
|
2554
|
+
nextItem?.focus();
|
|
2555
|
+
}, { signal });
|
|
2556
|
+
document.addEventListener('pointerdown', event => {
|
|
2557
|
+
if (trigger.getAttribute('aria-expanded') !== 'true')
|
|
2558
|
+
return;
|
|
2559
|
+
const target = getOwnedTarget(event);
|
|
2560
|
+
if (!target || this.contains(target) || panel.contains(target))
|
|
2561
|
+
return;
|
|
2562
|
+
close();
|
|
2563
|
+
}, { signal });
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
class LumenTabsBehaviorElement extends LumenElement {
|
|
2567
|
+
abortController;
|
|
2568
|
+
connectedCallback() {
|
|
2569
|
+
super.connectedCallback();
|
|
2570
|
+
if (!hasDocument())
|
|
2571
|
+
return;
|
|
2572
|
+
this.abortController?.abort();
|
|
2573
|
+
this.abortController = new AbortController();
|
|
2574
|
+
this.setupTabs(this.abortController.signal);
|
|
2575
|
+
}
|
|
2576
|
+
disconnectedCallback() {
|
|
2577
|
+
this.abortController?.abort();
|
|
2578
|
+
this.abortController = undefined;
|
|
2579
|
+
}
|
|
2580
|
+
setupTabs(signal) {
|
|
2581
|
+
const tabs = [...this.querySelectorAll('[role="tab"]')];
|
|
2582
|
+
const panels = [...this.querySelectorAll('[role="tabpanel"]')];
|
|
2583
|
+
if (!tabs.length || !panels.length)
|
|
2584
|
+
return;
|
|
2585
|
+
const activate = (tab) => {
|
|
2586
|
+
for (const item of tabs) {
|
|
2587
|
+
const selected = item === tab;
|
|
2588
|
+
const panelId = item.getAttribute('aria-controls');
|
|
2589
|
+
const panel = panelId ? document.getElementById(panelId) : null;
|
|
2590
|
+
item.setAttribute('aria-selected', String(selected));
|
|
2591
|
+
item.tabIndex = selected ? 0 : -1;
|
|
2592
|
+
if (panel) {
|
|
2593
|
+
panel.hidden = !selected;
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
activate(tabs.find(tab => tab.getAttribute('aria-selected') === 'true') ?? tabs[0]);
|
|
2598
|
+
for (const tab of tabs) {
|
|
2599
|
+
tab.addEventListener('click', () => { activate(tab); }, { signal });
|
|
2600
|
+
tab.addEventListener('keydown', event => {
|
|
2601
|
+
const keys = ['ArrowLeft', 'ArrowRight', 'Home', 'End'];
|
|
2602
|
+
if (!keys.includes(event.key))
|
|
2603
|
+
return;
|
|
2604
|
+
event.preventDefault();
|
|
2605
|
+
const currentIndex = tabs.indexOf(tab);
|
|
2606
|
+
const nextTab = tabs[getLoopedIndex(event.key, Math.max(0, currentIndex), tabs.length, ['ArrowRight'])];
|
|
2607
|
+
if (nextTab) {
|
|
2608
|
+
activate(nextTab);
|
|
2609
|
+
nextTab.focus();
|
|
2610
|
+
}
|
|
2611
|
+
}, { signal });
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
class LumenSelectBehaviorElement extends LumenElement {
|
|
2616
|
+
abortController;
|
|
2617
|
+
typeahead = '';
|
|
2618
|
+
typeaheadTimer;
|
|
2619
|
+
connectedCallback() {
|
|
2620
|
+
super.connectedCallback();
|
|
2621
|
+
if (!hasDocument())
|
|
2622
|
+
return;
|
|
2623
|
+
this.dataset.uiSelect = '';
|
|
2624
|
+
this.abortController?.abort();
|
|
2625
|
+
this.abortController = new AbortController();
|
|
2626
|
+
this.setupSelect(this.abortController.signal);
|
|
2627
|
+
}
|
|
2628
|
+
disconnectedCallback() {
|
|
2629
|
+
this.abortController?.abort();
|
|
2630
|
+
this.abortController = undefined;
|
|
2631
|
+
if (this.typeaheadTimer) {
|
|
2632
|
+
globalThis.clearTimeout(this.typeaheadTimer);
|
|
2633
|
+
this.typeaheadTimer = undefined;
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
setupSelect(signal) {
|
|
2637
|
+
const select = this.ensureNativeSelect();
|
|
2638
|
+
const control = this.ensureControl(select);
|
|
2639
|
+
const trigger = control.querySelector('[data-ui-select-trigger]');
|
|
2640
|
+
const listbox = control.querySelector('[data-ui-select-list]');
|
|
2641
|
+
if (!trigger || !listbox)
|
|
2642
|
+
return;
|
|
2643
|
+
this.renderOptions(select, listbox);
|
|
2644
|
+
control.hidden = false;
|
|
2645
|
+
select.dataset.uiEnhanced = 'true';
|
|
2646
|
+
select.dataset.uiSelectNative = '';
|
|
2647
|
+
select.tabIndex = -1;
|
|
2648
|
+
select.setAttribute('aria-hidden', 'true');
|
|
2649
|
+
trigger.disabled = select.disabled;
|
|
2650
|
+
trigger.setAttribute('aria-haspopup', 'listbox');
|
|
2651
|
+
trigger.setAttribute('role', 'combobox');
|
|
2652
|
+
for (const attribute of ['aria-label', 'aria-labelledby', 'aria-describedby', 'aria-invalid']) {
|
|
2653
|
+
const value = select.getAttribute(attribute);
|
|
2654
|
+
if (value) {
|
|
2655
|
+
trigger.setAttribute(attribute, value);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
if (select.required) {
|
|
2659
|
+
trigger.setAttribute('aria-required', 'true');
|
|
2660
|
+
}
|
|
2661
|
+
if (!trigger.hasAttribute('aria-label') && !trigger.hasAttribute('aria-labelledby')) {
|
|
2662
|
+
const label = [...select.labels].find(item => item.textContent.trim());
|
|
2663
|
+
if (label?.textContent) {
|
|
2664
|
+
trigger.setAttribute('aria-label', label.textContent.trim());
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
for (const label of select.labels) {
|
|
2668
|
+
label.addEventListener('click', event => {
|
|
2669
|
+
event.preventDefault();
|
|
2670
|
+
trigger.focus({ preventScroll: true });
|
|
2671
|
+
}, { signal });
|
|
2672
|
+
}
|
|
2673
|
+
this.syncValue(select, trigger);
|
|
2674
|
+
this.closeSelect(trigger, listbox);
|
|
2675
|
+
trigger.addEventListener('click', () => {
|
|
2676
|
+
if (trigger.getAttribute('aria-expanded') === 'true') {
|
|
2677
|
+
this.closeSelect(trigger, listbox);
|
|
2678
|
+
}
|
|
2679
|
+
else {
|
|
2680
|
+
this.openSelect(trigger, listbox);
|
|
2681
|
+
}
|
|
2682
|
+
}, { signal });
|
|
2683
|
+
trigger.addEventListener('keydown', event => {
|
|
2684
|
+
if (this.handleTypeahead(event))
|
|
2685
|
+
return;
|
|
2686
|
+
if (event.key === 'Escape') {
|
|
2687
|
+
this.closeSelect(trigger, listbox);
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter', ' '];
|
|
2691
|
+
if (!keys.includes(event.key))
|
|
2692
|
+
return;
|
|
2693
|
+
event.preventDefault();
|
|
2694
|
+
this.openSelect(trigger, listbox);
|
|
2695
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
2696
|
+
(this.getSelectedItem() ?? this.getEnabledItems()[0])?.focus();
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
this.focusOption(event.key);
|
|
2700
|
+
}, { signal });
|
|
2701
|
+
select.addEventListener('change', () => { this.syncValue(select, trigger); }, { signal });
|
|
2702
|
+
select.form?.addEventListener('reset', () => {
|
|
2703
|
+
globalThis.setTimeout(() => { this.syncValue(select, trigger); });
|
|
2704
|
+
}, { signal });
|
|
2705
|
+
for (const item of this.getItems()) {
|
|
2706
|
+
item.tabIndex = -1;
|
|
2707
|
+
item.addEventListener('click', () => { this.selectOption(select, trigger, listbox, item); }, { signal });
|
|
2708
|
+
item.addEventListener('keydown', event => {
|
|
2709
|
+
if (this.handleTypeahead(event, item))
|
|
2710
|
+
return;
|
|
2711
|
+
if (event.key === 'Escape') {
|
|
2712
|
+
this.closeSelect(trigger, listbox);
|
|
2713
|
+
trigger.focus({ preventScroll: true });
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
2717
|
+
event.preventDefault();
|
|
2718
|
+
this.selectOption(select, trigger, listbox, item);
|
|
2719
|
+
return;
|
|
2720
|
+
}
|
|
2721
|
+
const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
|
|
2722
|
+
if (!keys.includes(event.key))
|
|
2723
|
+
return;
|
|
2724
|
+
event.preventDefault();
|
|
2725
|
+
this.focusOption(event.key, item);
|
|
2726
|
+
}, { signal });
|
|
2727
|
+
}
|
|
2728
|
+
document.addEventListener('pointerdown', event => {
|
|
2729
|
+
if (trigger.getAttribute('aria-expanded') !== 'true')
|
|
2730
|
+
return;
|
|
2731
|
+
const target = getOwnedTarget(event);
|
|
2732
|
+
if (!target || this.contains(target))
|
|
2733
|
+
return;
|
|
2734
|
+
this.closeSelect(trigger, listbox);
|
|
2735
|
+
}, { signal });
|
|
2736
|
+
}
|
|
2737
|
+
ensureNativeSelect() {
|
|
2738
|
+
const existing = this.querySelector('[data-ui-select-native], select');
|
|
2739
|
+
if (existing)
|
|
2740
|
+
return existing;
|
|
2741
|
+
const select = document.createElement('select');
|
|
2742
|
+
const directOptions = [...this.children].filter((child) => child instanceof HTMLOptionElement);
|
|
2743
|
+
select.className = 'ui-select ui-select__native';
|
|
2744
|
+
select.dataset.uiSelectNative = '';
|
|
2745
|
+
for (const attribute of ['id', 'name', 'required', 'disabled', 'value']) {
|
|
2746
|
+
const value = this.getAttribute(attribute);
|
|
2747
|
+
if (value !== null) {
|
|
2748
|
+
select.setAttribute(attribute, value);
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
for (const option of directOptions) {
|
|
2752
|
+
select.append(option);
|
|
2753
|
+
}
|
|
2754
|
+
this.prepend(select);
|
|
2755
|
+
return select;
|
|
2756
|
+
}
|
|
2757
|
+
ensureControl(select) {
|
|
2758
|
+
const existing = this.querySelector('[data-ui-select-control]');
|
|
2759
|
+
if (existing)
|
|
2760
|
+
return existing;
|
|
2761
|
+
const control = document.createElement('div');
|
|
2762
|
+
const trigger = document.createElement('button');
|
|
2763
|
+
const value = document.createElement('span');
|
|
2764
|
+
const listbox = document.createElement('div');
|
|
2765
|
+
const selectId = select.id || createId('ui-select');
|
|
2766
|
+
select.id = selectId;
|
|
2767
|
+
control.className = 'ui-select__control';
|
|
2768
|
+
control.dataset.uiSelectControl = '';
|
|
2769
|
+
control.hidden = true;
|
|
2770
|
+
trigger.className = 'ui-select ui-select__trigger';
|
|
2771
|
+
trigger.dataset.uiSelectTrigger = '';
|
|
2772
|
+
trigger.id = `${selectId}-trigger`;
|
|
2773
|
+
trigger.type = 'button';
|
|
2774
|
+
trigger.setAttribute('aria-controls', `${selectId}-listbox`);
|
|
2775
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
2776
|
+
trigger.setAttribute('aria-haspopup', 'listbox');
|
|
2777
|
+
value.dataset.uiSelectValue = '';
|
|
2778
|
+
listbox.className = 'ui-select__list';
|
|
2779
|
+
listbox.dataset.uiSelectList = '';
|
|
2780
|
+
listbox.dataset.state = 'closed';
|
|
2781
|
+
listbox.hidden = true;
|
|
2782
|
+
listbox.id = `${selectId}-listbox`;
|
|
2783
|
+
listbox.setAttribute('role', 'listbox');
|
|
2784
|
+
trigger.append(value);
|
|
2785
|
+
control.append(trigger, listbox);
|
|
2786
|
+
select.after(control);
|
|
2787
|
+
return control;
|
|
2788
|
+
}
|
|
2789
|
+
renderOptions(select, listbox) {
|
|
2790
|
+
const items = [...select.options]
|
|
2791
|
+
.filter(option => !option.hasAttribute('data-ui-select-placeholder'))
|
|
2792
|
+
.map(option => {
|
|
2793
|
+
const item = document.createElement('button');
|
|
2794
|
+
item.type = 'button';
|
|
2795
|
+
item.setAttribute('role', 'option');
|
|
2796
|
+
item.dataset.uiSelectOption = '';
|
|
2797
|
+
item.dataset.value = option.value;
|
|
2798
|
+
item.textContent = option.label;
|
|
2799
|
+
if (option.disabled) {
|
|
2800
|
+
item.disabled = true;
|
|
2801
|
+
item.setAttribute('aria-disabled', 'true');
|
|
2802
|
+
}
|
|
2803
|
+
return item;
|
|
2804
|
+
});
|
|
2805
|
+
listbox.replaceChildren(...items);
|
|
2806
|
+
}
|
|
2807
|
+
syncValue(select, trigger) {
|
|
2808
|
+
const value = this.querySelector('[data-ui-select-value]');
|
|
2809
|
+
if (!value)
|
|
2810
|
+
return;
|
|
2811
|
+
const selectedOption = select.selectedOptions[0];
|
|
2812
|
+
const placeholder = select.querySelector('[data-ui-select-placeholder]');
|
|
2813
|
+
const hasSelection = Boolean(selectedOption && !selectedOption.hasAttribute('data-ui-select-placeholder'));
|
|
2814
|
+
const selectedLabel = hasSelection && selectedOption ? selectedOption.label : '';
|
|
2815
|
+
value.textContent = hasSelection
|
|
2816
|
+
? selectedLabel
|
|
2817
|
+
: placeholder?.textContent.trim() || 'Select an option';
|
|
2818
|
+
this.dataset.placeholder = hasSelection ? 'false' : 'true';
|
|
2819
|
+
trigger.setAttribute('aria-expanded', trigger.getAttribute('aria-expanded') ?? 'false');
|
|
2820
|
+
for (const item of this.getItems()) {
|
|
2821
|
+
item.setAttribute('aria-selected', String(hasSelection && item.dataset.value === select.value));
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
openSelect(trigger, listbox) {
|
|
2825
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
2826
|
+
listbox.hidden = false;
|
|
2827
|
+
listbox.dataset.state = 'open';
|
|
2828
|
+
}
|
|
2829
|
+
closeSelect(trigger, listbox) {
|
|
2830
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
2831
|
+
listbox.hidden = true;
|
|
2832
|
+
listbox.dataset.state = 'closed';
|
|
2833
|
+
}
|
|
2834
|
+
getItems() {
|
|
2835
|
+
return [...this.querySelectorAll(selectOptionSelector)];
|
|
2836
|
+
}
|
|
2837
|
+
getEnabledItems() {
|
|
2838
|
+
return this.getItems().filter(item => !item.hasAttribute('disabled') && item.getAttribute('aria-disabled') !== 'true');
|
|
2839
|
+
}
|
|
2840
|
+
getSelectedItem() {
|
|
2841
|
+
return this.getEnabledItems().find(item => item.getAttribute('aria-selected') === 'true');
|
|
2842
|
+
}
|
|
2843
|
+
selectOption(select, trigger, listbox, item) {
|
|
2844
|
+
if (item.hasAttribute('disabled') || item.getAttribute('aria-disabled') === 'true')
|
|
2845
|
+
return;
|
|
2846
|
+
select.value = item.dataset.value ?? '';
|
|
2847
|
+
select.dispatchEvent(new Event('input', { bubbles: true }));
|
|
2848
|
+
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
2849
|
+
this.syncValue(select, trigger);
|
|
2850
|
+
this.closeSelect(trigger, listbox);
|
|
2851
|
+
trigger.focus({ preventScroll: true });
|
|
2852
|
+
}
|
|
2853
|
+
focusOption(key, currentItem) {
|
|
2854
|
+
const items = this.getEnabledItems();
|
|
2855
|
+
if (!items.length)
|
|
2856
|
+
return;
|
|
2857
|
+
const current = currentItem ?? this.getSelectedItem() ?? items[0];
|
|
2858
|
+
if (!current)
|
|
2859
|
+
return;
|
|
2860
|
+
const currentIndex = items.indexOf(current);
|
|
2861
|
+
const nextItem = items[getLoopedIndex(key, Math.max(0, currentIndex), items.length, ['ArrowDown'])];
|
|
2862
|
+
nextItem?.focus();
|
|
2863
|
+
}
|
|
2864
|
+
handleTypeahead(event, currentItem) {
|
|
2865
|
+
if (!isPrintableKey(event))
|
|
2866
|
+
return false;
|
|
2867
|
+
event.preventDefault();
|
|
2868
|
+
this.typeahead += event.key.toLowerCase();
|
|
2869
|
+
if (this.typeaheadTimer) {
|
|
2870
|
+
globalThis.clearTimeout(this.typeaheadTimer);
|
|
2871
|
+
}
|
|
2872
|
+
this.typeaheadTimer = globalThis.setTimeout(() => {
|
|
2873
|
+
this.typeahead = '';
|
|
2874
|
+
}, 700);
|
|
2875
|
+
const trigger = this.querySelector('[data-ui-select-trigger]');
|
|
2876
|
+
const listbox = this.querySelector('[data-ui-select-list]');
|
|
2877
|
+
if (trigger && listbox) {
|
|
2878
|
+
this.openSelect(trigger, listbox);
|
|
2879
|
+
}
|
|
2880
|
+
this.focusTypeaheadOption(currentItem);
|
|
2881
|
+
return true;
|
|
2882
|
+
}
|
|
2883
|
+
focusTypeaheadOption(currentItem) {
|
|
2884
|
+
const items = this.getEnabledItems();
|
|
2885
|
+
if (!items.length || !this.typeahead)
|
|
2886
|
+
return;
|
|
2887
|
+
const current = currentItem ?? this.getSelectedItem() ?? items[0];
|
|
2888
|
+
if (!current)
|
|
2889
|
+
return;
|
|
2890
|
+
const currentIndex = items.indexOf(current);
|
|
2891
|
+
const startIndex = Math.max(0, currentIndex + 1);
|
|
2892
|
+
const orderedItems = [...items.slice(startIndex), ...items.slice(0, startIndex)];
|
|
2893
|
+
const nextItem = orderedItems.find(item => item.textContent.trim().toLowerCase().startsWith(this.typeahead));
|
|
2894
|
+
nextItem?.focus();
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
class LumenDataTableBehaviorElement extends LumenElement {
|
|
2898
|
+
abortController;
|
|
2899
|
+
connectedCallback() {
|
|
2900
|
+
super.connectedCallback();
|
|
2901
|
+
if (!hasDocument())
|
|
2902
|
+
return;
|
|
2903
|
+
if (this.hasAttribute('selectable') && this.dataset.uiDatatableSelectable !== 'true') {
|
|
2904
|
+
this.dataset.uiDatatableSelectable = 'true';
|
|
2905
|
+
}
|
|
2906
|
+
if (this.hasAttribute('name') && !this.dataset.uiDatatableName) {
|
|
2907
|
+
this.dataset.uiDatatableName = this.getAttribute('name') ?? '';
|
|
2908
|
+
}
|
|
2909
|
+
this.abortController?.abort();
|
|
2910
|
+
this.abortController = new AbortController();
|
|
2911
|
+
this.setupDataTable(this.abortController.signal);
|
|
2912
|
+
}
|
|
2913
|
+
disconnectedCallback() {
|
|
2914
|
+
this.abortController?.abort();
|
|
2915
|
+
this.abortController = undefined;
|
|
2916
|
+
}
|
|
2917
|
+
setupDataTable(signal) {
|
|
2918
|
+
const table = this.querySelector('table');
|
|
2919
|
+
if (!table)
|
|
2920
|
+
return;
|
|
2921
|
+
this.setupSelection(table, signal);
|
|
2922
|
+
this.setupSorting(table, signal);
|
|
2923
|
+
}
|
|
2924
|
+
setupSorting(table, signal) {
|
|
2925
|
+
const body = table.tBodies[0];
|
|
2926
|
+
if (!body)
|
|
2927
|
+
return;
|
|
2928
|
+
for (const [index, row] of [...body.rows].entries()) {
|
|
2929
|
+
row.dataset.uiDatatableIndex = row.dataset.uiDatatableIndex ?? String(index);
|
|
2930
|
+
}
|
|
2931
|
+
const headers = [...table.querySelectorAll('thead th')].filter(header => header.dataset.uiDatatableSortable === 'true' ||
|
|
2932
|
+
header.dataset.sortable === 'true' ||
|
|
2933
|
+
header.hasAttribute('data-sortable'));
|
|
2934
|
+
for (const header of headers) {
|
|
2935
|
+
const row = header.parentElement;
|
|
2936
|
+
const columnIndex = row ? [...row.children].indexOf(header) : -1;
|
|
2937
|
+
if (columnIndex < 0)
|
|
2938
|
+
continue;
|
|
2939
|
+
header.dataset.uiDatatableColumn = String(columnIndex);
|
|
2940
|
+
header.setAttribute('aria-sort', header.getAttribute('aria-sort') ?? 'none');
|
|
2941
|
+
const button = this.ensureSortButton(header);
|
|
2942
|
+
button.addEventListener('click', () => {
|
|
2943
|
+
const nextDirection = getNextDataTableSortDirection(this.dataset.uiDatatableSortColumn, columnIndex, header.getAttribute('aria-sort'));
|
|
2944
|
+
this.updateSort(table, header, nextDirection);
|
|
2945
|
+
this.sortRows(table, header, columnIndex, nextDirection);
|
|
2946
|
+
}, { signal });
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
ensureSortButton(header) {
|
|
2950
|
+
const existing = header.querySelector('[data-ui-datatable-sort]');
|
|
2951
|
+
if (existing)
|
|
2952
|
+
return existing;
|
|
2953
|
+
const button = document.createElement('button');
|
|
2954
|
+
button.type = 'button';
|
|
2955
|
+
button.className = 'ui-data-table__sort';
|
|
2956
|
+
button.dataset.uiDatatableSort = '';
|
|
2957
|
+
while (header.firstChild) {
|
|
2958
|
+
button.append(header.firstChild);
|
|
2959
|
+
}
|
|
2960
|
+
header.append(button);
|
|
2961
|
+
return button;
|
|
2962
|
+
}
|
|
2963
|
+
updateSort(table, header, direction) {
|
|
2964
|
+
for (const item of table.querySelectorAll('thead th[aria-sort]')) {
|
|
2965
|
+
item.setAttribute('aria-sort', item === header ? direction : 'none');
|
|
2966
|
+
item.dataset.sort = item === header ? direction : 'none';
|
|
2967
|
+
}
|
|
2968
|
+
this.dataset.uiDatatableSortDirection = direction;
|
|
2969
|
+
if (direction === 'none') {
|
|
2970
|
+
delete this.dataset.uiDatatableSortColumn;
|
|
2971
|
+
}
|
|
2972
|
+
else {
|
|
2973
|
+
this.dataset.uiDatatableSortColumn = header.dataset.uiDatatableColumn ?? '';
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
sortRows(table, header, columnIndex, direction) {
|
|
2977
|
+
const body = table.tBodies[0];
|
|
2978
|
+
if (!body)
|
|
2979
|
+
return;
|
|
2980
|
+
const rows = [...body.rows];
|
|
2981
|
+
const sortType = header.dataset.uiDatatableSortType;
|
|
2982
|
+
rows.sort((row, other) => {
|
|
2983
|
+
if (direction === 'none') {
|
|
2984
|
+
return Number(row.dataset.uiDatatableIndex ?? '0') - Number(other.dataset.uiDatatableIndex ?? '0');
|
|
2985
|
+
}
|
|
2986
|
+
const result = compareDataTableValues(getDataTableSortValue(row.cells[columnIndex]), getDataTableSortValue(other.cells[columnIndex]), sortType);
|
|
2987
|
+
return (direction === 'ascending' ? result : -result) ||
|
|
2988
|
+
Number(row.dataset.uiDatatableIndex ?? '0') - Number(other.dataset.uiDatatableIndex ?? '0');
|
|
2989
|
+
});
|
|
2990
|
+
for (const row of rows) {
|
|
2991
|
+
body.append(row);
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
setupSelection(table, signal) {
|
|
2995
|
+
const selectable = this.dataset.uiDatatableSelectable === 'true' || this.hasAttribute('selectable');
|
|
2996
|
+
if (!selectable)
|
|
2997
|
+
return;
|
|
2998
|
+
const headRow = table.tHead?.rows[0];
|
|
2999
|
+
const body = table.tBodies[0];
|
|
3000
|
+
if (!headRow || !body)
|
|
3001
|
+
return;
|
|
3002
|
+
const selectAll = this.ensureSelectAll(headRow);
|
|
3003
|
+
for (const [index, row] of getDataTableRows(table).entries()) {
|
|
3004
|
+
const checkbox = this.ensureRowSelect(row, index);
|
|
3005
|
+
checkbox.addEventListener('change', () => { this.syncSelection(table, selectAll, true); }, { signal });
|
|
3006
|
+
}
|
|
3007
|
+
selectAll.addEventListener('change', () => {
|
|
3008
|
+
for (const checkbox of table.querySelectorAll('[data-ui-datatable-row-select]')) {
|
|
3009
|
+
checkbox.checked = selectAll.checked;
|
|
3010
|
+
}
|
|
3011
|
+
this.syncSelection(table, selectAll, true);
|
|
3012
|
+
}, { signal });
|
|
3013
|
+
this.closest('form')?.addEventListener('reset', () => {
|
|
3014
|
+
globalThis.setTimeout(() => { this.syncSelection(table, selectAll, true); });
|
|
3015
|
+
}, { signal });
|
|
3016
|
+
this.syncSelection(table, selectAll);
|
|
3017
|
+
}
|
|
3018
|
+
ensureSelectAll(headRow) {
|
|
3019
|
+
const existing = headRow.querySelector('[data-ui-datatable-select-all]');
|
|
3020
|
+
if (existing)
|
|
3021
|
+
return existing;
|
|
3022
|
+
const headerCell = document.createElement('th');
|
|
3023
|
+
const selectAll = document.createElement('input');
|
|
3024
|
+
headerCell.className = 'ui-data-table__selection';
|
|
3025
|
+
headerCell.scope = 'col';
|
|
3026
|
+
selectAll.type = 'checkbox';
|
|
3027
|
+
selectAll.className = 'ui-data-table__checkbox';
|
|
3028
|
+
selectAll.dataset.uiDatatableSelectAll = '';
|
|
3029
|
+
selectAll.setAttribute('aria-label', 'Select all rows');
|
|
3030
|
+
headerCell.append(selectAll);
|
|
3031
|
+
headRow.prepend(headerCell);
|
|
3032
|
+
return selectAll;
|
|
3033
|
+
}
|
|
3034
|
+
ensureRowSelect(row, index) {
|
|
3035
|
+
const existing = row.querySelector('[data-ui-datatable-row-select]');
|
|
3036
|
+
row.dataset.uiDatatableRow = '';
|
|
3037
|
+
row.dataset.value = getDataTableRowValue(row, index);
|
|
3038
|
+
if (existing)
|
|
3039
|
+
return existing;
|
|
3040
|
+
const cell = document.createElement('td');
|
|
3041
|
+
const checkbox = document.createElement('input');
|
|
3042
|
+
const selected = row.dataset.state === 'selected' || row.getAttribute('aria-selected') === 'true';
|
|
3043
|
+
cell.className = 'ui-data-table__selection';
|
|
3044
|
+
checkbox.type = 'checkbox';
|
|
3045
|
+
checkbox.className = 'ui-data-table__checkbox';
|
|
3046
|
+
checkbox.dataset.uiDatatableRowSelect = '';
|
|
3047
|
+
checkbox.checked = selected;
|
|
3048
|
+
checkbox.defaultChecked = selected;
|
|
3049
|
+
checkbox.setAttribute('aria-label', `Select row ${index + 1}`);
|
|
3050
|
+
cell.append(checkbox);
|
|
3051
|
+
row.prepend(cell);
|
|
3052
|
+
return checkbox;
|
|
3053
|
+
}
|
|
3054
|
+
syncSelection(table, selectAll, dispatch = false) {
|
|
3055
|
+
const rows = getDataTableRows(table);
|
|
3056
|
+
const selectedValues = [];
|
|
3057
|
+
for (const [index, row] of rows.entries()) {
|
|
3058
|
+
const checkbox = row.querySelector('[data-ui-datatable-row-select]');
|
|
3059
|
+
const selected = checkbox?.checked === true;
|
|
3060
|
+
row.setAttribute('aria-selected', String(selected));
|
|
3061
|
+
if (selected) {
|
|
3062
|
+
row.dataset.state = 'selected';
|
|
3063
|
+
selectedValues.push(getDataTableRowValue(row, index));
|
|
3064
|
+
}
|
|
3065
|
+
else {
|
|
3066
|
+
delete row.dataset.state;
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
selectAll.checked = rows.length > 0 && selectedValues.length === rows.length;
|
|
3070
|
+
selectAll.indeterminate = selectedValues.length > 0 && selectedValues.length < rows.length;
|
|
3071
|
+
this.syncSelectionInputs(selectedValues);
|
|
3072
|
+
if (dispatch) {
|
|
3073
|
+
this.dispatchEvent(new CustomEvent('ui:datatable-selection-change', {
|
|
3074
|
+
bubbles: true,
|
|
3075
|
+
detail: { values: selectedValues }
|
|
3076
|
+
}));
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
syncSelectionInputs(selectedValues) {
|
|
3080
|
+
const name = this.dataset.uiDatatableName;
|
|
3081
|
+
let inputs = this.querySelector('[data-ui-datatable-inputs]');
|
|
3082
|
+
if (name) {
|
|
3083
|
+
if (!inputs) {
|
|
3084
|
+
inputs = document.createElement('div');
|
|
3085
|
+
inputs.hidden = true;
|
|
3086
|
+
inputs.dataset.uiDatatableInputs = '';
|
|
3087
|
+
this.append(inputs);
|
|
3088
|
+
}
|
|
3089
|
+
inputs.replaceChildren(...selectedValues.map(value => {
|
|
3090
|
+
const input = document.createElement('input');
|
|
3091
|
+
input.type = 'hidden';
|
|
3092
|
+
input.name = name;
|
|
3093
|
+
input.value = value;
|
|
3094
|
+
return input;
|
|
3095
|
+
}));
|
|
3096
|
+
}
|
|
3097
|
+
else {
|
|
3098
|
+
inputs?.replaceChildren();
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
class LumenVirtualListBehaviorElement extends LumenElement {
|
|
3103
|
+
abortController;
|
|
3104
|
+
connectedCallback() {
|
|
3105
|
+
super.connectedCallback();
|
|
3106
|
+
if (!hasDocument())
|
|
3107
|
+
return;
|
|
3108
|
+
this.abortController?.abort();
|
|
3109
|
+
this.abortController = new AbortController();
|
|
3110
|
+
this.setupVirtualList(this.abortController.signal);
|
|
3111
|
+
}
|
|
3112
|
+
disconnectedCallback() {
|
|
3113
|
+
this.abortController?.abort();
|
|
3114
|
+
this.abortController = undefined;
|
|
3115
|
+
}
|
|
3116
|
+
setupVirtualList(signal) {
|
|
3117
|
+
const items = [...this.children].filter((child) => child instanceof HTMLElement);
|
|
3118
|
+
if (!items.length)
|
|
3119
|
+
return;
|
|
3120
|
+
const update = () => {
|
|
3121
|
+
const overscan = this.getNumberAttribute('data-ui-overscan', 'overscan', 4, 0);
|
|
3122
|
+
const itemSize = this.getNumberAttribute('data-ui-item-size', 'item-size', 44);
|
|
3123
|
+
const range = getVirtualRange(this.scrollTop, this.clientHeight, itemSize, items.length, overscan);
|
|
3124
|
+
for (const [index, item] of items.entries()) {
|
|
3125
|
+
item.hidden = index < range.startIndex || index > range.endIndex;
|
|
3126
|
+
}
|
|
3127
|
+
this.dispatchEvent(new CustomEvent('ui:virtual-list-range', {
|
|
3128
|
+
bubbles: true,
|
|
3129
|
+
detail: range
|
|
3130
|
+
}));
|
|
3131
|
+
};
|
|
3132
|
+
this.addEventListener('scroll', update, { passive: true, signal });
|
|
3133
|
+
update();
|
|
3134
|
+
}
|
|
3135
|
+
getNumberAttribute(dataAttribute, attribute, fallback, minimum = 1) {
|
|
3136
|
+
const value = Number(this.getAttribute(dataAttribute) ?? this.getAttribute(attribute) ?? fallback);
|
|
3137
|
+
return Number.isFinite(value) && value >= minimum ? value : fallback;
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
class LumenThemeBuilderBehaviorElement extends LumenElement {
|
|
3141
|
+
abortController;
|
|
3142
|
+
currentExportFormat = 'css';
|
|
3143
|
+
currentExportValue = '';
|
|
3144
|
+
currentScheme = 'light';
|
|
3145
|
+
currentTokens = null;
|
|
3146
|
+
connectedCallback() {
|
|
3147
|
+
super.connectedCallback();
|
|
3148
|
+
if (!hasDocument())
|
|
3149
|
+
return;
|
|
3150
|
+
this.abortController?.abort();
|
|
3151
|
+
this.abortController = new AbortController();
|
|
3152
|
+
this.setupThemeBuilder(this.abortController.signal);
|
|
3153
|
+
}
|
|
3154
|
+
disconnectedCallback() {
|
|
3155
|
+
this.abortController?.abort();
|
|
3156
|
+
this.abortController = undefined;
|
|
3157
|
+
}
|
|
3158
|
+
setupThemeBuilder(signal) {
|
|
3159
|
+
const brandHue = this.querySelector('[data-ui-theme-brand-hue], [data-ui-theme-hue]');
|
|
3160
|
+
const brandHueNumber = this.querySelector('[data-ui-theme-brand-hue-number]');
|
|
3161
|
+
const accentHue = this.querySelector('[data-ui-theme-accent-hue]');
|
|
3162
|
+
const accentHueNumber = this.querySelector('[data-ui-theme-accent-hue-number]');
|
|
3163
|
+
const primaryColor = this.querySelector('[data-ui-theme-primary-color]');
|
|
3164
|
+
const primaryHex = this.querySelector('[data-ui-theme-primary-hex]');
|
|
3165
|
+
const secondaryColor = this.querySelector('[data-ui-theme-secondary-color]');
|
|
3166
|
+
const secondaryHex = this.querySelector('[data-ui-theme-secondary-hex]');
|
|
3167
|
+
const exportButton = this.querySelector('[data-ui-theme-export]');
|
|
3168
|
+
const output = this.querySelector('[data-ui-theme-output]');
|
|
3169
|
+
const exportFormatButtons = [...this.querySelectorAll('[data-ui-theme-export-format]')];
|
|
3170
|
+
const modeButtons = [...this.querySelectorAll('[data-ui-theme-mode]')];
|
|
3171
|
+
const schemeButtons = [...this.querySelectorAll('[data-ui-theme-scheme]')];
|
|
3172
|
+
const importButton = this.querySelector('[data-ui-theme-import]');
|
|
3173
|
+
const checkContrastButton = this.querySelector('[data-ui-theme-check-contrast]');
|
|
3174
|
+
const scoreElement = this.querySelector('[data-ui-theme-contrast-score]');
|
|
3175
|
+
const statusElement = this.querySelector('[data-ui-theme-contrast-status]');
|
|
3176
|
+
this.currentExportFormat = coerceThemeBuilderExportFormat(this.getButtonValue(exportFormatButtons, 'data-ui-theme-export-format', 'css'));
|
|
3177
|
+
this.currentScheme = coerceThemeBuilderScheme(this.getButtonValue(schemeButtons, 'data-ui-theme-scheme', this.getDefaultScheme()));
|
|
3178
|
+
const updateContrastUi = (tokens) => {
|
|
3179
|
+
if (!scoreElement && !statusElement)
|
|
3180
|
+
return;
|
|
3181
|
+
const score = scoreThemeContrast(tokens, 'ink', 'canvas');
|
|
3182
|
+
if (scoreElement)
|
|
3183
|
+
scoreElement.textContent = String(score.ratio);
|
|
3184
|
+
if (statusElement) {
|
|
3185
|
+
statusElement.textContent = score.wcagAA ? 'Passes WCAG AA' : 'Fails WCAG AA';
|
|
3186
|
+
statusElement.dataset.status = score.wcagAA ? 'pass' : 'fail';
|
|
3187
|
+
}
|
|
3188
|
+
};
|
|
3189
|
+
const update = (dispatch = true) => {
|
|
3190
|
+
const result = createThemeBuilderTokens({
|
|
3191
|
+
accentHue: accentHue?.value ?? accentHueNumber?.value ?? this.getAttribute('data-ui-theme-accent-hue') ?? null,
|
|
3192
|
+
hue: brandHue?.value ?? brandHueNumber?.value ?? this.getAttribute('data-ui-theme-brand-hue') ?? this.getAttribute('data-ui-theme-hue') ?? null,
|
|
3193
|
+
mode: this.getButtonValue(modeButtons, 'data-ui-theme-mode', 'generated'),
|
|
3194
|
+
primaryColor: primaryHex?.value ?? primaryColor?.value ?? null,
|
|
3195
|
+
scheme: this.currentScheme,
|
|
3196
|
+
secondaryColor: secondaryHex?.value ?? secondaryColor?.value ?? null
|
|
3197
|
+
});
|
|
3198
|
+
this.currentScheme = result.scheme;
|
|
3199
|
+
this.currentTokens = result.tokens;
|
|
3200
|
+
const target = this.getTarget();
|
|
3201
|
+
if (target) {
|
|
3202
|
+
this.applyTokens(target, result.tokens, result.scheme);
|
|
3203
|
+
}
|
|
3204
|
+
this.writeExport(output);
|
|
3205
|
+
updateContrastUi(result.tokens);
|
|
3206
|
+
if (dispatch) {
|
|
3207
|
+
this.dispatchEvent(new CustomEvent('ui:theme-change', {
|
|
3208
|
+
bubbles: true,
|
|
3209
|
+
detail: result
|
|
3210
|
+
}));
|
|
3211
|
+
}
|
|
3212
|
+
};
|
|
3213
|
+
this.bindHueInputs(brandHue, brandHueNumber, update, signal);
|
|
3214
|
+
this.bindHueInputs(accentHue, accentHueNumber, update, signal);
|
|
3215
|
+
this.bindHexInputs(primaryColor, primaryHex, update, signal);
|
|
3216
|
+
this.bindHexInputs(secondaryColor, secondaryHex, update, signal);
|
|
3217
|
+
for (const button of exportFormatButtons) {
|
|
3218
|
+
button.addEventListener('click', () => {
|
|
3219
|
+
this.currentExportFormat = coerceThemeBuilderExportFormat(button.getAttribute('data-ui-theme-export-format'));
|
|
3220
|
+
this.setPressedState(exportFormatButtons, 'data-ui-theme-export-format', this.currentExportFormat);
|
|
3221
|
+
this.writeExport(output);
|
|
3222
|
+
}, { signal });
|
|
3223
|
+
}
|
|
3224
|
+
for (const button of modeButtons) {
|
|
3225
|
+
button.addEventListener('click', () => {
|
|
3226
|
+
this.setPressedState(modeButtons, 'data-ui-theme-mode', coerceThemeBuilderMode(button.getAttribute('data-ui-theme-mode')));
|
|
3227
|
+
update();
|
|
3228
|
+
}, { signal });
|
|
3229
|
+
}
|
|
3230
|
+
for (const button of schemeButtons) {
|
|
3231
|
+
button.addEventListener('click', () => {
|
|
3232
|
+
this.currentScheme = coerceThemeBuilderScheme(button.getAttribute('data-ui-theme-scheme'));
|
|
3233
|
+
this.setPressedState(schemeButtons, 'data-ui-theme-scheme', this.currentScheme);
|
|
3234
|
+
update();
|
|
3235
|
+
}, { signal });
|
|
3236
|
+
}
|
|
3237
|
+
exportButton?.addEventListener('click', () => {
|
|
3238
|
+
if (this.currentTokens) {
|
|
3239
|
+
this.writeExport(output);
|
|
3240
|
+
}
|
|
3241
|
+
else {
|
|
3242
|
+
update(false);
|
|
3243
|
+
}
|
|
3244
|
+
const value = output?.value || this.currentExportValue;
|
|
3245
|
+
navigator.clipboard.writeText(value).catch(() => null);
|
|
3246
|
+
this.dispatchEvent(new CustomEvent('ui:theme-export', {
|
|
3247
|
+
bubbles: true,
|
|
3248
|
+
detail: {
|
|
3249
|
+
css: this.currentExportFormat === 'css' ? value : undefined,
|
|
3250
|
+
format: this.currentExportFormat,
|
|
3251
|
+
tokens: this.currentTokens,
|
|
3252
|
+
value
|
|
3253
|
+
}
|
|
3254
|
+
}));
|
|
3255
|
+
}, { signal });
|
|
3256
|
+
importButton?.addEventListener('click', () => {
|
|
3257
|
+
const value = output?.value || '';
|
|
3258
|
+
if (!value)
|
|
3259
|
+
return;
|
|
3260
|
+
const parsed = parseThemeCss(value);
|
|
3261
|
+
if (Object.keys(parsed).length === 0)
|
|
3262
|
+
return;
|
|
3263
|
+
this.currentTokens = parsed;
|
|
3264
|
+
const target = this.getTarget();
|
|
3265
|
+
if (target) {
|
|
3266
|
+
this.applyTokens(target, this.currentTokens, this.currentScheme);
|
|
3267
|
+
}
|
|
3268
|
+
this.writeExport(output);
|
|
3269
|
+
updateContrastUi(this.currentTokens);
|
|
3270
|
+
this.dispatchEvent(new CustomEvent('ui:theme-change', {
|
|
3271
|
+
bubbles: true,
|
|
3272
|
+
detail: {
|
|
3273
|
+
accentHue: 0,
|
|
3274
|
+
hue: 0,
|
|
3275
|
+
mode: 'manual',
|
|
3276
|
+
scheme: this.currentScheme,
|
|
3277
|
+
tokens: this.currentTokens
|
|
3278
|
+
}
|
|
3279
|
+
}));
|
|
3280
|
+
}, { signal });
|
|
3281
|
+
checkContrastButton?.addEventListener('click', () => {
|
|
3282
|
+
if (!this.currentTokens)
|
|
3283
|
+
return;
|
|
3284
|
+
this.currentTokens = tuneThemeContrast(this.currentTokens, 'ink', 'canvas');
|
|
3285
|
+
const target = this.getTarget();
|
|
3286
|
+
if (target) {
|
|
3287
|
+
this.applyTokens(target, this.currentTokens, this.currentScheme);
|
|
3288
|
+
}
|
|
3289
|
+
this.writeExport(output);
|
|
3290
|
+
updateContrastUi(this.currentTokens);
|
|
3291
|
+
this.dispatchEvent(new CustomEvent('ui:theme-change', {
|
|
3292
|
+
bubbles: true,
|
|
3293
|
+
detail: {
|
|
3294
|
+
accentHue: 0,
|
|
3295
|
+
hue: 0,
|
|
3296
|
+
mode: 'manual',
|
|
3297
|
+
scheme: this.currentScheme,
|
|
3298
|
+
tokens: this.currentTokens
|
|
3299
|
+
}
|
|
3300
|
+
}));
|
|
3301
|
+
}, { signal });
|
|
3302
|
+
update(false);
|
|
3303
|
+
}
|
|
3304
|
+
getDefaultScheme() {
|
|
3305
|
+
return document.documentElement.dataset.theme?.includes('dark') ? 'dark' : 'light';
|
|
3306
|
+
}
|
|
3307
|
+
getTarget() {
|
|
3308
|
+
const selector = this.dataset.uiThemeTarget;
|
|
3309
|
+
if (!selector)
|
|
3310
|
+
return document.documentElement;
|
|
3311
|
+
try {
|
|
3312
|
+
return document.querySelector(selector);
|
|
3313
|
+
}
|
|
3314
|
+
catch {
|
|
3315
|
+
return null;
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
getButtonValue(buttons, attribute, fallback) {
|
|
3319
|
+
const activeButton = buttons.find(button => button.getAttribute('aria-selected') === 'true' ||
|
|
3320
|
+
button.getAttribute('aria-pressed') === 'true');
|
|
3321
|
+
return activeButton?.getAttribute(attribute) ?? this.getAttribute(attribute) ?? fallback;
|
|
3322
|
+
}
|
|
3323
|
+
setPressedState(buttons, attribute, value) {
|
|
3324
|
+
for (const button of buttons) {
|
|
3325
|
+
const isPressed = button.getAttribute(attribute) === value;
|
|
3326
|
+
if (button.hasAttribute('aria-pressed')) {
|
|
3327
|
+
button.setAttribute('aria-pressed', String(isPressed));
|
|
3328
|
+
}
|
|
3329
|
+
button.classList.toggle('ui-button--secondary', isPressed);
|
|
3330
|
+
button.classList.toggle('ui-button--outline', !isPressed);
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
bindHueInputs(range, number, onChange, signal) {
|
|
3334
|
+
const sync = (source, target) => {
|
|
3335
|
+
if (target) {
|
|
3336
|
+
target.value = source.value;
|
|
3337
|
+
}
|
|
3338
|
+
onChange();
|
|
3339
|
+
};
|
|
3340
|
+
range?.addEventListener('input', () => { sync(range, number); }, { signal });
|
|
3341
|
+
number?.addEventListener('input', () => { sync(number, range); }, { signal });
|
|
3342
|
+
}
|
|
3343
|
+
bindHexInputs(color, text, onChange, signal) {
|
|
3344
|
+
color?.addEventListener('input', () => {
|
|
3345
|
+
if (text) {
|
|
3346
|
+
text.value = color.value;
|
|
3347
|
+
text.removeAttribute('aria-invalid');
|
|
3348
|
+
}
|
|
3349
|
+
onChange();
|
|
3350
|
+
}, { signal });
|
|
3351
|
+
text?.addEventListener('input', () => {
|
|
3352
|
+
const normalized = normalizeThemeBuilderHex(text.value);
|
|
3353
|
+
if (normalized) {
|
|
3354
|
+
if (color)
|
|
3355
|
+
color.value = normalized;
|
|
3356
|
+
text.removeAttribute('aria-invalid');
|
|
3357
|
+
onChange();
|
|
3358
|
+
}
|
|
3359
|
+
else {
|
|
3360
|
+
text.setAttribute('aria-invalid', 'true');
|
|
3361
|
+
}
|
|
3362
|
+
}, { signal });
|
|
3363
|
+
}
|
|
3364
|
+
applyTokens(target, tokens, scheme) {
|
|
3365
|
+
target.style.colorScheme = scheme;
|
|
3366
|
+
for (const [token, value] of Object.entries(tokens)) {
|
|
3367
|
+
target.style.setProperty(`--${token}`, value);
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
writeExport(output) {
|
|
3371
|
+
if (!this.currentTokens)
|
|
3372
|
+
return;
|
|
3373
|
+
this.currentExportValue = exportThemeBuilderValue(this.currentTokens, this.currentScheme, this.currentExportFormat);
|
|
3374
|
+
if (output) {
|
|
3375
|
+
output.value = this.currentExportValue;
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
class LumenTooltipBehaviorElement extends LumenElement {
|
|
3380
|
+
abortController;
|
|
3381
|
+
showTimer;
|
|
3382
|
+
connectedCallback() {
|
|
3383
|
+
super.connectedCallback();
|
|
3384
|
+
if (!hasDocument())
|
|
3385
|
+
return;
|
|
3386
|
+
this.abortController?.abort();
|
|
3387
|
+
this.abortController = new AbortController();
|
|
3388
|
+
this.setupTooltip(this.abortController.signal);
|
|
3389
|
+
}
|
|
3390
|
+
disconnectedCallback() {
|
|
3391
|
+
this.abortController?.abort();
|
|
3392
|
+
this.abortController = undefined;
|
|
3393
|
+
this.clearTimer();
|
|
3394
|
+
}
|
|
3395
|
+
setupTooltip(signal) {
|
|
3396
|
+
const tip = this.querySelector('[role="tooltip"]');
|
|
3397
|
+
const trigger = this.querySelector('[data-ui-tooltip-trigger], [data-ui-trigger], button, a[href], input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
3398
|
+
if (!tip)
|
|
3399
|
+
return;
|
|
3400
|
+
if (trigger && !trigger.closest('[role="tooltip"]')) {
|
|
3401
|
+
if (!tip.id) {
|
|
3402
|
+
tip.id = createId('ui-tooltip');
|
|
3403
|
+
}
|
|
3404
|
+
const describedBy = new Set((trigger.getAttribute('aria-describedby') ?? '').split(/\s+/).filter(Boolean));
|
|
3405
|
+
describedBy.add(tip.id);
|
|
3406
|
+
trigger.setAttribute('aria-describedby', [...describedBy].join(' '));
|
|
3407
|
+
}
|
|
3408
|
+
const show = (delay) => {
|
|
3409
|
+
this.clearTimer();
|
|
3410
|
+
this.showTimer = globalThis.setTimeout(() => {
|
|
3411
|
+
tip.style.removeProperty('visibility');
|
|
3412
|
+
}, delay);
|
|
3413
|
+
};
|
|
3414
|
+
const hide = () => {
|
|
3415
|
+
this.clearTimer();
|
|
3416
|
+
tip.style.visibility = 'hidden';
|
|
3417
|
+
};
|
|
3418
|
+
this.addEventListener('mouseenter', () => { show(250); }, { signal });
|
|
3419
|
+
this.addEventListener('mouseleave', hide, { signal });
|
|
3420
|
+
this.addEventListener('focusin', () => { show(0); }, { signal });
|
|
3421
|
+
this.addEventListener('focusout', hide, { signal });
|
|
3422
|
+
this.addEventListener('keydown', event => {
|
|
3423
|
+
if (event.key === 'Escape') {
|
|
3424
|
+
hide();
|
|
3425
|
+
}
|
|
3426
|
+
}, { signal });
|
|
3427
|
+
}
|
|
3428
|
+
clearTimer() {
|
|
3429
|
+
if (!this.showTimer)
|
|
3430
|
+
return;
|
|
3431
|
+
globalThis.clearTimeout(this.showTimer);
|
|
3432
|
+
this.showTimer = undefined;
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
class LumenSonnerBehaviorElement extends LumenElement {
|
|
3436
|
+
connectedCallback() {
|
|
3437
|
+
super.connectedCallback();
|
|
3438
|
+
normalizeToastViewport(this);
|
|
3439
|
+
installToastController();
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
class LumenToastBehaviorElement extends LumenElement {
|
|
3443
|
+
abortController;
|
|
3444
|
+
connectedCallback() {
|
|
3445
|
+
super.connectedCallback();
|
|
3446
|
+
this.dataset.uiToast = '';
|
|
3447
|
+
this.setAttribute('role', this.getAttribute('role') ?? (this.getAttribute('variant') === 'destructive' ? 'alert' : 'status'));
|
|
3448
|
+
this.setAttribute('aria-live', this.getAttribute('aria-live') ?? (this.getAttribute('variant') === 'destructive' ? 'assertive' : 'polite'));
|
|
3449
|
+
this.abortController?.abort();
|
|
3450
|
+
this.abortController = new AbortController();
|
|
3451
|
+
this.addEventListener('keydown', event => {
|
|
3452
|
+
if (event.key !== 'Escape')
|
|
3453
|
+
return;
|
|
3454
|
+
event.preventDefault();
|
|
3455
|
+
dismissToastElement(this);
|
|
3456
|
+
}, { signal: this.abortController.signal });
|
|
3457
|
+
}
|
|
3458
|
+
disconnectedCallback() {
|
|
3459
|
+
this.abortController?.abort();
|
|
3460
|
+
this.abortController = undefined;
|
|
3461
|
+
clearToastTimer(this);
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
class LumenBackToTopBehaviorElement extends LumenElement {
|
|
3465
|
+
connectedCallback() {
|
|
3466
|
+
super.connectedCallback();
|
|
3467
|
+
this.addEventListener('click', () => {
|
|
3468
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
3469
|
+
});
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
class LumenLanguageToggleBehaviorElement extends LumenElement {
|
|
3473
|
+
connectedCallback() {
|
|
3474
|
+
super.connectedCallback();
|
|
3475
|
+
this.addEventListener('click', () => {
|
|
3476
|
+
window.dispatchEvent(new CustomEvent('language-change'));
|
|
3477
|
+
});
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
class LumenParticlesBehaviorElement extends LumenElement {
|
|
3481
|
+
connectedCallback() {
|
|
3482
|
+
super.connectedCallback();
|
|
3483
|
+
if (!hasDocument())
|
|
3484
|
+
return;
|
|
3485
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
3486
|
+
return;
|
|
3487
|
+
if (this.children.length > 0)
|
|
3488
|
+
return;
|
|
3489
|
+
const densityConfig = { low: 15, medium: 25, high: 40 };
|
|
3490
|
+
const density = (this.getAttribute('data-ui-particles') || 'medium');
|
|
3491
|
+
const count = densityConfig[density];
|
|
3492
|
+
for (let i = 0; i < count; i++) {
|
|
3493
|
+
const el = document.createElement('div');
|
|
3494
|
+
const size = Math.random() * 12 + 8;
|
|
3495
|
+
const left = Math.random() * 100;
|
|
3496
|
+
const top = Math.random() * 100;
|
|
3497
|
+
const delay = Math.random() * 8;
|
|
3498
|
+
const duration = 20 + Math.random() * 15;
|
|
3499
|
+
el.style.cssText = `
|
|
3500
|
+
position: absolute;
|
|
3501
|
+
left: ${left}%;
|
|
3502
|
+
top: ${top}%;
|
|
3503
|
+
width: ${size}px;
|
|
3504
|
+
height: ${size}px;
|
|
3505
|
+
border-radius: 50%;
|
|
3506
|
+
background: var(--ui-surface-strong, currentColor);
|
|
3507
|
+
opacity: 0.1;
|
|
3508
|
+
animation: ui-particle-drift ${duration}s ease-in-out ${delay}s infinite;
|
|
3509
|
+
`;
|
|
3510
|
+
this.appendChild(el);
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
class LumenRatingBehaviorElement extends LumenElement {
|
|
3515
|
+
connectedCallback() {
|
|
3516
|
+
super.connectedCallback();
|
|
3517
|
+
this.addEventListener('click', (e) => {
|
|
3518
|
+
if (this.getAttribute('data-readonly') === 'true')
|
|
3519
|
+
return;
|
|
3520
|
+
const button = e.target.closest('.ui-rating__star');
|
|
3521
|
+
if (!button)
|
|
3522
|
+
return;
|
|
3523
|
+
const stars = [...this.querySelectorAll('.ui-rating__star')];
|
|
3524
|
+
const index = stars.indexOf(button);
|
|
3525
|
+
if (index === -1)
|
|
3526
|
+
return;
|
|
3527
|
+
const newValue = index + 1;
|
|
3528
|
+
this.setAttribute('data-value', newValue.toString());
|
|
3529
|
+
for (const [i, star] of stars.entries()) {
|
|
3530
|
+
star.setAttribute('data-active', i < newValue ? 'true' : 'false');
|
|
3531
|
+
}
|
|
3532
|
+
this.dispatchEvent(new CustomEvent('change', { detail: { value: newValue } }));
|
|
3533
|
+
});
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
class LumenScrollRevealBehaviorElement extends LumenElement {
|
|
3537
|
+
observer;
|
|
3538
|
+
connectedCallback() {
|
|
3539
|
+
super.connectedCallback();
|
|
3540
|
+
if (!hasDocument())
|
|
3541
|
+
return;
|
|
3542
|
+
this.dataset.uiScrollRevealBound = 'true';
|
|
3543
|
+
const delay = Math.max(0, Number(this.getAttribute('delay')) || 0);
|
|
3544
|
+
this.style.setProperty('--ui-reveal-delay', `${delay}ms`);
|
|
3545
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
3546
|
+
|| typeof IntersectionObserver === 'undefined') {
|
|
3547
|
+
this.classList.add('is-revealed');
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
const once = this.getAttribute('once') !== 'false';
|
|
3551
|
+
const threshold = Math.min(1, Math.max(0, Number(this.getAttribute('threshold')) || 0));
|
|
3552
|
+
this.observer = new IntersectionObserver((entries) => {
|
|
3553
|
+
for (const entry of entries) {
|
|
3554
|
+
entry.target.classList.toggle('is-revealed', entry.isIntersecting);
|
|
3555
|
+
if (entry.isIntersecting && once) {
|
|
3556
|
+
this.observer?.unobserve(entry.target);
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
}, { threshold });
|
|
3560
|
+
this.observer.observe(this);
|
|
3561
|
+
}
|
|
3562
|
+
disconnectedCallback() {
|
|
3563
|
+
this.observer?.disconnect();
|
|
3564
|
+
this.observer = undefined;
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
class LumenRevealGroupBehaviorElement extends LumenScrollRevealBehaviorElement {
|
|
3568
|
+
connectedCallback() {
|
|
3569
|
+
super.connectedCallback();
|
|
3570
|
+
const stagger = Math.max(0, Number(this.getAttribute('stagger')) || 80);
|
|
3571
|
+
this.style.setProperty('--ui-reveal-stagger', `${stagger}ms`);
|
|
3572
|
+
for (const [index, child] of [...this.children].entries()) {
|
|
3573
|
+
if (child instanceof HTMLElement) {
|
|
3574
|
+
child.style.setProperty('--ui-reveal-index', String(index));
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
class LumenAnimatedNumberBehaviorElement extends LumenElement {
|
|
3580
|
+
animationFrame;
|
|
3581
|
+
observer;
|
|
3582
|
+
connectedCallback() {
|
|
3583
|
+
super.connectedCallback();
|
|
3584
|
+
this.animateValue();
|
|
3585
|
+
}
|
|
3586
|
+
attributeChangedCallback() {
|
|
3587
|
+
super.attributeChangedCallback();
|
|
3588
|
+
if (this.isConnected)
|
|
3589
|
+
this.animateValue();
|
|
3590
|
+
}
|
|
3591
|
+
disconnectedCallback() {
|
|
3592
|
+
this.observer?.disconnect();
|
|
3593
|
+
if (this.animationFrame !== undefined)
|
|
3594
|
+
cancelAnimationFrame(this.animationFrame);
|
|
3595
|
+
}
|
|
3596
|
+
animateValue() {
|
|
3597
|
+
if (!hasDocument())
|
|
3598
|
+
return;
|
|
3599
|
+
this.observer?.disconnect();
|
|
3600
|
+
if (this.animationFrame !== undefined)
|
|
3601
|
+
cancelAnimationFrame(this.animationFrame);
|
|
3602
|
+
const from = Number(this.getAttribute('from'));
|
|
3603
|
+
const value = Number(this.getAttribute('value'));
|
|
3604
|
+
if (!Number.isFinite(from) || !Number.isFinite(value))
|
|
3605
|
+
return;
|
|
3606
|
+
const decimals = Math.max(0, Math.min(20, Number(this.getAttribute('decimals')) || 0));
|
|
3607
|
+
const prefix = this.getAttribute('prefix') ?? '';
|
|
3608
|
+
const suffix = this.getAttribute('suffix') ?? '';
|
|
3609
|
+
const formatter = new Intl.NumberFormat(this.getAttribute('locale') || undefined, {
|
|
3610
|
+
maximumFractionDigits: decimals,
|
|
3611
|
+
minimumFractionDigits: decimals
|
|
3612
|
+
});
|
|
3613
|
+
const format = (current) => `${prefix}${formatter.format(current)}${suffix}`;
|
|
3614
|
+
const finalValue = format(value);
|
|
3615
|
+
this.setAttribute('aria-label', finalValue);
|
|
3616
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
3617
|
+
|| typeof IntersectionObserver === 'undefined') {
|
|
3618
|
+
this.textContent = finalValue;
|
|
3619
|
+
return;
|
|
3620
|
+
}
|
|
3621
|
+
this.textContent = format(from);
|
|
3622
|
+
this.observer = new IntersectionObserver(entries => {
|
|
3623
|
+
if (!entries.some(entry => entry.isIntersecting))
|
|
3624
|
+
return;
|
|
3625
|
+
this.observer?.disconnect();
|
|
3626
|
+
const durationValue = getComputedStyle(this).getPropertyValue('--ui-motion-duration').trim();
|
|
3627
|
+
const duration = parseMotionDuration(durationValue);
|
|
3628
|
+
const startedAt = performance.now();
|
|
3629
|
+
const update = (now) => {
|
|
3630
|
+
const progress = Math.min(1, (now - startedAt) / Math.max(1, duration));
|
|
3631
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
3632
|
+
this.textContent = format(from + ((value - from) * eased));
|
|
3633
|
+
if (progress < 1) {
|
|
3634
|
+
this.animationFrame = requestAnimationFrame(update);
|
|
3635
|
+
}
|
|
3636
|
+
else {
|
|
3637
|
+
this.textContent = finalValue;
|
|
3638
|
+
}
|
|
3639
|
+
};
|
|
3640
|
+
this.animationFrame = requestAnimationFrame(update);
|
|
3641
|
+
}, { threshold: 0.15 });
|
|
3642
|
+
this.observer.observe(this);
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
class LumenThemeToggleBehaviorElement extends LumenElement {
|
|
3646
|
+
#isAnimating = false;
|
|
3647
|
+
connectedCallback() {
|
|
3648
|
+
super.connectedCallback();
|
|
3649
|
+
this.setAttribute('role', 'switch');
|
|
3650
|
+
this.setAttribute('aria-checked', String(this.#rootInDarkMode()));
|
|
3651
|
+
this.addEventListener('click', () => {
|
|
3652
|
+
if (this.#isAnimating)
|
|
3653
|
+
return;
|
|
3654
|
+
const isDark = this.#rootInDarkMode();
|
|
3655
|
+
const newTheme = isDark ? 'light' : 'dark';
|
|
3656
|
+
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
3657
|
+
if (prefersReducedMotion) {
|
|
3658
|
+
this.#dispatchChange(newTheme);
|
|
3659
|
+
return;
|
|
3660
|
+
}
|
|
3661
|
+
this.#circularReveal(isDark, newTheme);
|
|
3662
|
+
});
|
|
3663
|
+
}
|
|
3664
|
+
#rootInDarkMode() {
|
|
3665
|
+
const doc = document.documentElement;
|
|
3666
|
+
return doc.getAttribute('data-theme') === 'dark' || doc.classList.contains('dark');
|
|
3667
|
+
}
|
|
3668
|
+
#dispatchChange(newTheme) {
|
|
3669
|
+
const doc = document.documentElement;
|
|
3670
|
+
doc.setAttribute('data-theme', newTheme);
|
|
3671
|
+
if (newTheme === 'dark') {
|
|
3672
|
+
doc.classList.add('dark');
|
|
3673
|
+
}
|
|
3674
|
+
else {
|
|
3675
|
+
doc.classList.remove('dark');
|
|
3676
|
+
}
|
|
3677
|
+
localStorage.setItem('theme', newTheme);
|
|
3678
|
+
window.dispatchEvent(new CustomEvent('theme-change', { detail: newTheme }));
|
|
3679
|
+
this.setAttribute('aria-checked', String(this.#rootInDarkMode()));
|
|
3680
|
+
}
|
|
3681
|
+
#circularReveal(isDark, newTheme) {
|
|
3682
|
+
const isTouchDevice = window.matchMedia('(hover: none) and (pointer: coarse)').matches;
|
|
3683
|
+
if (isTouchDevice) {
|
|
3684
|
+
this.#dispatchChange(newTheme);
|
|
3685
|
+
return;
|
|
3686
|
+
}
|
|
3687
|
+
this.#isAnimating = true;
|
|
3688
|
+
const rect = this.getBoundingClientRect();
|
|
3689
|
+
const x = Math.round(rect.left + rect.width / 2);
|
|
3690
|
+
const y = Math.round(rect.top + rect.height / 2);
|
|
3691
|
+
const maxRadius = Math.hypot(Math.max(x, window.innerWidth - x), Math.max(y, window.innerHeight - y));
|
|
3692
|
+
const oldBg = isDark ? 'hsl(277 20% 10%)' : 'hsl(268 20% 98%)';
|
|
3693
|
+
const overlay = document.createElement('div');
|
|
3694
|
+
overlay.setAttribute('aria-hidden', 'true');
|
|
3695
|
+
Object.assign(overlay.style, {
|
|
3696
|
+
position: 'fixed',
|
|
3697
|
+
inset: '0',
|
|
3698
|
+
zIndex: '99999',
|
|
3699
|
+
pointerEvents: 'none',
|
|
3700
|
+
backgroundColor: oldBg,
|
|
3701
|
+
clipPath: `circle(${maxRadius}px at ${x}px ${y}px)`,
|
|
3702
|
+
willChange: 'clip-path',
|
|
3703
|
+
});
|
|
3704
|
+
document.body.appendChild(overlay);
|
|
3705
|
+
this.#dispatchChange(newTheme);
|
|
3706
|
+
requestAnimationFrame(() => {
|
|
3707
|
+
requestAnimationFrame(() => {
|
|
3708
|
+
overlay.style.transition = 'clip-path 580ms cubic-bezier(0.4, 0, 0.2, 1)';
|
|
3709
|
+
overlay.style.clipPath = `circle(0px at ${x}px ${y}px)`;
|
|
3710
|
+
const done = () => {
|
|
3711
|
+
overlay.remove();
|
|
3712
|
+
this.#isAnimating = false;
|
|
3713
|
+
};
|
|
3714
|
+
overlay.addEventListener('transitionend', done, { once: true });
|
|
3715
|
+
setTimeout(done, 750);
|
|
3716
|
+
});
|
|
3717
|
+
});
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
const createLumenElementClass = (config) => class extends LumenElement {
|
|
3721
|
+
static config = config;
|
|
3722
|
+
};
|
|
3723
|
+
const createLumenBehaviorElementClass = (BaseElement, config) => class extends BaseElement {
|
|
3724
|
+
static config = config;
|
|
3725
|
+
};
|
|
3726
|
+
const behaviorElementClasses = {
|
|
3727
|
+
AlertDialog: LumenDialogBehaviorElement,
|
|
3728
|
+
AnimatedNumber: LumenAnimatedNumberBehaviorElement,
|
|
3729
|
+
BackToTop: LumenBackToTopBehaviorElement,
|
|
3730
|
+
DataTable: LumenDataTableBehaviorElement,
|
|
3731
|
+
Dialog: LumenDialogBehaviorElement,
|
|
3732
|
+
DropdownMenu: LumenDisclosureBehaviorElement,
|
|
3733
|
+
Icon: LumenIconBehaviorElement,
|
|
3734
|
+
LanguageToggle: LumenLanguageToggleBehaviorElement,
|
|
3735
|
+
Particles: LumenParticlesBehaviorElement,
|
|
3736
|
+
Popover: LumenDisclosureBehaviorElement,
|
|
3737
|
+
Rating: LumenRatingBehaviorElement,
|
|
3738
|
+
RevealGroup: LumenRevealGroupBehaviorElement,
|
|
3739
|
+
ScrollReveal: LumenScrollRevealBehaviorElement,
|
|
3740
|
+
Select: LumenSelectBehaviorElement,
|
|
3741
|
+
Sonner: LumenSonnerBehaviorElement,
|
|
3742
|
+
Tabs: LumenTabsBehaviorElement,
|
|
3743
|
+
ThemeBuilder: LumenThemeBuilderBehaviorElement,
|
|
3744
|
+
ThemeToggle: LumenThemeToggleBehaviorElement,
|
|
3745
|
+
Toast: LumenToastBehaviorElement,
|
|
3746
|
+
Tooltip: LumenTooltipBehaviorElement,
|
|
3747
|
+
VirtualList: LumenVirtualListBehaviorElement
|
|
3748
|
+
};
|
|
3749
|
+
const elementClasses = Object.fromEntries(lumenComponentNames.map(componentName => {
|
|
3750
|
+
const behaviorClass = behaviorElementClasses[componentName];
|
|
3751
|
+
if (behaviorClass) {
|
|
3752
|
+
return [componentName, createLumenBehaviorElementClass(behaviorClass, elementConfigs[componentName])];
|
|
3753
|
+
}
|
|
3754
|
+
return [componentName, createLumenElementClass(elementConfigs[componentName])];
|
|
3755
|
+
}));
|
|
3756
|
+
const elementDefinitions = lumenComponentNames.map(componentName => {
|
|
3757
|
+
const config = elementConfigs[componentName];
|
|
3758
|
+
return [config.tagName, elementClasses[componentName]];
|
|
3759
|
+
});
|
|
3760
|
+
export const enhanceLumenElements = (scope = document) => {
|
|
3761
|
+
enhanceLumenDateRangePickers(scope);
|
|
3762
|
+
enhanceLumenDatePickers(scope);
|
|
3763
|
+
enhanceLumenInputOTPs(scope);
|
|
3764
|
+
initLumenFields(scope);
|
|
3765
|
+
initLumenForms(scope);
|
|
3766
|
+
};
|
|
3767
|
+
export const defineLumenElements = (customElementsRegistry = typeof customElements === 'undefined'
|
|
3768
|
+
? undefined
|
|
3769
|
+
: customElements) => {
|
|
3770
|
+
if (!customElementsRegistry)
|
|
3771
|
+
return;
|
|
3772
|
+
for (const [name, element] of elementDefinitions) {
|
|
3773
|
+
if (!registry.has(name) && !customElementsRegistry.get(name)) {
|
|
3774
|
+
customElementsRegistry.define(name, element);
|
|
3775
|
+
registry.add(name);
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
installToastController();
|
|
3779
|
+
installFormController();
|
|
3780
|
+
installRichTextEditorController();
|
|
3781
|
+
installScheduleController();
|
|
3782
|
+
installResizableController();
|
|
3783
|
+
installContextMenuController();
|
|
3784
|
+
installDateRangePickerController();
|
|
3785
|
+
installDatePickerController();
|
|
3786
|
+
installInputOtpController();
|
|
3787
|
+
installCalendarController();
|
|
3788
|
+
};
|
|
3789
|
+
export const installLumenControllers = () => {
|
|
3790
|
+
installDateRangePickerController();
|
|
3791
|
+
installDatePickerController();
|
|
3792
|
+
installInputOtpController();
|
|
3793
|
+
};
|
|
3794
|
+
export const lumenElementDefinitions = elementDefinitions;
|
|
3795
|
+
export const LumenAccordionElement = elementClasses.Accordion;
|
|
3796
|
+
export const LumenAlertElement = elementClasses.Alert;
|
|
3797
|
+
export const LumenAlertDialogElement = elementClasses.AlertDialog;
|
|
3798
|
+
export const LumenAgendaElement = elementClasses.Agenda;
|
|
3799
|
+
export const LumenAspectRatioElement = elementClasses.AspectRatio;
|
|
3800
|
+
export const LumenAttachmentElement = elementClasses.Attachment;
|
|
3801
|
+
export const LumenAutocompleteElement = elementClasses.Autocomplete;
|
|
3802
|
+
export const LumenAvatarElement = elementClasses.Avatar;
|
|
3803
|
+
export const LumenBadgeElement = elementClasses.Badge;
|
|
3804
|
+
export const LumenBreadcrumbElement = elementClasses.Breadcrumb;
|
|
3805
|
+
export const LumenBubbleElement = elementClasses.Bubble;
|
|
3806
|
+
export const LumenButtonElement = elementClasses.Button;
|
|
3807
|
+
export const LumenButtonGroupElement = elementClasses.ButtonGroup;
|
|
3808
|
+
export const LumenCalendarElement = elementClasses.Calendar;
|
|
3809
|
+
export const LumenCardElement = elementClasses.Card;
|
|
3810
|
+
export const LumenCarouselElement = elementClasses.Carousel;
|
|
3811
|
+
export const LumenChartElement = elementClasses.Chart;
|
|
3812
|
+
export const LumenCheckboxElement = elementClasses.Checkbox;
|
|
3813
|
+
export const LumenCollapsibleElement = elementClasses.Collapsible;
|
|
3814
|
+
export const LumenCodeElement = elementClasses.Code;
|
|
3815
|
+
export const LumenComboboxElement = elementClasses.Combobox;
|
|
3816
|
+
export const LumenCommandElement = elementClasses.Command;
|
|
3817
|
+
export const LumenColorPickerElement = elementClasses.ColorPicker;
|
|
3818
|
+
export const LumenContextMenuElement = elementClasses.ContextMenu;
|
|
3819
|
+
export const LumenDataTableElement = elementClasses.DataTable;
|
|
3820
|
+
export const LumenDatePickerElement = elementClasses.DatePicker;
|
|
3821
|
+
export const LumenDateRangePickerElement = elementClasses.DateRangePicker;
|
|
3822
|
+
export const LumenDialogElement = elementClasses.Dialog;
|
|
3823
|
+
export const LumenDirectionElement = elementClasses.Direction;
|
|
3824
|
+
export const LumenDrawerElement = elementClasses.Drawer;
|
|
3825
|
+
export const LumenDropdownMenuElement = elementClasses.DropdownMenu;
|
|
3826
|
+
export const LumenEmptyElement = elementClasses.Empty;
|
|
3827
|
+
export const LumenFieldElement = elementClasses.Field;
|
|
3828
|
+
export const LumenHoverCardElement = elementClasses.HoverCard;
|
|
3829
|
+
export const LumenIconElement = elementClasses.Icon;
|
|
3830
|
+
export const LumenInputElement = elementClasses.Input;
|
|
3831
|
+
export const LumenInputGroupElement = elementClasses.InputGroup;
|
|
3832
|
+
export const LumenInputOTPElement = elementClasses.InputOTP;
|
|
3833
|
+
export const LumenNumberFieldElement = elementClasses.NumberField;
|
|
3834
|
+
export const LumenItemElement = elementClasses.Item;
|
|
3835
|
+
export const LumenKbdElement = elementClasses.Kbd;
|
|
3836
|
+
export const LumenLabelElement = elementClasses.Label;
|
|
3837
|
+
export const LumenMarkerElement = elementClasses.Marker;
|
|
3838
|
+
export const LumenMenubarElement = elementClasses.Menubar;
|
|
3839
|
+
export const LumenMessageElement = elementClasses.Message;
|
|
3840
|
+
export const LumenMessageScrollerElement = elementClasses.MessageScroller;
|
|
3841
|
+
export const LumenNativeSelectElement = elementClasses.NativeSelect;
|
|
3842
|
+
export const LumenNavigationMenuElement = elementClasses.NavigationMenu;
|
|
3843
|
+
export const LumenPaginationElement = elementClasses.Pagination;
|
|
3844
|
+
export const LumenPhoneInputElement = elementClasses.PhoneInput;
|
|
3845
|
+
export const LumenPopoverElement = elementClasses.Popover;
|
|
3846
|
+
export const LumenProgressElement = elementClasses.Progress;
|
|
3847
|
+
export const LumenRadioGroupElement = elementClasses.RadioGroup;
|
|
3848
|
+
export const LumenResizableElement = elementClasses.Resizable;
|
|
3849
|
+
export const LumenRichTextEditorElement = elementClasses.RichTextEditor;
|
|
3850
|
+
export const LumenScrollAreaElement = elementClasses.ScrollArea;
|
|
3851
|
+
export const LumenScheduleElement = elementClasses.Schedule;
|
|
3852
|
+
export const LumenSearchFieldElement = elementClasses.SearchField;
|
|
3853
|
+
export const LumenSelectElement = elementClasses.Select;
|
|
3854
|
+
export const LumenSeparatorElement = elementClasses.Separator;
|
|
3855
|
+
export const LumenSheetElement = elementClasses.Sheet;
|
|
3856
|
+
export const LumenSidebarElement = elementClasses.Sidebar;
|
|
3857
|
+
export const LumenSkeletonElement = elementClasses.Skeleton;
|
|
3858
|
+
export const LumenSliderElement = elementClasses.Slider;
|
|
3859
|
+
export const LumenSonnerElement = elementClasses.Sonner;
|
|
3860
|
+
export const LumenSpinnerElement = elementClasses.Spinner;
|
|
3861
|
+
export const LumenSwitchElement = elementClasses.Switch;
|
|
3862
|
+
export const LumenTableElement = elementClasses.Table;
|
|
3863
|
+
export const LumenTabsElement = elementClasses.Tabs;
|
|
3864
|
+
export const LumenTagGroupElement = elementClasses.TagGroup;
|
|
3865
|
+
export const LumenTextareaElement = elementClasses.Textarea;
|
|
3866
|
+
export const LumenThemeBuilderElement = elementClasses.ThemeBuilder;
|
|
3867
|
+
export const LumenTimeFieldElement = elementClasses.TimeField;
|
|
3868
|
+
export const LumenToastElement = elementClasses.Toast;
|
|
3869
|
+
export const LumenToggleElement = elementClasses.Toggle;
|
|
3870
|
+
export const LumenToggleGroupElement = elementClasses.ToggleGroup;
|
|
3871
|
+
export const LumenTooltipElement = elementClasses.Tooltip;
|
|
3872
|
+
export const LumenTreeElement = elementClasses.Tree;
|
|
3873
|
+
export const LumenTreeGridElement = elementClasses.TreeGrid;
|
|
3874
|
+
export const LumenTypographyElement = elementClasses.Typography;
|
|
3875
|
+
export const LumenVirtualListElement = elementClasses.VirtualList;
|
|
3876
|
+
export const LumenLanguageToggleElement = elementClasses.LanguageToggle;
|
|
3877
|
+
export const LumenParticlesElement = elementClasses.Particles;
|
|
3878
|
+
export const LumenAnimatedNumberElement = elementClasses.AnimatedNumber;
|
|
3879
|
+
export const LumenRevealGroupElement = elementClasses.RevealGroup;
|
|
3880
|
+
export const LumenScrollRevealElement = elementClasses.ScrollReveal;
|
|
3881
|
+
export const LumenStatElement = elementClasses.Stat;
|
|
3882
|
+
export const LumenMeterElement = elementClasses.Meter;
|
|
3883
|
+
export const LumenNoteElement = elementClasses.Note;
|
|
3884
|
+
export const LumenRatingElement = elementClasses.Rating;
|
|
3885
|
+
export const LumenTimelineElement = elementClasses.Timeline;
|
|
3886
|
+
export const LumenAnimatedLogoElement = elementClasses.AnimatedLogo;
|
|
3887
|
+
export const LumenAnimatedPortraitElement = elementClasses.AnimatedPortrait;
|
|
3888
|
+
export const LumenButtonLinkElement = elementClasses.ButtonLink;
|
|
3889
|
+
export const LumenCoverImageElement = elementClasses.CoverImage;
|
|
3890
|
+
export const LumenGradientDividerElement = elementClasses.GradientDivider;
|