vanilla-jui 1.4.5 → 1.4.7
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 +6 -0
- package/dist/index.d.ts +1273 -1896
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +21 -2
- package/src/components/accordion.ts +297 -216
- package/src/components/flow.ts +222 -258
- package/src/components/form.ts +44 -102
- package/src/components/menu.ts +268 -356
- package/src/components/modal.ts +181 -641
- package/src/components/offcanvas.ts +225 -106
- package/src/components/pagination.ts +296 -326
- package/src/components/sticky.ts +55 -54
- package/src/components/swiper.ts +237 -247
- package/src/components/tabs.ts +278 -271
- package/src/components/toc.ts +42 -46
- package/src/core/Component.ts +69 -47
- package/src/css/accordion.css +2 -2
- package/src/css/animation.css +1 -5
- package/src/css/avatar.css +1 -1
- package/src/css/badge.css +1 -1
- package/src/css/breadcrumb.css +9 -10
- package/src/css/button.css +1 -1
- package/src/css/card.css +2 -2
- package/src/css/color.css +974 -1002
- package/src/css/color_p3.css +974 -1004
- package/src/css/components.css +1 -1
- package/src/css/content.css +72 -63
- package/src/css/drop.css +1 -1
- package/src/css/editor.css +14 -14
- package/src/css/flow.css +4 -14
- package/src/css/font.css +5 -4
- package/src/css/form.css +76 -52
- package/src/css/gradient.css +299 -222
- package/src/css/icon.css +57 -57
- package/src/css/index.css +1 -1
- package/src/css/layout.css +13 -10
- package/src/css/loading.css +60 -12
- package/src/css/menu.css +262 -188
- package/src/css/modal.css +1 -1
- package/src/css/offcanvas.css +9 -13
- package/src/css/pagination.css +5 -1
- package/src/css/popup.css +1 -1
- package/src/css/skeleton.css +3 -2
- package/src/css/status.css +2 -3
- package/src/css/swiper.css +1 -1
- package/src/css/table.css +1 -1
- package/src/css/tabs.css +12 -11
- package/src/css/tag.css +1 -1
- package/src/css/theme-palette.css +19 -5
- package/src/css/themes/default/accordion.css +61 -61
- package/src/css/themes/default/avatar.css +54 -54
- package/src/css/themes/default/badge.css +62 -62
- package/src/css/themes/default/breadcrumb.css +23 -23
- package/src/css/themes/default/button.css +203 -203
- package/src/css/themes/default/content.css +53 -53
- package/src/css/themes/default/drop.css +10 -10
- package/src/css/themes/default/editor.css +9 -9
- package/src/css/themes/default/index.css +1 -1
- package/src/css/themes/default/loading.css +26 -20
- package/src/css/themes/default/menu.css +75 -11
- package/src/css/themes/default/modal.css +32 -36
- package/src/css/themes/default/pagination.css +7 -7
- package/src/css/themes/default/popup.css +6 -6
- package/src/css/themes/default/swiper.css +32 -32
- package/src/css/themes/default/table.css +16 -16
- package/src/css/themes/default/tabs.css +30 -30
- package/src/css/themes/default/tag.css +71 -71
- package/src/css/themes/default/theme-palette.css +11 -11
- package/src/css/themes/default/tip.css +42 -42
- package/src/css/themes/default/toast.css +60 -60
- package/src/css/themes/default/toolbar.css +23 -29
- package/src/css/themes/default/tooltip.css +43 -13
- package/src/css/themes/index.css +1 -1
- package/src/css/tip.css +2 -2
- package/src/css/toast.css +3 -3
- package/src/css/tokens.css +12 -17
- package/src/css/toolbar.css +9 -10
- package/src/css/tooltip.css +3 -2
- package/src/index.ts +22 -19
- package/src/{components → primitives}/drop.ts +58 -49
- package/src/{components → primitives}/icons.ts +1 -1
- package/src/primitives/loading.ts +23 -0
- package/src/primitives/parabola.ts +388 -0
- package/src/primitives/popup.ts +61 -0
- package/src/{components → primitives}/theme.ts +89 -57
- package/src/{components → primitives}/toast.ts +18 -25
- package/src/{components → primitives}/tooltip.ts +100 -25
- package/src/utilities/browser.ts +23 -49
- package/src/utilities/class-name.ts +5 -0
- package/src/utilities/dom.ts +14 -32
- package/src/utilities/function.ts +4 -0
- package/src/utilities/http.ts +7 -7
- package/src/utilities/id.ts +57 -0
- package/src/utilities/object.ts +5 -0
- package/src/utilities/timer.ts +38 -0
- package/src/utilities/types.ts +420 -0
- package/src/{components → validation}/validator.ts +64 -53
- package/src/components/parabola.ts +0 -335
- package/src/utilities/cache.ts +0 -57
- package/src/utilities/core.ts +0 -614
- package/src/utilities/storage.ts +0 -46
package/src/components/form.ts
CHANGED
|
@@ -8,23 +8,20 @@ import {
|
|
|
8
8
|
} from 'vanilla-signal';
|
|
9
9
|
|
|
10
10
|
import Component, { type ComponentDOM } from '../core/Component.ts';
|
|
11
|
+
import { joinClasses } from '../utilities/class-name.ts';
|
|
11
12
|
import {
|
|
12
|
-
type ResolveSchema,
|
|
13
|
-
hasOwn,
|
|
14
|
-
isPlainObject,
|
|
15
|
-
randomId,
|
|
16
|
-
resolveProps,
|
|
17
|
-
validateParam,
|
|
18
|
-
} from '../utilities/core.ts';
|
|
19
|
-
import {
|
|
20
|
-
type DOMReference,
|
|
21
13
|
type RenderableContent,
|
|
22
14
|
all,
|
|
23
|
-
|
|
24
|
-
normalizeContentNodes,
|
|
25
|
-
requireContainer,
|
|
15
|
+
normalizeRenderableContentNodes,
|
|
26
16
|
} from '../utilities/dom.ts';
|
|
27
|
-
import {
|
|
17
|
+
import { randomId } from '../utilities/id.ts';
|
|
18
|
+
import { isPlainObject } from '../utilities/object.ts';
|
|
19
|
+
import {
|
|
20
|
+
type ResolveSchema,
|
|
21
|
+
resolveProps,
|
|
22
|
+
validateParam,
|
|
23
|
+
} from '../utilities/types.ts';
|
|
24
|
+
import { createValidator } from '../validation/validator.ts';
|
|
28
25
|
|
|
29
26
|
type FormValue = string | number | boolean;
|
|
30
27
|
type FormOptionInput = FormValue | FormOption;
|
|
@@ -35,7 +32,6 @@ type FormControlElement =
|
|
|
35
32
|
type FormStyle = string | Partial<CSSStyleDeclaration> | null;
|
|
36
33
|
export type FormDataValue = FormDataEntryValue | FormDataEntryValue[];
|
|
37
34
|
export type FormDataRecord = Record<string, FormDataValue>;
|
|
38
|
-
type FormContainer = Element | DocumentFragment | false | null;
|
|
39
35
|
type FormControlType =
|
|
40
36
|
| 'checkbox'
|
|
41
37
|
| 'custom'
|
|
@@ -159,7 +155,6 @@ interface FormState extends ResolvedFormProps {
|
|
|
159
155
|
|
|
160
156
|
interface FormDOM extends ComponentDOM {
|
|
161
157
|
root: HTMLFormElement | null;
|
|
162
|
-
container: DOMReference | DocumentFragment;
|
|
163
158
|
fields: Map<string, FormControlElement>;
|
|
164
159
|
}
|
|
165
160
|
|
|
@@ -169,8 +164,8 @@ interface FormCache {
|
|
|
169
164
|
}
|
|
170
165
|
|
|
171
166
|
interface ValidatorInstance {
|
|
172
|
-
root: Element | null;
|
|
173
|
-
|
|
167
|
+
dom: { root: Element | null };
|
|
168
|
+
props: (FormValidatorConfig & { onSubmit: null }) | null;
|
|
174
169
|
validate: () => boolean;
|
|
175
170
|
reset: () => void;
|
|
176
171
|
destroy: () => void;
|
|
@@ -251,11 +246,6 @@ const FORM_PROPS_SCHEMA = {
|
|
|
251
246
|
onReset: { default: null, types: ['function', 'null'] },
|
|
252
247
|
} satisfies ResolveSchema<FormProps>;
|
|
253
248
|
|
|
254
|
-
const FORM_UPDATE_RULE = {
|
|
255
|
-
validate: (value: unknown) => isPlainObject(value),
|
|
256
|
-
message: 'expects a props object.',
|
|
257
|
-
};
|
|
258
|
-
|
|
259
249
|
function cloneOptions(
|
|
260
250
|
options: readonly FormOptionInput[] | undefined
|
|
261
251
|
): FormOptionInput[] {
|
|
@@ -296,14 +286,6 @@ function stringifyFormValue(
|
|
|
296
286
|
return value.name;
|
|
297
287
|
}
|
|
298
288
|
|
|
299
|
-
function contentView<TContext>(
|
|
300
|
-
content: unknown,
|
|
301
|
-
context: TContext
|
|
302
|
-
): Node[] | null {
|
|
303
|
-
if (!isRenderableContent(content)) return null;
|
|
304
|
-
return normalizeContentNodes(content, context);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
289
|
function fieldIsRequired(
|
|
308
290
|
field: FormField,
|
|
309
291
|
rules: FormValidatorConfig['rules']
|
|
@@ -312,14 +294,10 @@ function fieldIsRequired(
|
|
|
312
294
|
return !!field.required || !!rules?.[field.name]?.required;
|
|
313
295
|
}
|
|
314
296
|
|
|
315
|
-
function resolveClassName(...parts: Array<string | false | null | undefined>) {
|
|
316
|
-
return parts.filter(Boolean).join(' ');
|
|
317
|
-
}
|
|
318
|
-
|
|
319
297
|
function resolveClassNames(value: unknown): FormClassNames {
|
|
320
298
|
return {
|
|
321
299
|
...DEFAULT_CLASS_NAMES,
|
|
322
|
-
...(isPlainObject(value) ? value : {}),
|
|
300
|
+
...(isPlainObject(value) ? (value as Partial<FormClassNames>) : {}),
|
|
323
301
|
} as FormClassNames;
|
|
324
302
|
}
|
|
325
303
|
|
|
@@ -331,7 +309,8 @@ function setElementStyle(element: HTMLElement, style: FormStyle): void {
|
|
|
331
309
|
return;
|
|
332
310
|
}
|
|
333
311
|
if (!isPlainObject(style)) return;
|
|
334
|
-
|
|
312
|
+
const styleRecord = style as Record<string, unknown>;
|
|
313
|
+
for (const [key, value] of Object.entries(styleRecord)) {
|
|
335
314
|
if (value == null) continue;
|
|
336
315
|
if (typeof value !== 'string' && typeof value !== 'number') continue;
|
|
337
316
|
const name = key.startsWith('--')
|
|
@@ -342,7 +321,9 @@ function setElementStyle(element: HTMLElement, style: FormStyle): void {
|
|
|
342
321
|
}
|
|
343
322
|
|
|
344
323
|
function cloneValidator(validator: unknown): FormValidatorConfig {
|
|
345
|
-
const source =
|
|
324
|
+
const source = (
|
|
325
|
+
isPlainObject(validator) ? validator : {}
|
|
326
|
+
) as Partial<FormValidatorConfig> & Record<string, unknown>;
|
|
346
327
|
return {
|
|
347
328
|
...source,
|
|
348
329
|
rules: isPlainObject(source.rules)
|
|
@@ -371,16 +352,15 @@ function normalizeProps(input: FormProps): ResolvedFormProps {
|
|
|
371
352
|
};
|
|
372
353
|
}
|
|
373
354
|
|
|
374
|
-
|
|
355
|
+
class Form extends Component<ResolvedFormProps, FormState, FormDOM> {
|
|
375
356
|
declare state: FormState;
|
|
376
357
|
validator: ValidatorInstance | null;
|
|
377
358
|
cache: FormCache;
|
|
378
359
|
|
|
379
|
-
constructor(input: FormProps = {}
|
|
360
|
+
constructor(input: FormProps = {}) {
|
|
380
361
|
const props = normalizeProps(input);
|
|
381
362
|
super(props);
|
|
382
363
|
|
|
383
|
-
this.dom.container = container;
|
|
384
364
|
this.dom.fields = new Map();
|
|
385
365
|
this.validator = null;
|
|
386
366
|
this.cleanup.view = null;
|
|
@@ -397,43 +377,23 @@ export class Form extends Component<ResolvedFormProps, FormState, FormDOM> {
|
|
|
397
377
|
});
|
|
398
378
|
}
|
|
399
379
|
|
|
400
|
-
|
|
380
|
+
get root(): HTMLFormElement | null {
|
|
401
381
|
return this.dom?.root || null;
|
|
402
382
|
}
|
|
403
383
|
|
|
404
|
-
|
|
384
|
+
set root(value: HTMLFormElement | null) {
|
|
405
385
|
this.dom.root = value;
|
|
406
386
|
}
|
|
407
387
|
|
|
408
|
-
|
|
409
|
-
if (
|
|
410
|
-
this.dom.container === false ||
|
|
411
|
-
this.dom.container == null ||
|
|
412
|
-
typeof this.dom.container === 'string'
|
|
413
|
-
) {
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
this.mount(this.dom.container as FormContainer);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
build(container: DOMReference = this.dom.container): this {
|
|
388
|
+
build(): this {
|
|
420
389
|
if (this.runtime.destroyed)
|
|
421
390
|
throw new Error('Form.build: instance destroyed');
|
|
422
391
|
if (this.cleanup.view) return this;
|
|
423
|
-
this.dom.container =
|
|
424
|
-
container === false
|
|
425
|
-
? document.createDocumentFragment()
|
|
426
|
-
: requireContainer(container, 'Form.container');
|
|
427
392
|
this.init(this.props);
|
|
428
|
-
return this;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
mount(container: FormContainer = this.dom.container as FormContainer): this {
|
|
432
|
-
if (this.cleanup.view) return this;
|
|
433
|
-
if (!container) return this;
|
|
434
393
|
|
|
394
|
+
const host = document.createElement('div');
|
|
435
395
|
this.cleanup.view = createRoot((dispose) => {
|
|
436
|
-
const viewDispose = render(() => this.view(),
|
|
396
|
+
const viewDispose = render(() => this.view(), host);
|
|
437
397
|
onCleanup(viewDispose);
|
|
438
398
|
return dispose;
|
|
439
399
|
});
|
|
@@ -445,7 +405,7 @@ export class Form extends Component<ResolvedFormProps, FormState, FormDOM> {
|
|
|
445
405
|
const className = this.state.className;
|
|
446
406
|
return jsx('form', {
|
|
447
407
|
className: () =>
|
|
448
|
-
|
|
408
|
+
joinClasses(
|
|
449
409
|
this.state.className.form,
|
|
450
410
|
this.state.vertical ? className.vertical : className.horizontal,
|
|
451
411
|
this.state.itemVertical
|
|
@@ -510,7 +470,7 @@ export class Form extends Component<ResolvedFormProps, FormState, FormDOM> {
|
|
|
510
470
|
const className = this.state.className;
|
|
511
471
|
|
|
512
472
|
return jsx('label', {
|
|
513
|
-
className:
|
|
473
|
+
className: joinClasses(
|
|
514
474
|
className.label,
|
|
515
475
|
fieldIsRequired(field, this.state.validator?.rules)
|
|
516
476
|
? className.required
|
|
@@ -541,7 +501,11 @@ export class Form extends Component<ResolvedFormProps, FormState, FormDOM> {
|
|
|
541
501
|
case 'switch':
|
|
542
502
|
return this.switchView(field, id);
|
|
543
503
|
case 'custom':
|
|
544
|
-
return
|
|
504
|
+
return normalizeRenderableContentNodes(field.content, {
|
|
505
|
+
form: this,
|
|
506
|
+
field,
|
|
507
|
+
index,
|
|
508
|
+
});
|
|
545
509
|
default:
|
|
546
510
|
return this.inputView(field, id);
|
|
547
511
|
}
|
|
@@ -601,7 +565,7 @@ export class Form extends Component<ResolvedFormProps, FormState, FormDOM> {
|
|
|
601
565
|
): HTMLElement {
|
|
602
566
|
const direction = field.vertical ? 'vertical' : 'horizontal';
|
|
603
567
|
const classNameConfig = this.state.className;
|
|
604
|
-
const className =
|
|
568
|
+
const className = joinClasses(
|
|
605
569
|
type === 'radio' ? classNameConfig.radio : classNameConfig.checkbox,
|
|
606
570
|
direction === 'vertical'
|
|
607
571
|
? classNameConfig.choiceVertical
|
|
@@ -649,7 +613,7 @@ export class Form extends Component<ResolvedFormProps, FormState, FormDOM> {
|
|
|
649
613
|
switchView(field: FormField, id: string): HTMLLabelElement {
|
|
650
614
|
const className = this.state.className;
|
|
651
615
|
return jsx('label', {
|
|
652
|
-
className:
|
|
616
|
+
className: joinClasses(
|
|
653
617
|
className.switch,
|
|
654
618
|
field.variant ? `is-${field.variant}` : className.switchDefault,
|
|
655
619
|
field.size ? `is-${field.size}` : className.switchSizeMd
|
|
@@ -684,7 +648,7 @@ export class Form extends Component<ResolvedFormProps, FormState, FormDOM> {
|
|
|
684
648
|
children: this.state.buttons.map((button) =>
|
|
685
649
|
jsx('button', {
|
|
686
650
|
type: button.type || 'button',
|
|
687
|
-
className:
|
|
651
|
+
className: joinClasses(
|
|
688
652
|
className.button,
|
|
689
653
|
button.theme ? `is-${button.theme}` : '',
|
|
690
654
|
button.className || ''
|
|
@@ -743,13 +707,13 @@ export class Form extends Component<ResolvedFormProps, FormState, FormDOM> {
|
|
|
743
707
|
onSubmit: null,
|
|
744
708
|
};
|
|
745
709
|
|
|
746
|
-
if (this.validator?.root === this.root) {
|
|
747
|
-
this.validator.
|
|
710
|
+
if (this.validator?.dom.root === this.root) {
|
|
711
|
+
this.validator.props = options;
|
|
748
712
|
return;
|
|
749
713
|
}
|
|
750
714
|
|
|
751
715
|
this.validator?.destroy();
|
|
752
|
-
this.validator =
|
|
716
|
+
this.validator = createValidator(
|
|
753
717
|
this.root,
|
|
754
718
|
options,
|
|
755
719
|
false
|
|
@@ -825,7 +789,7 @@ export class Form extends Component<ResolvedFormProps, FormState, FormDOM> {
|
|
|
825
789
|
const formData = new FormData(form);
|
|
826
790
|
|
|
827
791
|
for (const [key, value] of formData.entries()) {
|
|
828
|
-
if (hasOwn(data, key)) {
|
|
792
|
+
if (Object.hasOwn(data, key)) {
|
|
829
793
|
data[key] = Array.isArray(data[key])
|
|
830
794
|
? [...data[key], value]
|
|
831
795
|
: [data[key], value];
|
|
@@ -849,34 +813,15 @@ export class Form extends Component<ResolvedFormProps, FormState, FormDOM> {
|
|
|
849
813
|
return this;
|
|
850
814
|
}
|
|
851
815
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
_options = {}
|
|
855
|
-
): this {
|
|
856
|
-
const propsPatch = patch || {};
|
|
857
|
-
validateParam('props', propsPatch, FORM_UPDATE_RULE, 'Form.update');
|
|
858
|
-
const nextProps = normalizeProps(Object.assign({}, this.props, propsPatch));
|
|
859
|
-
|
|
860
|
-
this.props = this.cloneProps(nextProps);
|
|
816
|
+
setFields(fields: readonly FormField[]): this {
|
|
817
|
+
validateParam('fields', fields, 'array', 'Form.setFields');
|
|
861
818
|
this.cache.fieldIds.clear();
|
|
862
|
-
super.update(nextProps);
|
|
863
|
-
|
|
864
819
|
flushSync(() => {
|
|
865
|
-
|
|
866
|
-
for (const key of Object.keys(statePatch) as Array<
|
|
867
|
-
keyof ResolvedFormProps
|
|
868
|
-
>) {
|
|
869
|
-
this.state[key] = statePatch[key] as FormState[typeof key];
|
|
870
|
-
}
|
|
820
|
+
this.state.fields = cloneFields(fields);
|
|
871
821
|
});
|
|
872
822
|
return this;
|
|
873
823
|
}
|
|
874
824
|
|
|
875
|
-
setFields(fields: readonly FormField[]): this {
|
|
876
|
-
validateParam('fields', fields, 'array', 'Form.setFields');
|
|
877
|
-
return this.update({ fields });
|
|
878
|
-
}
|
|
879
|
-
|
|
880
825
|
resetFields(): this {
|
|
881
826
|
return this.setFields(this.cache.initial.fields);
|
|
882
827
|
}
|
|
@@ -945,9 +890,6 @@ export class Form extends Component<ResolvedFormProps, FormState, FormDOM> {
|
|
|
945
890
|
}
|
|
946
891
|
}
|
|
947
892
|
|
|
948
|
-
export function createForm(
|
|
949
|
-
|
|
950
|
-
container: DOMReference = false
|
|
951
|
-
): Form {
|
|
952
|
-
return new Form(props, container);
|
|
893
|
+
export function createForm(props: FormProps = {}): Form {
|
|
894
|
+
return new Form(props);
|
|
953
895
|
}
|