@xh/hoist 80.0.0-SNAPSHOT.1768323341476 β†’ 80.0.0-SNAPSHOT.1768360784265

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +27 -3
  2. package/admin/tabs/activity/tracking/ActivityTracking.scss +7 -7
  3. package/admin/tabs/userData/roles/details/RoleDetails.scss +6 -6
  4. package/build/types/cmp/form/FormModel.d.ts +1 -1
  5. package/build/types/cmp/form/field/BaseFieldModel.d.ts +7 -3
  6. package/build/types/cmp/form/field/SubformsFieldModel.d.ts +3 -3
  7. package/build/types/cmp/grid/Grid.d.ts +2 -2
  8. package/build/types/data/Field.d.ts +2 -1
  9. package/build/types/data/Store.d.ts +7 -4
  10. package/build/types/data/StoreRecord.d.ts +5 -0
  11. package/build/types/data/impl/RecordValidator.d.ts +9 -10
  12. package/build/types/data/impl/StoreValidator.d.ts +8 -7
  13. package/build/types/data/index.d.ts +1 -0
  14. package/build/types/data/validation/Rule.d.ts +5 -40
  15. package/build/types/data/validation/Types.d.ts +56 -0
  16. package/build/types/data/validation/constraints.d.ts +1 -1
  17. package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +3 -3
  18. package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +3 -3
  19. package/cmp/form/FormModel.ts +2 -2
  20. package/cmp/form/field/BaseFieldModel.ts +38 -18
  21. package/cmp/form/field/SubformsFieldModel.ts +5 -5
  22. package/cmp/grid/Grid.scss +31 -8
  23. package/cmp/grid/columns/Column.ts +24 -10
  24. package/cmp/input/HoistInput.scss +19 -1
  25. package/cmp/input/HoistInputModel.ts +10 -2
  26. package/data/Field.ts +2 -1
  27. package/data/Store.ts +16 -4
  28. package/data/StoreRecord.ts +11 -0
  29. package/data/impl/RecordValidator.ts +46 -28
  30. package/data/impl/StoreValidator.ts +22 -9
  31. package/data/index.ts +1 -0
  32. package/data/validation/Rule.ts +12 -52
  33. package/data/validation/Types.ts +81 -0
  34. package/data/validation/constraints.ts +2 -1
  35. package/desktop/appcontainer/OptionsDialog.scss +1 -1
  36. package/desktop/cmp/form/FormField.scss +128 -43
  37. package/desktop/cmp/form/FormField.ts +74 -40
  38. package/desktop/cmp/input/CodeInput.scss +13 -1
  39. package/desktop/cmp/input/RadioInput.scss +16 -4
  40. package/desktop/cmp/input/SwitchInput.scss +23 -5
  41. package/desktop/cmp/input/TextArea.scss +9 -1
  42. package/desktop/cmp/rest/impl/RestForm.scss +1 -1
  43. package/desktop/cmp/viewmanager/ViewManager.scss +7 -15
  44. package/kit/blueprint/styles.scss +4 -4
  45. package/kit/onsen/styles.scss +10 -2
  46. package/mobile/cmp/form/FormField.scss +52 -19
  47. package/mobile/cmp/form/FormField.ts +30 -21
  48. package/package.json +1 -1
  49. package/styles/XH.scss +1 -0
  50. package/styles/vars.scss +70 -12
  51. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -4,7 +4,12 @@
4
4
 
5
5
  ### πŸ’₯ Breaking Changes
6
6
 
7
- * Completed the refactoring away from `loadModel` to `loadObserver` started in v79:
7
+ * Modified several CSS classes related to `FormField` to better follow BEM conventions.
8
+ * ⚠️The commonly targeted `xh-form-field-label` class is now `xh-form-field__label`, although
9
+ please review new CSS vars (below) and consider using those instead of class-based selectors.
10
+ * Modifier classes now follow BEM conventions (e.g. `xh-form-field-invalid` is now
11
+ `xh-form-field--invalid`).
12
+ * Completed the refactoring from `loadModel` to `loadObserver` started in v79:
8
13
  * Renamed `XH.appLoadModel` to `XH.appLoadObserver`. The prior getter remains as an alias but is
9
14
  deprecated and scheduled for removal in v82.
