gform-react 2.7.2 → 2.8.0

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.
Files changed (42) hide show
  1. package/README.md +107 -30
  2. package/dist/cjs/gform-react.development.js +165 -140
  3. package/dist/cjs/gform-react.development.js.map +1 -1
  4. package/dist/cjs/gform-react.production.js +1 -1
  5. package/dist/cjs/gform-react.production.js.map +1 -1
  6. package/dist/esm/GForm.development.js +12 -9
  7. package/dist/esm/GForm.development.js.map +1 -1
  8. package/dist/esm/GForm.production.js +1 -1
  9. package/dist/esm/GForm.production.js.map +1 -1
  10. package/dist/esm/GInput.development.js +22 -21
  11. package/dist/esm/GInput.development.js.map +1 -1
  12. package/dist/esm/GInput.production.js +1 -1
  13. package/dist/esm/GInput.production.js.map +1 -1
  14. package/dist/esm/GValidator.development.js +5 -1
  15. package/dist/esm/GValidator.development.js.map +1 -1
  16. package/dist/esm/GValidator.production.js +1 -1
  17. package/dist/esm/GValidator.production.js.map +1 -1
  18. package/dist/esm/shared.development.js +133 -112
  19. package/dist/esm/shared.development.js.map +1 -1
  20. package/dist/esm/shared.production.js +1 -1
  21. package/dist/esm/shared.production.js.map +1 -1
  22. package/dist/esm/useFormSelector.development.js +3 -2
  23. package/dist/esm/useFormSelector.development.js.map +1 -1
  24. package/dist/esm/useFormSelector.production.js +1 -1
  25. package/dist/index.d.ts +1 -0
  26. package/native/dist/cjs/gform-react.development.js +151 -133
  27. package/native/dist/cjs/gform-react.development.js.map +1 -1
  28. package/native/dist/cjs/gform-react.production.js +1 -1
  29. package/native/dist/cjs/gform-react.production.js.map +1 -1
  30. package/native/dist/esm/RNGForm.development.js +4 -4
  31. package/native/dist/esm/RNGForm.development.js.map +1 -1
  32. package/native/dist/esm/RNGForm.production.js +1 -1
  33. package/native/dist/esm/RNGForm.production.js.map +1 -1
  34. package/native/dist/esm/RNGInput.development.js +23 -21
  35. package/native/dist/esm/RNGInput.development.js.map +1 -1
  36. package/native/dist/esm/RNGInput.production.js +1 -1
  37. package/native/dist/esm/RNGInput.production.js.map +1 -1
  38. package/native/dist/esm/shared.development.js +133 -112
  39. package/native/dist/esm/shared.development.js.map +1 -1
  40. package/native/dist/esm/shared.production.js +1 -1
  41. package/native/dist/esm/shared.production.js.map +1 -1
  42. package/package.json +3 -3
@@ -21,87 +21,52 @@ const typeValueDict = {
21
21
  number: 'valueAsNumber'
22
22
  };
23
23
  const _generateIdUnsafe = () => (+new Date()).toString(36) + (1 - Math.random()).toString(36).substring(2, 16);
