@strictly/react-form 0.0.27 → 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.
@@ -34,6 +34,7 @@ export type ContextOf<TypePathsToAdapters extends Partial<Readonly<Record<string
34
34
  export type FormMode = 'edit' | 'create';
35
35
  export declare abstract class FormModel<T extends Type, ValueToTypePaths extends Readonly<Record<string, string>>, TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<FlattenedValuesOfType<T, '*'>, ContextType>, ContextType = ContextOf<TypePathsToAdapters>, ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths>> {
36
36
  readonly type: T;
37
+ private readonly originalValue;
37
38
  protected readonly adapters: TypePathsToAdapters;
38
39
  protected readonly mode: FormMode;
39
40
  accessor value: MobxValueOfType<T>;
@@ -42,6 +43,7 @@ export declare abstract class FormModel<T extends Type, ValueToTypePaths extends
42
43
  accessor validation: FlattenedValidation<ValuePathsToAdapters>;
43
44
  private readonly flattenedTypeDefs;
44
45
  private readonly originalValues;
46
+ private readonly listIndicesToKeys;
45
47
  constructor(type: T, originalValue: ValueOfType<ReadonlyTypeOfType<T>>, adapters: TypePathsToAdapters, mode: FormMode);
46
48
  protected abstract toContext(value: ValueOfType<ReadonlyTypeOfType<T>>, valuePath: keyof ValuePathsToAdapters): ContextType;
47
49
  get forceMutableFields(): boolean;
@@ -55,11 +57,11 @@ export declare abstract class FormModel<T extends Type, ValueToTypePaths extends
55
57
  private maybeGetAdapterForValuePath;
56
58
  private getAdapterForValuePath;
57
59
  get dirty(): boolean;
60
+ get valueChanged(): boolean;
58
61
  typePath<K extends keyof ValueToTypePaths>(valuePath: K): ValueToTypePaths[K];
59
62
  setFieldValue<K extends keyof ValuePathsToAdapters>(valuePath: K, value: ToOfFieldAdapter<ValuePathsToAdapters[K]>, validation?: Validation): boolean;
60
63
  addListItem<K extends keyof FlattenedListTypesOfType<T>>(valuePath: K, elementValue?: Maybe<ElementOfArray<FlattenedValuesOfType<T>[K]>>, index?: number): void;
61
64
  removeListItem<K extends keyof FlattenedListTypesOfType<T>>(...elementValuePaths: readonly `${K}.${number}`[]): void;
62
- protected moveListItem<K extends keyof FlattenedListTypesOfType<T>>(fromValuePath: K, toValuePath: K): void;
63
65
  private internalSetFieldValue;
64
66
  /**
65
67
  * Forces an error onto a field. Error will be removed if the field value changes
@@ -43,7 +43,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
43
43
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
44
44
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
45
45
  };
46
- import { assertExists, assertExistsAndReturn, assertState, checkValidNumber, map, toArray, UnreachableError, } from '@strictly/base';
46
+ import { assertExists, assertExistsAndReturn, checkValidNumber, map, toArray, UnreachableError, } from '@strictly/base';
47
47
  import { equals, flattenAccessorsOfType, flattenTypesOfType, flattenValuesOfType, flattenValueTo, jsonPathPop, mobxCopy, valuePathToTypePath, } from '@strictly/define';
48
48
  import { computed, observable, runInAction, } from 'mobx';
49
49
  import { UnreliableFieldConversionType, } from 'types/field_converters';
@@ -73,6 +73,7 @@ let FormModel = (() => {
73
73
  let _get_knownFields_decorators;
74
74
  let _get_accessors_decorators;
75
75
  let _get_dirty_decorators;
76
+ let _get_valueChanged_decorators;
76
77
  return _a = class FormModel {
77
78
  get value() { return __classPrivateFieldGet(this, _FormModel_value_accessor_storage, "f"); }
78
79
  set value(value) { __classPrivateFieldSet(this, _FormModel_value_accessor_storage, value, "f"); }
@@ -89,6 +90,12 @@ let FormModel = (() => {
89
90
  writable: true,
90
91
  value: (__runInitializers(this, _instanceExtraInitializers), type)
91
92
  });
93
+ Object.defineProperty(this, "originalValue", {
94
+ enumerable: true,
95
+ configurable: true,
96
+ writable: true,
97
+ value: originalValue
98
+ });
92
99
  Object.defineProperty(this, "adapters", {
93
100
  enumerable: true,
94
101
  configurable: true,
@@ -119,7 +126,14 @@ let FormModel = (() => {
119
126
  writable: true,
120
127
  value: void 0
121
128
  });
122
- this.originalValues = flattenValuesOfType(type, originalValue);
129
+ // maintains the value paths of lists when the original order is destroyed by deletes or reordering
130
+ Object.defineProperty(this, "listIndicesToKeys", {
131
+ enumerable: true,
132
+ configurable: true,
133
+ writable: true,
134
+ value: {}
135
+ });
136
+ this.originalValues = flattenValuesOfType(type, originalValue, this.listIndicesToKeys);
123
137
  this.value = mobxCopy(type, originalValue);
124
138
  this.flattenedTypeDefs = flattenTypesOfType(type);
125
139
  // pre-populate field overrides for consistent behavior when default information is overwritten
@@ -139,7 +153,7 @@ let FormModel = (() => {
139
153
  }
140
154
  // cannot call this.context yet as the "this" pointer has not been fully created
141
155
  return convert(fieldValue, valuePath, contextValue);
142
- });
156
+ }, this.listIndicesToKeys);
143
157
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
144
158
  this.fieldOverrides = map(conversions, function (_k, v) {
145
159
  return v && [v.value];
@@ -179,7 +193,7 @@ let FormModel = (() => {
179
193
  valuePath,
180
194
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
181
195
  typePath);
182
- });
196
+ }, this.listIndicesToKeys);
183
197
  }
184
198
  maybeSynthesizeFieldByValuePath(valuePath) {
185
199
  let typePath;
@@ -282,6 +296,8 @@ let FormModel = (() => {
282
296
  error,
283
297
  readonly: readonly && !this.forceMutableFields,
284
298
  required,
299
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
300
+ listIndexToKey: this.listIndicesToKeys[valuePath],
285
301
  };
286
302
  }
287
303
  getAccessorForValuePath(valuePath) {
@@ -291,7 +307,7 @@ let FormModel = (() => {
291
307
  get accessors() {
292
308
  return flattenAccessorsOfType(this.type, this.value, (value) => {
293
309
  this.value = mobxCopy(this.type, value);
294
- });
310
+ }, this.listIndicesToKeys);
295
311
  }
296
312
  maybeGetAdapterForValuePath(valuePath) {
297
313
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -308,6 +324,10 @@ let FormModel = (() => {
308
324
  return this.isFieldDirty(valuePath);
309
325
  });
310
326
  }
327
+ get valueChanged() {
328
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
329
+ return !equals(this.type, this.value, this.originalValue);
330
+ }
311
331
  typePath(valuePath) {
312
332
  return valuePathToTypePath(this.type, valuePath, true);
313
333
  }
@@ -341,113 +361,40 @@ let FormModel = (() => {
341
361
  element,
342
362
  ...originalList.slice(definedIndex),
343
363
  ];
344
- // shuffle the overrides around to account for new indices
345
- // to so this we need to sort the array indices in descending order
346
- const targetPaths = Object.keys(this.fieldOverrides).filter(function (v) {
347
- return v.startsWith(`${listValuePath}.`);
348
- }).map(function (v) {
349
- const parts = v.substring(listValuePath.length + 1).split('.');
350
- const index = parseInt(parts[0]);
351
- return [
352
- index,
353
- parts.slice(1),
354
- ];
355
- }).filter(function ([index]) {
356
- return index >= definedIndex;
357
- }).sort(function ([a], [b]) {
358
- // descending
359
- return b - a;
360
- });
361
364
  runInAction(() => {
362
- targetPaths.forEach(([index, postfix,]) => {
363
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
364
- const fromJsonPath = [
365
- listValuePath,
366
- `${index}`,
367
- ...postfix,
368
- ].join('.');
369
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
370
- const toJsonPath = [
371
- listValuePath,
372
- `${index + 1}`,
373
- ...postfix,
374
- ].join('.');
375
- const fieldOverride = this.fieldOverrides[fromJsonPath];
376
- delete this.fieldOverrides[fromJsonPath];
377
- this.fieldOverrides[toJsonPath] = fieldOverride;
378
- const validation = this.validation[fromJsonPath];
379
- delete this.validation[fromJsonPath];
380
- this.validation[toJsonPath] = validation;
381
- });
382
365
  accessor.set(newList);
383
366
  // delete any value overrides so the new list isn't shadowed
384
367
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
385
368
  delete this.fieldOverrides[listValuePath];
369
+ const indicesToKeys = assertExistsAndReturn(this.listIndicesToKeys[listValuePath], 'no index to key mapping for list {}', listValuePath);
370
+ const nextKey = indicesToKeys[indicesToKeys.length - 1];
371
+ // insert the next key
372
+ indicesToKeys.splice(definedIndex, 0, nextKey);
373
+ // create the new next key
374
+ indicesToKeys[indicesToKeys.length - 1] = nextKey + 1;
386
375
  });
387
376
  }
388
377
  removeListItem(...elementValuePaths) {
389
- // sort and reverse so we delete last to first so indices of sequential deletions are preserved
390
- const orderedElementValuePaths = elementValuePaths.toSorted().reverse();
391
378
  runInAction(() => {
392
- orderedElementValuePaths.forEach(elementValuePath => {
393
- const [listValuePath, elementIndexString,] = assertExistsAndReturn(jsonPathPop(elementValuePath), 'expected a path with two or more segments {}', elementValuePath);
379
+ elementValuePaths.forEach(elementValuePath => {
380
+ var _b;
381
+ const [listValuePath, elementKeyString,] = assertExistsAndReturn(jsonPathPop(elementValuePath), 'expected a path with two or more segments {}', elementValuePath);
394
382
  const accessor = this.accessors[listValuePath];
395
- const elementIndex = checkValidNumber(parseInt(elementIndexString), 'unexpected index {} ({})', elementIndexString, elementValuePath);
396
- const newList = [...accessor.value];
397
- assertState(elementIndex >= 0 && elementIndex < newList.length, 'invalid index from path {} ({})', elementIndex, elementValuePath);
398
- newList.splice(elementIndex, 1);
399
- // shuffle the overrides around to account for new indices
400
- // to so this we need to sort the array indices in descending order
401
- const targetPaths = Object.keys(this.fieldOverrides).filter(function (v) {
402
- return v.startsWith(`${listValuePath}.`);
403
- }).map(function (v) {
404
- const parts = v.substring(listValuePath.length + 1).split('.');
405
- const index = parseInt(parts[0]);
406
- return [
407
- index,
408
- parts.slice(1),
409
- ];
410
- }).filter(function ([index]) {
411
- return index > elementIndex;
412
- }).sort(function ([a], [b]) {
413
- // descending
414
- return a - b;
415
- });
416
- targetPaths.forEach(([index, postfix,]) => {
417
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
418
- const fromJsonPath = [
419
- listValuePath,
420
- `${index}`,
421
- ...postfix,
422
- ].join('.');
383
+ const elementKey = checkValidNumber(parseInt(elementKeyString), 'unexpected id {} ({})', elementKeyString, elementValuePath);
384
+ const indicesToKeys = this.listIndicesToKeys[listValuePath];
385
+ const elementIndex = (_b = indicesToKeys === null || indicesToKeys === void 0 ? void 0 : indicesToKeys.indexOf(elementKey)) !== null && _b !== void 0 ? _b : -1;
386
+ if (elementIndex >= 0) {
387
+ const newList = [...accessor.value];
388
+ newList.splice(elementIndex, 1);
389
+ accessor.set(newList);
390
+ // delete any value overrides so the new list isn't shadowed
423
391
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
424
- const toJsonPath = [
425
- listValuePath,
426
- `${index - 1}`,
427
- ...postfix,
428
- ].join('.');
429
- const fieldOverride = this.fieldOverrides[fromJsonPath];
430
- delete this.fieldOverrides[fromJsonPath];
431
- this.fieldOverrides[toJsonPath] = fieldOverride;
432
- const validation = this.validation[fromJsonPath];
433
- delete this.validation[fromJsonPath];
434
- this.validation[toJsonPath] = validation;
435
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
436
- this.moveListItem(fromJsonPath, toJsonPath);
437
- });
438
- accessor.set(newList);
439
- // delete any value overrides so the new list isn't shadowed
440
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
441
- delete this.fieldOverrides[listValuePath];
392
+ delete this.fieldOverrides[listValuePath];
393
+ indicesToKeys.splice(elementIndex, 1);
394
+ }
442
395
  });
443
396
  });
444
397
  }
445
- moveListItem(fromValuePath, toValuePath) {
446
- // do nothing, this is for subclasses to override
447
- // put in some nonsense so TS doesn't complain about the parameters not being used
448
- fromValuePath;
449
- toValuePath;
450
- }
451
398
  internalSetFieldValue(valuePath, value, validation) {
452
399
  const { revert } = this.getAdapterForValuePath(valuePath);
453
400
  assertExists(revert, 'setting value not supported {}', valuePath);
@@ -528,7 +475,7 @@ let FormModel = (() => {
528
475
  });
529
476
  }
530
477
  isValuePathActive(valuePath) {
531
- const values = flattenValuesOfType(this.type, this.value);
478
+ const values = flattenValuesOfType(this.type, this.value, this.listIndicesToKeys);
532
479
  const keys = new Set(Object.keys(values));
533
480
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
534
481
  return keys.has(valuePath);
@@ -606,6 +553,7 @@ let FormModel = (() => {
606
553
  _get_knownFields_decorators = [computed];
607
554
  _get_accessors_decorators = [computed];
608
555
  _get_dirty_decorators = [computed];
556
+ _get_valueChanged_decorators = [computed];
609
557
  __esDecorate(_a, null, _value_decorators, { kind: "accessor", name: "value", static: false, private: false, access: { has: obj => "value" in obj, get: obj => obj.value, set: (obj, value) => { obj.value = value; } }, metadata: _metadata }, _value_initializers, _value_extraInitializers);
610
558
  __esDecorate(_a, null, _fieldOverrides_decorators, { kind: "accessor", name: "fieldOverrides", static: false, private: false, access: { has: obj => "fieldOverrides" in obj, get: obj => obj.fieldOverrides, set: (obj, value) => { obj.fieldOverrides = value; } }, metadata: _metadata }, _fieldOverrides_initializers, _fieldOverrides_extraInitializers);
611
559
  __esDecorate(_a, null, _errorOverrides_decorators, { kind: "accessor", name: "errorOverrides", static: false, private: false, access: { has: obj => "errorOverrides" in obj, get: obj => obj.errorOverrides, set: (obj, value) => { obj.errorOverrides = value; } }, metadata: _metadata }, _errorOverrides_initializers, _errorOverrides_extraInitializers);
@@ -614,6 +562,7 @@ let FormModel = (() => {
614
562
  __esDecorate(_a, null, _get_knownFields_decorators, { kind: "getter", name: "knownFields", static: false, private: false, access: { has: obj => "knownFields" in obj, get: obj => obj.knownFields }, metadata: _metadata }, null, _instanceExtraInitializers);
615
563
  __esDecorate(_a, null, _get_accessors_decorators, { kind: "getter", name: "accessors", static: false, private: false, access: { has: obj => "accessors" in obj, get: obj => obj.accessors }, metadata: _metadata }, null, _instanceExtraInitializers);
616
564
  __esDecorate(_a, null, _get_dirty_decorators, { kind: "getter", name: "dirty", static: false, private: false, access: { has: obj => "dirty" in obj, get: obj => obj.dirty }, metadata: _metadata }, null, _instanceExtraInitializers);
565
+ __esDecorate(_a, null, _get_valueChanged_decorators, { kind: "getter", name: "valueChanged", static: false, private: false, access: { has: obj => "valueChanged" in obj, get: obj => obj.valueChanged }, metadata: _metadata }, null, _instanceExtraInitializers);
617
566
  if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
618
567
  })(),
619
568
  _a;
@@ -546,19 +546,20 @@ describe('all', function () {
546
546
  });
547
547
  it.each([
548
548
  [
549
- '$.0',
549
+ // new
550
+ '$.3',
550
551
  '0',
551
552
  ],
552
553
  [
553
- '$.1',
554
+ '$.0',
554
555
  'x',
555
556
  ],
556
557
  [
557
- '$.2',
558
+ '$.1',
558
559
  '3',
559
560
  ],
560
561
  [
561
- '$.3',
562
+ '$.2',
562
563
  'z',
563
564
  ],
564
565
  ])('it reports the value of field %s as %s', function (path, fieldValue) {
@@ -567,19 +568,20 @@ describe('all', function () {
567
568
  });
568
569
  it.each([
569
570
  [
570
- '$.0',
571
+ // new
572
+ '$.3',
571
573
  undefined,
572
574
  ],
573
575
  [
574
- '$.1',
576
+ '$.0',
575
577
  IS_NAN_ERROR,
576
578
  ],
577
579
  [
578
- '$.2',
580
+ '$.1',
579
581
  undefined,
580
582
  ],
581
583
  [
582
- '$.3',
584
+ '$.2',
583
585
  IS_NAN_ERROR,
584
586
  ],
585
587
  ])('it reports the error of field %s', function (path, error) {
@@ -636,11 +638,11 @@ describe('all', function () {
636
638
  });
637
639
  it('updates the field values and errors', function () {
638
640
  expect(model.fields).toEqual({
639
- '$.0': expect.objectContaining({
641
+ '$.1': expect.objectContaining({
640
642
  value: '3',
641
643
  error: undefined,
642
644
  }),
643
- '$.1': expect.objectContaining({
645
+ '$.2': expect.objectContaining({
644
646
  value: 'z',
645
647
  error: IS_NAN_ERROR,
646
648
  }),
@@ -663,7 +665,7 @@ describe('all', function () {
663
665
  value: 'x',
664
666
  error: IS_NAN_ERROR,
665
667
  }),
666
- '$.1': expect.objectContaining({
668
+ '$.2': expect.objectContaining({
667
669
  value: 'z',
668
670
  error: IS_NAN_ERROR,
669
671
  }),
@@ -679,7 +681,7 @@ describe('all', function () {
679
681
  });
680
682
  it('updates the field values and errors', function () {
681
683
  expect(model.fields).toEqual({
682
- '$.0': expect.objectContaining({
684
+ '$.2': expect.objectContaining({
683
685
  value: 'z',
684
686
  error: IS_NAN_ERROR,
685
687
  }),
@@ -6,11 +6,12 @@ import { type ValueTypeOfField } from 'types/value_type_of_field';
6
6
  import { type MantineFieldComponent, type MantineForm } from './types';
7
7
  export type SuppliedListProps<Value = any, ListPath extends string = string> = {
8
8
  values: readonly Value[];
9
+ indexKeys: number[];
9
10
  listPath: ListPath;
10
11
  };
11
12
  export declare function createList<F extends Fields, K extends keyof ListFieldsOfFields<F>, Props extends SuppliedListProps<ElementOfArray<ValueTypeOfField<F[K]>>> & {
12
13
  children: (valuePath: `${K}.${number}`, value: ElementOfArray<ValueTypeOfField<F[K]>>, index: number) => React.ReactNode;
13
14
  }>(this: MantineForm<F>, valuePath: K, List: ComponentType<Props>): MantineFieldComponent<SuppliedListProps<ElementOfArray<ValueTypeOfField<F[K]>>>, Props, never>;
14
- export declare function DefaultList<Value, ListPath extends string>({ values, listPath, children, }: SuppliedListProps<Value, ListPath> & {
15
+ export declare function DefaultList<Value, ListPath extends string>({ values, indexKeys, listPath, children, }: SuppliedListProps<Value, ListPath> & {
15
16
  children: (valuePath: `${ListPath}.${number}`, value: Value, index: number) => React.ReactNode;
16
17
  }): import("react/jsx-runtime").JSX.Element;
@@ -1,19 +1,31 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { assertExistsAndReturn, } from '@strictly/base';
2
3
  import { Fragment, } from 'react';
3
4
  import { createUnsafePartialObserverComponent } from 'util/partial';
4
5
  export function createList(valuePath, List) {
5
6
  const propSource = () => {
6
- const values = [...this.fields[valuePath].value];
7
+ const field = this.fields[valuePath];
8
+ const values = [...field.value];
7
9
  return {
8
10
  values,
9
11
  listPath: valuePath,
12
+ indexKeys: assertExistsAndReturn(field.listIndexToKey, 'list index to key mapping missing in {}', valuePath),
10
13
  };
11
14
  };
12
15
  return createUnsafePartialObserverComponent(List, propSource);
13
16
  }
14
- export function DefaultList({ values, listPath, children, }) {
17
+ export function DefaultList({ values, indexKeys, listPath, children, }) {
15
18
  return (_jsx(_Fragment, { children: values.map(function (value, index) {
16
- const valuePath = `${listPath}.${index}`;
19
+ return [
20
+ value,
21
+ index,
22
+ indexKeys[index],
23
+ ];
24
+ }).filter(function ([_value, _index, key,]) {
25
+ // omit entries without keys
26
+ return key != null;
27
+ }).map(function ([value, index, key,]) {
28
+ const valuePath = `${listPath}.${key}`;
17
29
  return (_jsx(Fragment, { children: children(valuePath, value, index) }, valuePath));
18
30
  }) }));
19
31
  }
@@ -26,6 +26,7 @@ export const Empty = {
26
26
  readonly: false,
27
27
  required: false,
28
28
  value: [],
29
+ listIndexToKey: [0],
29
30
  },
30
31
  },
31
32
  },
@@ -42,6 +43,13 @@ export const Populated = {
42
43
  'C',
43
44
  'D',
44
45
  ],
46
+ listIndexToKey: [
47
+ 100,
48
+ 1,
49
+ 33,
50
+ 5,
51
+ 101,
52
+ ],
45
53
  },
46
54
  },
47
55
  },