@xh/hoist 73.0.0-SNAPSHOT.1745690606180 → 73.0.0-SNAPSHOT.1745976013413

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/CHANGELOG.md CHANGED
@@ -10,6 +10,8 @@ Requires `hoist-core >= 30.0` with new APIs to support the consolidated Admin Co
10
10
 
11
11
  * Added a new "Clients" Admin Console tab- a consolidated view of all websocket-connected clients
12
12
  across all instances in the cluster.
13
+ * Updated `FormModel` to support `persistWith` for storing and recalling its values, including
14
+ developer options to persist all or a provided subset of fields.
13
15
 
14
16
  ### 🐞 Bug Fixes
15
17
 
@@ -1,4 +1,4 @@
1
- import { HoistModel, PlainObject } from '@xh/hoist/core';
1
+ import { HoistModel, PersistOptions, PlainObject } from '@xh/hoist/core';
2
2
  import { ValidationState } from '@xh/hoist/data';
3
3
  import { BaseFieldConfig, BaseFieldModel } from './field/BaseFieldModel';
4
4
  import { SubformsFieldConfig, SubformsFieldModel } from './field/SubformsFieldModel';
@@ -9,11 +9,19 @@ export interface FormConfig {
9
9
  fields?: Array<BaseFieldModel | BaseFieldConfig | SubformsFieldConfig | SubformsFieldModel>;
10
10
  /** Map of initial values for fields in this model. */
11
11
  initialValues?: PlainObject;
12
+ /** Options governing persistence of the form state. */
13
+ persistWith?: FormPersistOptions;
12
14
  disabled?: boolean;
13
15
  readonly?: boolean;
14
16
  /** @internal */
15
17
  xhImpl?: boolean;
16
18
  }
19
+ export interface FormPersistOptions extends PersistOptions {
20
+ /** If persisting only a subset of all fields, provide an array of field names. */
21
+ includeFields?: string[];
22
+ /** If excluding a subset of all fields, provide an array of field names. */
23
+ excludeFields?: string[];
24
+ }
17
25
  /**
18
26
  * FormModel is the main entry point for Form specification. This Model's `fields` collection holds
19
27
  * multiple FieldModel instances, which in turn hold the state of user edited data and the
@@ -57,7 +65,7 @@ export declare class FormModel extends HoistModel {
57
65
  * See {@link getData} instead if you need to get or react to the values of *any/all* fields.
58
66
  */
59
67
  get values(): PlainObject;
60
- constructor({ fields, initialValues, disabled, readonly, xhImpl }?: FormConfig);
68
+ constructor({ fields, initialValues, disabled, persistWith, readonly, xhImpl }?: FormConfig);
61
69
  getField(fieldName: string): BaseFieldModel;
62
70
  /**
63
71
  * Snapshot of current field values, keyed by field name.
@@ -127,4 +135,7 @@ export declare class FormModel extends HoistModel {
127
135
  /** Trigger the display of validation errors (if any) by bound FormField components. */
128
136
  displayValidation(): void;
129
137
  private createValuesProxy;
138
+ private initPersist;
139
+ private serialize;
140
+ private deserialize;
130
141
  }
@@ -4,14 +4,37 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
- import {HoistModel, managed, PlainObject} from '@xh/hoist/core';
7
+ import {
8
+ HoistModel,
9
+ managed,
10
+ PersistableState,
11
+ PersistenceProvider,
12
+ PersistOptions,
13
+ PlainObject
14
+ } from '@xh/hoist/core';
8
15
  import {ValidationState} from '@xh/hoist/data';
9
16
  import {action, bindable, computed, makeObservable, observable} from '@xh/hoist/mobx';
10
17
  import {throwIf} from '@xh/hoist/utils/js';
11
- import {flatMap, forEach, forOwn, map, mapValues, pickBy, some, values} from 'lodash';
18
+ import {
19
+ flatMap,
20
+ forEach,
21
+ forOwn,
22
+ isArray,
23
+ isDate,
24
+ isObject,
25
+ isString,
26
+ map,
27
+ mapValues,
28
+ pick,
29
+ pickBy,
30
+ some,
31
+ values,
32
+ without
33
+ } from 'lodash';
12
34
  import {BaseFieldConfig, BaseFieldModel} from './field/BaseFieldModel';
13
35
  import {FieldModel} from './field/FieldModel';
14
36
  import {SubformsFieldConfig, SubformsFieldModel} from './field/SubformsFieldModel';
