vanilla-jui 1.5.13 → 1.5.15
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/dist/index.d.ts +17 -27
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/form.ts +143 -192
- package/src/css/content.css +0 -34
- package/src/css/form.css +5 -0
- package/src/css/table.css +18 -14
- package/src/css/themes/default/table.css +6 -4
- package/src/css/tokens.css +2 -2
package/package.json
CHANGED
package/src/components/form.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
type ElementProps,
|
|
4
4
|
bindAttr,
|
|
5
5
|
createDeepStore,
|
|
6
|
-
createEffect,
|
|
7
6
|
createMemo,
|
|
8
7
|
createSignal,
|
|
9
8
|
flushSync,
|
|
@@ -62,6 +61,8 @@ export interface FormClassNames {
|
|
|
62
61
|
helpInvalid: string;
|
|
63
62
|
buttons: string;
|
|
64
63
|
button: string;
|
|
64
|
+
submitBtn: string;
|
|
65
|
+
resetBtn: string;
|
|
65
66
|
input: string;
|
|
66
67
|
textarea: string;
|
|
67
68
|
select: string;
|
|
@@ -73,8 +74,6 @@ export interface FormClassNames {
|
|
|
73
74
|
radioLabel: string;
|
|
74
75
|
radioText: string;
|
|
75
76
|
switch: string;
|
|
76
|
-
switchDefault: string;
|
|
77
|
-
switchSizeMd: string;
|
|
78
77
|
switchSlider: string;
|
|
79
78
|
}
|
|
80
79
|
|
|
@@ -126,16 +125,6 @@ export interface FormField {
|
|
|
126
125
|
content?: RenderableContent<FormControlContext>;
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
export interface FormButton {
|
|
130
|
-
type?: string;
|
|
131
|
-
text?: RenderableContent<Form>;
|
|
132
|
-
label?: RenderableContent<Form>;
|
|
133
|
-
theme?: string;
|
|
134
|
-
action?: string;
|
|
135
|
-
disabled?: boolean;
|
|
136
|
-
className?: string;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
128
|
export interface FormValidatorConfig {
|
|
140
129
|
rules?: Record<string, Record<string, unknown>>;
|
|
141
130
|
messages?: Record<string, Record<string, string>>;
|
|
@@ -145,16 +134,25 @@ export interface FormValidatorConfig {
|
|
|
145
134
|
[key: string]: unknown;
|
|
146
135
|
}
|
|
147
136
|
|
|
148
|
-
export type
|
|
137
|
+
export type FormButtons = boolean | string;
|
|
138
|
+
|
|
139
|
+
export interface FormText {
|
|
140
|
+
submit: RenderableContent<Form>;
|
|
141
|
+
reset: RenderableContent<Form>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export type FormTextConfig = Partial<FormText>;
|
|
149
145
|
|
|
150
146
|
export interface FormProps extends Record<string, unknown> {
|
|
151
147
|
id?: string | null;
|
|
152
148
|
vertical?: boolean;
|
|
153
149
|
itemVertical?: boolean;
|
|
150
|
+
size?: string;
|
|
154
151
|
style?: FormStyle;
|
|
155
152
|
fields?: readonly FormItem<FormField>[];
|
|
156
|
-
buttons?:
|
|
157
|
-
buttonsPosition?:
|
|
153
|
+
buttons?: FormButtons;
|
|
154
|
+
buttonsPosition?: string;
|
|
155
|
+
text?: FormTextConfig;
|
|
158
156
|
className?: FormClassNameConfig;
|
|
159
157
|
validator?: FormValidatorConfig;
|
|
160
158
|
onSubmit?:
|
|
@@ -167,10 +165,12 @@ interface ResolvedFormProps extends Record<string, unknown> {
|
|
|
167
165
|
id: string;
|
|
168
166
|
vertical: boolean;
|
|
169
167
|
itemVertical: boolean;
|
|
168
|
+
size: string;
|
|
170
169
|
style: FormStyle;
|
|
171
170
|
fields: FormItem<FormField>[];
|
|
172
|
-
buttons:
|
|
173
|
-
buttonsPosition:
|
|
171
|
+
buttons: FormButtons;
|
|
172
|
+
buttonsPosition: string;
|
|
173
|
+
text: FormText;
|
|
174
174
|
className: FormClassNames;
|
|
175
175
|
validator: FormValidatorConfig;
|
|
176
176
|
onSubmit: ((data: FormDataRecord, form: Form) => void | Promise<void>) | null;
|
|
@@ -178,22 +178,11 @@ interface ResolvedFormProps extends Record<string, unknown> {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
interface FormState extends Record<string, unknown> {
|
|
181
|
-
id: string;
|
|
182
|
-
vertical: boolean;
|
|
183
|
-
itemVertical: boolean;
|
|
184
|
-
style: FormStyle;
|
|
185
181
|
fields: FormItem<FormField>[];
|
|
186
|
-
buttons: FormButton[];
|
|
187
|
-
className: FormClassNames;
|
|
188
|
-
validator: FormValidatorConfig;
|
|
189
|
-
onSubmit: ((data: FormDataRecord, form: Form) => void | Promise<void>) | null;
|
|
190
|
-
onReset: ((event: Event, form: Form) => void) | null;
|
|
191
182
|
submitting: boolean;
|
|
192
|
-
data: FormDataRecord | null;
|
|
193
183
|
}
|
|
194
184
|
|
|
195
185
|
interface FormCache {
|
|
196
|
-
initial: ResolvedFormProps;
|
|
197
186
|
fieldIds: Map<string | number, string>;
|
|
198
187
|
}
|
|
199
188
|
|
|
@@ -225,6 +214,8 @@ const DEFAULT_CLASS_NAMES: FormClassNames = {
|
|
|
225
214
|
helpInvalid: 'is-invalid',
|
|
226
215
|
buttons: 'form-buttons',
|
|
227
216
|
button: 'j-button',
|
|
217
|
+
submitBtn: 'is-primary',
|
|
218
|
+
resetBtn: 'is-ghost',
|
|
228
219
|
input: 'j-input',
|
|
229
220
|
textarea: 'j-textarea',
|
|
230
221
|
select: 'j-select',
|
|
@@ -235,26 +226,14 @@ const DEFAULT_CLASS_NAMES: FormClassNames = {
|
|
|
235
226
|
choiceGroup: 'is-group',
|
|
236
227
|
radioLabel: 'radio-label',
|
|
237
228
|
radioText: 'radio-text',
|
|
238
|
-
switch: 'j-switch',
|
|
239
|
-
switchDefault: 'is-default',
|
|
240
|
-
switchSizeMd: 'is-md',
|
|
229
|
+
switch: 'j-switch is-default',
|
|
241
230
|
switchSlider: 'switch-slider',
|
|
242
231
|
};
|
|
243
232
|
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
theme: 'primary',
|
|
249
|
-
action: 'submit',
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
type: 'reset',
|
|
253
|
-
text: t('Reset', locales),
|
|
254
|
-
theme: 'ghost',
|
|
255
|
-
action: 'reset',
|
|
256
|
-
},
|
|
257
|
-
];
|
|
233
|
+
const DEFAULT_TEXT: FormText = {
|
|
234
|
+
submit: t('Submit', locales),
|
|
235
|
+
reset: t('Reset', locales),
|
|
236
|
+
};
|
|
258
237
|
|
|
259
238
|
// Guard against malformed dynamic chains that never terminate.
|
|
260
239
|
const FORM_ITEM_LIMIT = 1000;
|
|
@@ -272,22 +251,15 @@ const FORM_PROPS_SCHEMA = {
|
|
|
272
251
|
},
|
|
273
252
|
vertical: { default: true, type: 'boolean' },
|
|
274
253
|
itemVertical: { default: true, type: 'boolean' },
|
|
254
|
+
size: { default: 'md', type: 'string' },
|
|
275
255
|
style: { default: '', types: ['string', 'object', 'null'] },
|
|
276
256
|
fields: { default: [], type: 'array' },
|
|
277
|
-
buttons: {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (Array.isArray(value)) return cloneButtons(value);
|
|
284
|
-
return value;
|
|
285
|
-
},
|
|
286
|
-
},
|
|
287
|
-
buttonsPosition: {
|
|
288
|
-
default: 'end',
|
|
289
|
-
type: 'string',
|
|
290
|
-
enum: ['start', 'center', 'end'],
|
|
257
|
+
buttons: { default: true, types: ['boolean', 'string'] },
|
|
258
|
+
buttonsPosition: { default: 'start', type: 'string' },
|
|
259
|
+
text: {
|
|
260
|
+
default: DEFAULT_TEXT,
|
|
261
|
+
type: 'object',
|
|
262
|
+
normalize: (value: unknown) => resolveText(value),
|
|
291
263
|
},
|
|
292
264
|
className: {
|
|
293
265
|
default: DEFAULT_CLASS_NAMES,
|
|
@@ -331,13 +303,6 @@ function cloneFields(
|
|
|
331
303
|
return fields.map((item) => cloneFormItem(item));
|
|
332
304
|
}
|
|
333
305
|
|
|
334
|
-
function cloneButtons(
|
|
335
|
-
buttons: readonly FormButton[] | undefined
|
|
336
|
-
): FormButton[] {
|
|
337
|
-
if (!Array.isArray(buttons)) return [];
|
|
338
|
-
return buttons.map((button) => ({ ...button }));
|
|
339
|
-
}
|
|
340
|
-
|
|
341
306
|
function flattenFormItems(
|
|
342
307
|
rootsInput: readonly FormItem<FormField>[],
|
|
343
308
|
resolveItem: (item: FormItem<FormField>) => FormItem<FormField>
|
|
@@ -419,6 +384,20 @@ function resolveClassNames(value: unknown): FormClassNames {
|
|
|
419
384
|
} as FormClassNames;
|
|
420
385
|
}
|
|
421
386
|
|
|
387
|
+
function resolveText(value: unknown): FormText {
|
|
388
|
+
return {
|
|
389
|
+
...DEFAULT_TEXT,
|
|
390
|
+
...(isPlainObject(value) ? (value as FormTextConfig) : {}),
|
|
391
|
+
} as FormText;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function renderFormText(
|
|
395
|
+
content: RenderableContent<Form>,
|
|
396
|
+
form: Form
|
|
397
|
+
): RenderableContent<Form> {
|
|
398
|
+
return typeof content === 'function' ? content(form) : content;
|
|
399
|
+
}
|
|
400
|
+
|
|
422
401
|
function setElementStyle(element: HTMLElement, style: FormStyle): void {
|
|
423
402
|
element.removeAttribute('style');
|
|
424
403
|
if (!style) return;
|
|
@@ -438,8 +417,8 @@ function setElementStyle(element: HTMLElement, style: FormStyle): void {
|
|
|
438
417
|
}
|
|
439
418
|
}
|
|
440
419
|
|
|
441
|
-
function resolveButtonsJustifyContent(position:
|
|
442
|
-
return position === 'center' ?
|
|
420
|
+
function resolveButtonsJustifyContent(position: string): string {
|
|
421
|
+
return position === 'center' ? position : `flex-${position}`;
|
|
443
422
|
}
|
|
444
423
|
|
|
445
424
|
function cloneValidator(validator: unknown): FormValidatorConfig {
|
|
@@ -466,10 +445,12 @@ function normalizeProps(input: FormProps): ResolvedFormProps {
|
|
|
466
445
|
id: String(props.id),
|
|
467
446
|
vertical: Boolean(props.vertical),
|
|
468
447
|
itemVertical: Boolean(props.itemVertical),
|
|
448
|
+
size: props.size as string,
|
|
469
449
|
style: props.style as FormStyle,
|
|
470
450
|
fields,
|
|
471
|
-
buttons:
|
|
472
|
-
buttonsPosition: props.buttonsPosition as
|
|
451
|
+
buttons: props.buttons as FormButtons,
|
|
452
|
+
buttonsPosition: props.buttonsPosition as string,
|
|
453
|
+
text: resolveText(props.text),
|
|
473
454
|
className: resolveClassNames(props.className),
|
|
474
455
|
validator: cloneValidator(props.validator),
|
|
475
456
|
onSubmit: props.onSubmit as ResolvedFormProps['onSubmit'],
|
|
@@ -493,24 +474,6 @@ export type Form = FunctionalComponent<
|
|
|
493
474
|
FormActions
|
|
494
475
|
>;
|
|
495
476
|
|
|
496
|
-
function cloneResolvedProps(props: ResolvedFormProps): ResolvedFormProps {
|
|
497
|
-
return {
|
|
498
|
-
...props,
|
|
499
|
-
fields: cloneFields(props.fields),
|
|
500
|
-
buttons: cloneButtons(props.buttons),
|
|
501
|
-
className: resolveClassNames(props.className),
|
|
502
|
-
validator: cloneValidator(props.validator),
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function cloneStateProps(
|
|
507
|
-
props: ResolvedFormProps
|
|
508
|
-
): Omit<FormState, 'submitting' | 'data'> {
|
|
509
|
-
const stateProps: Partial<ResolvedFormProps> = cloneResolvedProps(props);
|
|
510
|
-
delete stateProps.buttonsPosition;
|
|
511
|
-
return stateProps as Omit<FormState, 'submitting' | 'data'>;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
477
|
function autoComplete(type: string): string {
|
|
515
478
|
if (type === 'password') return 'current-password';
|
|
516
479
|
if (type === 'email') return 'email';
|
|
@@ -574,16 +537,11 @@ function radioNodeListItems(value: RadioNodeList | Element): Element[] {
|
|
|
574
537
|
|
|
575
538
|
export function createForm(input: FormProps = {}): Form {
|
|
576
539
|
const props = normalizeProps(input);
|
|
577
|
-
const initial = cloneResolvedProps(props);
|
|
578
|
-
const buttonsJustifyContent = resolveButtonsJustifyContent(
|
|
579
|
-
props.buttonsPosition
|
|
580
|
-
);
|
|
581
540
|
const state = createDeepStore({
|
|
582
|
-
|
|
541
|
+
fields: cloneFields(props.fields),
|
|
583
542
|
submitting: false,
|
|
584
|
-
data: null,
|
|
585
543
|
}) as FormState;
|
|
586
|
-
const cache: FormCache = {
|
|
544
|
+
const cache: FormCache = { fieldIds: new Map() };
|
|
587
545
|
const fieldKeys = new WeakMap<object, string>();
|
|
588
546
|
const [chainVersion, setChainVersion] = createSignal(0);
|
|
589
547
|
let runtimeItems = new WeakMap<FormItem<FormField>, FormItem<FormField>>();
|
|
@@ -623,14 +581,14 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
623
581
|
if (field.id) return field.id;
|
|
624
582
|
const key = field.name || index;
|
|
625
583
|
if (!cache.fieldIds.has(key)) {
|
|
626
|
-
cache.fieldIds.set(key, `${
|
|
584
|
+
cache.fieldIds.set(key, `${props.id}_field_${index}`);
|
|
627
585
|
}
|
|
628
586
|
return cache.fieldIds.get(key) as string;
|
|
629
587
|
};
|
|
630
588
|
const syncValidator = (): void => {
|
|
631
589
|
const element = form.element;
|
|
632
590
|
if (!element) return;
|
|
633
|
-
const options = { ...
|
|
591
|
+
const options = { ...props.validator, onSubmit: null };
|
|
634
592
|
if (validator?.element === element) {
|
|
635
593
|
validator.props = options;
|
|
636
594
|
return;
|
|
@@ -673,10 +631,9 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
673
631
|
const data = collectData();
|
|
674
632
|
flushSync(() => {
|
|
675
633
|
state.submitting = true;
|
|
676
|
-
state.data = data;
|
|
677
634
|
});
|
|
678
635
|
try {
|
|
679
|
-
await Promise.resolve(
|
|
636
|
+
await Promise.resolve(props.onSubmit?.(data, form));
|
|
680
637
|
} finally {
|
|
681
638
|
if (!form.runtime.destroyed) {
|
|
682
639
|
flushSync(() => {
|
|
@@ -752,6 +709,16 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
752
709
|
'data-field-item': () => fieldAccessor().name || id,
|
|
753
710
|
});
|
|
754
711
|
|
|
712
|
+
const formSizeClass = (): string => (props.size ? `is-${props.size}` : '');
|
|
713
|
+
|
|
714
|
+
const controlSizeClass = (field: FormField): string => {
|
|
715
|
+
const size = field.size || props.size;
|
|
716
|
+
return size ? `is-${size}` : '';
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
const controlClassName = (field: FormField, base: string): string =>
|
|
720
|
+
joinClasses(field.className || base, controlSizeClass(field));
|
|
721
|
+
|
|
755
722
|
const choiceGroupView = (
|
|
756
723
|
fieldAccessor: () => FormField,
|
|
757
724
|
id: string,
|
|
@@ -761,7 +728,7 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
761
728
|
className: () => {
|
|
762
729
|
const field = fieldAccessor();
|
|
763
730
|
const direction = field.vertical ? 'vertical' : 'horizontal';
|
|
764
|
-
const names =
|
|
731
|
+
const names = props.className;
|
|
765
732
|
return joinClasses(
|
|
766
733
|
type === 'radio' ? names.radio : names.checkbox,
|
|
767
734
|
direction === 'vertical'
|
|
@@ -781,7 +748,7 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
781
748
|
const optionId = `${id}_${optionIndex}`;
|
|
782
749
|
return jsx('label', {
|
|
783
750
|
className: () =>
|
|
784
|
-
type === 'radio' ?
|
|
751
|
+
type === 'radio' ? props.className.radioLabel : '',
|
|
785
752
|
'data-field-choice': () => fieldAccessor().name || id,
|
|
786
753
|
for: optionId,
|
|
787
754
|
children: [
|
|
@@ -812,7 +779,7 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
812
779
|
}),
|
|
813
780
|
jsx('span', {
|
|
814
781
|
className: () =>
|
|
815
|
-
type === 'radio' ?
|
|
782
|
+
type === 'radio' ? props.className.radioText : '',
|
|
816
783
|
'data-field-choice-text': '',
|
|
817
784
|
children: asRenderable(
|
|
818
785
|
item.text ?? item.label ?? stringifyFormValue(item.value)
|
|
@@ -836,7 +803,7 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
836
803
|
'textarea',
|
|
837
804
|
controlProps<HTMLTextAreaElement>(fieldAccessor, id, {
|
|
838
805
|
className: () =>
|
|
839
|
-
fieldAccessor().className ||
|
|
806
|
+
fieldAccessor().className || props.className.textarea,
|
|
840
807
|
placeholder: () => fieldAccessor().placeholder || '',
|
|
841
808
|
readonly: () => !!fieldAccessor().readonly,
|
|
842
809
|
ref: (element: HTMLTextAreaElement) =>
|
|
@@ -859,7 +826,8 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
859
826
|
return jsx(
|
|
860
827
|
'select',
|
|
861
828
|
controlProps<HTMLSelectElement>(fieldAccessor, id, {
|
|
862
|
-
className: () =>
|
|
829
|
+
className: () =>
|
|
830
|
+
controlClassName(fieldAccessor(), props.className.select),
|
|
863
831
|
ref: (element: HTMLSelectElement) =>
|
|
864
832
|
bindNonEmptyAttribute(
|
|
865
833
|
element,
|
|
@@ -899,13 +867,8 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
899
867
|
if (type === 'switch') {
|
|
900
868
|
return jsx('label', {
|
|
901
869
|
className: () => {
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
return joinClasses(
|
|
905
|
-
names.switch,
|
|
906
|
-
field.variant ? `is-${field.variant}` : names.switchDefault,
|
|
907
|
-
field.size ? `is-${field.size}` : names.switchSizeMd
|
|
908
|
-
);
|
|
870
|
+
const names = props.className;
|
|
871
|
+
return joinClasses(names.switch, controlSizeClass(fieldAccessor()));
|
|
909
872
|
},
|
|
910
873
|
'data-field-switch': () => fieldAccessor().name || id,
|
|
911
874
|
for: id,
|
|
@@ -926,7 +889,7 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
926
889
|
},
|
|
927
890
|
}),
|
|
928
891
|
jsx('span', {
|
|
929
|
-
className: () =>
|
|
892
|
+
className: () => props.className.switchSlider,
|
|
930
893
|
'data-field-switch-slider': '',
|
|
931
894
|
}),
|
|
932
895
|
],
|
|
@@ -947,7 +910,8 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
947
910
|
return jsx('input', {
|
|
948
911
|
...controlProps<HTMLInputElement>(fieldAccessor, id, {
|
|
949
912
|
type: inputType,
|
|
950
|
-
className: () =>
|
|
913
|
+
className: () =>
|
|
914
|
+
controlClassName(fieldAccessor(), props.className.input),
|
|
951
915
|
placeholder: () => fieldAccessor().placeholder || '',
|
|
952
916
|
readonly: () => !!fieldAccessor().readonly,
|
|
953
917
|
autocomplete: () =>
|
|
@@ -987,23 +951,23 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
987
951
|
};
|
|
988
952
|
const fieldTitle = (): HTMLElement | null => {
|
|
989
953
|
if (!hasFieldLabel()) return null;
|
|
990
|
-
const
|
|
954
|
+
const labelProps = {
|
|
991
955
|
className: () =>
|
|
992
956
|
joinClasses(
|
|
993
|
-
|
|
994
|
-
fieldIsRequired(fieldAccessor(),
|
|
995
|
-
?
|
|
957
|
+
props.className.label,
|
|
958
|
+
fieldIsRequired(fieldAccessor(), props.validator.rules)
|
|
959
|
+
? props.className.required
|
|
996
960
|
: ''
|
|
997
961
|
),
|
|
998
962
|
'data-field-label': () => fieldAccessor().name || id,
|
|
999
963
|
children: () => asRenderable(fieldAccessor().label),
|
|
1000
964
|
};
|
|
1001
|
-
if (isGroupedControl()) return jsx('legend',
|
|
1002
|
-
return jsx('label', { ...
|
|
965
|
+
if (isGroupedControl()) return jsx('legend', labelProps) as HTMLElement;
|
|
966
|
+
return jsx('label', { ...labelProps, for: id }) as HTMLElement;
|
|
1003
967
|
};
|
|
1004
968
|
const controlContainer = (): HTMLElement =>
|
|
1005
969
|
jsx('div', {
|
|
1006
|
-
className: () =>
|
|
970
|
+
className: () => props.className.control,
|
|
1007
971
|
'data-field-control': () =>
|
|
1008
972
|
fieldAccessor().name || String(indexAccessor()),
|
|
1009
973
|
children: [
|
|
@@ -1024,7 +988,7 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
1024
988
|
],
|
|
1025
989
|
}) as HTMLElement;
|
|
1026
990
|
const rootProps = {
|
|
1027
|
-
className: () =>
|
|
991
|
+
className: () => props.className.item,
|
|
1028
992
|
'data-form-field': () => fieldAccessor().name || String(indexAccessor()),
|
|
1029
993
|
style: () => ({
|
|
1030
994
|
display: itemAccessor().type === 'hidden' ? 'none' : '',
|
|
@@ -1041,44 +1005,48 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
1041
1005
|
indexAccessor: () => number
|
|
1042
1006
|
): HTMLElement => fieldView(itemAccessor, indexAccessor);
|
|
1043
1007
|
|
|
1008
|
+
const submitButtonView = (): HTMLButtonElement =>
|
|
1009
|
+
jsx('button', {
|
|
1010
|
+
type: 'submit',
|
|
1011
|
+
className: () =>
|
|
1012
|
+
joinClasses(
|
|
1013
|
+
props.className.button,
|
|
1014
|
+
props.className.submitBtn,
|
|
1015
|
+
formSizeClass()
|
|
1016
|
+
),
|
|
1017
|
+
'data-action': 'submit',
|
|
1018
|
+
disabled: () => state.submitting,
|
|
1019
|
+
children: () => {
|
|
1020
|
+
const content = asRenderable(renderFormText(props.text.submit, form));
|
|
1021
|
+
return state.submitting ? [createLoading(), content] : content;
|
|
1022
|
+
},
|
|
1023
|
+
}) as HTMLButtonElement;
|
|
1024
|
+
|
|
1025
|
+
const resetButtonView = (): HTMLButtonElement =>
|
|
1026
|
+
jsx('button', {
|
|
1027
|
+
type: 'reset',
|
|
1028
|
+
className: () =>
|
|
1029
|
+
joinClasses(
|
|
1030
|
+
props.className.button,
|
|
1031
|
+
props.className.resetBtn,
|
|
1032
|
+
formSizeClass()
|
|
1033
|
+
),
|
|
1034
|
+
'data-action': 'reset',
|
|
1035
|
+
disabled: () => state.submitting,
|
|
1036
|
+
children: () => asRenderable(renderFormText(props.text.reset, form)),
|
|
1037
|
+
}) as HTMLButtonElement;
|
|
1038
|
+
|
|
1044
1039
|
const buttonsView = (): HTMLElement =>
|
|
1045
1040
|
jsx('div', {
|
|
1046
|
-
className:
|
|
1041
|
+
className: props.className.buttons,
|
|
1047
1042
|
style: {
|
|
1048
|
-
justifyContent:
|
|
1043
|
+
justifyContent: resolveButtonsJustifyContent(props.buttonsPosition),
|
|
1049
1044
|
},
|
|
1050
1045
|
'data-field-buttons': '',
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
`${button.action || button.type || 'button'}:${index}`,
|
|
1056
|
-
children: (buttonAccessor: () => FormButton) =>
|
|
1057
|
-
jsx('button', {
|
|
1058
|
-
type: () => buttonAccessor().type || 'button',
|
|
1059
|
-
className: () => {
|
|
1060
|
-
const button = buttonAccessor();
|
|
1061
|
-
return joinClasses(
|
|
1062
|
-
state.className.button,
|
|
1063
|
-
button.theme ? `is-${button.theme}` : '',
|
|
1064
|
-
button.className || ''
|
|
1065
|
-
);
|
|
1066
|
-
},
|
|
1067
|
-
'data-action': () => {
|
|
1068
|
-
const button = buttonAccessor();
|
|
1069
|
-
return button.action || button.type || 'button';
|
|
1070
|
-
},
|
|
1071
|
-
disabled: () => state.submitting || !!buttonAccessor().disabled,
|
|
1072
|
-
children: () => {
|
|
1073
|
-
const button = buttonAccessor();
|
|
1074
|
-
const content = asRenderable(button.text ?? button.label ?? '');
|
|
1075
|
-
const submitting =
|
|
1076
|
-
state.submitting &&
|
|
1077
|
-
(button.type === 'submit' || button.action === 'submit');
|
|
1078
|
-
return submitting ? [createLoading(), content] : content;
|
|
1079
|
-
},
|
|
1080
|
-
}),
|
|
1081
|
-
}),
|
|
1046
|
+
children:
|
|
1047
|
+
props.buttons === 'reverse'
|
|
1048
|
+
? [resetButtonView(), submitButtonView()]
|
|
1049
|
+
: [submitButtonView(), resetButtonView()],
|
|
1082
1050
|
}) as HTMLElement;
|
|
1083
1051
|
|
|
1084
1052
|
form = defineComponent<
|
|
@@ -1094,9 +1062,6 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
1094
1062
|
validate,
|
|
1095
1063
|
reset() {
|
|
1096
1064
|
validator?.reset();
|
|
1097
|
-
flushSync(() => {
|
|
1098
|
-
state.data = null;
|
|
1099
|
-
});
|
|
1100
1065
|
return form;
|
|
1101
1066
|
},
|
|
1102
1067
|
collectData,
|
|
@@ -1124,7 +1089,7 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
1124
1089
|
cache.fieldIds.clear();
|
|
1125
1090
|
runtimeItems = new WeakMap();
|
|
1126
1091
|
flushSync(() => {
|
|
1127
|
-
state.fields = cloneFields(
|
|
1092
|
+
state.fields = cloneFields(props.fields);
|
|
1128
1093
|
});
|
|
1129
1094
|
return form;
|
|
1130
1095
|
},
|
|
@@ -1135,42 +1100,31 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
1135
1100
|
...(Object.hasOwn(patch, 'fields')
|
|
1136
1101
|
? { fields: cloneFields(patch.fields) }
|
|
1137
1102
|
: {}),
|
|
1138
|
-
...(Object.hasOwn(patch, 'buttons')
|
|
1139
|
-
? { buttons: cloneButtons(patch.buttons) }
|
|
1140
|
-
: {}),
|
|
1141
|
-
...(Object.hasOwn(patch, 'className')
|
|
1142
|
-
? { className: resolveClassNames(patch.className) }
|
|
1143
|
-
: {}),
|
|
1144
|
-
...(Object.hasOwn(patch, 'validator')
|
|
1145
|
-
? { validator: cloneValidator(patch.validator) }
|
|
1146
|
-
: {}),
|
|
1147
1103
|
};
|
|
1148
1104
|
},
|
|
1149
1105
|
view: () => {
|
|
1150
1106
|
const element = jsx('form', {
|
|
1151
|
-
className: (
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
id:
|
|
1162
|
-
novalidate:
|
|
1107
|
+
className: joinClasses(
|
|
1108
|
+
props.className.form,
|
|
1109
|
+
props.vertical
|
|
1110
|
+
? props.className.vertical
|
|
1111
|
+
: props.className.horizontal,
|
|
1112
|
+
props.itemVertical
|
|
1113
|
+
? props.className.itemVertical
|
|
1114
|
+
: props.className.itemHorizontal,
|
|
1115
|
+
formSizeClass()
|
|
1116
|
+
),
|
|
1117
|
+
id: props.id,
|
|
1118
|
+
novalidate: props.validator.vanilla === false,
|
|
1163
1119
|
'data-form': 'root',
|
|
1164
|
-
'data-form-layout':
|
|
1165
|
-
'data-form-field-layout':
|
|
1166
|
-
|
|
1120
|
+
'data-form-layout': props.vertical ? 'vertical' : 'horizontal',
|
|
1121
|
+
'data-form-field-layout': props.itemVertical
|
|
1122
|
+
? 'vertical'
|
|
1123
|
+
: 'horizontal',
|
|
1167
1124
|
onSubmit: (event: Event) => void handleSubmit(event),
|
|
1168
1125
|
onReset: (event: Event) => {
|
|
1169
1126
|
resetValidationState();
|
|
1170
|
-
|
|
1171
|
-
state.data = null;
|
|
1172
|
-
});
|
|
1173
|
-
state.onReset?.(event, form);
|
|
1127
|
+
props.onReset?.(event, form);
|
|
1174
1128
|
},
|
|
1175
1129
|
children: [
|
|
1176
1130
|
For({
|
|
@@ -1178,13 +1132,10 @@ export function createForm(input: FormProps = {}): Form {
|
|
|
1178
1132
|
key: itemKey,
|
|
1179
1133
|
children: itemView,
|
|
1180
1134
|
}),
|
|
1181
|
-
buttonsView(),
|
|
1135
|
+
props.buttons !== false ? buttonsView() : null,
|
|
1182
1136
|
],
|
|
1183
1137
|
}) as HTMLFormElement;
|
|
1184
|
-
|
|
1185
|
-
createEffect(() => {
|
|
1186
|
-
if (validator) validator.props = { ...state.validator, onSubmit: null };
|
|
1187
|
-
});
|
|
1138
|
+
setElementStyle(element, props.style);
|
|
1188
1139
|
return element;
|
|
1189
1140
|
},
|
|
1190
1141
|
onBuild() {
|
package/src/css/content.css
CHANGED
|
@@ -572,41 +572,7 @@
|
|
|
572
572
|
============================================ */
|
|
573
573
|
|
|
574
574
|
.j-content table {
|
|
575
|
-
min-width: 280px;
|
|
576
|
-
max-width: 100%;
|
|
577
|
-
margin: var(--content-block-space) 0;
|
|
578
|
-
border-collapse: collapse;
|
|
579
|
-
font-size: 0.9375em;
|
|
580
575
|
display: block;
|
|
581
|
-
overflow-x: auto;
|
|
582
|
-
-webkit-overflow-scrolling: touch;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
.j-content th,
|
|
586
|
-
.j-content td {
|
|
587
|
-
padding: 0.75em 1em;
|
|
588
|
-
border-bottom: 1px solid var(--content-border-subtle);
|
|
589
|
-
text-align: left;
|
|
590
|
-
vertical-align: top;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
.j-content th {
|
|
594
|
-
background-color: var(--content-bg-subtle);
|
|
595
|
-
font-weight: 600;
|
|
596
|
-
color: var(--content-heading-fg);
|
|
597
|
-
white-space: nowrap;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
.j-content thead th {
|
|
601
|
-
border-bottom: 2px solid var(--content-border);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
.j-content tbody tr:hover {
|
|
605
|
-
background-color: var(--content-bg-subtle);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
.j-content tbody tr:last-child td {
|
|
609
|
-
border-bottom: none;
|
|
610
576
|
}
|
|
611
577
|
|
|
612
578
|
/* ============================================
|
package/src/css/form.css
CHANGED