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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-jui",
3
- "version": "1.5.13",
3
+ "version": "1.5.15",
4
4
  "private": false,
5
5
  "description": "A lightweight, reactive, and fully customizable vanilla JS UI library.",
6
6
  "keywords": [
@@ -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 ButtonsPosition = 'start' | 'center' | 'end';
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?: boolean | readonly FormButton[];
157
- buttonsPosition?: 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: FormButton[];
173
- buttonsPosition: 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 DEFAULT_BUTTONS: FormButton[] = [
245
- {
246
- type: 'submit',
247
- text: t('Submit', locales),
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
- default: DEFAULT_BUTTONS,
279
- types: ['boolean', 'array'],
280
- normalize: (value: unknown) => {
281
- if (value === false) return [];
282
- if (value === true) return cloneButtons(DEFAULT_BUTTONS);
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: ButtonsPosition): string {
442
- return position === 'center' ? 'center' : `flex-${position}`;
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: cloneButtons(props.buttons as readonly FormButton[]),
472
- buttonsPosition: props.buttonsPosition as ButtonsPosition,
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
- ...cloneStateProps(props),
541
+ fields: cloneFields(props.fields),
583
542
  submitting: false,
584
- data: null,
585
543
  }) as FormState;
586
- const cache: FormCache = { initial, fieldIds: new Map() };
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, `${state.id}_field_${index}`);
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 = { ...state.validator, onSubmit: null };
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(state.onSubmit?.(data, form));
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 = state.className;
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' ? state.className.radioLabel : '',
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' ? state.className.radioText : '',
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 || state.className.textarea,
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: () => fieldAccessor().className || state.className.select,
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 field = fieldAccessor();
903
- const names = state.className;
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: () => state.className.switchSlider,
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: () => fieldAccessor().className || state.className.input,
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 props = {
954
+ const labelProps = {
991
955
  className: () =>
992
956
  joinClasses(
993
- state.className.label,
994
- fieldIsRequired(fieldAccessor(), state.validator.rules)
995
- ? state.className.required
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', props) as HTMLElement;
1002
- return jsx('label', { ...props, for: id }) as HTMLElement;
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: () => state.className.control,
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: () => state.className.item,
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: () => state.className.buttons,
1041
+ className: props.className.buttons,
1047
1042
  style: {
1048
- justifyContent: buttonsJustifyContent,
1043
+ justifyContent: resolveButtonsJustifyContent(props.buttonsPosition),
1049
1044
  },
1050
1045
  'data-field-buttons': '',
1051
- hidden: () => state.buttons.length === 0,
1052
- children: For({
1053
- each: () => state.buttons,
1054
- key: (button: FormButton, index: number) =>
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(initial.fields);
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
- joinClasses(
1153
- state.className.form,
1154
- state.vertical
1155
- ? state.className.vertical
1156
- : state.className.horizontal,
1157
- state.itemVertical
1158
- ? state.className.itemVertical
1159
- : state.className.itemHorizontal
1160
- ),
1161
- id: () => state.id,
1162
- novalidate: () => state.validator.vanilla === false,
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': () => (state.vertical ? 'vertical' : 'horizontal'),
1165
- 'data-form-field-layout': () =>
1166
- state.itemVertical ? 'vertical' : 'horizontal',
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
- flushSync(() => {
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
- createEffect(() => setElementStyle(element, state.style));
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() {
@@ -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
@@ -15,6 +15,11 @@
15
15
  gap: var(--form-gap-horizontal, var(--s2));
16
16
  }
17
17
 
18
+ .j-form.is-lg {
19
+ --form-gap-vertical: var(--s3);
20
+ --form-gap-horizontal: var(--s3);
21
+ }
22
+
18
23
  .j-form .form-field {
19
24
  gap: var(--form-label-gap, var(--space));
20
25
  margin: 0;