@strictly/react-form 0.0.31 → 0.0.33

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.
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { observer } from 'mobx-react';
2
+ import { Observer } from 'mobx-react';
3
3
  import { forwardRef, useMemo, } from 'react';
4
4
  export function createSimplePartialComponent(Component, curriedProps) {
5
5
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -65,7 +65,7 @@ export function createPartialObserverComponent(Component, curriedPropsSource, ad
65
65
  }
66
66
  export function createUnsafePartialObserverComponent(Component, curriedPropsSource, additionalPropKeys = []) {
67
67
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
68
- return observer(forwardRef(function (props, ref) {
68
+ return forwardRef(function (props, ref) {
69
69
  // forward ref types are really difficult to work with
70
70
  // still needs a cast as `extends ComponentType<any>` != `ComponentType<any>`
71
71
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unnecessary-type-assertion
@@ -90,10 +90,12 @@ export function createUnsafePartialObserverComponent(Component, curriedPropsSour
90
90
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
91
91
  Object.assign({}, props),
92
92
  ]);
93
- // TODO is there any way we can memoize this transformation?
94
- const curriedProps = curriedPropsSource(additionalProps);
95
- return (_jsx(C, Object.assign({ ref: ref }, curriedProps, exposedProps)));
96
- }));
93
+ return (_jsx(Observer, { children: () => {
94
+ // TODO is there any way we can memoize this transformation?
95
+ const curriedProps = curriedPropsSource(additionalProps);
96
+ return (_jsx(C, Object.assign({ ref: ref }, curriedProps, exposedProps)));
97
+ } }));
98
+ });
97
99
  }
