@strictly/react-form 0.0.26 → 0.0.28
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/core/mobx/form_model.d.ts +3 -0
- package/.out/core/mobx/form_model.js +48 -91
- package/.out/core/mobx/hooks.js +2 -2
- package/.out/core/mobx/specs/form_model.tests.js +14 -12
- package/.out/mantine/create_list.d.ts +2 -1
- package/.out/mantine/create_list.js +15 -3
- package/.out/mantine/specs/list_hooks.stories.js +8 -0
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.out/types/field.d.ts +1 -0
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-check-types.log +1 -1
- package/core/mobx/form_model.ts +47 -106
- package/core/mobx/hooks.tsx +2 -2
- package/core/mobx/specs/form_model.tests.ts +14 -12
- package/dist/index.cjs +79 -110
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +70 -100
- package/mantine/create_list.tsx +27 -3
- package/mantine/specs/__snapshots__/list_hooks.tests.tsx.snap +3 -3
- package/mantine/specs/list_hooks.stories.tsx +8 -0
- package/package.json +1 -1
- package/types/field.ts +1 -0
package/.out/types/field.d.ts
CHANGED
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
|
-
[
|
|
11
|
-
[
|
|
12
|
-
[
|
|
13
|
-
[
|
|
10
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m62.88 KB[39m
|
|
11
|
+
[32mCJS[39m ⚡️ Build success in 150ms
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m58.76 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 163ms
|
|
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 32.
|
|
15
|
+
[32mDTS[39m ⚡️ Build success in 31145ms
|
|
16
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m38.21 KB[39m
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m38.21 KB[39m
|
|
18
|
+
Done in 32.31s.
|
package/core/mobx/form_model.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
assertExists,
|
|
3
3
|
assertExistsAndReturn,
|
|
4
|
-
assertState,
|
|
5
4
|
checkValidNumber,
|
|
6
5
|
type ElementOfArray,
|
|
7
6
|
map,
|
|
@@ -161,13 +160,16 @@ export abstract class FormModel<
|
|
|
161
160
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
161
|
private readonly originalValues: Record<string, any>
|
|
163
162
|
|
|
163
|
+
// maintains the value paths of lists when the original order is destroyed by deletes or reordering
|
|
164
|
+
private readonly listIndicesToKeys: Record<string, number[]> = {}
|
|
165
|
+
|
|
164
166
|
constructor(
|
|
165
167
|
readonly type: T,
|
|
166
|
-
originalValue: ValueOfType<ReadonlyTypeOfType<T>>,
|
|
168
|
+
private readonly originalValue: ValueOfType<ReadonlyTypeOfType<T>>,
|
|
167
169
|
protected readonly adapters: TypePathsToAdapters,
|
|
168
170
|
protected readonly mode: FormMode,
|
|
169
171
|
) {
|
|
170
|
-
this.originalValues = flattenValuesOfType<ReadonlyTypeOfType<T>>(type, originalValue)
|
|
172
|
+
this.originalValues = flattenValuesOfType<ReadonlyTypeOfType<T>>(type, originalValue, this.listIndicesToKeys)
|
|
171
173
|
this.value = mobxCopy(type, originalValue)
|
|
172
174
|
this.flattenedTypeDefs = flattenTypesOfType(type)
|
|
173
175
|
// pre-populate field overrides for consistent behavior when default information is overwritten
|
|
@@ -202,6 +204,7 @@ export abstract class FormModel<
|
|
|
202
204
|
// cannot call this.context yet as the "this" pointer has not been fully created
|
|
203
205
|
return convert(fieldValue, valuePath, contextValue)
|
|
204
206
|
},
|
|
207
|
+
this.listIndicesToKeys,
|
|
205
208
|
)
|
|
206
209
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
207
210
|
this.fieldOverrides = map(conversions, function (_k, v) {
|
|
@@ -260,6 +263,7 @@ export abstract class FormModel<
|
|
|
260
263
|
typePath as keyof TypePathsToAdapters,
|
|
261
264
|
)
|
|
262
265
|
},
|
|
266
|
+
this.listIndicesToKeys,
|
|
263
267
|
)
|
|
264
268
|
}
|
|
265
269
|
|
|
@@ -335,7 +339,10 @@ export abstract class FormModel<
|
|
|
335
339
|
}
|
|
336
340
|
}
|
|
337
341
|
|
|
338
|
-
private synthesizeFieldByPaths(
|
|
342
|
+
private synthesizeFieldByPaths(
|
|
343
|
+
valuePath: keyof ValuePathsToAdapters,
|
|
344
|
+
typePath: keyof TypePathsToAdapters,
|
|
345
|
+
): Field | undefined {
|
|
339
346
|
const field = this.getField(valuePath, typePath)
|
|
340
347
|
if (field == null) {
|
|
341
348
|
return
|
|
@@ -391,6 +398,8 @@ export abstract class FormModel<
|
|
|
391
398
|
error,
|
|
392
399
|
readonly: readonly && !this.forceMutableFields,
|
|
393
400
|
required,
|
|
401
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
402
|
+
listIndexToKey: this.listIndicesToKeys[valuePath as string],
|
|
394
403
|
}
|
|
395
404
|
}
|
|
396
405
|
|
|
@@ -408,6 +417,7 @@ export abstract class FormModel<
|
|
|
408
417
|
(value: ValueOfType<T>): void => {
|
|
409
418
|
this.value = mobxCopy(this.type, value)
|
|
410
419
|
},
|
|
420
|
+
this.listIndicesToKeys,
|
|
411
421
|
)
|
|
412
422
|
}
|
|
413
423
|
|
|
@@ -434,6 +444,12 @@ export abstract class FormModel<
|
|
|
434
444
|
})
|
|
435
445
|
}
|
|
436
446
|
|
|
447
|
+
@computed
|
|
448
|
+
get valueChanged() {
|
|
449
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
450
|
+
return !equals(this.type, this.value, this.originalValue as ValueOfType<T>)
|
|
451
|
+
}
|
|
452
|
+
|
|
437
453
|
typePath<K extends keyof ValueToTypePaths>(valuePath: K): ValueToTypePaths[K] {
|
|
438
454
|
return valuePathToTypePath<ValueToTypePaths, K>(this.type, valuePath, true)
|
|
439
455
|
}
|
|
@@ -482,128 +498,53 @@ export abstract class FormModel<
|
|
|
482
498
|
element,
|
|
483
499
|
...originalList.slice(definedIndex),
|
|
484
500
|
]
|
|
485
|
-
// shuffle the overrides around to account for new indices
|
|
486
|
-
// to so this we need to sort the array indices in descending order
|
|
487
|
-
const targetPaths = Object.keys(this.fieldOverrides).filter(function (v) {
|
|
488
|
-
return v.startsWith(`${listValuePath}.`)
|
|
489
|
-
}).map(function (v) {
|
|
490
|
-
const parts = v.substring(listValuePath.length + 1).split('.')
|
|
491
|
-
const index = parseInt(parts[0])
|
|
492
|
-
return [
|
|
493
|
-
index,
|
|
494
|
-
parts.slice(1),
|
|
495
|
-
] as const
|
|
496
|
-
}).filter(function ([index]) {
|
|
497
|
-
return index >= definedIndex
|
|
498
|
-
}).sort(function ([a], [b]) {
|
|
499
|
-
// descending
|
|
500
|
-
return b - a
|
|
501
|
-
})
|
|
502
501
|
runInAction(() => {
|
|
503
|
-
targetPaths.forEach(([
|
|
504
|
-
index,
|
|
505
|
-
postfix,
|
|
506
|
-
]) => {
|
|
507
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
508
|
-
const fromJsonPath = [
|
|
509
|
-
listValuePath,
|
|
510
|
-
`${index}`,
|
|
511
|
-
...postfix,
|
|
512
|
-
].join('.') as keyof ValuePathsToAdapters
|
|
513
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
514
|
-
const toJsonPath = [
|
|
515
|
-
listValuePath,
|
|
516
|
-
`${index + 1}`,
|
|
517
|
-
...postfix,
|
|
518
|
-
].join('.') as keyof ValuePathsToAdapters
|
|
519
|
-
const fieldOverride = this.fieldOverrides[fromJsonPath]
|
|
520
|
-
delete this.fieldOverrides[fromJsonPath]
|
|
521
|
-
this.fieldOverrides[toJsonPath] = fieldOverride
|
|
522
|
-
const validation = this.validation[fromJsonPath]
|
|
523
|
-
delete this.validation[fromJsonPath]
|
|
524
|
-
this.validation[toJsonPath] = validation
|
|
525
|
-
})
|
|
526
502
|
accessor.set(newList)
|
|
527
503
|
// delete any value overrides so the new list isn't shadowed
|
|
528
504
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
529
505
|
delete this.fieldOverrides[listValuePath as keyof ValuePathsToAdapters]
|
|
506
|
+
const indicesToKeys = assertExistsAndReturn(
|
|
507
|
+
this.listIndicesToKeys[listValuePath],
|
|
508
|
+
'no index to key mapping for list {}',
|
|
509
|
+
listValuePath,
|
|
510
|
+
)
|
|
511
|
+
const nextKey = indicesToKeys[indicesToKeys.length - 1]
|
|
512
|
+
// insert the next key
|
|
513
|
+
indicesToKeys.splice(definedIndex, 0, nextKey)
|
|
514
|
+
// create the new next key
|
|
515
|
+
indicesToKeys[indicesToKeys.length - 1] = nextKey + 1
|
|
530
516
|
})
|
|
531
517
|
}
|
|
532
518
|
|
|
533
519
|
removeListItem<K extends keyof FlattenedListTypesOfType<T>>(...elementValuePaths: readonly `${K}.${number}`[]) {
|
|
534
|
-
// sort and reverse so we delete last to first so indices of sequential deletions are preserved
|
|
535
|
-
const orderedElementValuePaths = elementValuePaths.toSorted().reverse()
|
|
536
520
|
runInAction(() => {
|
|
537
|
-
|
|
521
|
+
elementValuePaths.forEach(elementValuePath => {
|
|
538
522
|
const [
|
|
539
523
|
listValuePath,
|
|
540
|
-
|
|
524
|
+
elementKeyString,
|
|
541
525
|
] = assertExistsAndReturn(
|
|
542
526
|
jsonPathPop(elementValuePath),
|
|
543
527
|
'expected a path with two or more segments {}',
|
|
544
528
|
elementValuePath,
|
|
545
529
|
)
|
|
546
530
|
const accessor = this.accessors[listValuePath]
|
|
547
|
-
const
|
|
548
|
-
parseInt(
|
|
549
|
-
'unexpected
|
|
550
|
-
|
|
531
|
+
const elementKey = checkValidNumber(
|
|
532
|
+
parseInt(elementKeyString),
|
|
533
|
+
'unexpected id {} ({})',
|
|
534
|
+
elementKeyString,
|
|
551
535
|
elementValuePath,
|
|
552
536
|
)
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
elementIndex,
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
newList.splice(elementIndex, 1)
|
|
561
|
-
|
|
562
|
-
// shuffle the overrides around to account for new indices
|
|
563
|
-
// to so this we need to sort the array indices in descending order
|
|
564
|
-
const targetPaths = Object.keys(this.fieldOverrides).filter(function (v) {
|
|
565
|
-
return v.startsWith(`${listValuePath}.`)
|
|
566
|
-
}).map(function (v) {
|
|
567
|
-
const parts = v.substring(listValuePath.length + 1).split('.')
|
|
568
|
-
const index = parseInt(parts[0])
|
|
569
|
-
return [
|
|
570
|
-
index,
|
|
571
|
-
parts.slice(1),
|
|
572
|
-
] as const
|
|
573
|
-
}).filter(function ([index]) {
|
|
574
|
-
return index > elementIndex
|
|
575
|
-
}).sort(function ([a], [b]) {
|
|
576
|
-
// descending
|
|
577
|
-
return a - b
|
|
578
|
-
})
|
|
579
|
-
|
|
580
|
-
targetPaths.forEach(([
|
|
581
|
-
index,
|
|
582
|
-
postfix,
|
|
583
|
-
]) => {
|
|
584
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
585
|
-
const fromJsonPath = [
|
|
586
|
-
listValuePath,
|
|
587
|
-
`${index}`,
|
|
588
|
-
...postfix,
|
|
589
|
-
].join('.') as keyof ValuePathsToAdapters
|
|
537
|
+
const indicesToKeys = this.listIndicesToKeys[listValuePath]
|
|
538
|
+
const elementIndex = indicesToKeys?.indexOf(elementKey) ?? -1
|
|
539
|
+
if (elementIndex >= 0) {
|
|
540
|
+
const newList = [...accessor.value]
|
|
541
|
+
newList.splice(elementIndex, 1)
|
|
542
|
+
accessor.set(newList)
|
|
543
|
+
// delete any value overrides so the new list isn't shadowed
|
|
590
544
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
...postfix,
|
|
595
|
-
].join('.') as keyof ValuePathsToAdapters
|
|
596
|
-
const fieldOverride = this.fieldOverrides[fromJsonPath]
|
|
597
|
-
delete this.fieldOverrides[fromJsonPath]
|
|
598
|
-
this.fieldOverrides[toJsonPath] = fieldOverride
|
|
599
|
-
const validation = this.validation[fromJsonPath]
|
|
600
|
-
delete this.validation[fromJsonPath]
|
|
601
|
-
this.validation[toJsonPath] = validation
|
|
602
|
-
})
|
|
603
|
-
accessor.set(newList)
|
|
604
|
-
// delete any value overrides so the new list isn't shadowed
|
|
605
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
606
|
-
delete this.fieldOverrides[listValuePath as keyof ValuePathsToAdapters]
|
|
545
|
+
delete this.fieldOverrides[listValuePath as keyof ValuePathsToAdapters]
|
|
546
|
+
indicesToKeys.splice(elementIndex, 1)
|
|
547
|
+
}
|
|
607
548
|
})
|
|
608
549
|
})
|
|
609
550
|
}
|
|
@@ -706,7 +647,7 @@ export abstract class FormModel<
|
|
|
706
647
|
}
|
|
707
648
|
|
|
708
649
|
isValuePathActive<K extends keyof ValuePathsToAdapters>(valuePath: K): boolean {
|
|
709
|
-
const values = flattenValuesOfType(this.type, this.value)
|
|
650
|
+
const values = flattenValuesOfType(this.type, this.value, this.listIndicesToKeys)
|
|
710
651
|
const keys = new Set(Object.keys(values))
|
|
711
652
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
712
653
|
return keys.has(valuePath as string)
|
package/core/mobx/hooks.tsx
CHANGED
|
@@ -66,8 +66,8 @@ export function useDefaultMobxFormHooks<
|
|
|
66
66
|
// (e.g. changing a discriminator)
|
|
67
67
|
// TODO debounce?
|
|
68
68
|
setTimeout(function () {
|
|
69
|
-
// only start validation if the user has changed the field
|
|
70
|
-
if (model.isValuePathActive(path) && model.isFieldDirty(path)) {
|
|
69
|
+
// 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) {
|
|
71
71
|
// further workaround to make sure we don't downgrade the existing validation
|
|
72
72
|
model.validateField(path, Math.max(Validation.Changed, model.getValidation(path)))
|
|
73
73
|
}
|
|
@@ -828,19 +828,20 @@ describe('all', function () {
|
|
|
828
828
|
|
|
829
829
|
it.each([
|
|
830
830
|
[
|
|
831
|
-
|
|
831
|
+
// new
|
|
832
|
+
'$.3',
|
|
832
833
|
'0',
|
|
833
834
|
],
|
|
834
835
|
[
|
|
835
|
-
'$.
|
|
836
|
+
'$.0',
|
|
836
837
|
'x',
|
|
837
838
|
],
|
|
838
839
|
[
|
|
839
|
-
'$.
|
|
840
|
+
'$.1',
|
|
840
841
|
'3',
|
|
841
842
|
],
|
|
842
843
|
[
|
|
843
|
-
'$.
|
|
844
|
+
'$.2',
|
|
844
845
|
'z',
|
|
845
846
|
],
|
|
846
847
|
] as const)('it reports the value of field %s as %s', function (path, fieldValue) {
|
|
@@ -849,19 +850,20 @@ describe('all', function () {
|
|
|
849
850
|
|
|
850
851
|
it.each([
|
|
851
852
|
[
|
|
852
|
-
|
|
853
|
+
// new
|
|
854
|
+
'$.3',
|
|
853
855
|
undefined,
|
|
854
856
|
],
|
|
855
857
|
[
|
|
856
|
-
'$.
|
|
858
|
+
'$.0',
|
|
857
859
|
IS_NAN_ERROR,
|
|
858
860
|
],
|
|
859
861
|
[
|
|
860
|
-
'$.
|
|
862
|
+
'$.1',
|
|
861
863
|
undefined,
|
|
862
864
|
],
|
|
863
865
|
[
|
|
864
|
-
'$.
|
|
866
|
+
'$.2',
|
|
865
867
|
IS_NAN_ERROR,
|
|
866
868
|
],
|
|
867
869
|
] as const)('it reports the error of field %s', function (path, error) {
|
|
@@ -926,11 +928,11 @@ describe('all', function () {
|
|
|
926
928
|
|
|
927
929
|
it('updates the field values and errors', function () {
|
|
928
930
|
expect(model.fields).toEqual({
|
|
929
|
-
'$.
|
|
931
|
+
'$.1': expect.objectContaining({
|
|
930
932
|
value: '3',
|
|
931
933
|
error: undefined,
|
|
932
934
|
}),
|
|
933
|
-
'$.
|
|
935
|
+
'$.2': expect.objectContaining({
|
|
934
936
|
value: 'z',
|
|
935
937
|
error: IS_NAN_ERROR,
|
|
936
938
|
}),
|
|
@@ -956,7 +958,7 @@ describe('all', function () {
|
|
|
956
958
|
value: 'x',
|
|
957
959
|
error: IS_NAN_ERROR,
|
|
958
960
|
}),
|
|
959
|
-
'$.
|
|
961
|
+
'$.2': expect.objectContaining({
|
|
960
962
|
value: 'z',
|
|
961
963
|
error: IS_NAN_ERROR,
|
|
962
964
|
}),
|
|
@@ -975,7 +977,7 @@ describe('all', function () {
|
|
|
975
977
|
|
|
976
978
|
it('updates the field values and errors', function () {
|
|
977
979
|
expect(model.fields).toEqual({
|
|
978
|
-
'$.
|
|
980
|
+
'$.2': expect.objectContaining({
|
|
979
981
|
value: 'z',
|
|
980
982
|
error: IS_NAN_ERROR,
|
|
981
983
|
}),
|