vanilla-jui 1.5.11 → 1.5.13
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 +24 -20
- package/dist/index.d.ts +8 -1
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +2 -2
- package/src/components/form.ts +6 -1
- package/src/components/swiper.ts +104 -30
- package/src/css/content.css +10 -3
- package/src/css/form.css +17 -17
- package/src/css/menu.css +7 -6
- package/src/css/table.css +1 -0
- package/src/css/themes/default/menu.css +1 -0
- package/src/css/themes/default/table.css +1 -0
- package/src/css/tokens.css +12 -12
- package/src/validation/validator.ts +33 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vanilla-jui",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.13",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A lightweight, reactive, and fully customizable vanilla JS UI library.",
|
|
6
6
|
"keywords": [
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"vanilla-create-storage": "^1.0.4",
|
|
65
|
-
"vanilla-signal": "^1.1.
|
|
65
|
+
"vanilla-signal": "^1.1.15",
|
|
66
66
|
"vanilla-signal-i18n": "^1.1.2"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
package/src/components/form.ts
CHANGED
|
@@ -23,7 +23,10 @@ import {
|
|
|
23
23
|
resolveProps,
|
|
24
24
|
validateParam,
|
|
25
25
|
} from '../utilities/types.ts';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
createValidator,
|
|
28
|
+
type ValidatorClassNameConfig,
|
|
29
|
+
} from '../validation/validator.ts';
|
|
27
30
|
import { createLoading } from '../primitives/loading.ts';
|
|
28
31
|
import { t } from 'vanilla-signal-i18n';
|
|
29
32
|
import locales from '../locales/index.ts';
|
|
@@ -137,6 +140,7 @@ export interface FormValidatorConfig {
|
|
|
137
140
|
rules?: Record<string, Record<string, unknown>>;
|
|
138
141
|
messages?: Record<string, Record<string, string>>;
|
|
139
142
|
vanilla?: boolean;
|
|
143
|
+
className?: ValidatorClassNameConfig;
|
|
140
144
|
onSubmit?: (() => void) | null;
|
|
141
145
|
[key: string]: unknown;
|
|
142
146
|
}
|
|
@@ -640,6 +644,7 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
640
644
|
if (!element) return;
|
|
641
645
|
for (const control of Array.from(element.elements)) {
|
|
642
646
|
control.classList.remove('is-valid', 'is-invalid');
|
|
647
|
+
control.removeAttribute('data-valid');
|
|
643
648
|
}
|
|
644
649
|
for (const help of all<HTMLElement>(
|
|
645
650
|
'[data-field-help], [data-validator-help]',
|
package/src/components/swiper.ts
CHANGED
|
@@ -75,6 +75,12 @@ interface TrackItem {
|
|
|
75
75
|
key: string;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
interface NormalizedItemCacheEntry {
|
|
79
|
+
item: NormalizedSwiperDataItem;
|
|
80
|
+
keys: string[];
|
|
81
|
+
values: unknown[];
|
|
82
|
+
}
|
|
83
|
+
|
|
78
84
|
export interface SwiperSlideContext {
|
|
79
85
|
swiper: Swiper;
|
|
80
86
|
item: NormalizedSwiperDataItem;
|
|
@@ -291,6 +297,8 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
|
|
|
291
297
|
}) as SwiperState;
|
|
292
298
|
const events = createEventManager();
|
|
293
299
|
const itemKeys = new WeakMap<object, string>();
|
|
300
|
+
const normalizedCache = new Map<string, NormalizedItemCacheEntry>();
|
|
301
|
+
const trackItemCache = new Map<string, TrackItem>();
|
|
294
302
|
const imageCleanups = new Set<() => void>();
|
|
295
303
|
let swiper!: Swiper;
|
|
296
304
|
let wrapper: HTMLElement | null = null;
|
|
@@ -311,9 +319,31 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
|
|
|
311
319
|
if (!itemKeys.has(item)) itemKeys.set(item, randomId());
|
|
312
320
|
return itemKeys.get(item) as string;
|
|
313
321
|
};
|
|
322
|
+
const readItemEntries = (
|
|
323
|
+
item: SwiperDataItem
|
|
324
|
+
): { keys: string[]; values: unknown[] } => {
|
|
325
|
+
const keys = Object.keys(item);
|
|
326
|
+
return {
|
|
327
|
+
keys,
|
|
328
|
+
values: keys.map((key) => item[key]),
|
|
329
|
+
};
|
|
330
|
+
};
|
|
331
|
+
const sameItemEntries = (
|
|
332
|
+
entry: NormalizedItemCacheEntry | undefined,
|
|
333
|
+
keys: string[],
|
|
334
|
+
values: unknown[]
|
|
335
|
+
): boolean => {
|
|
336
|
+
if (!entry || entry.keys.length !== keys.length) return false;
|
|
337
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
338
|
+
if (entry.keys[index] !== keys[index]) return false;
|
|
339
|
+
if (!Object.is(entry.values[index], values[index])) return false;
|
|
340
|
+
}
|
|
341
|
+
return true;
|
|
342
|
+
};
|
|
314
343
|
const normalizeData = (
|
|
315
344
|
data: SwiperDataItem[]
|
|
316
345
|
): NormalizedSwiperDataItem[] => {
|
|
346
|
+
const activeKeys = new Set<string>();
|
|
317
347
|
const items = data.map((item, index) => {
|
|
318
348
|
validateParam(
|
|
319
349
|
String(index),
|
|
@@ -321,13 +351,26 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
|
|
|
321
351
|
SWIPER_DATA_ITEM_RULE,
|
|
322
352
|
'Swiper.state.data'
|
|
323
353
|
);
|
|
324
|
-
|
|
354
|
+
const key = itemKey(item);
|
|
355
|
+
activeKeys.add(key);
|
|
356
|
+
const { keys, values } = readItemEntries(item);
|
|
357
|
+
const cached = normalizedCache.get(key);
|
|
358
|
+
if (cached && sameItemEntries(cached, keys, values)) {
|
|
359
|
+
cached.item.index = index;
|
|
360
|
+
return cached.item;
|
|
361
|
+
}
|
|
362
|
+
const normalized = {
|
|
325
363
|
...item,
|
|
326
364
|
blank: item.blank !== false,
|
|
327
365
|
index,
|
|
328
|
-
key
|
|
366
|
+
key,
|
|
329
367
|
};
|
|
368
|
+
normalizedCache.set(key, { item: normalized, keys, values });
|
|
369
|
+
return normalized;
|
|
330
370
|
});
|
|
371
|
+
for (const key of normalizedCache.keys()) {
|
|
372
|
+
if (!activeKeys.has(key)) normalizedCache.delete(key);
|
|
373
|
+
}
|
|
331
374
|
if (!items.some((item) => item.sort != null)) return items;
|
|
332
375
|
return items.sort((a, b) => {
|
|
333
376
|
if (a.sort == null && b.sort == null) return a.index - b.index;
|
|
@@ -336,31 +379,47 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
|
|
|
336
379
|
return a.sort - b.sort || a.index - b.index;
|
|
337
380
|
});
|
|
338
381
|
};
|
|
382
|
+
const trackItem = (
|
|
383
|
+
item: NormalizedSwiperDataItem,
|
|
384
|
+
index: number,
|
|
385
|
+
clone: TrackItem['clone']
|
|
386
|
+
): TrackItem => {
|
|
387
|
+
const key =
|
|
388
|
+
clone === null
|
|
389
|
+
? `real:${item.key}:${Boolean(item.url)}`
|
|
390
|
+
: `clone:${clone}:${item.key}:${Boolean(item.url)}`;
|
|
391
|
+
const cached = trackItemCache.get(key);
|
|
392
|
+
if (cached && cached.item === item && cached.clone === clone) {
|
|
393
|
+
cached.index = index;
|
|
394
|
+
return cached;
|
|
395
|
+
}
|
|
396
|
+
const next = { item, index, clone, key } satisfies TrackItem;
|
|
397
|
+
trackItemCache.set(key, next);
|
|
398
|
+
return next;
|
|
399
|
+
};
|
|
339
400
|
const createTrackItems = (items: NormalizedSwiperDataItem[]): TrackItem[] => {
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
index,
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
})
|
|
346
|
-
if (!props.loop || items.length <= 1)
|
|
401
|
+
const activeKeys = new Set<string>();
|
|
402
|
+
const real = items.map((item, index) => {
|
|
403
|
+
const current = trackItem(item, index, null);
|
|
404
|
+
activeKeys.add(current.key);
|
|
405
|
+
return current;
|
|
406
|
+
});
|
|
407
|
+
if (!props.loop || items.length <= 1) {
|
|
408
|
+
for (const key of trackItemCache.keys()) {
|
|
409
|
+
if (!activeKeys.has(key)) trackItemCache.delete(key);
|
|
410
|
+
}
|
|
411
|
+
return real;
|
|
412
|
+
}
|
|
347
413
|
const first = items[0];
|
|
348
414
|
const last = items[items.length - 1];
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
{
|
|
358
|
-
item: first,
|
|
359
|
-
index: 0,
|
|
360
|
-
clone: 'first',
|
|
361
|
-
key: `clone:first:${first.key}:${Boolean(first.url)}`,
|
|
362
|
-
},
|
|
363
|
-
];
|
|
415
|
+
const lastClone = trackItem(last, items.length - 1, 'last');
|
|
416
|
+
const firstClone = trackItem(first, 0, 'first');
|
|
417
|
+
activeKeys.add(lastClone.key);
|
|
418
|
+
activeKeys.add(firstClone.key);
|
|
419
|
+
for (const key of trackItemCache.keys()) {
|
|
420
|
+
if (!activeKeys.has(key)) trackItemCache.delete(key);
|
|
421
|
+
}
|
|
422
|
+
return [lastClone, ...real, firstClone];
|
|
364
423
|
};
|
|
365
424
|
const realCount = (): number => normalizedItems().length;
|
|
366
425
|
const trackCount = (): number => trackItems().length;
|
|
@@ -373,6 +432,10 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
|
|
|
373
432
|
if (index === trackCount() - 1) return 0;
|
|
374
433
|
return index - 1;
|
|
375
434
|
};
|
|
435
|
+
const toRealIndexFromTrackPosition = (index: number): number => {
|
|
436
|
+
if (!props.loop) return Math.max(0, index);
|
|
437
|
+
return toRealIndex(index);
|
|
438
|
+
};
|
|
376
439
|
const trackIndexForRealIndex = (index: number): number => {
|
|
377
440
|
const count = realCount();
|
|
378
441
|
if (!count) return 0;
|
|
@@ -710,11 +773,22 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
|
|
|
710
773
|
});
|
|
711
774
|
};
|
|
712
775
|
|
|
713
|
-
const slideView = (
|
|
776
|
+
const slideView = (
|
|
777
|
+
itemAccessor: () => TrackItem,
|
|
778
|
+
trackIndexAccessor: () => number
|
|
779
|
+
): HTMLElement => {
|
|
780
|
+
const realIndex = (): number =>
|
|
781
|
+
toRealIndexFromTrackPosition(trackIndexAccessor());
|
|
714
782
|
const slideContent = () => {
|
|
715
|
-
const { item
|
|
716
|
-
const context = { swiper, item, index };
|
|
783
|
+
const { item } = itemAccessor();
|
|
717
784
|
if (item.children != null) {
|
|
785
|
+
const context = {
|
|
786
|
+
swiper,
|
|
787
|
+
item,
|
|
788
|
+
get index() {
|
|
789
|
+
return realIndex();
|
|
790
|
+
},
|
|
791
|
+
} as SwiperSlideContext;
|
|
718
792
|
return asRenderable(
|
|
719
793
|
typeof item.children === 'function'
|
|
720
794
|
? item.children(context)
|
|
@@ -742,12 +816,12 @@ export function createSwiper(input: SwiperProps = {}): Swiper {
|
|
|
742
816
|
};
|
|
743
817
|
const commonProps = {
|
|
744
818
|
className: props.className.slide,
|
|
745
|
-
'data-swiper-slide': () => String(
|
|
819
|
+
'data-swiper-slide': () => String(realIndex()),
|
|
746
820
|
'data-swiper-index': () =>
|
|
747
|
-
itemAccessor().clone ? null : String(
|
|
821
|
+
itemAccessor().clone ? null : String(realIndex()),
|
|
748
822
|
'data-clone': () => itemAccessor().clone,
|
|
749
823
|
role: 'group',
|
|
750
|
-
'aria-label': () => `Slide ${
|
|
824
|
+
'aria-label': () => `Slide ${realIndex() + 1}`,
|
|
751
825
|
children: slideContent,
|
|
752
826
|
};
|
|
753
827
|
return itemAccessor().item.url
|
package/src/css/content.css
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
margin: var(--content-heading-space) 0
|
|
32
32
|
calc(var(--content-heading-space) * 0.75);
|
|
33
33
|
font-weight: 600;
|
|
34
|
-
line-height: 1.3;
|
|
34
|
+
/* line-height: 1.3; */
|
|
35
35
|
color: var(--content-heading-fg);
|
|
36
36
|
letter-spacing: -0.02em;
|
|
37
37
|
}
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
.j-content h2 {
|
|
47
47
|
font-size: 1.625em;
|
|
48
48
|
font-weight: 650;
|
|
49
|
-
padding-bottom: 0.
|
|
49
|
+
padding-bottom: 0.25em;
|
|
50
50
|
border-bottom: 1px solid var(--content-border-subtle);
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -458,9 +458,15 @@
|
|
|
458
458
|
.hljs-comment,
|
|
459
459
|
.hljs-code,
|
|
460
460
|
.hljs-formula {
|
|
461
|
-
color: var(--ui-
|
|
461
|
+
color: var(--ui-fg-soft);
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
+
/* .dark .hljs-comment,
|
|
465
|
+
.dark .hljs-code,
|
|
466
|
+
.dark .hljs-formula {
|
|
467
|
+
color: var(--ui-gray-shade);
|
|
468
|
+
} */
|
|
469
|
+
|
|
464
470
|
/* prettylights-syntax-entity-tag */
|
|
465
471
|
.hljs-name,
|
|
466
472
|
.hljs-quote,
|
|
@@ -581,6 +587,7 @@
|
|
|
581
587
|
padding: 0.75em 1em;
|
|
582
588
|
border-bottom: 1px solid var(--content-border-subtle);
|
|
583
589
|
text-align: left;
|
|
590
|
+
vertical-align: top;
|
|
584
591
|
}
|
|
585
592
|
|
|
586
593
|
.j-content th {
|
package/src/css/form.css
CHANGED
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
margin: 0;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
.j-form .form-field .field-control .help-block
|
|
73
|
+
.j-form .form-field .field-control .help-block[data-valid='false'] {
|
|
74
74
|
color: var(--state-danger);
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -188,12 +188,12 @@
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
/* input validate */
|
|
191
|
-
/* .j-input
|
|
192
|
-
.j-select
|
|
193
|
-
.j-textarea
|
|
194
|
-
.j-input
|
|
195
|
-
.j-select
|
|
196
|
-
.j-textarea
|
|
191
|
+
/* .j-input[data-valid='true'],
|
|
192
|
+
.j-select[data-valid='true'],
|
|
193
|
+
.j-textarea[data-valid='true'],
|
|
194
|
+
.j-input[data-valid='false'],
|
|
195
|
+
.j-select[data-valid='false'],
|
|
196
|
+
.j-textarea[data-valid='false'] {
|
|
197
197
|
padding-right: calc(1.4285714286em + 0.875rem);
|
|
198
198
|
background-repeat: no-repeat;
|
|
199
199
|
background-position: right calc(0.3571428572em + 0.21875rem) center;
|
|
@@ -201,16 +201,16 @@
|
|
|
201
201
|
calc(0.7142857143em + 0.4375rem);
|
|
202
202
|
} */
|
|
203
203
|
|
|
204
|
-
/* .j-input
|
|
205
|
-
.j-select
|
|
206
|
-
.j-textarea
|
|
204
|
+
/* .j-input[data-valid='true'],
|
|
205
|
+
.j-select[data-valid='true'],
|
|
206
|
+
.j-textarea[data-valid='true'] {
|
|
207
207
|
border-color: var(--form-valid-border-color, var(--state-success));
|
|
208
208
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%232fb344' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='20 6 9 17 4 12'%3e%3c/polyline%3e%3c/svg%3e");
|
|
209
209
|
} */
|
|
210
210
|
|
|
211
|
-
.j-input
|
|
212
|
-
.j-select
|
|
213
|
-
.j-textarea
|
|
211
|
+
.j-input[data-valid='false'],
|
|
212
|
+
.j-select[data-valid='false'],
|
|
213
|
+
.j-textarea[data-valid='false'] {
|
|
214
214
|
border-color: var(--form-invalid-border-color, var(--state-danger));
|
|
215
215
|
/* background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23d63939' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cline x1='18' y1='6' x2='6' y2='18'%3e%3c/line%3e%3cline x1='6' y1='6' x2='18' y2='18'%3e%3c/line%3e%3c/svg%3e"); */
|
|
216
216
|
}
|
|
@@ -861,14 +861,14 @@
|
|
|
861
861
|
box-shadow: var(--input-focus-shadow, none);
|
|
862
862
|
}
|
|
863
863
|
|
|
864
|
-
/* .j-input
|
|
865
|
-
.j-select
|
|
864
|
+
/* .j-input[data-valid='true']:focus-within,
|
|
865
|
+
.j-select[data-valid='true']:focus-within {
|
|
866
866
|
border-color: transparent;
|
|
867
867
|
outline-color: var(--form-valid-border-color, var(--state-success));
|
|
868
868
|
} */
|
|
869
869
|
|
|
870
|
-
.j-input
|
|
871
|
-
.j-select
|
|
870
|
+
.j-input[data-valid='false']:focus-within,
|
|
871
|
+
.j-select[data-valid='false']:focus-within {
|
|
872
872
|
border-color: transparent;
|
|
873
873
|
outline-color: var(--form-invalid-border-color, var(--state-danger));
|
|
874
874
|
}
|
package/src/css/menu.css
CHANGED
|
@@ -363,6 +363,7 @@
|
|
|
363
363
|
overflow-y: auto;
|
|
364
364
|
scrollbar-width: thin;
|
|
365
365
|
scrollbar-color: var(--ui-border) transparent;
|
|
366
|
+
height: 100%;
|
|
366
367
|
}
|
|
367
368
|
|
|
368
369
|
.j-menu[data-menu-type='mobile'] .menu {
|
|
@@ -416,13 +417,13 @@
|
|
|
416
417
|
width: 100%;
|
|
417
418
|
visibility: hidden;
|
|
418
419
|
opacity: 0;
|
|
419
|
-
transform: translateX(80%);
|
|
420
|
+
/* transform: translateX(80%); */
|
|
420
421
|
transition:
|
|
421
|
-
transform 0.3s ease-in-out,
|
|
422
|
-
opacity 0.3s ease-in-out
|
|
423
|
-
visibility 0.3s ease-in-out;
|
|
422
|
+
/* transform 0.3s ease-in-out, */
|
|
423
|
+
/* visibility 0.3s ease-in-out, */ opacity 0.3s ease-in-out;
|
|
424
424
|
max-width: 100%;
|
|
425
425
|
background: var(--ui-bg);
|
|
426
|
+
padding-block: 0;
|
|
426
427
|
}
|
|
427
428
|
|
|
428
429
|
.j-menu[data-menu-type='mobile'] .menu .sub-menu .menu-item:hover a {
|
|
@@ -440,9 +441,9 @@
|
|
|
440
441
|
> .sub-menu {
|
|
441
442
|
visibility: visible;
|
|
442
443
|
opacity: 1;
|
|
443
|
-
transform: translateX(0);
|
|
444
|
-
height: 100%;
|
|
444
|
+
/* transform: translateX(0); */
|
|
445
445
|
z-index: var(--z-submenu);
|
|
446
|
+
height: 100%;
|
|
446
447
|
}
|
|
447
448
|
|
|
448
449
|
/* bottom menu */
|
package/src/css/table.css
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
--table-border-color: var(--ui-border);
|
|
11
11
|
--table-cell-padding: 0.5em 0.75em;
|
|
12
12
|
--table-cell-text-align: left;
|
|
13
|
+
--table-cell-vertical-align: top;
|
|
13
14
|
--table-link-fg: var(--tone-solid);
|
|
14
15
|
--table-striped-bg: var(--tone-soft);
|
|
15
16
|
--table-hover-bg: var(--tone-soft);
|
package/src/css/tokens.css
CHANGED
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
--z-badge: 2;
|
|
16
16
|
--z-toolbar: 2;
|
|
17
17
|
--z-submenu: 5;
|
|
18
|
+
--z-overlay: 9500;
|
|
19
|
+
--z-offcanvas: 9600;
|
|
20
|
+
--z-popup: 9700;
|
|
21
|
+
--z-toast: 9800;
|
|
18
22
|
--z-drop: 9900;
|
|
19
|
-
--z-overlay: 9950;
|
|
20
|
-
--z-offcanvas: 9960;
|
|
21
|
-
--z-popup: 9970;
|
|
22
|
-
--z-toast: 9980;
|
|
23
23
|
|
|
24
24
|
/* --- Spacing --- */
|
|
25
25
|
--space: 0.25rem;
|
|
@@ -317,7 +317,7 @@
|
|
|
317
317
|
--ui-disabled-fg: #64748b;
|
|
318
318
|
--ui-overlay: rgba(2, 6, 23, 0.7);
|
|
319
319
|
--ui-overlay-muted: rgba(2, 6, 23, 0.45);
|
|
320
|
-
--ui-popup-bg: rgba(17, 24, 39, 0.
|
|
320
|
+
--ui-popup-bg: rgba(17, 24, 39, 0.45);
|
|
321
321
|
--ui-input-color: #f9fafb;
|
|
322
322
|
--ui-input-border: rgba(148, 163, 184, 0.2);
|
|
323
323
|
--ui-input-focus-border: #a78bfa;
|
|
@@ -1162,13 +1162,13 @@
|
|
|
1162
1162
|
}
|
|
1163
1163
|
|
|
1164
1164
|
.j-radius-round {
|
|
1165
|
-
--radius-2xs:
|
|
1166
|
-
--radius-xs:
|
|
1167
|
-
--radius-sm:
|
|
1168
|
-
--radius-md:
|
|
1169
|
-
--radius-lg:
|
|
1170
|
-
--radius-xl:
|
|
1171
|
-
--radius-2xl:
|
|
1165
|
+
--radius-2xs: 0.375rem;
|
|
1166
|
+
--radius-xs: 0.5rem;
|
|
1167
|
+
--radius-sm: 0.75rem;
|
|
1168
|
+
--radius-md: 1rem;
|
|
1169
|
+
--radius-lg: 999px;
|
|
1170
|
+
--radius-xl: 999px;
|
|
1171
|
+
--radius-2xl: 999px;
|
|
1172
1172
|
}
|
|
1173
1173
|
|
|
1174
1174
|
/* ============================================
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { jsx } from 'vanilla-signal';
|
|
2
2
|
|
|
3
|
+
import { joinClasses } from '../utilities/class-name.ts';
|
|
3
4
|
import { type DOMReference, all, resolveElement } from '../utilities/dom.ts';
|
|
4
5
|
import { createEventManager } from '../utilities/events.ts';
|
|
6
|
+
import { isPlainObject } from '../utilities/object.ts';
|
|
5
7
|
import {
|
|
6
8
|
type ResolveSchema,
|
|
7
9
|
resolveProps,
|
|
@@ -45,13 +47,21 @@ interface ValidatorProps extends Record<string, unknown> {
|
|
|
45
47
|
rules?: Record<string, ValidatorRule>;
|
|
46
48
|
messages?: ValidatorMessageMap;
|
|
47
49
|
vanilla?: boolean;
|
|
50
|
+
className?: ValidatorClassNameConfig;
|
|
48
51
|
onSubmit?: ((validator: ValidatorInstance) => void) | null;
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
export interface ValidatorClassNames {
|
|
55
|
+
help: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type ValidatorClassNameConfig = Partial<ValidatorClassNames>;
|
|
59
|
+
|
|
51
60
|
interface ResolvedValidatorProps extends Record<string, unknown> {
|
|
52
61
|
rules: Record<string, ValidatorRule>;
|
|
53
62
|
messages: ValidatorMessageMap;
|
|
54
63
|
vanilla: boolean;
|
|
64
|
+
className: ValidatorClassNames;
|
|
55
65
|
onSubmit: ((validator: ValidatorInstance) => void) | null;
|
|
56
66
|
}
|
|
57
67
|
|
|
@@ -75,19 +85,36 @@ export interface ValidatorInstance {
|
|
|
75
85
|
destroy(): void;
|
|
76
86
|
}
|
|
77
87
|
|
|
88
|
+
const DEFAULT_CLASS_NAMES: ValidatorClassNames = {
|
|
89
|
+
help: 'help-block',
|
|
90
|
+
};
|
|
91
|
+
|
|
78
92
|
const VALIDATOR_PROPS_SCHEMA = {
|
|
79
93
|
rules: { default: {}, type: 'object' },
|
|
80
94
|
messages: { default: {}, type: 'object' },
|
|
81
95
|
vanilla: { default: false, type: 'boolean' },
|
|
96
|
+
className: {
|
|
97
|
+
default: DEFAULT_CLASS_NAMES,
|
|
98
|
+
type: 'object',
|
|
99
|
+
normalize: (value: unknown) => resolveClassNames(value),
|
|
100
|
+
},
|
|
82
101
|
onSubmit: { default: null, types: ['function', 'null'] },
|
|
83
102
|
} satisfies ResolveSchema<ValidatorProps>;
|
|
84
103
|
|
|
104
|
+
function resolveClassNames(value: unknown): ValidatorClassNames {
|
|
105
|
+
return {
|
|
106
|
+
...DEFAULT_CLASS_NAMES,
|
|
107
|
+
...(isPlainObject(value) ? (value as ValidatorClassNameConfig) : {}),
|
|
108
|
+
} as ValidatorClassNames;
|
|
109
|
+
}
|
|
110
|
+
|
|
85
111
|
function normalizeProps(input: ValidatorProps): ResolvedValidatorProps {
|
|
86
112
|
const props = resolveProps(input, VALIDATOR_PROPS_SCHEMA, 'Validator.props');
|
|
87
113
|
return {
|
|
88
114
|
rules: props.rules as Record<string, ValidatorRule>,
|
|
89
115
|
messages: props.messages as ValidatorMessageMap,
|
|
90
116
|
vanilla: props.vanilla as boolean,
|
|
117
|
+
className: props.className as ValidatorClassNames,
|
|
91
118
|
onSubmit: props.onSubmit as ResolvedValidatorProps['onSubmit'],
|
|
92
119
|
};
|
|
93
120
|
}
|
|
@@ -353,10 +380,7 @@ function showError(
|
|
|
353
380
|
rule: string,
|
|
354
381
|
customMessage = ''
|
|
355
382
|
): void {
|
|
356
|
-
|
|
357
|
-
element.classList.remove('is-valid');
|
|
358
|
-
element.classList.add('is-invalid');
|
|
359
|
-
}
|
|
383
|
+
element.dataset.valid = 'false';
|
|
360
384
|
|
|
361
385
|
const error = customMessage || props?.messages[name]?.[rule] || '';
|
|
362
386
|
if (!error) return;
|
|
@@ -372,11 +396,13 @@ function showError(
|
|
|
372
396
|
) || null;
|
|
373
397
|
if (!help) {
|
|
374
398
|
help = jsx('div', {
|
|
375
|
-
|
|
399
|
+
'data-valid': 'false',
|
|
376
400
|
'data-validator-help': name,
|
|
377
401
|
}) as HTMLElement;
|
|
378
402
|
formControl.appendChild(help);
|
|
379
403
|
}
|
|
404
|
+
help.className = joinClasses(props?.className.help);
|
|
405
|
+
help.dataset.valid = 'false';
|
|
380
406
|
help.textContent = error;
|
|
381
407
|
}
|
|
382
408
|
|
|
@@ -388,9 +414,7 @@ function showSuccess(element: ValidatorElement): void {
|
|
|
388
414
|
}
|
|
389
415
|
}
|
|
390
416
|
|
|
391
|
-
|
|
392
|
-
element.classList.remove('is-invalid');
|
|
393
|
-
}
|
|
417
|
+
element.removeAttribute('data-valid');
|
|
394
418
|
}
|
|
395
419
|
|
|
396
420
|
export function createValidator(
|
|
@@ -543,7 +567,7 @@ export function createValidator(
|
|
|
543
567
|
if (native) root.reset();
|
|
544
568
|
for (const control of Array.from(root.elements)) {
|
|
545
569
|
if (isValidatorElement(control)) {
|
|
546
|
-
control.
|
|
570
|
+
control.removeAttribute('data-valid');
|
|
547
571
|
}
|
|
548
572
|
}
|
|
549
573
|
for (const help of all<HTMLElement>('[data-validator-help]', root)) {
|