vanilla-jui 1.4.8 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/index.d.ts +402 -977
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +3 -3
- package/src/components/accordion.ts +467 -349
- package/src/components/flow.ts +779 -1133
- package/src/components/form.ts +666 -509
- package/src/components/menu.ts +199 -326
- package/src/components/modal.ts +510 -747
- package/src/components/offcanvas.ts +281 -361
- package/src/components/pagination.ts +207 -374
- package/src/components/swiper.ts +688 -1168
- package/src/components/tabs.ts +468 -788
- package/src/components/toc.ts +191 -244
- package/src/core/component.ts +332 -0
- package/src/core/motion.ts +298 -0
- package/src/core/presence.ts +233 -0
- package/src/core/scheduler.ts +91 -0
- package/src/core/view.ts +31 -0
- package/src/css/accordion.css +94 -3
- package/src/css/color.css +4 -5
- package/src/css/color_p3.css +6 -5
- package/src/css/content.css +18 -24
- package/src/css/drop.css +4 -1
- package/src/css/form.css +8 -0
- package/src/css/gradient.css +64 -131
- package/src/css/layout.css +9 -23
- package/src/css/menu.css +10 -5
- package/src/css/modal.css +2 -1
- package/src/css/offcanvas.css +1 -17
- package/src/css/swiper.css +2 -1
- package/src/css/tabs.css +8 -17
- package/src/css/themes/default/accordion.css +0 -48
- package/src/css/themes/default/swiper.css +1 -1
- package/src/css/themes/default/tabs.css +4 -6
- package/src/css/toast.css +1 -17
- package/src/css/tokens.css +18 -35
- package/src/css/tooltip.css +2 -1
- package/src/index.ts +8 -3
- package/src/locales/index.ts +3 -0
- package/src/primitives/drop.ts +253 -262
- package/src/primitives/loading.ts +22 -4
- package/src/primitives/parabola.ts +123 -164
- package/src/primitives/sticky.ts +297 -0
- package/src/primitives/theme.ts +164 -222
- package/src/primitives/toast.ts +309 -284
- package/src/primitives/tooltip.ts +65 -66
- package/src/utilities/browser.ts +38 -0
- package/src/utilities/dom.ts +0 -54
- package/src/utilities/refs.ts +61 -0
- package/src/utilities/state.ts +73 -0
- package/src/utilities/timer.ts +11 -31
- package/src/validation/validator.ts +289 -389
- package/src/components/sticky.ts +0 -297
- package/src/core/Component.ts +0 -377
- package/src/utilities/scheduler.ts +0 -156
package/src/primitives/theme.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { t } from 'vanilla-signal-i18n';
|
|
|
12
12
|
import locales from '../locales/index.ts';
|
|
13
13
|
import { joinClasses } from '../utilities/class-name.ts';
|
|
14
14
|
import { all } from '../utilities/dom.ts';
|
|
15
|
-
import { createEventManager
|
|
15
|
+
import { createEventManager } from '../utilities/events.ts';
|
|
16
16
|
import { isPlainObject } from '../utilities/object.ts';
|
|
17
17
|
|
|
18
18
|
export type ThemeConfigKey = 'mode' | 'theme' | 'radius' | 'shadow' | 'font';
|
|
@@ -59,11 +59,6 @@ export interface ThemeResolvedOptions {
|
|
|
59
59
|
className: ThemeClassNames;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
interface ThemeCleanup {
|
|
63
|
-
bindings: Set<() => void>;
|
|
64
|
-
events: IEventManager;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
62
|
export interface ThemeInstance {
|
|
68
63
|
props: ThemeResolvedOptions;
|
|
69
64
|
createPanel(
|
|
@@ -135,12 +130,14 @@ function createThemeStorage(): Storage {
|
|
|
135
130
|
return createStorage({
|
|
136
131
|
driver: 'cookie',
|
|
137
132
|
codec: 'json',
|
|
133
|
+
namespace: '',
|
|
134
|
+
keySeparator: '',
|
|
138
135
|
driverOptions: {
|
|
139
136
|
path: '/',
|
|
140
137
|
sameSite: 'Lax',
|
|
141
138
|
secure: typeof location !== 'undefined' && location.protocol === 'https:',
|
|
142
139
|
},
|
|
143
|
-
ttl: 864e5,
|
|
140
|
+
ttl: 864e5 * 7, // 7 days
|
|
144
141
|
});
|
|
145
142
|
}
|
|
146
143
|
|
|
@@ -159,151 +156,121 @@ function normalizeStoredConfig(value: unknown): Partial<ThemeOptions> | null {
|
|
|
159
156
|
return Object.keys(config).length ? config : null;
|
|
160
157
|
}
|
|
161
158
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
159
|
+
function defaultPanelConfig(
|
|
160
|
+
translate: (key: string) => string
|
|
161
|
+
): ThemePanelGroup[] {
|
|
162
|
+
return [
|
|
163
|
+
{
|
|
164
|
+
title: translate('Primary'),
|
|
165
|
+
type: 'theme',
|
|
166
|
+
buttons: [
|
|
167
|
+
['gray', translate('Gray')],
|
|
168
|
+
['olive', translate('Olive')],
|
|
169
|
+
['tomato', translate('Tomato')],
|
|
170
|
+
['ruby', translate('Ruby')],
|
|
171
|
+
['pink', translate('Pink')],
|
|
172
|
+
['violet', translate('Violet')],
|
|
173
|
+
['indigo', translate('Indigo')],
|
|
174
|
+
['blue', translate('Blue')],
|
|
175
|
+
['teal', translate('Teal')],
|
|
176
|
+
['grass', translate('Grass')],
|
|
177
|
+
['mint', translate('Mint')],
|
|
178
|
+
['lime', translate('Lime')],
|
|
179
|
+
['yellow', translate('Yellow')],
|
|
180
|
+
['orange', translate('Orange')],
|
|
181
|
+
['gold', translate('Gold')],
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
title: translate('Radius'),
|
|
186
|
+
type: 'radius',
|
|
187
|
+
buttons: [
|
|
188
|
+
['none', translate('None')],
|
|
189
|
+
['sm', translate('sm')],
|
|
190
|
+
['md', translate('md')],
|
|
191
|
+
['lg', translate('lg')],
|
|
192
|
+
['xl', translate('XL')],
|
|
193
|
+
['round', translate('Round')],
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
title: translate('Shadow'),
|
|
198
|
+
type: 'shadow',
|
|
199
|
+
buttons: [
|
|
200
|
+
['none', translate('None')],
|
|
201
|
+
['sm', translate('sm')],
|
|
202
|
+
['md', translate('md')],
|
|
203
|
+
['lg', translate('lg')],
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
title: translate('Font'),
|
|
208
|
+
type: 'font',
|
|
209
|
+
buttons: [
|
|
210
|
+
['sm', translate('sm')],
|
|
211
|
+
['md', translate('md')],
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
title: translate('Mode'),
|
|
216
|
+
type: 'mode',
|
|
217
|
+
buttons: [
|
|
218
|
+
['light', translate('Light')],
|
|
219
|
+
['dark', translate('Dark')],
|
|
220
|
+
['auto', translate('Auto')],
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
];
|
|
224
|
+
}
|
|
215
225
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
226
|
+
export function createTheme(options: ThemeOptions = {}): ThemeInstance {
|
|
227
|
+
const props = createDeepStore(normalizeOptions(options));
|
|
228
|
+
const runtime = { configVersion: 0, destroyed: false };
|
|
229
|
+
const storage = createThemeStorage();
|
|
230
|
+
const bindings = new Set<() => void>();
|
|
231
|
+
const events = createEventManager();
|
|
232
|
+
const translate = (key: string): string => t(key, locales);
|
|
233
|
+
const scheme = (): string =>
|
|
234
|
+
window.matchMedia?.('(prefers-color-scheme: dark)').matches
|
|
235
|
+
? 'dark'
|
|
236
|
+
: 'light';
|
|
237
|
+
const saveConfig = (): void => {
|
|
238
|
+
const { mode, theme, radius, shadow, font } = props;
|
|
219
239
|
const config: ThemeStoredConfig = {
|
|
220
240
|
mode,
|
|
221
241
|
theme,
|
|
222
242
|
radius,
|
|
223
243
|
shadow,
|
|
224
244
|
font,
|
|
225
|
-
render,
|
|
245
|
+
render: mode === 'auto' ? scheme() : mode,
|
|
226
246
|
};
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
private scheme(): string {
|
|
232
|
-
return window.matchMedia?.('(prefers-color-scheme: dark)').matches
|
|
233
|
-
? 'dark'
|
|
234
|
-
: 'light';
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
private bindActiveButtons(root: HTMLElement): void {
|
|
247
|
+
void storage.set(props.key, config).catch(() => {});
|
|
248
|
+
};
|
|
249
|
+
const bindActiveButtons = (root: HTMLElement): void => {
|
|
238
250
|
const dispose = createRoot((dispose) => {
|
|
239
|
-
const
|
|
240
|
-
for (const item of items) {
|
|
251
|
+
for (const item of all<HTMLElement>('[data-theme-group]', root)) {
|
|
241
252
|
const type = item.dataset.themeGroup;
|
|
242
253
|
if (!isThemeConfigKey(type)) continue;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
254
|
+
for (const button of all<HTMLButtonElement>(
|
|
255
|
+
'[data-theme-button]',
|
|
256
|
+
item
|
|
257
|
+
)) {
|
|
246
258
|
const isActive = () =>
|
|
247
|
-
button.dataset.themeValue === String(
|
|
248
|
-
bindClass(button,
|
|
259
|
+
button.dataset.themeValue === String(props[type]);
|
|
260
|
+
bindClass(button, props.className.active, isActive);
|
|
249
261
|
bindAttr(button, 'aria-selected', () => (isActive() ? 'true' : null));
|
|
250
262
|
}
|
|
251
263
|
}
|
|
252
|
-
|
|
253
264
|
return dispose;
|
|
254
265
|
});
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
'[data-theme-button]'
|
|
264
|
-
);
|
|
265
|
-
if (!button) return;
|
|
266
|
-
|
|
267
|
-
const groupEl = button.closest<HTMLElement>('[data-theme-group]');
|
|
268
|
-
const type = groupEl?.dataset.themeGroup;
|
|
269
|
-
const value = button.dataset.themeValue;
|
|
270
|
-
if (!groupEl || !isThemeConfigKey(type) || !value) return;
|
|
271
|
-
if (button.classList.contains(this.props.className.active)) return;
|
|
272
|
-
|
|
273
|
-
const previous = this.props[type];
|
|
274
|
-
this.runtime.configVersion += 1;
|
|
275
|
-
flushSync(() => {
|
|
276
|
-
this.props[type] = value;
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
const root = document.documentElement;
|
|
280
|
-
if (type === 'mode') {
|
|
281
|
-
const actual = value === 'auto' ? this.scheme() : value;
|
|
282
|
-
root.classList.remove('light', 'dark', previous);
|
|
283
|
-
root.classList.add(actual);
|
|
284
|
-
} else {
|
|
285
|
-
const prefix = THEME_CLASS_PREFIX[type];
|
|
286
|
-
const toRemove = Array.from(root.classList).filter((className) =>
|
|
287
|
-
className.startsWith(prefix)
|
|
288
|
-
);
|
|
289
|
-
if (toRemove.length) root.classList.remove(...toRemove);
|
|
290
|
-
root.classList.add(`${prefix}${value}`);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
this.saveConfig();
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
private unbindEvent(): void {
|
|
298
|
-
this.cleanup?.events.clear();
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
createPanel(
|
|
302
|
-
containerClass: string | null = null,
|
|
303
|
-
panelConfig: ThemePanelGroup[] | null = null
|
|
304
|
-
): HTMLElement {
|
|
305
|
-
const className = this.props.className;
|
|
306
|
-
const groups = panelConfig || this.defaultPanelConfig();
|
|
266
|
+
bindings.add(dispose);
|
|
267
|
+
};
|
|
268
|
+
const createPanel: ThemeInstance['createPanel'] = (
|
|
269
|
+
containerClass = null,
|
|
270
|
+
panelConfig = null
|
|
271
|
+
) => {
|
|
272
|
+
const className = props.className;
|
|
273
|
+
const groups = panelConfig || defaultPanelConfig(translate);
|
|
307
274
|
const panel = jsx('div', {
|
|
308
275
|
className: containerClass || className.panel,
|
|
309
276
|
'data-theme-palette': 'root',
|
|
@@ -314,7 +281,7 @@ class Theme implements ThemeInstance {
|
|
|
314
281
|
style: {
|
|
315
282
|
margin: '0 0 1rem',
|
|
316
283
|
},
|
|
317
|
-
children:
|
|
284
|
+
children: translate('t'),
|
|
318
285
|
}),
|
|
319
286
|
jsx('div', {
|
|
320
287
|
className: className.container,
|
|
@@ -335,7 +302,7 @@ class Theme implements ThemeInstance {
|
|
|
335
302
|
'data-theme-items': group.type,
|
|
336
303
|
children: group.buttons.map(([value, label]) => {
|
|
337
304
|
const isActive =
|
|
338
|
-
String(
|
|
305
|
+
String(props[group.type]) === String(value);
|
|
339
306
|
return jsx('button', {
|
|
340
307
|
className: joinClasses(
|
|
341
308
|
className.button,
|
|
@@ -374,99 +341,74 @@ class Theme implements ThemeInstance {
|
|
|
374
341
|
],
|
|
375
342
|
}) as HTMLElement;
|
|
376
343
|
|
|
377
|
-
|
|
344
|
+
bindActiveButtons(panel);
|
|
378
345
|
return panel;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
this.runtime.configVersion += 1;
|
|
346
|
+
};
|
|
347
|
+
const setConfig = (newConfig: ThemeOptions): void => {
|
|
348
|
+
runtime.configVersion += 1;
|
|
383
349
|
flushSync(() => {
|
|
384
|
-
Object.assign(
|
|
350
|
+
Object.assign(props, newConfig, {
|
|
385
351
|
className: mergeClassNames({
|
|
386
|
-
...
|
|
352
|
+
...props.className,
|
|
387
353
|
...newConfig.className,
|
|
388
354
|
}),
|
|
389
355
|
});
|
|
390
356
|
});
|
|
391
|
-
|
|
392
|
-
}
|
|
357
|
+
saveConfig();
|
|
358
|
+
};
|
|
359
|
+
const destroy = (): void => {
|
|
360
|
+
if (runtime.destroyed) return;
|
|
361
|
+
runtime.destroyed = true;
|
|
362
|
+
events.clear();
|
|
363
|
+
for (const dispose of bindings) dispose();
|
|
364
|
+
bindings.clear();
|
|
365
|
+
void storage.close().catch(() => {});
|
|
366
|
+
};
|
|
393
367
|
|
|
394
|
-
|
|
395
|
-
if (
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
368
|
+
events.on('palette', document.body, 'click', (event) => {
|
|
369
|
+
if (!(event.target instanceof Element)) return;
|
|
370
|
+
const button = event.target.closest<HTMLButtonElement>(
|
|
371
|
+
'[data-theme-button]'
|
|
372
|
+
);
|
|
373
|
+
const group = button?.closest<HTMLElement>('[data-theme-group]');
|
|
374
|
+
const type = group?.dataset.themeGroup;
|
|
375
|
+
const value = button?.dataset.themeValue;
|
|
376
|
+
if (!button || !isThemeConfigKey(type) || !value) return;
|
|
377
|
+
if (button.classList.contains(props.className.active)) return;
|
|
378
|
+
|
|
379
|
+
const previous = props[type];
|
|
380
|
+
runtime.configVersion += 1;
|
|
381
|
+
flushSync(() => {
|
|
382
|
+
props[type] = value;
|
|
383
|
+
});
|
|
403
384
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
['teal', this.translate('Teal')],
|
|
419
|
-
['grass', this.translate('Grass')],
|
|
420
|
-
['mint', this.translate('Mint')],
|
|
421
|
-
['lime', this.translate('Lime')],
|
|
422
|
-
['yellow', this.translate('Yellow')],
|
|
423
|
-
['orange', this.translate('Orange')],
|
|
424
|
-
['gold', this.translate('Gold')],
|
|
425
|
-
],
|
|
426
|
-
},
|
|
427
|
-
{
|
|
428
|
-
title: this.translate('Radius'),
|
|
429
|
-
type: 'radius',
|
|
430
|
-
buttons: [
|
|
431
|
-
['none', this.translate('None')],
|
|
432
|
-
['sm', this.translate('sm')],
|
|
433
|
-
['md', this.translate('md')],
|
|
434
|
-
['lg', this.translate('lg')],
|
|
435
|
-
['xl', this.translate('XL')],
|
|
436
|
-
['round', this.translate('Round')],
|
|
437
|
-
],
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
title: this.translate('Shadow'),
|
|
441
|
-
type: 'shadow',
|
|
442
|
-
buttons: [
|
|
443
|
-
['none', this.translate('None')],
|
|
444
|
-
['sm', this.translate('sm')],
|
|
445
|
-
['md', this.translate('md')],
|
|
446
|
-
['lg', this.translate('lg')],
|
|
447
|
-
],
|
|
448
|
-
},
|
|
449
|
-
{
|
|
450
|
-
title: this.translate('Font'),
|
|
451
|
-
type: 'font',
|
|
452
|
-
buttons: [
|
|
453
|
-
['sm', this.translate('sm')],
|
|
454
|
-
['md', this.translate('md')],
|
|
455
|
-
],
|
|
456
|
-
},
|
|
457
|
-
{
|
|
458
|
-
title: this.translate('Mode'),
|
|
459
|
-
type: 'mode',
|
|
460
|
-
buttons: [
|
|
461
|
-
['light', this.translate('Light')],
|
|
462
|
-
['dark', this.translate('Dark')],
|
|
463
|
-
['auto', this.translate('Auto')],
|
|
464
|
-
],
|
|
465
|
-
},
|
|
466
|
-
];
|
|
467
|
-
}
|
|
468
|
-
}
|
|
385
|
+
const root = document.documentElement;
|
|
386
|
+
if (type === 'mode') {
|
|
387
|
+
root.classList.remove('light', 'dark', previous);
|
|
388
|
+
root.classList.add(value === 'auto' ? scheme() : value);
|
|
389
|
+
} else {
|
|
390
|
+
const prefix = THEME_CLASS_PREFIX[type];
|
|
391
|
+
const obsolete = Array.from(root.classList).filter((className) =>
|
|
392
|
+
className.startsWith(prefix)
|
|
393
|
+
);
|
|
394
|
+
if (obsolete.length) root.classList.remove(...obsolete);
|
|
395
|
+
root.classList.add(`${prefix}${value}`);
|
|
396
|
+
}
|
|
397
|
+
saveConfig();
|
|
398
|
+
});
|
|
469
399
|
|
|
470
|
-
|
|
471
|
-
|
|
400
|
+
const configVersion = runtime.configVersion;
|
|
401
|
+
void storage
|
|
402
|
+
.get<unknown>(props.key)
|
|
403
|
+
.then((result) => {
|
|
404
|
+
if (runtime.destroyed || runtime.configVersion !== configVersion) return;
|
|
405
|
+
const config = normalizeStoredConfig(result);
|
|
406
|
+
if (!config) return;
|
|
407
|
+
flushSync(() => {
|
|
408
|
+
Object.assign(props, config);
|
|
409
|
+
});
|
|
410
|
+
})
|
|
411
|
+
.catch(() => {});
|
|
412
|
+
|
|
413
|
+
return { props, createPanel, setConfig, destroy };
|
|
472
414
|
}
|