37
+ import {isLocalDate, LocalDate} from '@xh/hoist/utils/datetime';
15
38
 
16
39
  export interface FormConfig {
17
40
  /**
@@ -22,6 +45,9 @@ export interface FormConfig {
22
45
  /** Map of initial values for fields in this model. */
23
46
  initialValues?: PlainObject;
24
47
 
48
+ /** Options governing persistence of the form state. */
49
+ persistWith?: FormPersistOptions;
50
+
25
51
  disabled?: boolean;
26
52
  readonly?: boolean;
27
53
 
@@ -29,6 +55,13 @@ export interface FormConfig {
29
55
  xhImpl?: boolean;
30
56
  }
31
57
 
58
+ export interface FormPersistOptions extends PersistOptions {
59
+ /** If persisting only a subset of all fields, provide an array of field names. */
60
+ includeFields?: string[];
61
+ /** If excluding a subset of all fields, provide an array of field names. */
62
+ excludeFields?: string[];
63
+ }
64
+
32
65
  /**
33
66
  * FormModel is the main entry point for Form specification. This Model's `fields` collection holds
34
67
  * multiple FieldModel instances, which in turn hold the state of user edited data and the
@@ -87,6 +120,7 @@ export class FormModel extends HoistModel {
87
120
  fields = [],
88
121
  initialValues = {},
89
122
  disabled = false,
123
+ persistWith = null,
90
124
  readonly = false,
91
125
  xhImpl = false
92
126
  }: FormConfig = {}) {
@@ -97,6 +131,7 @@ export class FormModel extends HoistModel {
97
131
  this.disabled = disabled;
98
132
  this.readonly = readonly;
99
133
  const models = {};
134
+
100
135
  fields.forEach((f: any) => {
101
136
  const model =
102
137
  f instanceof BaseFieldModel
@@ -111,6 +146,7 @@ export class FormModel extends HoistModel {
111
146
  this.fields = models;
112
147
 
113
148
  this.init(initialValues);
149
+ if (persistWith) this.initPersist(persistWith);
114
150
 
115
151
  // Set the owning formModel *last* after all fields in place with data.
116
152
  // This (currently) kicks off the validation and other reactivity.
@@ -274,4 +310,50 @@ export class FormModel extends HoistModel {
274
310
  }
275
311
  );
276
312
  }
313
+
314
+ private initPersist({
315
+ includeFields = null,
316
+ excludeFields = null,
317
+ path = 'formValues',
318
+ ...rootPersistWith
319
+ }: FormPersistOptions) {
320
+ const allFields = Object.keys(this.fields);
321
+ const fieldNamesToPersist = excludeFields
322
+ ? without(allFields, ...excludeFields)
323
+ : (includeFields ?? allFields);
324
+
325
+ PersistenceProvider.create({
326
+ persistOptions: {
327
+ path,
328
+ ...rootPersistWith
329
+ },
330
+ target: {
331
+ getPersistableState: () =>
332
+ new PersistableState(this.serialize(pick(this.getData(), fieldNamesToPersist))),
333
+ setPersistableState: ({value: formValues}) =>
334
+ this.setValues(this.deserialize(pick(formValues, fieldNamesToPersist)))
335
+ },
336
+ owner: this
337
+ });
338
+ }
339
+
340
+ private serialize = (formValue: unknown) => {
341
+ if (isArray(formValue)) return formValue.map(this.serialize);
342
+ if (isDate(formValue)) return {_xhType: 'date', value: formValue.toJSON()};
343
+ if (isLocalDate(formValue)) return {_xhType: 'localDate', value: formValue.toJSON()};
344
+ if (isObject(formValue)) return mapValues(formValue, this.serialize);
345
+ return formValue;
346
+ };
347
+
348
+ private deserialize = (formValue: unknown) => {
349
+ if (isArray(formValue)) return formValue.map(this.deserialize);
350
+ if (isObject(formValue)) {
351
+ if ('_xhType' in formValue && 'value' in formValue && isString(formValue.value)) {
352
+ if (formValue._xhType === 'date') return new Date(formValue.value);
353
+ if (formValue._xhType === 'localDate') return LocalDate.get(formValue.value);
354
+ }
355
+ return mapValues(formValue, this.deserialize);
356
+ }
357
+ return formValue;
358
+ };
277
359
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "73.0.0-SNAPSHOT.1745690606180",
3
+ "version": "73.0.0-SNAPSHOT.1745976013413",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",