24
- const _copyStateFields = (source, destination) => {
25
- for (const key in destination.fields) {
26
- const sourceField = source.fields[key];
27
- const destField = destination.fields[key];
28
- if (!sourceField || sourceField.type !== destField.type) continue;
29
- destination.fields[key] = {
30
- ...destField,
31
- ...sourceField
32
- };
33
- }
34
- };
35
- const _buildFormInitialValues = (rows = []) => {
36
- const fields = {};
37
- const inputs = _findInputs(rows);
38
- inputs.forEach(config => {
39
- if (fields[config.formKey]) {
40
- console.warn(`DEV ONLY - [Duplicate Keys] - field with key '${config.formKey}' already defined.`);
41
- }
42
- const {
43
- required = false,
44
- max,
45
- maxLength,
46
- min,
47
- minLength,
48
- step,
49
- pattern,
50
- type = "text",
51
- defaultValue,
52
- value,
53
- checked,
54
- defaultChecked,
55
- formKey,
56
- debounce,
57
- validatorKey
58
- } = config;
59
- const defaultProps = defaultFieldProps[type] || defaultFieldProps.text;
60
- const inputValue = value || defaultValue || checked || defaultChecked || defaultProps.value;
61
- fields[formKey] = {
62
- formKey,
63
- type,
64
- required,
65
- max,
66
- maxLength,
67
- min,
68
- minLength,
69
- step,
70
- pattern,
71
- value: inputValue,
72
- validatorKey,
73
- debounce,
74
- dirty: false,
75
- touched: false,
76
- gid: _generateIdUnsafe()
77
- };
78
- });
24
+ const _buildInputInitialValues = input => {
25
+ const {
26
+ required = false,
27
+ max,
28
+ maxLength,
29
+ min,
30
+ minLength,
31
+ step,
32
+ pattern,
33
+ type = "text",
34
+ defaultValue,
35
+ value,
36
+ checked,
37
+ defaultChecked,
38
+ formKey,
39
+ debounce,
40
+ validatorKey
41
+ } = input;
42
+ const defaultProps = defaultFieldProps[type] || defaultFieldProps.text;
43
+ const inputValue = value || defaultValue || checked || defaultChecked || defaultProps.value;
79
44
  return {
80
- fields: fields
81
- };
82
- };
83
- const _findInputs = (root, total = []) => {
84
- if (!root) return total;
85
- React$1.Children.forEach(root, child => {
86
- if (!React$1.isValidElement(child)) return;
87
- if (child.props) {
88
- const {
89
- formKey,
90
- children
91
- } = child.props;
92
- if (formKey) {
93
- total.push(child.props);
94
- }
95
- if (children) {
96
- _findInputs(children, total);
97
- }
45
+ formKey,
46
+ type,
47
+ required,
48
+ max,
49
+ maxLength,
50
+ min,
51
+ minLength,
52
+ step,
53
+ pattern,
54
+ value: inputValue,
55
+ validatorKey,
56
+ debounce,
57
+ dirty: false,
58
+ touched: false,
59
+ gid: _generateIdUnsafe(),
60
+ error: false,
61
+ errorText: '',
62
+ dispatchChanges(changes) {},
63
+ checkValidity() {
64
+ return false;
98
65
  }
99
- });
100
- return total;
66
+ };
101
67
  };
102
- const _findValidityKey = (validity, exclude = []) => {
68
+ const _findValidityKey = validity => {
103
69
  for (const key in validity) {
104
- if (exclude.includes(key)) continue;
105
70
  if (key !== 'valid' && validity[key]) {
106
71
  return key;
107
72
  }
@@ -265,30 +230,32 @@ const useFormHandlers = (getState, setState, validators = {}, optimized = false)
265
230
  const _viHandler = (input, e) => {
266
231
  if (!input) return;
267
232
  const element = e && e.target;
233
+ input.touched = true;
268
234
  if (typeof document !== 'undefined' && (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement)) {
269
235
  if (!input.checkValidity) input.checkValidity = () => element.checkValidity();
270
- const hasInitialValue = !input.dirty && input.value && !input.touched;
236
+ const hasInitialValue = !input.dirty && input.value;
271
237
  if (hasInitialValue) {
272
- _checkInputManually(input);
238
+ const {
239
+ validityKey = 'custom'
240
+ } = _checkInputManually(input);
241
+ element.setCustomValidity(validityKey);
273
242
  _dispatchChanges(input, input.formKey);
274
243
  return;
275
244
  }
276
245
  element.setCustomValidity('');
277
- const exclude = input.type && (input.pattern || hasCustomValidation(input)) ? ['typeMismatch'] : [];
278
- const validityKey = _findValidityKey(element.validity, exclude);
246
+ const validityKey = _findValidityKey(element.validity);
279
247
  _validateInput(input, validityKey, v => element.setCustomValidity(v));
280
248
  if (!validityKey && input.error) {
281
249
  element.setCustomValidity(input.errorText || 'error');
282
250
  }
283
251
  _dispatchChanges(input, input.formKey);
284
252
  } else {
285
- input.checkValidity = () => _checkInputManually(input);
253
+ input.checkValidity = () => _checkInputManually(input).isValid;
286
254
  input.checkValidity();
287
255
  _dispatchChanges(input, input.formKey);
288
256
  }
289
257
  };
290
258
  const _checkInputManually = input => {
291
- const exclude = input.type && (input.pattern || hasCustomValidation(input)) ? ['typeMismatch'] : [];
292
259
  let validityKey = _findValidityKey({
293
260
  valueMissing: input.required && !input.value || false,
294
261
  typeMismatch: _checkTypeMismatch(input),
@@ -297,28 +264,34 @@ const useFormHandlers = (getState, setState, validators = {}, optimized = false)
297
264
  patternMismatch: input.pattern && _checkResult(input.pattern, input.value) || false,
298
265
  rangeUnderflow: input.min && Number(input.value) < Number(input.min) || false,
299
266
  rangeOverflow: input.max && Number(input.value) > Number(input.max) || false
300
- }, exclude);
267
+ });
301
268
  if (!validityKey && input.error) {
302
269
  validityKey = 'customError';
303
270
  }
304
271
  _validateInput(input, validityKey);
305
- return !input.error;
272
+ return {
273
+ isValid: !input.error,
274
+ validityKey
275
+ };
306
276
  };
307
277
  const _updateInputHandler = (input, e, unknown) => {
308
278
  input.value = _extractValue(e, unknown);
279
+ input.dirty = true;
309
280
  _viHandler(input, e);
310
281
  };
311
282
  const _validateInput = (input, validityKey, setValidity) => {
312
283
  const inputValidator = validators[input.validatorKey || input.formKey] || validators['*'];
313
284
  {
314
285
  if (validityKey && !inputValidator?.hasConstraint(validityKey)) {
315
- if (validityKey === 'typeMismatch') console.warn(`DEV ONLY - [Missing Validator] - the input '${input.formKey}' has described the constraint '${validityMap[validityKey]}' however, a correspond validator/custom validation/pattern validator is missing.\nadd '${handlersMap[validityMap[validityKey]]}' or 'withCustomValidation' or '${handlersMap[validityMap.patternMismatch]}' to the input validator.\nexample:\nconst validators: GValidators = {\n\temail: new GValidator().withPatternMismatchMessage('pattern mismatch'),\n\t...\n}\n\nor either remove the constraint '${validityMap[validityKey]}' from the input props`);else console.warn(`DEV ONLY - [Missing Validator] - the input '${input.formKey}' has described the constraint '${validityMap[validityKey]}' however, a correspond validator is missing.\nadd '${handlersMap[validityMap[validityKey]]}' to the input validator.\nexample:\nconst validators: GValidators = {\n\temail: new GValidator().withPatternMismatchMessage('pattern mismatch'),\n\t...\n}\n\nor either remove the constraint '${validityMap[validityKey]}' from the input props`);
286
+ if (validityKey === 'typeMismatch') {
287
+ if (!inputValidator?.handlers.length) console.warn(`DEV ONLY - [Missing Validator] - the input '${input.formKey}' has described the constraint '${validityMap[validityKey]}' however, a correspond validator is missing.\nadd '${handlersMap[validityMap[validityKey]]}' or 'withCustomValidation' or '${handlersMap[validityMap.patternMismatch]}' to the input validator.\nexample:\nconst validators: GValidators = {\n\temail: new GValidator().withTypeMismatchMessage('pattern mismatch'),\n\t...\n}\nif you added on of these validators then the input is still suffering from '${validityKey}' violation.\n\nor either remove the constraint '${validityMap[validityKey]}' from the input props.\n`);else console.warn(`DEV ONLY - [Missing Validator] - the input '${input.formKey}' has described the constraint '${validityMap[validityKey]}' however, a correspond validator is missing or not satisfies the native constraint.\nadd '${handlersMap[validityMap[validityKey]]}' or 'withCustomValidation' to the input validator.\nexample:\nconst validators: GValidators = {\n\temail: new GValidator().withTypeMismatchMessage('pattern mismatch'),\n\t...\n}\n\nif you already have a Custom Validation then the input is still not satisfies the native type pattern.\neither enforce it or remove the constraint '${validityMap[validityKey]}' from the input props`);
288
+ } else console.warn(`DEV ONLY - [Missing Validator] - the input '${input.formKey}' has described the constraint '${validityMap[validityKey]}' however, a correspond validator is missing.\nadd '${handlersMap[validityMap[validityKey]]}' to the input validator.\nexample:\nconst validators: GValidators = {\n\temail: new GValidator().withPatternMismatchMessage('pattern mismatch'),\n\t...\n}\n\nor either remove the constraint '${validityMap[validityKey]}' from the input props`);
289
+ console.warn(`form submition is prevented due to violation(s) of input '${input.formKey}': violation '${validityKey}' caused by '${validityMap[validityKey]}' property (<Ginput ${validityMap[validityKey]}={...} />)`);
316
290
  }
317
291
  }
318
292
  if (inputValidator) {
319
293
  __validateInput(input, inputValidator, validityKey, setValidity);
320
294
  }
321
- input.touched = true;
322
295
  };
323
296
  const _dispatchChanges = (changes, key) => setState(prev => {
324
297
  if (key) {
@@ -341,12 +314,14 @@ const useFormHandlers = (getState, setState, validators = {}, optimized = false)
341
314
  const __validateInput = (input, inputValidator, validityKey, setValidity) => {
342
315
  const fields = getState().fields;
343
316
  for (const index in inputValidator.constraintHandlers) {
344
- const result = inputValidator.constraintHandlers[index](input, validityKey);
317
+ const handler = inputValidator.constraintHandlers[index];
318
+ const result = handler(input, validityKey);
345
319
  input.error = _checkResult(result, input.value);
346
320
  if (input.error) return;
347
321
  }
348
322
  for (const index in inputValidator.handlers) {
349
- const result = inputValidator.handlers[index](input, fields);
323
+ const handler = inputValidator.handlers[index];
324
+ const result = handler(input, fields);
350
325
  input.error = _checkResult(result, input.value);
351
326
  if (input.error) return;
352
327
  }
@@ -356,7 +331,8 @@ const useFormHandlers = (getState, setState, validators = {}, optimized = false)
356
331
  _debounce(input.debounce || 300, `${input.gid}-async`).then(() => {
357
332
  const validateAsync = async () => {
358
333
  for (const index in inputValidator.asyncHandlers) {
359
- const result = await inputValidator.asyncHandlers[index](input, fields);
334
+ const handler = inputValidator.asyncHandlers[index];
335
+ const result = await handler(input, fields);
360
336
  input.error = _checkResult(result, input.value);
361
337
  if (input.error) break;
362
338
  }
@@ -373,10 +349,6 @@ const useFormHandlers = (getState, setState, validators = {}, optimized = false)
373
349
  });
374
350
  }
375
351
  };
376
- const hasCustomValidation = input => {
377
- const validator = validators[input.validatorKey || input.formKey] || validators['*'];
378
- return validator && (validator.asyncHandlers.length > 0 || validator.handlers.length > 0);
379
- };
380
352
  return {
381
353
  _updateInputHandler,
382
354
  _viHandler,
@@ -391,7 +363,8 @@ const GFormContextProvider = ({
391
363
  children,
392
364
  initialState,
393
365
  validators,
394
- optimized
366
+ optimized,
367
+ formRef
395
368
  }) => {
396
369
  const stateRef = React$1.useRef(initialState);
397
370
  const listeners = React$1.useRef(null);
@@ -402,19 +375,59 @@ const GFormContextProvider = ({
402
375
  return;
403
376
  }
404
377
  stateRef.current = nextState;
405
- listeners.current.forEach(l => l());
378
+ listeners.current.forEach(listener => listener());
406
379
  }, []);
407
380
  const handlers = useFormHandlers(() => stateRef.current, setState, validators, optimized);
381
+ const getInputElement = React$1.useCallback(formKey => {
382
+ if (formRef && formRef.current) {
383
+ return formRef.current[formKey];
384
+ }
385
+ }, []);
386
+ const registerField = React$1.useCallback(config => {
387
+ const prev = stateRef.current;
388
+ if (prev.fields[config.formKey]) {
389
+ {
390
+ console.warn(`DEV ONLY - [Duplicate Keys] - field with key '${config.formKey}' already defined.`);
391
+ }
392
+ return;
393
+ }
394
+ const inputState = _buildInputInitialValues(config);
395
+ stateRef.current = {
396
+ ...prev,
397
+ fields: {
398
+ ...prev.fields,
399
+ [config.formKey]: {
400
+ ...inputState,
401
+ dispatchChanges: changes => handlers._dispatchChanges(changes, config.formKey)
402
+ }
403
+ }
404
+ };
405
+ }, [handlers]);
406
+ const unregisterField = React$1.useCallback(formKey => {
407
+ const prev = stateRef.current;
408
+ if (!prev.fields[formKey]) return;
409
+ const {
410
+ [formKey]: _,
411
+ ...remainingFields
412
+ } = prev.fields;
413
+ stateRef.current = {
414
+ ...prev,
415
+ fields: remainingFields
416
+ };
417
+ listeners.current.forEach(listener => listener());
418
+ }, []);
408
419
  const store = React$1.useMemo(() => {
409
420
  if (!listeners.current) {
410
421
  listeners.current = new Set();
422
+ stateRef.current = initialState;
423
+ for (const fieldKey in initialState.fields) {
424
+ initialState.fields[fieldKey].dispatchChanges = changes => handlers._dispatchChanges(changes, fieldKey);
425
+ }
411
426
  } else {
412
427
  listeners.current.clear();
413
- _copyStateFields(stateRef.current, initialState);
414
- }
415
- stateRef.current = initialState;
416
- for (const fieldKey in initialState.fields) {
417
- initialState.fields[fieldKey].dispatchChanges = changes => handlers._dispatchChanges(changes, fieldKey);
428
+ for (const fieldKey in stateRef.current.fields) {
429
+ stateRef.current.fields[fieldKey].dispatchChanges = changes => handlers._dispatchChanges(changes, fieldKey);
430
+ }
418
431
  }
419
432
  return {
420
433
  getState: () => stateRef.current,
@@ -423,7 +436,10 @@ const GFormContextProvider = ({
423
436
  listeners.current.add(listener);
424
437
  return () => listeners.current.delete(listener);
425
438
  },
426
- handlers
439
+ handlers,
440
+ registerField,
441
+ unregisterField,
442
+ getInputElement
427
443
  };
428
444
  }, [initialState]);
429
445
  return React$1.createElement(GFormContext.Provider, {
@@ -432,7 +448,7 @@ const GFormContextProvider = ({
432
448
  };
433
449
  const useFormStore = () => {
434
450
  const store = React$1.useContext(GFormContext);
435
- if (!store.getState) throw new Error('useGFormStore must be used within `GForm` component');
451
+ if (!store.getState) throw new Error('useFormStore must be used within `GForm` component');
436
452
  return store;
437
453
  };
438
454
  const useFormSelector = selector => {
@@ -501,9 +517,9 @@ const RNGForm = React$1.forwardRef(({
501
517
  validators,
502
518
  ...props
503
519
  }, ref) => {
504
- const initialState = React$1.useMemo(() => {
505
- return _buildFormInitialValues(typeof children === 'function' ? children({}) : children);
506
- }, [children]);
520
+ const initialState = React$1.useMemo(() => ({
521
+ fields: {}
522
+ }), []);
507
523
  return React.createElement(GFormContextProvider, {
508
524
  initialState: initialState,
509
525
  validators: validators
@@ -518,22 +534,37 @@ const makeSelectFields = (keys = []) => createSelector(selectFields, fields => {
518
534
  return selected.length ? selected : null;
519
535
  });
520
536
 
521
- const _RNGInput = React$1.forwardRef(({
522
- formKey,
523
- element,
524
- type,
525
- fetch,
526
- fetchDeps,
527
- debounce = 300,
528
- defaultValue,
529
- validatorKey,
530
- value,
531
- ...rest
532
- }, ref) => {
533
- const inputState = useFormSelector(state => state.fields[formKey]);
537
+ const _RNGInput = React$1.forwardRef((props, ref) => {
534
538
  const store = useFormStore();
539
+ const {
540
+ formKey,
541
+ element,
542
+ type,
543
+ fetch,
544
+ fetchDeps,
545
+ debounce = 300,
546
+ defaultValue,
547
+ validatorKey,
548
+ value,
549
+ ...rest
550
+ } = props;
551
+ if (!store.getState().fields[formKey]) {
552
+ store.registerField(props);
553
+ }
554
+ const inputState = useFormSelector(state => state.fields[formKey]);
555
+ const _fetchDeps = useFormSelector(makeSelectFields(fetchDeps));
556
+ React$1.useEffect(() => {
557
+ if (inputState.value) {
558
+ store.handlers._viHandler(inputState, {
559
+ target: store.getInputElement(formKey)
560
+ });
561
+ }
562
+ return () => {
563
+ store.unregisterField(formKey);
564
+ };
565
+ }, []);
535
566
  const _element = React$1.useMemo(() => {
536
- const value = inputState.value || '';
567
+ const value = inputState.value ?? '';
537
568
  const _props = {
538
569
  ...rest,
539
570
  value,
@@ -556,24 +587,11 @@ const _RNGInput = React$1.forwardRef(({
556
587
  value: e
557
588
  });
558
589
  };
559
- if (!inputState.touched) {
560
- _props.onFocus = rest.onFocus ? e => {
561
- rest.onFocus(e);
562
- inputState.dispatchChanges({
563
- touched: true
564
- });
565
- } : () => {
566
- inputState.dispatchChanges({
567
- touched: true
568
- });
569
- };
570
- }
571
590
  if (element) {
572
591
  return element(inputState, _props);
573
592
  }
574
593
  return React.createElement(reactNative.TextInput, _props);
575
594
  }, [inputState, element]);
576
- const _fetchDeps = useFormSelector(makeSelectFields(fetchDeps));
577
595
  React$1.useEffect(() => {
578
596
  if (fetch) {
579
597
  _debounce(debounce, `${inputState.gid}-fetch`).then(() => {