10
15
  * Renamed `AppContainerModel.loadModel` to `loadObserver`. This is primarily an internal model,
@@ -17,6 +22,8 @@
17
22
 
18
23
  ### 🎁 New Features
19
24
 
25
+ * Enhanced `Field.rules` to support `warning` and `info` severity. Useful for non-blocking
26
+ validation scenarios, such as providing guidance to users without preventing form submission.
20
27
  * Added new `AppMenuButton.renderWithUserProfile` prop as a built-in alternative to the default
21
28
  hamburger menu. Set to `true` to render the current user's initials instead or provide a function
22
29
  to render a custom element for the user.
@@ -27,8 +34,25 @@
27
34
  * Introduced new `FilterBindTarget` and `FilterValueSource` interfaces to generalize the data
28
35
  sources that could be used with `FilterChooserModel` and `GridFilterModel`. Both `Store` and
29
36
  `View` implement these interfaces, meaning no changes are required for apps, but it is now
30
- possible to use these models with other, alternate implementations if needed.
31
- * Added new static typeguard methods on `Store`, `View`, and `Filter` + subclasses.
37
+ possible to use these models with alternate implementations.
38
+ * Added new static typeguard methods on `Store`, `View`, and `Filter` + its subclasses.
39
+ * Removed `RecordErrorMap` + reorganized validation types (not expected to impact most apps).
40
+
41
+ ### ✨ Styles
42
+
43
+ * Applied the app-wide `--xh-font-family` to `input` elements. Previously these had continued to
44
+ take a default font defined by the browser stylesheet.
45
+ * Customize for inputs if needed via `--xh-input-font-family`.
46
+ * Note that the switch to Hoist's default Inter font w/tabular numbers might require some
47
+ inputs w/tight sizing to be made wider to avoid clipping (e.g. `DateInputs` sized to fit).
48
+ * Updated + added validation-related `FormField` CSS classes and variables to account for new `info`
49
+ and `warning` validation levels. Additionally validation messages and the `info` text element no
50
+ longer clip at a single line - they will wrap as needed.
51
+ * Added new CSS variables for `FormField` to allow easier customization of commonly adjusted styles,
52
+ with a focus on labels. See `vars.scss` for the full list. Consider replacing existing class-based
53
+ CSS overrides with overrides to variables where possible.
54
+ * Added new CSS variables `--xh-intent-danger-text-color` (and others). Consider using these when
55
+ styling text with Hoist intent colors to enhance legibility in dark mode.
32
56
 
33
57
  ## 79.0.0 - 2026-01-05
34
58
 
@@ -9,7 +9,7 @@
9
9
  .xh-toolbar .xh-form-field {
10
10
  margin: 0 0 0 var(--xh-tbar-item-pad-px);
11
11
 
12
- .xh-form-field-label {
12
+ .xh-form-field__label {
13
13
  min-width: 0 !important;
14
14
  margin-right: 6px;
15
15
  }
@@ -60,7 +60,12 @@
60
60
  margin: 0;
61
61
  padding: 2px var(--xh-pad-px);
62
62
 
63
- &.xh-form-field-readonly .xh-form-field-readonly-display {
63
+ &__label {
64
+ font-size: var(--xh-font-size-small-px);
65
+ padding: 0;
66
+ }
67
+
68
+ &__readonly-display {
64
69
  padding: var(--xh-pad-half-px);
65
70
  }
66
71
 
@@ -68,11 +73,6 @@
68
73
  background-color: var(--xh-grid-bg-odd);
69
74
  }
70
75
  }
71
-
72
- .xh-form-field-label {
73
- font-size: var(--xh-font-size-small-px);
74
- padding: 0;
75
- }
76
76
  }
77
77
 
78
78
  h3 {
@@ -6,7 +6,12 @@
6
6
  margin: 0;
7
7
  padding: 2px var(--xh-pad-px);
8
8
 
9
- &.xh-form-field-readonly .xh-form-field-readonly-display {
9
+ &__label {
10
+ font-size: var(--xh-font-size-small-px);
11
+ padding: 0;
12
+ }
13
+
14
+ &__readonly-display {
10
15
  padding: var(--xh-pad-half-px);
11
16
  }
12
17
 
@@ -14,10 +19,5 @@
14
19
  background-color: var(--xh-grid-bg-odd);
15
20
  }
16
21
  }
