@steveesamson/microform 1.0.9 → 1.0.11

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.
@@ -0,0 +1,3 @@
1
+ import type { FormOptions, FormAction, FormValues, FormErrors, FormSanity } from './types.js';
2
+ import type { Params } from './internal.svelte.js';
3
+ export declare const formAction: (values: FormValues, errors: FormErrors, unfits: FormErrors, sanity: FormSanity, options: FormOptions, validationMap: Params) => FormAction;
@@ -1,4 +1,3 @@
1
- import { get } from 'svelte/store';
2
1
  import { useValidator } from './form-validators.js';
3
2
  import { getEditableContent } from './utils.js';
4
3
  const isField = (node) => {
@@ -16,12 +15,11 @@ const isRadio = (node) => {
16
15
  return node instanceof HTMLInputElement && ['radio'].includes(node.type.toLowerCase());
17
16
  };
18
17
  const checkFormFitness = (values, validationMap, validate) => {
19
- const _values = get(values);
20
18
  for (const [name, { validations }] of Object.entries(validationMap)) {
21
- validate({ name, value: _values[name], validations });
19
+ validate({ name, value: values[name], validations });
22
20
  }
23
21
  };
24
- export const formAction = (values, errors, unfits, isdirty, options, validationMap) => {
22
+ export const formAction = (values, errors, unfits, sanity, options, validationMap) => {
25
23
  const { validators: customValidators } = options;
26
24
  const { validate, validators } = useValidator(errors, values);
27
25
  // override
@@ -30,12 +28,13 @@ export const formAction = (values, errors, unfits, isdirty, options, validationM
30
28
  validators[key] = val;
31
29
  }
32
30
  }
31
+ const hasError = (next) => !!next;
33
32
  return (node, eventProps) => {
34
33
  const nodeName = isField(node) ? node.name : '';
35
34
  const { name: dsname = nodeName } = node.dataset || {};
36
35
  const { name = dsname, validations = [], validateEvent = (options.validateEvent = 'blur'), html = false } = eventProps || {};
37
36
  validationMap[name] = { validations, html, nodeRef: node };
38
- const storedValue = get(values)[name] || '';
37
+ const storedValue = values[name] || '';
39
38
  let defValue = storedValue;
40
39
  if (isField(node) && !isExcluded(node)) {
41
40
  defValue = node.value || storedValue;
@@ -45,58 +44,47 @@ export const formAction = (values, errors, unfits, isdirty, options, validationM
45
44
  defValue = node.innerHTML || storedValue;
46
45
  node.innerHTML = defValue;
47
46
  }
48
- values.update((data) => {
49
- return { ...data, [name]: defValue };
50
- });
51
- let unsubscribe;
47
+ values[name] = defValue;
52
48
  const updateNode = (e) => {
53
- if (!unsubscribe) {
54
- unsubscribe = values.subscribe((data) => {
55
- validate({ name, value: data[name], validations, node });
56
- });
57
- }
58
49
  if (isField(node) && !isExcluded(node)) {
59
50
  const value = e.target.value || '';
60
- values.update((data) => {
61
- return { ...data, [name]: value };
62
- });
51
+ values[name] = value;
63
52
  }
64
53
  else if (node.isContentEditable) {
65
54
  const { value: htm, text } = getEditableContent({ target: node }, html);
66
- values.update((data) => {
67
- return { ...data, [name]: htm, [`${name}-text`]: text };
68
- });
55
+ values[name] = htm;
56
+ values[`${name}-text`] = text;
69
57
  }
70
58
  else if (isCheckbox(node)) {
71
59
  const { checked, value: val } = node;
72
- const { [name]: fieldValue } = get(values);
73
- let current = fieldValue.split(',');
60
+ const fieldValue = String(values[name] ?? '');
61
+ let current = fieldValue?.split(',');
74
62
  if (checked) {
75
63
  current.push(val);
76
64
  }
77
65
  else {
78
66
  current = current.filter((next) => next !== val);
79
67
  }
80
- values.update((data) => {
81
- return { ...data, [name]: [...new Set(current)].join(',') };
82
- });
68
+ values[name] = [...new Set(current)].join(',');
83
69
  }
84
70
  else if (isRadio(node)) {
85
71
  const { value: fvalue } = node;
86
- values.update((data) => {
87
- return { ...data, [name]: fvalue };
88
- });
72
+ values[name] = fvalue;
89
73
  }
74
+ validate({ name, value: values[name], validations, node });
75
+ };
76
+ $effect(() => {
90
77
  const { validate: validateUnfit } = useValidator(unfits, values, validators);
91
78
  checkFormFitness(values, validationMap, validateUnfit);
92
- isdirty.set(true);
93
- };
94
- node.addEventListener(validateEvent, updateNode);
95
- return {
96
- destroy() {
97
- unsubscribe?.();
79
+ const withErrors = Object.values(errors).some(hasError);
80
+ const withUnfits = Object.values(unfits).some(hasError);
81
+ sanity.ok = !withErrors && !withUnfits;
82
+ });
83
+ $effect(() => {
84
+ node.addEventListener(validateEvent, updateNode);
85
+ return () => {
98
86
  node.removeEventListener(validateEvent, updateNode);
99
- }
100
- };
87
+ };
88
+ });
101
89
  };
102
90
  };
@@ -1,6 +1,4 @@
1
- import { type Writable } from 'svelte/store';
2
- import type { ValidateArgs, ValidatorType, ValidatorMap } from './types.js';
3
- import type { Params } from './internal.js';
1
+ import type { ValidateArgs, ValidatorType, ValidatorMap, FormErrors, FormValues } from './types.js';
4
2
  export declare const IS_REQUIRED = "required";
5
3
  export declare const IS_EMAIL = "email";
6
4
  export declare const IS_URL = "url";
@@ -16,7 +14,7 @@ export declare const IS_MIN = "min";
16
14
  export declare const IS_MAX = "max";
17
15
  export declare const IT_MATCHES = "match";
18
16
  export declare const IS_FILE_SIZE_MB = "file-size-mb";
19
- export declare const useValidator: (errors: Writable<Params>, values: Writable<Params>, validators?: ValidatorMap<ValidatorType>) => {
17
+ export declare const useValidator: (errors: FormErrors, values: FormValues, validators?: ValidatorMap<ValidatorType>) => {
20
18
  validate: ({ name, value, validations, node }: ValidateArgs) => Promise<void>;
21
19
  validators: ValidatorMap<ValidatorType>;
22
20
  };
@@ -1,4 +1,3 @@
1
- import { get } from 'svelte/store';
2
1
  import { makeName, isValidFileSize } from './utils.js';
3
2
  const regexes = {
4
3
  number: /^[-+]?[0-9]+(\.[0-9]+)?$/g,
@@ -60,7 +59,7 @@ const getDefaultValidators = () => {
60
59
  : '';
61
60
  },
62
61
  [IS_MIN_LEN]: ({ value, label, parts }) => {
63
- if (!!value) {
62
+ if (value) {
64
63
  if (!parts || parts.length < 2) {
65
64
  return `${label}: min-length validation requires minimum length.`;
66
65
  }
@@ -72,7 +71,7 @@ const getDefaultValidators = () => {
72
71
  return '';
73
72
  },
74
73
  [IS_MAX_LEN]: ({ value, label, parts }) => {
75
- if (!!value) {
74
+ if (value) {
76
75
  if (!parts || parts.length < 2) {
77
76
  return `${label}: max-length validation requires maximum length.`;
78
77
  }
@@ -84,7 +83,7 @@ const getDefaultValidators = () => {
84
83
  return '';
85
84
  },
86
85
  [IS_LEN]: ({ value, label, parts }) => {
87
- if (!!value) {
86
+ if (value) {
88
87
  if (!parts || parts.length < 2) {
89
88
  return `${label}: length validation requires length.`;
90
89
  }
@@ -96,7 +95,7 @@ const getDefaultValidators = () => {
96
95
  return '';
97
96
  },
98
97
  [IS_MAX]: ({ value, label, parts }) => {
99
- if (!!value) {
98
+ if (value) {
100
99
  if (!parts || parts.length < 2) {
101
100
  return `${label}: max validation requires the maximum value.`;
102
101
  }
@@ -108,7 +107,7 @@ const getDefaultValidators = () => {
108
107
  return '';
109
108
  },
110
109
  [IS_MIN]: ({ value, label, parts }) => {
111
- if (!!value) {
110
+ if (value) {
112
111
  if (!parts || parts.length < 2) {
113
112
  return `${label}: min validation requires the minimum value.`;
114
113
  }
@@ -131,7 +130,7 @@ const getDefaultValidators = () => {
131
130
  return '';
132
131
  },
133
132
  [IS_FILE_SIZE_MB]: ({ value, node, label, parts }) => {
134
- if (!!value) {
133
+ if (value) {
135
134
  if (!parts || parts.length < 2) {
136
135
  return `${label}: max file size in MB validation requires maximum file size of mb value.`;
137
136
  }
@@ -145,9 +144,7 @@ const getDefaultValidators = () => {
145
144
  };
146
145
  export const useValidator = (errors, values, validators = getDefaultValidators()) => {
147
146
  const setError = (name, error) => {
148
- errors.update((prev) => {
149
- return { ...prev, [name]: error };
150
- });
147
+ errors[name] = error ?? '';
151
148
  };
152
149
  return {
153
150
  validate: async ({ name, value, validations = [], node = undefined }) => {
@@ -164,7 +161,7 @@ export const useValidator = (errors, values, validators = getDefaultValidators()
164
161
  name,
165
162
  label: makeName(name),
166
163
  value,
167
- values: get(values),
164
+ values,
168
165
  node,
169
166
  parts
170
167
  });
@@ -1,55 +1,42 @@
1
- import { writable, derived, get } from 'svelte/store';
2
- import { formAction } from './form-action.js';
3
- import { bindStateToStore } from './utils.js';
1
+ import { formAction } from './form-action.svelte.js';
2
+ import { formState } from './internal.svelte.js';
3
+ import { resetObject } from './utils.js';
4
4
  const microform = (props) => {
5
5
  // form default values
6
6
  const data = props?.data || {};
7
- // form values
8
- const _values = writable({ ...data });
9
- // internal checks
10
- const unfits = writable({});
11
- // external form errors
12
- const _errors = writable({});
13
- const isdirty = writable(false);
14
- const isclean = derived([_errors, unfits], ([$errors, $unfits]) => {
15
- const errVals = Object.values($errors);
16
- const unfitVals = Object.values($unfits);
17
- return ((errVals.length === 0 ||
18
- errVals.reduce((comm, next) => comm && !next, true)) &&
19
- (unfitVals.length === 0 ||
20
- unfitVals.reduce((comm, next) => comm && !next, true)));
21
- });
22
- const _valid = derived([isclean, isdirty], ([$isclean, $isdirty]) => {
23
- return $isclean && $isdirty;
24
- });
7
+ // form state
8
+ const state = formState(data);
25
9
  const validationMap = {};
26
10
  const { options = {
27
11
  validateEvent: 'blur',
28
12
  validators: {}
29
13
  } } = props || {};
30
- const form = formAction(_values, _errors, unfits, isdirty, options, validationMap);
14
+ const form = formAction(state.values, state.errors, state.unfits, state.sanity, options, validationMap);
31
15
  const handleSubmit = (e, handler) => {
32
16
  e.preventDefault();
33
- if (!get(_valid))
17
+ if (!state.sanity.ok)
34
18
  return;
35
- handler({ ...get(_values) });
19
+ handler({ ...state.values });
36
20
  };
37
21
  const onsubmit = (handler) => {
38
- const onSubmit = async (e) => {
22
+ return (e) => {
39
23
  handleSubmit(e, handler);
40
24
  };
41
- return onSubmit;
42
25
  };
43
26
  const submit = (formNode, handler) => {
44
- formNode.addEventListener('submit', (e) => {
45
- handleSubmit(e, handler);
27
+ $effect(() => {
28
+ const localHandler = (e) => {
29
+ handleSubmit(e, handler);
30
+ };
31
+ formNode.addEventListener('submit', localHandler);
32
+ return () => formNode.removeEventListener('submit', localHandler);
46
33
  });
47
34
  };
48
35
  const reset = () => {
49
- _errors.set({});
50
- unfits.set({});
51
- _values.set({ ...data });
52
- isdirty.set(false);
36
+ resetObject(state.values, data);
37
+ resetObject(state.errors);
38
+ resetObject(state.unfits);
39
+ state.sanity.ok = false;
53
40
  for (const [name, { nodeRef, html }] of Object.entries(validationMap).filter(([, { nodeRef }]) => !!nodeRef)) {
54
41
  if (nodeRef) {
55
42
  if (nodeRef.isContentEditable) {
@@ -61,18 +48,10 @@ const microform = (props) => {
61
48
  }
62
49
  }
63
50
  };
64
- const values = $state({ ...data });
65
- const errors = $state({});
66
- const sanity = $state({ ok: get(_valid) });
67
- bindStateToStore(values, _values);
68
- bindStateToStore(errors, _errors);
69
- _valid.subscribe((changes) => {
70
- sanity.ok = changes;
71
- });
72
51
  return {
73
- values,
74
- errors,
75
- sanity,
52
+ values: state.values,
53
+ errors: state.errors,
54
+ sanity: state.sanity,
76
55
  form,
77
56
  submit,
78
57
  onsubmit,
@@ -0,0 +1,12 @@
1
+ import type { FormValues, FormErrors, FormSanity } from "./types.js";
2
+ export type Params<T = any> = {
3
+ [key: string | number | symbol]: T;
4
+ };
5
+ export type Primitive = string | number | boolean | string[] | number[] | boolean[];
6
+ export type FormState = {
7
+ values: FormValues;
8
+ errors: FormErrors;
9
+ sanity: FormSanity;
10
+ unfits: FormErrors;
11
+ };
12
+ export declare const formState: (data: Params) => FormState;
@@ -0,0 +1,15 @@
1
+ export const formState = (data) => {
2
+ // form values
3
+ const values = $state({ ...data });
4
+ // external form errors
5
+ const errors = $state({});
6
+ const sanity = $state({ ok: false });
7
+ // internal checks
8
+ const unfits = $state({});
9
+ return {
10
+ values,
11
+ errors,
12
+ sanity,
13
+ unfits,
14
+ };
15
+ };
package/dist/types.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { type Writable } from 'svelte/store';
2
- import type { Params } from './internal.js';
1
+ import type { Params } from './internal.svelte.js';
3
2
  export type Validator = `max:${number}` | `min:${number}` | `len:${number}` | `minlen:${number}` | `maxlen:${number}` | `file-size-mb:${number}` | `match:${string}` | 'required' | 'email' | 'integer' | 'number' | 'alpha' | 'alphanum' | 'url' | 'ip';
4
3
  export type ValidatorKey = 'required' | 'email' | 'integer' | 'number' | 'alpha' | 'alphanum' | 'url' | 'ip' | `max` | `min` | `len` | `minlen` | `maxlen` | `file-size-mb` | `match`;
5
4
  export type FieldProps = {
@@ -29,7 +28,7 @@ export type FormReturn = {
29
28
  export type ValidateEvent = 'input' | 'change' | 'keyup' | 'blur' | 'keydown';
30
29
  export type FormValues = Params;
31
30
  export type FormErrors = Params;
32
- export type Dirty = Writable<boolean>;
31
+ export type Dirty = boolean;
33
32
  export type ActionOptions = {
34
33
  validateEvent?: ValidateEvent;
35
34
  name?: string;
@@ -37,11 +36,11 @@ export type ActionOptions = {
37
36
  node?: HTMLElement;
38
37
  html?: boolean;
39
38
  };
40
- export type FormAction = (node: HTMLElement, eventProps?: ActionOptions) => FormReturn;
39
+ export type FormAction = (node: HTMLElement, eventProps?: ActionOptions) => void;
41
40
  export type FormSubmitEvent = SubmitEvent & {
42
41
  currentTarget: EventTarget & HTMLFormElement;
43
42
  };
44
- export type FormSubmit = (_data: Params) => void;
43
+ export type FormSubmit = (_data: Params) => (void | Promise<void>);
45
44
  export type FormOptions = {
46
45
  validateEvent?: ValidateEvent;
47
46
  validators?: Partial<ValidatorMap<ValidatorType>>;
@@ -57,9 +56,9 @@ export type MicroFormReturn = {
57
56
  values: FormValues;
58
57
  errors: FormErrors;
59
58
  sanity: FormSanity;
60
- form: (node: HTMLElement, eventProps?: ActionOptions) => FormReturn;
59
+ form: (node: HTMLElement, eventProps?: ActionOptions) => void;
61
60
  submit: (formNode: HTMLFormElement, handler: FormSubmit) => void;
62
- onsubmit: (handler: FormSubmit) => (e: Event) => Promise<void>;
61
+ onsubmit: (handler: FormSubmit) => (e: Event) => void;
63
62
  reset: () => void;
64
63
  };
65
64
  export type Microform = (props?: MicroFormProps) => MicroFormReturn;
package/dist/types.js CHANGED
@@ -1 +1 @@
1
- import {} from 'svelte/store';
1
+ export {};
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { Writable } from 'svelte/store';
2
- import type { Params } from './internal.js';
1
+ import type { Params } from "./internal.svelte.js";
2
+ import type { FormValues } from "./types.js";
3
3
  type TEvent = {
4
4
  target: HTMLElement;
5
5
  };
@@ -9,5 +9,5 @@ export declare const getEditableContent: (e: TEvent, isHtml: boolean) => {
9
9
  };
10
10
  export declare const makeName: (str: string) => string;
11
11
  export declare const isValidFileSize: (node: HTMLInputElement | undefined, maxFileSizeInMB: number) => string;
12
- export declare const bindStateToStore: (state: Params, store: Writable<Params>) => void;
12
+ export declare const resetObject: (target: FormValues, data?: Params | undefined) => void;
13
13
  export {};
package/dist/utils.js CHANGED
@@ -43,10 +43,21 @@ export const isValidFileSize = (node, maxFileSizeInMB) => {
43
43
  }
44
44
  return '';
45
45
  };
46
- export const bindStateToStore = (state, store) => {
47
- store.subscribe((changes) => {
48
- for (const [k, v] of Object.entries(changes)) {
49
- state[k] = v;
46
+ export const resetObject = (target, data = undefined) => {
47
+ if (data) {
48
+ const defaultKeys = Object.keys({ ...data });
49
+ for (const [key,] of Object.entries(target)) {
50
+ if (defaultKeys.includes(key)) {
51
+ target[key] = data[key];
52
+ }
53
+ else {
54
+ delete target[key];
55
+ }
50
56
  }
51
- });
57
+ }
58
+ else {
59
+ for (const [key,] of Object.entries(target)) {
60
+ target[key] = '';
61
+ }
62
+ }
52
63
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steveesamson/microform",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "postbuild": "touch ./docs/.nojekyll",
@@ -1,4 +0,0 @@
1
- import { type Writable } from 'svelte/store';
2
- import type { FormOptions, FormAction } from './types.js';
3
- import type { Params } from './internal.js';
4
- export declare const formAction: (values: Writable<Params>, errors: Writable<Params>, unfits: Writable<Params>, isdirty: Writable<boolean>, options: FormOptions, validationMap: Params) => FormAction;
@@ -1,4 +0,0 @@
1
- export type Params<T = any> = {
2
- [key: string | number | symbol]: T;
3
- };
4
- export type Primitive = string | number | boolean | string[] | number[] | boolean[];
package/dist/internal.js DELETED
@@ -1 +0,0 @@
1
- export {};