@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.
- package/.out/.storybook/preview.js +7 -0
- package/.out/core/mobx/FormModel.d.ts +3 -2
- package/.out/core/mobx/FormModel.js +46 -34
- package/.out/core/mobx/hooks.js +14 -5
- package/.out/core/mobx/peek.d.ts +5 -0
- package/.out/core/mobx/peek.js +16 -0
- package/.out/index.d.ts +1 -0
- package/.out/index.js +1 -0
- package/.out/mantine/createFieldView.d.ts +3 -2
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.out/util/Partial.js +8 -6
- package/.storybook/preview.tsx +8 -0
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-check-types.log +1 -1
- package/.turbo/turbo-release$colon$exports.log +1 -1
- package/core/mobx/FormModel.ts +29 -22
- package/core/mobx/hooks.tsx +17 -5
- package/core/mobx/peek.ts +17 -0
- package/dist/index.cjs +94 -65
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +103 -73
- package/index.ts +1 -0
- package/mantine/createFieldView.tsx +2 -1
- package/mantine/hooks.tsx +1 -1
- package/package.json +7 -6
- package/util/Partial.tsx +57 -54
package/.out/util/Partial.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
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
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
package/.storybook/preview.tsx
CHANGED
|
@@ -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: {
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -7,12 +7,12 @@ $ tsup
|
|
|
7
7
|
[34mCLI[39m Target: es6
|
|
8
8
|
[34mCJS[39m Build start
|
|
9
9
|
[34mESM[39m Build start
|
|
10
|
-
[32mCJS[39m [1mdist/index.cjs [22m[
|
|
11
|
-
[32mCJS[39m ⚡️ Build success in
|
|
12
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
13
|
-
[32mESM[39m ⚡️ Build success in
|
|
10
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m64.19 KB[39m
|
|
11
|
+
[32mCJS[39m ⚡️ Build success in 102ms
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m60.01 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 112ms
|
|
14
14
|
[34mDTS[39m Build start
|
|
15
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[32m38.
|
|
17
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m38.
|
|
18
|
-
Done in
|
|
15
|
+
[32mDTS[39m ⚡️ Build success in 6977ms
|
|
16
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m38.52 KB[39m
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m38.52 KB[39m
|
|
18
|
+
Done in 8.18s.
|
package/core/mobx/FormModel.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
425
|
+
this.observableValue,
|
|
418
426
|
(value: ValueOfType<T>): void => {
|
|
419
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
712
|
-
|
|
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
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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 => {
|
package/core/mobx/hooks.tsx
CHANGED
|
@@ -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
|
|
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 (
|
|
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,
|
|
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
|
-
|
|
82
|
-
|
|
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
|
+
}
|