98
100
  export function usePartialObserverComponent(
99
101
  // has to be first so eslint react-hooks/exhaustive-deps can find the callback
@@ -1,8 +1,16 @@
1
1
  import '@mantine/core/styles.css'
2
2
  import { MantineProvider } from '@mantine/core'
3
3
  import { type Preview } from '@storybook/react'
4
+ import { configure } from 'mobx'
4
5
  import { StrictMode } from 'react'
5
6
 
7
+ // turn on all useful mobx warnings in storybook to try to catch bad behavior
8
+ configure({
9
+ enforceActions: 'observed',
10
+ observableRequiresReaction: true,
11
+ reactionRequiresObservable: true,
12
+ })
13
+
6
14
  const preview: Preview = {
7
15
  parameters: {
8
16
  controls: {
@@ -7,12 +7,12 @@ $ tsup
7
7
  CLI Target: es6
8
8
  CJS Build start
9
9
  ESM Build start
10
- CJS dist/index.cjs 62.94 KB
11
- CJS ⚡️ Build success in 142ms
12
- ESM dist/index.js 58.83 KB
13
- ESM ⚡️ Build success in 148ms
10
+ CJS dist/index.cjs 64.19 KB
11
+ CJS ⚡️ Build success in 102ms
12
+ ESM dist/index.js 60.01 KB
13
+ ESM ⚡️ Build success in 112ms
14
14
  DTS Build start
15
- DTS ⚡️ Build success in 12298ms
16
- DTS dist/index.d.cts 38.21 KB
17
- DTS dist/index.d.ts 38.21 KB
18
- Done in 13.48s.
15
+ DTS ⚡️ Build success in 6977ms
16
+ DTS dist/index.d.cts 38.52 KB
17
+ DTS dist/index.d.ts 38.52 KB
18
+ Done in 8.18s.
@@ -1,3 +1,3 @@
1
1
  yarn run v1.22.22
2
2
  $ tsc -b
3
- Done in 0.41s.
3
+ Done in 0.36s.
@@ -1,3 +1,3 @@
1
1
  yarn run v1.22.22
2
2
  $ json -f package.json -f package.exports.json --merge > package.release.json
3
- Done in 0.12s.
3
+ Done in 0.11s.
@@ -11,6 +11,7 @@ import {
11
11
  import {
12
12
  type Accessor,
13
13
  type AnyValueType,
14
+ copy,
14
15
  equals,
15
16
  flattenAccessorsOfType,
16
17
  type FlattenedValuesOfType,
@@ -27,6 +28,7 @@ import {
27
28
  valuePathToTypePath,
28
29
  } from '@strictly/define'
29
30
  import {
31
+ action,
30
32
  computed,
31
33
  observable,
32
34
  runInAction,
@@ -146,7 +148,7 @@ export abstract class FormModel<
146
148
  >,
147
149
  > {
148
150
  @observable.ref
149
- accessor value: MobxValueOfType<T>
151
+ private accessor observableValue: MobxValueOfType<T>
150
152
  @observable.shallow
151
153
  accessor fieldOverrides: FlattenedFieldOverrides<ValuePathsToAdapters>
152
154
  @observable.shallow
@@ -170,13 +172,13 @@ export abstract class FormModel<
170
172
  protected readonly mode: FormMode,
171
173
  ) {
172
174
  this.originalValues = flattenValuesOfType<ReadonlyTypeOfType<T>>(type, originalValue, this.listIndicesToKeys)
173
- this.value = mobxCopy(type, originalValue)
175
+ this.observableValue = mobxCopy(type, originalValue)
174
176
  this.flattenedTypeDefs = flattenTypesOfType(type)
175
177
  // pre-populate field overrides for consistent behavior when default information is overwritten
176
178
  // then returned to
177
179
  const conversions = flattenValueTo(
178
180
  type,
179
- this.value,
181
+ originalValue,
180
182
  () => {},
181
183
  (
182
184
  _t: StrictTypeDef,
@@ -228,6 +230,12 @@ export abstract class FormModel<
228
230
  }
229
231
  }
230
232
 
233
+ @computed
234
+ get value(): ValueOfType<ReadonlyTypeOfType<T>> {
235
+ // copy and strip out the mobx so this computed will fire every time anything changes
236
+ return copy(this.type, this.observableValue)
237
+ }
238
+
231
239
  @computed
232
240
  get fields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>> {
233
241
  return new Proxy<SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>>>(
@@ -252,7 +260,7 @@ export abstract class FormModel<
252
260
  private get knownFields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>> {
253
261
  return flattenValueTo(
254
262
  this.type,
255
- this.value,
263
+ this.observableValue,
256
264
  () => {},
257
265
  // TODO swap these to valuePath, typePath in flatten
258
266
  (_t: StrictTypeDef, _v: AnyValueType, _setter, typePath, valuePath): Field | undefined => {
@@ -302,7 +310,7 @@ export abstract class FormModel<
302
310
  const accessor = this.getAccessorForValuePath(valuePath)
303
311
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
304
312
  const fieldTypeDef = this.flattenedTypeDefs[typePath as string]
305
- const context = this.toContext(this.value, valuePath)
313
+ const context = this.toContext(this.observableValue, valuePath)
306
314
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
307
315
  const defaultValue = create(valuePath as string, context)
308
316
 
@@ -414,9 +422,9 @@ export abstract class FormModel<
414
422
  get accessors(): Readonly<Record<string, Accessor>> {
415
423
  return flattenAccessorsOfType<T, Readonly<Record<string, Accessor>>>(
416
424
  this.type,
417
- this.value,
425
+ this.observableValue,
418
426
  (value: ValueOfType<T>): void => {
419
- this.value = mobxCopy(this.type, value)
427
+ this.observableValue = mobxCopy(this.type, value)
420
428
  },
421
429
  this.listIndicesToKeys,
422
430
  )
@@ -448,13 +456,14 @@ export abstract class FormModel<
448
456
  @computed
449
457
  get valueChanged() {
450
458
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
451
- return !equals(this.type, this.value, this.originalValue as ValueOfType<T>)
459
+ return !equals(this.type, this.observableValue, this.originalValue as ValueOfType<T>)
452
460
  }
453
461
 
454
462
  typePath<K extends keyof ValueToTypePaths>(valuePath: K): ValueToTypePaths[K] {
455
463
  return valuePathToTypePath<ValueToTypePaths, K>(this.type, valuePath, true)
456
464
  }
457
465
 
466
+ @action
458
467
  setFieldValue<K extends keyof ValuePathsToAdapters>(
459
468
  valuePath: K,
460
469
  value: ToOfFieldAdapter<ValuePathsToAdapters[K]>,
@@ -490,7 +499,7 @@ export abstract class FormModel<
490
499
  elementTypePath as string,
491
500
  // TODO what can we use for the value path here?
492
501
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
493
- this.toContext(this.value, valuePath as unknown as keyof ValuePathsToAdapters),
502
+ this.toContext(this.observableValue, valuePath as unknown as keyof ValuePathsToAdapters),
494
503
  )
495
504
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
496
505
  const originalList: any[] = accessor.value
@@ -560,7 +569,7 @@ export abstract class FormModel<
560
569
  assertExists(revert, 'setting value not supported {}', valuePath)
561
570
 
562
571
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
563
- const conversion = revert(value, valuePath as any, this.toContext(this.value, valuePath))
572
+ const conversion = revert(value, valuePath as any, this.toContext(this.observableValue, valuePath))
564
573
  const accessor = this.getAccessorForValuePath(valuePath)
565
574
  return runInAction(() => {
566
575
  this.fieldOverrides[valuePath] = [value]
@@ -623,7 +632,7 @@ export abstract class FormModel<
623
632
  create,
624
633
  } = adapter
625
634
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
626
- const context = this.toContext(this.value, valuePath as unknown as keyof ValuePathsToAdapters)
635
+ const context = this.toContext(this.observableValue, valuePath as unknown as keyof ValuePathsToAdapters)
627
636
  const value = create(valuePath, context)
628
637
  const {
629
638
  value: displayValue,
@@ -643,12 +652,12 @@ export abstract class FormModel<
643
652
  // TODO this isn't correct, should reload from value
644
653
  this.fieldOverrides = {}
645
654
  this.errorOverrides = {}
646
- this.value = mobxCopy(this.type, value)
655
+ this.observableValue = mobxCopy(this.type, value)
647
656
  })
648
657
  }
649
658
 
650
659
  isValuePathActive<K extends keyof ValuePathsToAdapters>(valuePath: K): boolean {
651
- const values = flattenValuesOfType(this.type, this.value, this.listIndicesToKeys)
660
+ const values = flattenValuesOfType(this.type, this.observableValue, this.listIndicesToKeys)
652
661
  const keys = new Set(Object.keys(values))
653
662
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
654
663
  return keys.has(valuePath as string)
@@ -704,25 +713,23 @@ export abstract class FormModel<
704
713
  return displayedValue !== originalDisplayedValue
705
714
  }
706
715
 
716
+ @action
707
717
  validateField<K extends keyof ValuePathsToAdapters>(
708
718
  valuePath: K,
709
719
  validation: Validation = Validation.Always,
710
720
  ): boolean {
711
- runInAction(() => {
712
- this.validation[valuePath] = validation
713
- delete this.errorOverrides[valuePath]
714
- })
721
+ this.validation[valuePath] = validation
722
+ delete this.errorOverrides[valuePath]
715
723
  return this.fields[valuePath].error == null
716
724
  }
717
725
 
726
+ @action
718
727
  validateAll(validation: Validation = Validation.Always): boolean {
719
728
  const accessors = toArray(this.accessors)
720
729
 
721
- runInAction(() => {
722
- accessors.forEach(([valuePath]) => {
723
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
724
- this.validation[valuePath as keyof ValuePathsToAdapters] = validation
725
- })
730
+ accessors.forEach(([valuePath]) => {
731
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
732
+ this.validation[valuePath as keyof ValuePathsToAdapters] = validation
726
733
  })
727
734
  return accessors.every(
728
735
  ([valuePath]): boolean => {
@@ -10,6 +10,7 @@ import {
10
10
  type FormModel,
11
11
  Validation,
12
12
  } from './FormModel'
13
+ import { peek } from './peek'
13
14
 
14
15
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
16
  type ValueOfModel<M extends FormModel<any, any, any, any, any>> = M extends FormModel<infer T, any, any, any, any>
@@ -41,7 +42,8 @@ export function useDefaultMobxFormHooks<
41
42
  path: Path,
42
43
  value: ValueTypeOfField<F[Path]>,
43
44
  ) {
44
- const validation = Math.min(model.getValidation(path), Validation.Changed)
45
+ const activeValidation = peek(() => model.getValidation(path))
46
+ const validation = Math.min(activeValidation, Validation.Changed)
45
47
  model.setFieldValue<Path>(path, value, validation)
46
48
  },
47
49
  [model],
@@ -66,10 +68,18 @@ export function useDefaultMobxFormHooks<
66
68
  // (e.g. changing a discriminator)
67
69
  // TODO debounce?
68
70
  setTimeout(function () {
71
+ const [
72
+ validate,
73
+ activeValidation,
74
+ ] = peek(() => [
75
+ model.isValuePathActive(path) && model.isFieldDirty(path) && model.fields[path].error == null,
76
+ model.getValidation(path),
77
+ ])
69
78
  // only start validation if the user has changed the field and there isn't already an error visible
70
- if (model.isValuePathActive(path) && model.isFieldDirty(path) && model.fields[path].error == null) {
79
+ if (validate) {
80
+ const validation = Math.max(Validation.Changed, activeValidation)
71
81
  // further workaround to make sure we don't downgrade the existing validation
72
- model.validateField(path, Math.max(Validation.Changed, model.getValidation(path)))
82
+ model.validateField(path, validation)
73
83
  }
74
84
  }, 100)
75
85
  },
@@ -78,8 +88,10 @@ export function useDefaultMobxFormHooks<
78
88
 
79
89
  const onFormSubmit = useCallback(
80
90
  function () {
81
- if (model.validateSubmit()) {
82
- onValidFormSubmit?.(model.value)
91
+ const valid = peek(() => model.validateSubmit())
92
+ if (valid && onValidFormSubmit) {
93
+ const value = peek(() => model.value)
94
+ onValidFormSubmit(value)
83
95
  }
84
96
  },
85
97
  [
@@ -0,0 +1,17 @@
1
+ import { when } from 'mobx'
2
+
3
+ /**
4
+ * Used for when you want to look at the value of an observable without observing it (or triggering
5
+ * the mobx runtime linter)
6
+ */
7
+ export function peek<T>(operation: () => T): T {
8
+ let result: T
9
+ // when will make mobx think we are observing the value
10
+ void when(() => {
11
+ // trick mobx runtime linting
12
+ result = operation()
13
+ return true
14
+ })
15
+ // biome-ignore lint/style/noNonNullAssertion: the result is always there
16
+ return result!
17
+ }