@xh/hoist 73.0.0-SNAPSHOT.1745973083869 → 73.0.0-SNAPSHOT.1746025071597
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 +7 -1
- package/admin/AdminUtils.ts +0 -5
- package/admin/App.scss +6 -0
- package/admin/AppModel.ts +5 -17
- package/admin/{tabs/client/clients/ClientsColumns.ts → columns/Clients.ts} +20 -53
- package/admin/columns/Core.ts +34 -35
- package/admin/columns/Tracking.ts +74 -44
- package/admin/columns/index.ts +1 -0
- package/admin/tabs/activity/tracking/ActivityTracking.scss +0 -18
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +205 -296
- package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +51 -81
- package/admin/tabs/activity/tracking/charts/ChartsModel.ts +218 -0
- package/admin/tabs/activity/tracking/charts/ChartsPanel.ts +76 -0
- package/admin/tabs/activity/tracking/detail/ActivityDetailModel.ts +60 -114
- package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +40 -63
- package/admin/tabs/client/clients/ClientsModel.ts +10 -11
- package/admin/tabs/cluster/instances/memory/MemoryMonitorModel.ts +2 -1
- package/build/types/admin/AdminUtils.d.ts +0 -2
- package/build/types/admin/AppModel.d.ts +1 -4
- package/build/types/admin/{tabs/client/clients/ClientsColumns.d.ts → columns/Clients.d.ts} +3 -7
- package/build/types/admin/columns/Core.d.ts +5 -5
- package/build/types/admin/columns/Tracking.d.ts +7 -4
- package/build/types/admin/columns/index.d.ts +1 -0
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +26 -30
- package/build/types/admin/tabs/activity/tracking/charts/ChartsModel.d.ts +34 -0
- package/build/types/admin/tabs/activity/tracking/charts/ChartsPanel.d.ts +2 -0
- package/build/types/admin/tabs/activity/tracking/detail/ActivityDetailModel.d.ts +1 -13
- package/build/types/cmp/form/FormModel.d.ts +40 -17
- package/build/types/cmp/form/field/SubformsFieldModel.d.ts +18 -20
- package/build/types/core/HoistBase.d.ts +2 -2
- package/build/types/data/cube/CubeField.d.ts +5 -4
- package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +3 -3
- package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +3 -3
- package/cmp/error/ErrorBoundaryModel.ts +1 -1
- package/cmp/form/FormModel.ts +112 -20
- package/cmp/form/field/SubformsFieldModel.ts +22 -28
- package/cmp/grid/impl/GridHScrollbar.ts +2 -1
- package/core/HoistBase.ts +12 -12
- package/data/cube/CubeField.ts +18 -17
- package/package.json +1 -1
- package/svc/TrackService.ts +2 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/admin/tabs/activity/tracking/chart/AggChartModel.ts +0 -218
- package/admin/tabs/activity/tracking/chart/AggChartPanel.ts +0 -61
- package/admin/tabs/activity/tracking/datafields/DataFieldsEditor.ts +0 -147
- package/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.ts +0 -133
- package/build/types/admin/tabs/activity/tracking/chart/AggChartModel.d.ts +0 -33
- package/build/types/admin/tabs/activity/tracking/chart/AggChartPanel.d.ts +0 -2
- package/build/types/admin/tabs/activity/tracking/datafields/DataFieldsEditor.d.ts +0 -2
- package/build/types/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.d.ts +0 -46
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { ChartModel } from '@xh/hoist/cmp/chart';
|
|
3
|
+
import { HoistModel } from '@xh/hoist/core';
|
|
4
|
+
import { PanelModel } from '@xh/hoist/desktop/cmp/panel';
|
|
5
|
+
import { ActivityTrackingModel } from '../ActivityTrackingModel';
|
|
6
|
+
export declare class ChartsModel extends HoistModel {
|
|
7
|
+
panelModel: PanelModel;
|
|
8
|
+
activityTrackingModel: ActivityTrackingModel;
|
|
9
|
+
/** metric to chart on Y axis - one of:
|
|
10
|
+
* + entryCount - count of total track log entries within the primary dim group.
|
|
11
|
+
* + count - count of unique secondary dim values within the primary dim group.
|
|
12
|
+
* + elapsed - avg elapsed time in ms for the primary dim group.
|
|
13
|
+
*/
|
|
14
|
+
metric: 'entryCount' | 'count' | 'elapsed';
|
|
15
|
+
/** show weekends on the activity chart */
|
|
16
|
+
incWeekends: boolean;
|
|
17
|
+
categoryChartModel: ChartModel;
|
|
18
|
+
timeseriesChartModel: ChartModel;
|
|
19
|
+
get showAsTimeseries(): boolean;
|
|
20
|
+
get chartModel(): ChartModel;
|
|
21
|
+
get primaryDim(): string;
|
|
22
|
+
get secondaryDim(): string;
|
|
23
|
+
get data(): import("../../../../../data").StoreRecord[];
|
|
24
|
+
get dimensions(): string[];
|
|
25
|
+
constructor();
|
|
26
|
+
getLabelForMetric(metric: any, multiline: any): string | import("react").ReactElement<{
|
|
27
|
+
children?: import("react").ReactNode;
|
|
28
|
+
}, any>;
|
|
29
|
+
private selectRow;
|
|
30
|
+
onLinked(): void;
|
|
31
|
+
private loadChart;
|
|
32
|
+
private getSeriesData;
|
|
33
|
+
private getUnitsForDim;
|
|
34
|
+
}
|
|
@@ -6,23 +6,11 @@ export declare class ActivityDetailModel extends HoistModel {
|
|
|
6
6
|
activityTrackingModel: ActivityTrackingModel;
|
|
7
7
|
gridModel: GridModel;
|
|
8
8
|
formModel: FormModel;
|
|
9
|
-
|
|
10
|
-
* Optional dot-delimited path(s) to filter the displayed `data` payload down to a particular
|
|
11
|
-
* node or nodes, for easier browsing of records with a large data payload. Multiple paths
|
|
12
|
-
* can be separated with `|`.
|
|
13
|
-
*/
|
|
14
|
-
formattedDataFilterPath: string;
|
|
15
|
-
/** Stringified, pretty-printed, optionally path-filtered `data` payload. */
|
|
16
|
-
formattedData: string;
|
|
9
|
+
formattedData: any;
|
|
17
10
|
get hasSelection(): boolean;
|
|
18
11
|
constructor();
|
|
19
12
|
onLinked(): void;
|
|
20
13
|
private showActivityEntriesAsync;
|
|
21
14
|
private getAllLeafRows;
|
|
22
|
-
/** Extract data from a (detail) grid record and flush it into our form for display. */
|
|
23
15
|
private showEntryDetail;
|
|
24
|
-
private updateFormattedData;
|
|
25
|
-
private createAndSetCoreModels;
|
|
26
|
-
private createGridModel;
|
|
27
|
-
private createSingleEntryFormModel;
|
|
28
16
|
}
|
|
@@ -1,39 +1,49 @@
|
|
|
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';
|
|
5
5
|
export interface FormConfig {
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* FieldModels, or configurations to create them, for all data fields managed by this FormModel.
|
|
8
|
+
*/
|
|
7
9
|
fields?: Array<BaseFieldModel | BaseFieldConfig | SubformsFieldConfig | SubformsFieldModel>;
|
|
8
10
|
/** Map of initial values for fields in this model. */
|
|
9
11
|
initialValues?: PlainObject;
|
|
12
|
+
/** Options governing persistence of the form state. */
|
|
13
|
+
persistWith?: FormPersistOptions;
|
|
10
14
|
disabled?: boolean;
|
|
11
15
|
readonly?: boolean;
|
|
12
16
|
/** @internal */
|
|
13
17
|
xhImpl?: boolean;
|
|
14
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
|
+
}
|
|
15
25
|
/**
|
|
16
|
-
* FormModel is the main entry point for Form specification. This Model's `fields`
|
|
17
|
-
* multiple
|
|
18
|
-
*
|
|
26
|
+
* FormModel is the main entry point for Form specification. This Model's `fields` collection holds
|
|
27
|
+
* multiple FieldModel instances, which in turn hold the state of user edited data and the
|
|
28
|
+
* validation rules around editing that data.
|
|
19
29
|
*
|
|
20
30
|
* A complete representation of all fields and data within a Form can be produced via this model's
|
|
21
|
-
*
|
|
31
|
+
* `getData()` method, making it easy to harvest all values for e.g. submission to a server.
|
|
22
32
|
*
|
|
23
|
-
* Individual field values are also available as observables via this model's
|
|
24
|
-
*
|
|
33
|
+
* Individual field values are also available as observables via this model's `values` proxy. An
|
|
34
|
+
* application model can setup a reaction to track changes to any value and execute app-specific
|
|
25
35
|
* logic such as disabling one field based on the state of another, or setting up cascading options.
|
|
26
36
|
*
|
|
27
37
|
* This Model provides an overall validation state, determined by the current validation state of
|
|
28
38
|
* its fields as per their configured rules and constraints.
|
|
29
39
|
*
|
|
30
|
-
* FormModels can be nested via
|
|
31
|
-
*
|
|
40
|
+
* FormModels can be nested via SubformsFieldModels, a specialized type of FieldModel that itself
|
|
41
|
+
* manages a collection of child FormModels. This allows use cases where Forms support editing of
|
|
32
42
|
* dynamic collections of complex objects with their own internal validation rules (e.g. a FormModel
|
|
33
43
|
* representing a market order might have multiple nested FormModels to represent execution splits,
|
|
34
44
|
* where each split has its own internal fields for broker, quantity, and time).
|
|
35
45
|
*
|
|
36
|
-
*
|
|
46
|
+
* @see FieldModel for details on state and validation maintained at the individual field level.
|
|
37
47
|
*/
|
|
38
48
|
export declare class FormModel extends HoistModel {
|
|
39
49
|
/** Container object for FieldModel instances, keyed by field name.*/
|
|
@@ -55,7 +65,7 @@ export declare class FormModel extends HoistModel {
|
|
|
55
65
|
* See {@link getData} instead if you need to get or react to the values of *any/all* fields.
|
|
56
66
|
*/
|
|
57
67
|
get values(): PlainObject;
|
|
58
|
-
constructor({ fields, initialValues, disabled, readonly, xhImpl }?: FormConfig);
|
|
68
|
+
constructor({ fields, initialValues, disabled, persistWith, readonly, xhImpl }?: FormConfig);
|
|
59
69
|
getField(fieldName: string): BaseFieldModel;
|
|
60
70
|
/**
|
|
61
71
|
* Snapshot of current field values, keyed by field name.
|
|
@@ -85,6 +95,7 @@ export declare class FormModel extends HoistModel {
|
|
|
85
95
|
init(initialValues?: PlainObject): void;
|
|
86
96
|
/**
|
|
87
97
|
* Set the value of one or more fields on this form.
|
|
98
|
+
*
|
|
88
99
|
* @param values - map of field name to value.
|
|
89
100
|
*/
|
|
90
101
|
setValues(values: PlainObject): void;
|
|
@@ -92,12 +103,16 @@ export declare class FormModel extends HoistModel {
|
|
|
92
103
|
get isDirty(): boolean;
|
|
93
104
|
/**
|
|
94
105
|
* The Field that is currently focused on this form.
|
|
95
|
-
*
|
|
106
|
+
*
|
|
107
|
+
* @see FieldModel.focus() for important information on this method
|
|
108
|
+
* and its limitations.
|
|
96
109
|
*/
|
|
97
110
|
get focusedField(): BaseFieldModel;
|
|
98
111
|
/**
|
|
99
112
|
* Focus a field on this form.
|
|
100
|
-
*
|
|
113
|
+
*
|
|
114
|
+
* @see FieldModel.focus() for important information on this method
|
|
115
|
+
* and its limitations.
|
|
101
116
|
*/
|
|
102
117
|
focusField(name: string): void;
|
|
103
118
|
get validationState(): ValidationState;
|
|
@@ -107,12 +122,20 @@ export declare class FormModel extends HoistModel {
|
|
|
107
122
|
get isValid(): boolean;
|
|
108
123
|
/** List of all validation errors for this form. */
|
|
109
124
|
get allErrors(): string[];
|
|
110
|
-
/**
|
|
125
|
+
/**
|
|
126
|
+
* Recompute all validations and return true if the form is valid.
|
|
127
|
+
*
|
|
128
|
+
* @param opts - set 'display' to true to trigger the display of
|
|
129
|
+
* validation errors (if any) by bound FormField components after validation
|
|
130
|
+
* is complete.
|
|
131
|
+
*/
|
|
111
132
|
validateAsync(opts?: {
|
|
112
|
-
/** True to trigger display of validation errors (if any) by bound `FormField`s after validation is complete. */
|
|
113
133
|
display?: boolean;
|
|
114
134
|
}): Promise<boolean>;
|
|
115
|
-
/** Trigger the display of validation errors (if any) by bound
|
|
135
|
+
/** Trigger the display of validation errors (if any) by bound FormField components. */
|
|
116
136
|
displayValidation(): void;
|
|
117
137
|
private createValuesProxy;
|
|
138
|
+
private initPersist;
|
|
139
|
+
private serialize;
|
|
140
|
+
private deserialize;
|
|
118
141
|
}
|
|
@@ -4,37 +4,32 @@ import { FormModel } from '../FormModel';
|
|
|
4
4
|
import { BaseFieldModel, BaseFieldConfig } from './BaseFieldModel';
|
|
5
5
|
import { FormConfig } from '../FormModel';
|
|
6
6
|
export interface SubformsFieldConfig extends BaseFieldConfig {
|
|
7
|
-
/**
|
|
8
|
-
* Config for a {@link FormModel} to be auto-created to manage and validate the data for each
|
|
9
|
-
*
|
|
10
|
-
*/
|
|
7
|
+
/** Config for FormModel representing a subform. */
|
|
11
8
|
subforms: FormConfig;
|
|
12
9
|
/**
|
|
13
|
-
* Initial value of this field.
|
|
14
|
-
* initialized to provide value.
|
|
10
|
+
* Initial value of this field. If a function, will be
|
|
11
|
+
* executed dynamically when form is initialized to provide value.
|
|
15
12
|
*/
|
|
16
13
|
initialValue?: any[];
|
|
17
14
|
}
|
|
18
15
|
/**
|
|
19
|
-
* A data field in a form whose value is a collection of
|
|
20
|
-
* with arbitrary internal complexity. A dedicated {@link FormModel} is auto-created to manage and
|
|
21
|
-
* validate each object independently.
|
|
16
|
+
* A data field in a form whose value is a collection of FormModels (subforms).
|
|
22
17
|
*
|
|
23
|
-
* Applications should initialize this field with an array of objects.
|
|
24
|
-
* into an array of managed FormModels which will form the value of this field.
|
|
18
|
+
* Applications should initialize this field with an array of objects. These values will be
|
|
19
|
+
* loaded into an array of managed FormModels which will form the value of this field.
|
|
25
20
|
*
|
|
26
21
|
* Applications should *not* modify the value property directly, unless they wish to reinitialize
|
|
27
|
-
* all existing form contents to new values.
|
|
28
|
-
*
|
|
22
|
+
* all existing form contents to new values. Use the methods add() or remove() to
|
|
23
|
+
* adjust the contents of the collection while preserving existing form state.
|
|
29
24
|
*
|
|
30
|
-
* Validation rules for the entire collection may be specified as for any field, but
|
|
31
|
-
* the subforms will also bubble up to this field, affecting its overall
|
|
25
|
+
* Validation rules for the entire collection may be specified as for any field, but
|
|
26
|
+
* validations on the subforms will also bubble up to this field, affecting its overall
|
|
27
|
+
* validation state.
|
|
32
28
|
*/
|
|
33
29
|
export declare class SubformsFieldModel extends BaseFieldModel {
|
|
34
|
-
/** (Sub)FormModels created by this model, tracked to support cleanup. */
|
|
35
30
|
private createdModels;
|
|
36
31
|
private formConfig;
|
|
37
|
-
private
|
|
32
|
+
private origInitialValues;
|
|
38
33
|
constructor({ subforms, initialValue, ...rest }: SubformsFieldConfig);
|
|
39
34
|
get hasFocus(): boolean;
|
|
40
35
|
focus(): void;
|
|
@@ -54,11 +49,14 @@ export declare class SubformsFieldModel extends BaseFieldModel {
|
|
|
54
49
|
display?: boolean;
|
|
55
50
|
}): Promise<boolean>;
|
|
56
51
|
protected deriveValidationState(): ValidationState;
|
|
57
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Add a new record (subform) to this field.
|
|
54
|
+
*
|
|
55
|
+
* @param initialValues - object containing initial values for new record.
|
|
56
|
+
* @param index - index in collection where subform should be inserted.
|
|
57
|
+
*/
|
|
58
58
|
add(opts?: {
|
|
59
|
-
/** Initial values for the new object/subform. */
|
|
60
59
|
initialValues?: PlainObject;
|
|
61
|
-
/** Index within the collection where the new subform should be inserted. */
|
|
62
60
|
index?: number;
|
|
63
61
|
}): void;
|
|
64
62
|
remove(formModel: FormModel): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { PersistOptions, DebounceSpec, Some } from './';
|
|
1
2
|
import { comparer } from '@xh/hoist/mobx';
|
|
2
3
|
import { IAutorunOptions, IReactionOptions } from 'mobx/dist/api/autorun';
|
|
3
|
-
import {
|
|
4
|
-
import { DebounceSpec, PersistOptions, Some } from './';
|
|
4
|
+
import { IReactionDisposer, IEqualsComparer } from 'mobx/dist/internal';
|
|
5
5
|
export interface HoistBaseClass {
|
|
6
6
|
new (...args: any[]): HoistBase;
|
|
7
7
|
isHoistBase: boolean;
|
|
@@ -3,8 +3,11 @@ import { Aggregator, AverageAggregator, AverageStrictAggregator, ChildCountAggre
|
|
|
3
3
|
export interface CubeFieldSpec extends FieldSpec {
|
|
4
4
|
/** True to allow this field to be used for grouping.*/
|
|
5
5
|
isDimension?: boolean;
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Instance of a Hoist Cube Aggregator (from the aggregate package), or string alias for the
|
|
8
|
+
* same (e.g. 'MAX').
|
|
9
|
+
*/
|
|
10
|
+
aggregator?: Aggregator | 'AVG' | 'AVG_STRICT' | 'CHILD_COUNT' | 'LEAF_COUNT' | 'MAX' | 'MIN' | 'NULL' | 'SINGLE' | 'SUM' | 'SUM_STRICT' | 'UNIQUE';
|
|
8
11
|
/** Function to determine if aggregation should be performed at a given level of a query result. */
|
|
9
12
|
canAggregateFn?: CanAggregateFn;
|
|
10
13
|
/** True if any further groupings below this dimension would be derivative (have only one member). */
|
|
@@ -16,8 +19,6 @@ export interface CubeFieldSpec extends FieldSpec {
|
|
|
16
19
|
*/
|
|
17
20
|
parentDimension?: string;
|
|
18
21
|
}
|
|
19
|
-
/** Convenient (and serializable) alias for one of Hoist's Cube {@link Aggregator} classes. */
|
|
20
|
-
export type AggregatorToken = 'AVG' | 'AVG_STRICT' | 'CHILD_COUNT' | 'LEAF_COUNT' | 'MAX' | 'MIN' | 'NULL' | 'SINGLE' | 'SUM' | 'SUM_STRICT' | 'UNIQUE';
|
|
21
22
|
/**
|
|
22
23
|
* @param dimension - dimension of aggregation
|
|
23
24
|
* @param value - value of record on dimension
|
|
@@ -86,7 +86,7 @@ export declare const autoRefreshAppOption: ({ formFieldProps, inputProps }?: Aut
|
|
|
86
86
|
suppressContentEditableWarning?: boolean;
|
|
87
87
|
suppressHydrationWarning?: boolean;
|
|
88
88
|
accessKey?: string;
|
|
89
|
-
autoCapitalize?: "
|
|
89
|
+
autoCapitalize?: "off" | "none" | "on" | "sentences" | "words" | "characters" | (string & {});
|
|
90
90
|
autoFocus?: boolean;
|
|
91
91
|
contentEditable?: "inherit" | (boolean | "true" | "false") | "plaintext-only";
|
|
92
92
|
dir?: string;
|
|
@@ -121,7 +121,7 @@ export declare const autoRefreshAppOption: ({ formFieldProps, inputProps }?: Aut
|
|
|
121
121
|
results?: number;
|
|
122
122
|
security?: string;
|
|
123
123
|
unselectable?: "off" | "on";
|
|
124
|
-
inputMode?: "
|
|
124
|
+
inputMode?: "search" | "text" | "none" | "tel" | "url" | "email" | "numeric" | "decimal";
|
|
125
125
|
is?: string;
|
|
126
126
|
"aria-activedescendant"?: string;
|
|
127
127
|
"aria-atomic"?: boolean | "true" | "false";
|
|
@@ -140,7 +140,7 @@ export declare const autoRefreshAppOption: ({ formFieldProps, inputProps }?: Aut
|
|
|
140
140
|
"aria-description"?: string;
|
|
141
141
|
"aria-details"?: string;
|
|
142
142
|
"aria-disabled"?: boolean | "true" | "false";
|
|
143
|
-
"aria-dropeffect"?: "
|
|
143
|
+
"aria-dropeffect"?: "link" | "none" | "copy" | "execute" | "move" | "popup";
|
|
144
144
|
"aria-errormessage"?: string;
|
|
145
145
|
"aria-expanded"?: boolean | "true" | "false";
|
|
146
146
|
"aria-flowto"?: string;
|
|
@@ -84,7 +84,7 @@ export declare const themeAppOption: ({ formFieldProps, inputProps }?: ThemeAppO
|
|
|
84
84
|
suppressContentEditableWarning?: boolean;
|
|
85
85
|
suppressHydrationWarning?: boolean;
|
|
86
86
|
accessKey?: string;
|
|
87
|
-
autoCapitalize?: "
|
|
87
|
+
autoCapitalize?: "off" | "none" | "on" | "sentences" | "words" | "characters" | (string & {});
|
|
88
88
|
autoFocus?: boolean;
|
|
89
89
|
contentEditable?: "inherit" | (boolean | "true" | "false") | "plaintext-only";
|
|
90
90
|
dir?: string;
|
|
@@ -119,7 +119,7 @@ export declare const themeAppOption: ({ formFieldProps, inputProps }?: ThemeAppO
|
|
|
119
119
|
results?: number;
|
|
120
120
|
security?: string;
|
|
121
121
|
unselectable?: "off" | "on";
|
|
122
|
-
inputMode?: "
|
|
122
|
+
inputMode?: "search" | "text" | "none" | "tel" | "url" | "email" | "numeric" | "decimal";
|
|
123
123
|
is?: string;
|
|
124
124
|
"aria-activedescendant"?: string;
|
|
125
125
|
"aria-atomic"?: boolean | "true" | "false";
|
|
@@ -138,7 +138,7 @@ export declare const themeAppOption: ({ formFieldProps, inputProps }?: ThemeAppO
|
|
|
138
138
|
"aria-description"?: string;
|
|
139
139
|
"aria-details"?: string;
|
|
140
140
|
"aria-disabled"?: boolean | "true" | "false";
|
|
141
|
-
"aria-dropeffect"?: "
|
|
141
|
+
"aria-dropeffect"?: "link" | "none" | "copy" | "execute" | "move" | "popup";
|
|
142
142
|
"aria-errormessage"?: string;
|
|
143
143
|
"aria-expanded"?: boolean | "true" | "false";
|
|
144
144
|
"aria-flowto"?: string;
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {ExceptionHandlerOptions, HoistModel, XH} from '@xh/hoist/core';
|
|
8
|
-
import {action, makeObservable, observable} from '@xh/hoist/mobx';
|
|
9
8
|
import {isFunction} from 'lodash';
|
|
9
|
+
import {action, makeObservable, observable} from 'mobx';
|
|
10
10
|
import {ReactNode} from 'react';
|
|
11
11
|
|
|
12
12
|
export interface ErrorBoundaryConfig {
|
package/cmp/form/FormModel.ts
CHANGED
|
@@ -4,22 +4,50 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
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 {
|
|
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
|
+
/**
|
|
41
|
+
* FieldModels, or configurations to create them, for all data fields managed by this FormModel.
|
|
42
|
+
*/
|
|
18
43
|
fields?: Array<BaseFieldModel | BaseFieldConfig | SubformsFieldConfig | SubformsFieldModel>;
|
|
19
44
|
|
|
20
45
|
/** Map of initial values for fields in this model. */
|
|
21
46
|
initialValues?: PlainObject;
|
|
22
47
|
|
|
48
|
+
/** Options governing persistence of the form state. */
|
|
49
|
+
persistWith?: FormPersistOptions;
|
|
50
|
+
|
|
23
51
|
disabled?: boolean;
|
|
24
52
|
readonly?: boolean;
|
|
25
53
|
|
|
@@ -27,28 +55,35 @@ export interface FormConfig {
|
|
|
27
55
|
xhImpl?: boolean;
|
|
28
56
|
}
|
|
29
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
|
+
|
|
30
65
|
/**
|
|
31
|
-
* FormModel is the main entry point for Form specification. This Model's `fields`
|
|
32
|
-
* multiple
|
|
33
|
-
*
|
|
66
|
+
* FormModel is the main entry point for Form specification. This Model's `fields` collection holds
|
|
67
|
+
* multiple FieldModel instances, which in turn hold the state of user edited data and the
|
|
68
|
+
* validation rules around editing that data.
|
|
34
69
|
*
|
|
35
70
|
* A complete representation of all fields and data within a Form can be produced via this model's
|
|
36
|
-
*
|
|
71
|
+
* `getData()` method, making it easy to harvest all values for e.g. submission to a server.
|
|
37
72
|
*
|
|
38
|
-
* Individual field values are also available as observables via this model's
|
|
39
|
-
*
|
|
73
|
+
* Individual field values are also available as observables via this model's `values` proxy. An
|
|
74
|
+
* application model can setup a reaction to track changes to any value and execute app-specific
|
|
40
75
|
* logic such as disabling one field based on the state of another, or setting up cascading options.
|
|
41
76
|
*
|
|
42
77
|
* This Model provides an overall validation state, determined by the current validation state of
|
|
43
78
|
* its fields as per their configured rules and constraints.
|
|
44
79
|
*
|
|
45
|
-
* FormModels can be nested via
|
|
46
|
-
*
|
|
80
|
+
* FormModels can be nested via SubformsFieldModels, a specialized type of FieldModel that itself
|
|
81
|
+
* manages a collection of child FormModels. This allows use cases where Forms support editing of
|
|
47
82
|
* dynamic collections of complex objects with their own internal validation rules (e.g. a FormModel
|
|
48
83
|
* representing a market order might have multiple nested FormModels to represent execution splits,
|
|
49
84
|
* where each split has its own internal fields for broker, quantity, and time).
|
|
50
85
|
*
|
|
51
|
-
*
|
|
86
|
+
* @see FieldModel for details on state and validation maintained at the individual field level.
|
|
52
87
|
*/
|
|
53
88
|
export class FormModel extends HoistModel {
|
|
54
89
|
/** Container object for FieldModel instances, keyed by field name.*/
|
|
@@ -85,6 +120,7 @@ export class FormModel extends HoistModel {
|
|
|
85
120
|
fields = [],
|
|
86
121
|
initialValues = {},
|
|
87
122
|
disabled = false,
|
|
123
|
+
persistWith = null,
|
|
88
124
|
readonly = false,
|
|
89
125
|
xhImpl = false
|
|
90
126
|
}: FormConfig = {}) {
|
|
@@ -95,6 +131,7 @@ export class FormModel extends HoistModel {
|
|
|
95
131
|
this.disabled = disabled;
|
|
96
132
|
this.readonly = readonly;
|
|
97
133
|
const models = {};
|
|
134
|
+
|
|
98
135
|
fields.forEach((f: any) => {
|
|
99
136
|
const model =
|
|
100
137
|
f instanceof BaseFieldModel
|
|
@@ -109,6 +146,7 @@ export class FormModel extends HoistModel {
|
|
|
109
146
|
this.fields = models;
|
|
110
147
|
|
|
111
148
|
this.init(initialValues);
|
|
149
|
+
if (persistWith) this.initPersist(persistWith);
|
|
112
150
|
|
|
113
151
|
// Set the owning formModel *last* after all fields in place with data.
|
|
114
152
|
// This (currently) kicks off the validation and other reactivity.
|
|
@@ -162,6 +200,7 @@ export class FormModel extends HoistModel {
|
|
|
162
200
|
|
|
163
201
|
/**
|
|
164
202
|
* Set the value of one or more fields on this form.
|
|
203
|
+
*
|
|
165
204
|
* @param values - map of field name to value.
|
|
166
205
|
*/
|
|
167
206
|
@action
|
|
@@ -181,7 +220,9 @@ export class FormModel extends HoistModel {
|
|
|
181
220
|
//-----------------------------------
|
|
182
221
|
/**
|
|
183
222
|
* The Field that is currently focused on this form.
|
|
184
|
-
*
|
|
223
|
+
*
|
|
224
|
+
* @see FieldModel.focus() for important information on this method
|
|
225
|
+
* and its limitations.
|
|
185
226
|
*/
|
|
186
227
|
@computed
|
|
187
228
|
get focusedField(): BaseFieldModel {
|
|
@@ -190,7 +231,9 @@ export class FormModel extends HoistModel {
|
|
|
190
231
|
|
|
191
232
|
/**
|
|
192
233
|
* Focus a field on this form.
|
|
193
|
-
*
|
|
234
|
+
*
|
|
235
|
+
* @see FieldModel.focus() for important information on this method
|
|
236
|
+
* and its limitations.
|
|
194
237
|
*/
|
|
195
238
|
focusField(name: string) {
|
|
196
239
|
this.getField(name)?.focus();
|
|
@@ -223,18 +266,21 @@ export class FormModel extends HoistModel {
|
|
|
223
266
|
return flatMap(this.fields, s => s.allErrors);
|
|
224
267
|
}
|
|
225
268
|
|
|
226
|
-
/**
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
269
|
+
/**
|
|
270
|
+
* Recompute all validations and return true if the form is valid.
|
|
271
|
+
*
|
|
272
|
+
* @param opts - set 'display' to true to trigger the display of
|
|
273
|
+
* validation errors (if any) by bound FormField components after validation
|
|
274
|
+
* is complete.
|
|
275
|
+
*/
|
|
276
|
+
async validateAsync(opts?: {display?: boolean}): Promise<boolean> {
|
|
231
277
|
const {display = true} = opts ?? {},
|
|
232
278
|
promises = map(this.fields, m => m.validateAsync({display}));
|
|
233
279
|
await Promise.all(promises);
|
|
234
280
|
return this.isValid;
|
|
235
281
|
}
|
|
236
282
|
|
|
237
|
-
/** Trigger the display of validation errors (if any) by bound
|
|
283
|
+
/** Trigger the display of validation errors (if any) by bound FormField components. */
|
|
238
284
|
displayValidation() {
|
|
239
285
|
forOwn(this.fields, m => m.displayValidation());
|
|
240
286
|
}
|
|
@@ -264,4 +310,50 @@ export class FormModel extends HoistModel {
|
|
|
264
310
|
}
|
|
265
311
|
);
|
|
266
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
|
+
};
|
|
267
359
|
}
|