17
-
18
- .xh-form-field-label {
19
- font-size: var(--xh-font-size-small-px);
20
- padding: 0;
21
- }
22
22
  }
23
23
  }
@@ -119,7 +119,7 @@ export declare class FormModel extends HoistModel {
119
119
  get isValid(): boolean;
120
120
  /** List of all validation errors for this form. */
121
121
  get allErrors(): string[];
122
- /** Recompute all validations and return true if the form is valid. */
122
+ /** Recompute all ValidationResults and return true if the form is valid. */
123
123
  validateAsync(opts?: FormValidateOptions): Promise<boolean>;
124
124
  /** Trigger the display of validation errors (if any) by bound `FormField`s. */
125
125
  displayValidation(): void;
@@ -1,5 +1,5 @@
1
1
  import { HoistModel } from '@xh/hoist/core';
2
- import { Rule, RuleLike, ValidationState } from '@xh/hoist/data';
2
+ import { Rule, RuleLike, ValidationResult, ValidationState } from '@xh/hoist/data';
3
3
  import { FormModel } from '../FormModel';
4
4
  export interface BaseFieldConfig {
5
5
  /** Unique name for this field within its parent FormModel. */
@@ -58,7 +58,7 @@ export declare abstract class BaseFieldModel extends HoistModel {
58
58
  private _formModel;
59
59
  private origInitialValue;
60
60
  boundInputRef: import("react").RefObject<unknown> & import("react").RefCallback<unknown>;
61
- private _errors;
61
+ private validationResultsInternal;
62
62
  private validationTask;
63
63
  private validationRunId;
64
64
  constructor({ name, displayName, initialValue, disabled, readonly, rules }: BaseFieldConfig);
@@ -79,8 +79,12 @@ export declare abstract class BaseFieldModel extends HoistModel {
79
79
  setValue(v: any): void;
80
80
  /** All validation errors for this field. */
81
81
  get errors(): string[];
82
+ /** All ValidationResults for this field. */
83
+ get validationResults(): ValidationResult[];
82
84
  /** All validation errors for this field and its sub-forms. */
83
85
  get allErrors(): string[];
86
+ /** All ValidationResults for this field and its sub-forms. */
87
+ get allValidationResults(): ValidationResult[];
84
88
  /**
85
89
  * Set the initial value of the field, reset to that value, and reset validation state.
86
90
  *
@@ -137,7 +141,7 @@ export declare abstract class BaseFieldModel extends HoistModel {
137
141
  */
138
142
  get isRequired(): boolean;
139
143
  /**
140
- * Recompute all validations and return true if the field is valid.
144
+ * Recompute all ValidationResults and return true if the field is valid.
141
145
  *
142
146
  * @param display - true to trigger the display of validation errors (if any)
143
147
  * by the bound FormField component after validation is complete.
@@ -1,5 +1,5 @@
1
1
  import { PlainObject } from '@xh/hoist/core';
2
- import { ValidationState } from '@xh/hoist/data';
2
+ import { ValidationResult, ValidationState } from '@xh/hoist/data';
3
3
  import { FormModel } from '../FormModel';
4
4
  import { BaseFieldModel, BaseFieldConfig } from './BaseFieldModel';
5
5
  import { FormConfig } from '../FormModel';
@@ -33,7 +33,7 @@ export interface SubformAddOptions {
33
33
  * all existing form contents to new values. Call {@link add} or {@link remove} on one of these
34
34
  * fields to adjust the contents of its collection while preserving existing state.
35
35
  *
36
- * Validation rules for the entire collection may be specified as for any field, but validations on
36
+ * Validation rules for the entire collection may be specified as for any field, but ValidationResults on
37
37
  * the subforms will also bubble up to this field, affecting its overall validation state.
38
38
  */
39
39
  export declare class SubformsFieldModel extends BaseFieldModel {
@@ -51,7 +51,7 @@ export declare class SubformsFieldModel extends BaseFieldModel {
51
51
  setValue(v: any): void;
52
52
  get formModel(): FormModel;
53
53
  set formModel(formModel: FormModel);
54
- get allErrors(): string[];
54
+ get allValidationResults(): ValidationResult[];
55
55
  reset(): void;
56
56
  displayValidation(includeSubforms?: boolean): void;
57
57
  get isValidationPending(): boolean;
@@ -55,7 +55,7 @@ export declare class GridLocalModel extends HoistModel {
55
55
  getColumnDefs(): Array<ColDef | ColGroupDef>;
56
56
  getContextMenuItems: (params: GetContextMenuItemsParams) => (string | import("ag-grid-community").MenuItemDef<any, any>)[];
57
57
  dataReaction(): {
58
- track: () => (boolean | RecordSet | import("../../data").StoreRecord[] | import("@xh/hoist/core").VSide)[];
58
+ track: () => (boolean | import("../../data").StoreRecord[] | RecordSet | import("@xh/hoist/core").VSide)[];
59
59
  run: () => void;
60
60
  };
61
61
  selectionReaction(): {
@@ -92,7 +92,7 @@ export declare class GridLocalModel extends HoistModel {
92
92
  run: () => void;
93
93
  };
94
94
  validationDisplayReaction(): {
95
- track: () => (boolean | import("../../data/impl/StoreValidator").StoreErrorMap)[];
95
+ track: () => (boolean | import("../../data").StoreValidationMessagesMap)[];
96
96
  run: () => void;
97
97
  debounce: number;
98
98
  };
@@ -1,4 +1,5 @@
1
- import { Rule, RuleLike } from './validation/Rule';
1
+ import { RuleLike } from '@xh/hoist/data/validation/Types';
2
+ import { Rule } from './validation/Rule';
2
3
  /**
3
4
  * Constructor arguments for a Hoist data package Field.
4
5
  */
@@ -1,7 +1,7 @@
1
1
  import { HoistBase, PlainObject, Some } from '@xh/hoist/core';
2
- import { Field, FieldSpec, Filter, FilterBindTarget, FilterLike, FilterValueSource, StoreRecord, StoreRecordId, StoreRecordOrId } from '@xh/hoist/data';
2
+ import { Field, FieldSpec, Filter, FilterBindTarget, FilterLike, FilterValueSource, StoreRecord, StoreRecordId, StoreRecordOrId, StoreValidationMessagesMap, StoreValidationResultsMap, ValidationResult } from '@xh/hoist/data';
3
+ import { StoreValidator } from '@xh/hoist/data/impl/StoreValidator';
3
4
  import { RecordSet } from './impl/RecordSet';
4
- import { StoreErrorMap, StoreValidator } from './impl/StoreValidator';
5
5
  export interface StoreConfig {
6
6
  /** Field names, configs, or instances. */
7
7
  fields?: Array<string | FieldSpec | Field>;
@@ -339,11 +339,14 @@ export declare class Store extends HoistBase implements FilterBindTarget, Filter
339
339
  /** True if the store is empty before filters have been applied */
340
340
  get allEmpty(): boolean;
341
341
  get maxDepth(): number;
342
- get errors(): StoreErrorMap;
342
+ get errors(): StoreValidationMessagesMap;
343
+ get validationResults(): StoreValidationResultsMap;
343
344
  /** Count of all validation errors for the store. */
344
345
  get errorCount(): number;
345
346
  /** Array of all errors for this store. */
346
347
  get allErrors(): string[];
348
+ /** Array of all ValidationResults for this store. */
349
+ get allValidationResults(): ValidationResult[];
347
350
  /**
348
351
  * Get a record by ID, or null if no matching record found.
349
352
  *
@@ -387,7 +390,7 @@ export declare class Store extends HoistBase implements FilterBindTarget, Filter
387
390
  get isValid(): boolean;
388
391
  /** True if the store is confirmed to be NotValid. */
389
392
  get isNotValid(): boolean;
390
- /** Recompute validations for all records and return true if the store is valid. */
393
+ /** Recompute ValidationResults for all records and return true if the store is valid. */
391
394
  validateAsync(): Promise<boolean>;
392
395
  /** Destroy this store, cleaning up any resources used. */
393
396
  destroy(): void;
@@ -1,4 +1,5 @@
1
1
  import { PlainObject } from '@xh/hoist/core';
2
+ import { ValidationResult } from '@xh/hoist/data/validation/Types';
2
3
  import { Store } from './Store';
3
4
  import { ValidationState } from './validation/ValidationState';
4
5
  import { RecordValidator } from './impl/RecordValidator';
@@ -81,8 +82,12 @@ export declare class StoreRecord {
81
82
  get validationState(): ValidationState;
82
83
  /** Map of field names to list of errors. */
83
84
  get errors(): Record<string, string[]>;
85
+ /** Map of field names to list of ValidationResults. */
86
+ get validationResults(): Record<string, ValidationResult[]>;
84
87
  /** Array of all errors for this record. */
85
88
  get allErrors(): string[];
89
+ /** Array of all ValidationResults for this record. */
90
+ get allValidationResults(): ValidationResult[];
86
91
  /** Count of all validation errors for the record. */
87
92
  get errorCount(): number;
88
93
  /** True if any fields are currently recomputing their validation state. */
@@ -1,14 +1,13 @@
1
- import { Field, Rule, StoreRecord, StoreRecordId, ValidationState } from '@xh/hoist/data';
2
- import { TaskObserver } from '../../core';
1
+ import { Field, RecordValidationMessagesMap, RecordValidationResultsMap, Rule, StoreRecord, StoreRecordId, ValidationResult, ValidationState } from '@xh/hoist/data';
3
2
  /**
4
3
  * Computes validation state for a StoreRecord.
5
4
  * @internal
6
5
  */
7
6
  export declare class RecordValidator {
8
7
  record: StoreRecord;
9
- _fieldErrors: RecordErrorMap;
10
- _validationTask: TaskObserver;
11
- _validationRunId: number;
8
+ private fieldValidations;
9
+ private validationTask;
10
+ private validationRunId;
12
11
  get id(): StoreRecordId;
13
12
  /** True if the record is confirmed to be Valid. */
14
13
  get isValid(): boolean;
@@ -17,7 +16,9 @@ export declare class RecordValidator {
17
16
  /** The current validation state of the record. */
18
17
  get validationState(): ValidationState;
19
18
  /** Map of field names to field-level errors. */
20
- get errors(): RecordErrorMap;
19
+ get errors(): RecordValidationMessagesMap;
20
+ /** Map of field names to field-level ValidationResults. */
21
+ get validationResults(): RecordValidationResultsMap;
21
22
  /** Count of all validation errors for the record. */
22
23
  get errorCount(): number;
23
24
  /** True if any fields are currently recomputing their validation state. */
@@ -27,13 +28,11 @@ export declare class RecordValidator {
27
28
  record: StoreRecord;
28
29
  });
29
30
  /**
30
- * Recompute validations for the record and return true if valid.
31
+ * Recompute ValidationResults for the record and return true if valid.
31
32
  */
32
33
  validateAsync(): Promise<boolean>;
33
34
  /** The current validation state for the record. */
34
35
  getValidationState(): ValidationState;
35
- evaluateRuleAsync(record: StoreRecord, field: Field, rule: Rule): Promise<string[]>;
36
+ evaluateRuleAsync(record: StoreRecord, field: Field, rule: Rule): Promise<ValidationResult[]>;
36
37
  ruleIsActive(record: StoreRecord, field: Field, rule: Rule): boolean;
37
38
  }
38
- /** Map of Field names to Field-level error lists. */
39
- export type RecordErrorMap = Record<string, string[]>;
@@ -1,6 +1,6 @@
1
1
  import { HoistBase } from '@xh/hoist/core';
2
- import { RecordErrorMap, RecordValidator } from './RecordValidator';
3
- import { ValidationState } from '../validation/ValidationState';
2
+ import { StoreValidationMessagesMap, StoreValidationResultsMap, ValidationState } from '@xh/hoist/data';
3
+ import { RecordValidator } from './RecordValidator';
4
4
  import { Store } from '../Store';
5
5
  import { StoreRecordId } from '../StoreRecord';
6
6
  /**
@@ -16,9 +16,11 @@ export declare class StoreValidator extends HoistBase {
16
16
  /** The current validation state of the store. */
17
17
  get validationState(): ValidationState;
18
18
  /** Map of StoreRecord IDs to StoreRecord-level error maps. */
19
- get errors(): StoreErrorMap;
19
+ get errors(): StoreValidationMessagesMap;
20
20
  /** Count of all validation errors for the store. */
21
21
  get errorCount(): number;
22
+ /** Map of StoreRecord IDs to StoreRecord-level ValidationResults maps. */
23
+ get validationResults(): StoreValidationResultsMap;
22
24
  /** True if any records are currently recomputing their validation state. */
23
25
  get isPending(): boolean;
24
26
  get validators(): RecordValidator[];
@@ -27,13 +29,14 @@ export declare class StoreValidator extends HoistBase {
27
29
  store: Store;
28
30
  });
29
31
  /**
30
- * Recompute validations for the store and return true if valid.
32
+ * Recompute ValidationResults for the store and return true if valid.
31
33
  */
32
34
  validateAsync(): Promise<boolean>;
33
35
  /** @returns The current validation state for the store. */
34
36
  getValidationState(): ValidationState;
35
37
  /** @returns map of StoreRecord IDs to StoreRecord-level error maps. */
36
- getErrorMap(): StoreErrorMap;
38
+ getErrorMap(): StoreValidationMessagesMap;
39
+ getValidationResultsMap(): StoreValidationResultsMap;
37
40
  /**
38
41
  * @param id - ID of RecordValidator (should match record.id)
39
42
  */
@@ -43,5 +46,3 @@ export declare class StoreValidator extends HoistBase {
43
46
  private validateInChunksAsync;
44
47
  private mapValidators;
45
48
  }
46
- /** Map of StoreRecord IDs to StoreRecord-level error maps. */
47
- export type StoreErrorMap = Record<StoreRecordId, RecordErrorMap>;
@@ -31,3 +31,4 @@ export * from './cube/ViewRowData';
31
31
  export * from './validation/constraints';
32
32
  export * from './validation/Rule';
33
33
  export * from './validation/ValidationState';
34
+ export * from './validation/Types';
@@ -1,6 +1,4 @@
1
- import { Awaitable, PlainObject, Some } from '../../core';
2
- import { StoreRecord } from '../StoreRecord';
3
- import { BaseFieldModel } from '../../cmp/form';
1
+ import { Constraint, RuleSpec, ValidationResult, ValidationSeverity, When } from '@xh/hoist/data';
4
2
  /**
5
3
  * Immutable object representing a validation rule.
6
4
  */
@@ -10,42 +8,9 @@ export declare class Rule {
10
8
  constructor(spec: RuleSpec);
11
9
  }
12
10
  /**
13
- * Function to validate a value.
11
+ * Utility to determine the maximum severity from a list of ValidationResults.
14
12
  *
15
- * @param fieldState - context w/value for the constraint's target Field.
16
- * @param allValues - current values for all fields in form, keyed by field name.
17
- * @returns String(s) or array of strings describing errors, or null or undefined if rule passes
18
- * successfully. May return a Promise of strings for async validation.
13
+ * @param validationResults - list of ValidationResults to evaluate.
14
+ * @returns The highest severity level found, or null if none.
19
15
  */
20
- export type Constraint<T = any> = (fieldState: FieldState<T>, allValues: PlainObject) => Awaitable<Some<string>>;
21
- /**
22
- * Function to determine when to perform validation on a value.
23
- *
24
- * @param entity - the entity being evaluated. Typically a field for store validation or
25
- * a BaseFieldModel for Form validation.
26
- * @param allValues - current values for all fields in form or record, keyed by field name.
27
- * @returns true if this rule is currently active.
28
- */
29
- export type When = (entity: any, allValues: PlainObject) => boolean;
30
- export interface FieldState<T = any> {
31
- /** Current value of the field */
32
- value: T;
33
- /** Name of the field */
34
- name: string;
35
- /** Display name of the field */
36
- displayName: string;
37
- /** Record being validated, if validating Store data. */
38
- record?: StoreRecord;
39
- /** FieldModel being validated, if validating Form data. */
40
- fieldModel?: BaseFieldModel;
41
- }
42
- export interface RuleSpec {
43
- /** Function(s) to perform validation. */
44
- check: Some<Constraint>;
45
- /**
46
- * Function to determine when this rule is active.
47
- * If not specified rule is considered to be always active.
48
- */
49
- when?: When;
50
- }
51
- export type RuleLike = RuleSpec | Constraint | Rule;
16
+ export declare function maxSeverity(validationResults: ValidationResult[]): ValidationSeverity;
@@ -0,0 +1,56 @@
1
+ import type { BaseFieldModel } from '@xh/hoist/cmp/form';
2
+ import type { Awaitable, PlainObject, Some } from '@xh/hoist/core';
3
+ import type { Rule, StoreRecord, StoreRecordId } from '@xh/hoist/data';
4
+ /**
5
+ * Function to validate a value.
6
+ *
7
+ * @param fieldState - context w/value for the constraint's target Field.
8
+ * @param allValues - current values for all fields in form, keyed by field name.
9
+ * @returns ValidationResult(s) or string(s) describing errors or null / undefined if rule passes.
10
+ * May return a Promise resolving to the same for async validation.
11
+ */
12
+ export type Constraint<T = any> = (fieldState: FieldState<T>, allValues: PlainObject) => Awaitable<Some<string | ValidationResult>>;
13
+ /**
14
+ * Function to determine when to perform validation on a value.
15
+ *
16
+ * @param entity - the entity being evaluated. Typically a field for store validation or
17
+ * a BaseFieldModel for Form validation.
18
+ * @param allValues - current values for all fields in form or record, keyed by field name.
19
+ * @returns true if this rule is currently active.
20
+ */
21
+ export type When = (entity: any, allValues: PlainObject) => boolean;
22
+ export interface FieldState<T = any> {
23
+ /** Current value of the field */
24
+ value: T;
25
+ /** Name of the field */
26
+ name: string;
27
+ /** Display name of the field */
28
+ displayName: string;
29
+ /** Record being validated, if validating Store data. */
30
+ record?: StoreRecord;
31
+ /** FieldModel being validated, if validating Form data. */
32
+ fieldModel?: BaseFieldModel;
33
+ }
34
+ export interface RuleSpec {
35
+ /** Function(s) to perform validation. */
36
+ check: Some<Constraint>;
37
+ /**
38
+ * Function to determine when this rule is active.
39
+ * If not specified rule is considered to be always active.
40
+ */
41
+ when?: When;
42
+ }
43
+ export type RuleLike = RuleSpec | Constraint | Rule;
44
+ export interface ValidationResult {
45
+ severity: ValidationSeverity;
46
+ message: string;
47
+ }
48
+ export type ValidationSeverity = 'error' | 'warning' | 'info';
49
+ /** Map of StoreRecord IDs to StoreRecord-level messages maps. */
50
+ export type StoreValidationMessagesMap = Record<StoreRecordId, RecordValidationMessagesMap>;
51
+ /** Map of StoreRecord IDs to StoreRecord-level ValidationResults maps. */
52
+ export type StoreValidationResultsMap = Record<StoreRecordId, RecordValidationResultsMap>;
53
+ /** Map of Field names to Field-level Validation lists. */
54
+ export type RecordValidationResultsMap = Record<string, ValidationResult[]>;
55
+ /** Map of Field names to Field-level validation message lists. */
56
+ export type RecordValidationMessagesMap = Record<string, string[]>;
@@ -1,5 +1,5 @@
1
+ import { Constraint } from '@xh/hoist/data';
1
2
  import { LocalDate } from '@xh/hoist/utils/datetime';
2
- import { Constraint } from './Rule';
3
3
  /**
4
4
  * A set of validation functions to assist in form field validation.
5
5
  */
@@ -72,14 +72,12 @@ export declare const autoRefreshAppOption: ({ formFieldProps, inputProps }?: Aut
72
72
  position?: import("csstype").Property.Position;
73
73
  display?: string;
74
74
  testId?: string;
75
- id?: string | undefined;
76
- onError?: import("react").ReactEventHandler<HTMLDivElement>;
77
- defaultValue?: string | number | readonly string[] | undefined;
78
75
  slot?: string | undefined;
79
76
  style?: import("react").CSSProperties | undefined;
80
77
  title?: string | undefined;
81
78
  hidden?: boolean | undefined;
82
79
  defaultChecked?: boolean | undefined;
80
+ defaultValue?: string | number | readonly string[] | undefined;
83
81
  suppressContentEditableWarning?: boolean | undefined;
84
82
  suppressHydrationWarning?: boolean | undefined;
85
83
  accessKey?: string | undefined;
@@ -89,6 +87,7 @@ export declare const autoRefreshAppOption: ({ formFieldProps, inputProps }?: Aut
89
87
  dir?: string | undefined;
90
88
  draggable?: (boolean | "true" | "false") | undefined;
91
89
  enterKeyHint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined;
90
+ id?: string | undefined;
92
91
  lang?: string | undefined;
93
92
  nonce?: string | undefined;
94
93
  spellCheck?: (boolean | "true" | "false") | undefined;
@@ -207,6 +206,7 @@ export declare const autoRefreshAppOption: ({ formFieldProps, inputProps }?: Aut
207
206
  onInvalidCapture?: import("react").FormEventHandler<HTMLDivElement>;
208
207
  onLoad?: import("react").ReactEventHandler<HTMLDivElement>;
209
208
  onLoadCapture?: import("react").ReactEventHandler<HTMLDivElement>;
209
+ onError?: import("react").ReactEventHandler<HTMLDivElement>;
210
210
  onErrorCapture?: import("react").ReactEventHandler<HTMLDivElement>;
211
211
  onKeyDown?: import("react").KeyboardEventHandler<HTMLDivElement>;
212
212
  onKeyDownCapture?: import("react").KeyboardEventHandler<HTMLDivElement>;
@@ -70,14 +70,12 @@ export declare const themeAppOption: ({ formFieldProps, inputProps }?: ThemeAppO
70
70
  position?: import("csstype").Property.Position;
71
71
  display?: string;
72
72
  testId?: string;
73
- id?: string | undefined;
74
- onError?: import("react").ReactEventHandler<HTMLDivElement>;
75
- defaultValue?: string | number | readonly string[] | undefined;
76
73
  slot?: string | undefined;
77
74
  style?: import("react").CSSProperties | undefined;
78
75
  title?: string | undefined;
79
76
  hidden?: boolean | undefined;
80
77
  defaultChecked?: boolean | undefined;
78
+ defaultValue?: string | number | readonly string[] | undefined;
81
79
  suppressContentEditableWarning?: boolean | undefined;
82
80
  suppressHydrationWarning?: boolean | undefined;
83
81
  accessKey?: string | undefined;
@@ -87,6 +85,7 @@ export declare const themeAppOption: ({ formFieldProps, inputProps }?: ThemeAppO
87
85
  dir?: string | undefined;
88
86
  draggable?: (boolean | "true" | "false") | undefined;
89
87
  enterKeyHint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined;
88
+ id?: string | undefined;
90
89
  lang?: string | undefined;
91
90
  nonce?: string | undefined;
92
91
  spellCheck?: (boolean | "true" | "false") | undefined;
@@ -205,6 +204,7 @@ export declare const themeAppOption: ({ formFieldProps, inputProps }?: ThemeAppO
205
204
  onInvalidCapture?: import("react").FormEventHandler<HTMLDivElement>;
206
205
  onLoad?: import("react").ReactEventHandler<HTMLDivElement>;
207
206
  onLoadCapture?: import("react").ReactEventHandler<HTMLDivElement>;
207
+ onError?: import("react").ReactEventHandler<HTMLDivElement>;
208
208
  onErrorCapture?: import("react").ReactEventHandler<HTMLDivElement>;
209
209
  onKeyDown?: import("react").KeyboardEventHandler<HTMLDivElement>;
210
210
  onKeyDownCapture?: import("react").KeyboardEventHandler<HTMLDivElement>;
@@ -256,7 +256,7 @@ export class FormModel extends HoistModel {
256
256
 
257
257
  /** True if all fields are valid. */
258
258
  get isValid(): boolean {
259
- return this.validationState == 'Valid';
259
+ return this.validationState === 'Valid';
260
260
  }
261
261
 
262
262
  /** List of all validation errors for this form. */
@@ -264,7 +264,7 @@ export class FormModel extends HoistModel {
264
264
  return flatMap(this.fields, s => s.allErrors);
265
265
  }
266
266
 
267
- /** Recompute all validations and return true if the form is valid. */
267
+ /** Recompute all ValidationResults and return true if the form is valid. */
268
268
  async validateAsync(opts?: FormValidateOptions): Promise<boolean> {
269
269
  const {display = true} = opts ?? {},
270
270
  promises = map(this.fields, m => m.validateAsync({display}));