ngx-t-forms-types 0.0.30 → 0.0.34
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/dist/interfaces/Form/formSubmissionHandleInterface.d.ts +6 -0
- package/dist/interfaces/FormBuilder/DefaultEelement.js +76 -0
- package/dist/interfaces/FormBuilder/DefaultInputConfigInterface.d.ts +13 -0
- package/dist/interfaces/FormBuilder/FormInputKeys.d.ts +6 -0
- package/dist/interfaces/FormBuilder/FormInputKeys.js +6 -0
- package/dist/interfaces/FormBuilder/inputConfig/ElementEditConfig.js +188 -3
- package/dist/interfaces/Import/ImportIdentity.d.ts +32 -0
- package/dist/interfaces/Import/ImportIdentity.js +1 -0
- package/dist/interfaces/Import/ImportProgress.d.ts +12 -1
- package/dist/interfaces/Import/ImportRowState.d.ts +52 -1
- package/dist/interfaces/Import/index.d.ts +2 -1
- package/dist/interfaces/formInput/APIDataFetchingConfigurationInterface.d.ts +34 -0
- package/dist/interfaces/formInput/BasicFormInputInterface.d.ts +1 -0
- package/dist/interfaces/formInput/BasicFormInputInterface.js +1 -0
- package/dist/interfaces/formInput/IMscoaAccount.d.ts +19 -3
- package/dist/interfaces/formInput/ISelectInputInterface.d.ts +12 -0
- package/dist/interfaces/formInput/MultipleInterface.d.ts +10 -0
- package/dist/interfaces/formInput/WorkflowDocumentPicker.d.ts +2 -0
- package/dist/schemas/FormInputSchema.js +41 -2
- package/dist/schemas/MatOptionsSchema.js +7 -0
- package/dist/schemas/MscoaConfigSchema.js +1 -0
- package/dist/schemas/index.d.ts +2 -1
- package/dist/schemas/index.js +2 -1
- package/dist/skillet/authoring-rules.d.ts +41 -0
- package/dist/skillet/authoring-rules.js +61 -0
- package/dist/skillet/extra-members.d.ts +143 -0
- package/dist/skillet/extra-members.js +79 -0
- package/dist/skillet/form.d.ts +170 -0
- package/dist/skillet/form.js +139 -0
- package/dist/skillet/index.d.ts +43 -0
- package/dist/skillet/index.js +46 -0
- package/dist/skillet/input-members.d.ts +375 -0
- package/dist/skillet/input-members.js +186 -0
- package/dist/skillet/internal/coupling.d.ts +63 -0
- package/dist/skillet/internal/coupling.js +69 -0
- package/dist/skillet/internal/editor-guidance.d.ts +64 -0
- package/dist/skillet/internal/editor-guidance.js +291 -0
- package/dist/skillet/members/calculated-field.d.ts +155 -0
- package/dist/skillet/members/calculated-field.js +105 -0
- package/dist/skillet/members/conditional.d.ts +69 -0
- package/dist/skillet/members/conditional.js +16 -0
- package/dist/skillet/members/document-picker.d.ts +182 -0
- package/dist/skillet/members/document-picker.js +111 -0
- package/dist/skillet/members/mat-options.d.ts +187 -0
- package/dist/skillet/members/mat-options.js +92 -0
- package/dist/skillet/members/mscoa.d.ts +182 -0
- package/dist/skillet/members/mscoa.js +129 -0
- package/dist/skillet/members/pagination.d.ts +59 -0
- package/dist/skillet/members/pagination.js +16 -0
- package/dist/skillet/members/table.d.ts +178 -0
- package/dist/skillet/members/table.js +105 -0
- package/dist/skillet/members/validators.d.ts +197 -0
- package/dist/skillet/members/validators.js +74 -0
- package/dist/skillet/members/value.d.ts +154 -0
- package/dist/skillet/members/value.js +97 -0
- package/dist/skillet/shared-types.d.ts +57 -0
- package/dist/skillet/shared-types.js +69 -0
- package/dist/skillet/tests/house-rules.spec.d.ts +1 -0
- package/dist/skillet/tests/house-rules.spec.js +162 -0
- package/dist/skillet/tests/skillet-coupling.spec.d.ts +1 -0
- package/dist/skillet/tests/skillet-coupling.spec.js +191 -0
- package/dist/skillet/tests/variant-assembly.spec.d.ts +1 -0
- package/dist/skillet/tests/variant-assembly.spec.js +643 -0
- package/dist/skillet/variants.d.ts +138 -0
- package/dist/skillet/variants.js +536 -0
- package/dist/skillet/workflow-context.d.ts +95 -0
- package/dist/skillet/workflow-context.js +128 -0
- package/package.json +78 -60
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { s } from '@hashbrownai/core';
|
|
2
|
+
import { ElementTypes } from '../interfaces/formInput/index.js';
|
|
3
|
+
import { type WorkflowPickerSchemaOptions } from './members/document-picker.js';
|
|
4
|
+
import { type MatOptionsSchemaOptions } from './members/mat-options.js';
|
|
5
|
+
import { type ValidatorsSchemaOptions } from './members/validators.js';
|
|
6
|
+
/**
|
|
7
|
+
* Per-element column schemas, assembled from the registries.
|
|
8
|
+
*
|
|
9
|
+
* ## The element decides the shape
|
|
10
|
+
*
|
|
11
|
+
* A flat object carrying every property an input might have is the wrong shape
|
|
12
|
+
* to hand a model: nothing in it says `trueLabel` belongs to a toggle and
|
|
13
|
+
* `fileType` to an upload, so it will put them wherever. Each element instead
|
|
14
|
+
* gets its own object containing only its own properties, tagged with
|
|
15
|
+
* `element: s.literal(...)`, and the column is the union of those.
|
|
16
|
+
*
|
|
17
|
+
* ## The partition is not invented here
|
|
18
|
+
*
|
|
19
|
+
* `DefaultInputConfig[element].properties` already states which properties an
|
|
20
|
+
* element has, and `requiredProperties` which it must have — the form builder
|
|
21
|
+
* has been driven by that list all along. Assembly reads it rather than
|
|
22
|
+
* restating it, so an element gaining a property in the builder gains it in the
|
|
23
|
+
* schema too, with no second list to keep in step.
|
|
24
|
+
*
|
|
25
|
+
* Three kinds of listed key are skipped, each for its own reason: editor-only
|
|
26
|
+
* rows that store nothing, members still pending a module, and members the
|
|
27
|
+
* system owns or has deprecated. Anything skipped for a reason not on that list
|
|
28
|
+
* is recorded in {@link UNRESOLVED_VARIANT_KEYS}, which a test asserts is
|
|
29
|
+
* empty — so a key that falls through goes noticed rather than silently missing
|
|
30
|
+
* from every generated form.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* The keys `baseFormColumnInputsSchema` requires of every column, read off the
|
|
34
|
+
* Joi schema itself.
|
|
35
|
+
*
|
|
36
|
+
* `DefaultInputConfig.properties` does NOT list all of them — it lists the rows
|
|
37
|
+
* the settings panel shows, and the builder shows no row for `element` (chosen
|
|
38
|
+
* by dragging) nor usually for `colSize` and `dataType` (taken from the element
|
|
39
|
+
* template). Assembling from `properties` alone therefore produced variants
|
|
40
|
+
* that could never pass `validateForm()`: a generated toggle came out with no
|
|
41
|
+
* `dataType` and was rejected every time.
|
|
42
|
+
*
|
|
43
|
+
* Deriving the set from Joi rather than restating it means the generation
|
|
44
|
+
* schema cannot come to disagree with the validator about what a column must
|
|
45
|
+
* have — the same reason the enums are read at runtime rather than copied.
|
|
46
|
+
*
|
|
47
|
+
* ## Why an empty validate() rather than describe()
|
|
48
|
+
*
|
|
49
|
+
* `describe()` reads this straight off the schema and is the obvious way to do
|
|
50
|
+
* it — but Joi's BROWSER build ships with manifest functionality compiled out,
|
|
51
|
+
* so calling it throws `AssertError: Manifest functionality disabled`. Since
|
|
52
|
+
* this module is evaluated on import, that would take down any Angular
|
|
53
|
+
* consumer of the skillet subpath the moment it loaded.
|
|
54
|
+
*
|
|
55
|
+
* Validating an empty object reaches the same fact through `validate()`, which
|
|
56
|
+
* is core Joi and present in both builds: every unsatisfied `any.required`
|
|
57
|
+
* names a key the schema demands. Verified to return an identical set.
|
|
58
|
+
*/
|
|
59
|
+
export declare const JOI_MANDATORY_COLUMN_KEYS: readonly string[];
|
|
60
|
+
/**
|
|
61
|
+
* Modelled members `formColumnInputsSchema` does not declare, and which a
|
|
62
|
+
* generated form therefore must not carry.
|
|
63
|
+
*
|
|
64
|
+
* **This set is empty today, and a test asserts it stays that way.** It is kept
|
|
65
|
+
* because it is the guard that made the problem visible in the first place, and
|
|
66
|
+
* because it costs one `validate()` call at load to keep it visible.
|
|
67
|
+
*
|
|
68
|
+
* ## What it caught
|
|
69
|
+
*
|
|
70
|
+
* Joi objects reject unknown keys, so a column carrying an undeclared member
|
|
71
|
+
* does not merely lose it — the whole form fails validation:
|
|
72
|
+
*
|
|
73
|
+
* "Slides > Columns > Placeholder" is not allowed
|
|
74
|
+
*
|
|
75
|
+
* Eleven members were in that state: `placeholder`, `autocomplete`, `disabled`,
|
|
76
|
+
* the four prefix/suffix affixes, `selectUpto`, `showTotalsOf`, and the two
|
|
77
|
+
* table configs. Every one was declared on an interface and most were listed in
|
|
78
|
+
* `DefaultInputConfig.properties`, so the generator offered them and the
|
|
79
|
+
* validator rejected them. They have since been added to
|
|
80
|
+
* `baseFormColumnInputsSchema`, given editor rows, and scoped to the elements
|
|
81
|
+
* that can actually use them.
|
|
82
|
+
*
|
|
83
|
+
* ## Why it is derived rather than listed
|
|
84
|
+
*
|
|
85
|
+
* Nothing here had to be edited when the validator was fixed. The set is
|
|
86
|
+
* computed from the validator at load, so it emptied itself — and it will fill
|
|
87
|
+
* itself again the moment a member is modelled that Joi does not accept. A
|
|
88
|
+
* hand-maintained list would have needed deleting, and would not have been
|
|
89
|
+
* watching afterwards.
|
|
90
|
+
*
|
|
91
|
+
* Probed against `formColumnInputsSchema` rather than the base, because
|
|
92
|
+
* `formInputs` is appended there; probing the base would report a list's own
|
|
93
|
+
* contents as unknown and quietly withdraw every item list.
|
|
94
|
+
*/
|
|
95
|
+
export declare const JOI_UNKNOWN_COLUMN_KEYS: ReadonlySet<string>;
|
|
96
|
+
/**
|
|
97
|
+
* Property keys assembly could not resolve to a node, and could not account for
|
|
98
|
+
* as a deliberate omission. Should always be empty; a test enforces it.
|
|
99
|
+
*/
|
|
100
|
+
export declare const UNRESOLVED_VARIANT_KEYS: string[];
|
|
101
|
+
/** The elements worth offering before the exotic ones have been proven out. */
|
|
102
|
+
export declare const COMMON_ELEMENTS: readonly ElementTypes[];
|
|
103
|
+
/**
|
|
104
|
+
* How many levels of nested list contents to offer by default.
|
|
105
|
+
*
|
|
106
|
+
* One level means a form may contain an item list, and that list's rows may
|
|
107
|
+
* contain ordinary fields — which is the shape nearly every real item list has.
|
|
108
|
+
* Two would allow a list inside a list; it is available, but it multiplies the
|
|
109
|
+
* schema by roughly the size of a whole column union each time, so it is not
|
|
110
|
+
* the default.
|
|
111
|
+
*/
|
|
112
|
+
export declare const DEFAULT_NESTING_DEPTH = 1;
|
|
113
|
+
export interface ColumnSchemaOptions extends MatOptionsSchemaOptions, WorkflowPickerSchemaOptions, ValidatorsSchemaOptions {
|
|
114
|
+
/**
|
|
115
|
+
* Which elements the model may choose between. Defaults to every element the
|
|
116
|
+
* builder configures. Narrowing this to {@link COMMON_ELEMENTS} makes the
|
|
117
|
+
* union markedly smaller and the choice markedly more reliable.
|
|
118
|
+
*/
|
|
119
|
+
elements?: readonly ElementTypes[];
|
|
120
|
+
/**
|
|
121
|
+
* How deeply item lists may nest. Defaults to {@link DEFAULT_NESTING_DEPTH}.
|
|
122
|
+
* Zero withdraws the item-list element entirely — see
|
|
123
|
+
* {@link buildColumnSchema} for why it cannot simply be offered without its
|
|
124
|
+
* contents.
|
|
125
|
+
*/
|
|
126
|
+
depth?: number;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Builds the column schema: a discriminated union over the offered elements.
|
|
130
|
+
*
|
|
131
|
+
* @param options which elements to offer, how deeply item lists may nest, and
|
|
132
|
+
* the endpoints and workflows a `matOptions` source or a document picker may
|
|
133
|
+
* select between. See `members/mat-options.ts` for why those are supplied
|
|
134
|
+
* rather than authored.
|
|
135
|
+
*/
|
|
136
|
+
export declare function createColumnSchema(options?: ColumnSchemaOptions): s.HashbrownType<unknown>;
|
|
137
|
+
/** Which properties each element contributes, for inspection and testing. */
|
|
138
|
+
export declare function variantKeys(element: ElementTypes, options?: ColumnSchemaOptions): string[];
|
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
import { s } from '@hashbrownai/core';
|
|
2
|
+
import { DefaultInputConfig } from '../interfaces/FormBuilder/DefaultEelement.js';
|
|
3
|
+
import { AllFormInputPrimaryKeys } from '../interfaces/FormBuilder/FormInputKeys.js';
|
|
4
|
+
import { ElementTypes, InputDataTypes, InputTypes } from '../interfaces/formInput/index.js';
|
|
5
|
+
import { baseFormColumnInputsSchema, formColumnInputsSchema, } from '../schemas/FormInputSchema.js';
|
|
6
|
+
import { EDITOR_ONLY_KEYS, PENDING_EXTRA_MEMBERS, extraDraftMemberSchemas, extraInputMemberSchemas, } from './extra-members.js';
|
|
7
|
+
import { DEPRECATED_INPUT_MEMBERS, PENDING_INPUT_MEMBERS, SYSTEM_OWNED_INPUT_MEMBERS, draftMemberSchemas, inputMemberSchemas, } from './input-members.js';
|
|
8
|
+
import { optional } from './internal/coupling.js';
|
|
9
|
+
import { ALWAYS_SHOWN_MEMBERS } from './internal/editor-guidance.js';
|
|
10
|
+
import { calculatedFieldRulesDraftSchema } from './members/calculated-field.js';
|
|
11
|
+
import { createWorkflowPickerSchema, } from './members/document-picker.js';
|
|
12
|
+
import { createMatOptionsSchema, } from './members/mat-options.js';
|
|
13
|
+
import { createValidatorsSchema, } from './members/validators.js';
|
|
14
|
+
/**
|
|
15
|
+
* Per-element column schemas, assembled from the registries.
|
|
16
|
+
*
|
|
17
|
+
* ## The element decides the shape
|
|
18
|
+
*
|
|
19
|
+
* A flat object carrying every property an input might have is the wrong shape
|
|
20
|
+
* to hand a model: nothing in it says `trueLabel` belongs to a toggle and
|
|
21
|
+
* `fileType` to an upload, so it will put them wherever. Each element instead
|
|
22
|
+
* gets its own object containing only its own properties, tagged with
|
|
23
|
+
* `element: s.literal(...)`, and the column is the union of those.
|
|
24
|
+
*
|
|
25
|
+
* ## The partition is not invented here
|
|
26
|
+
*
|
|
27
|
+
* `DefaultInputConfig[element].properties` already states which properties an
|
|
28
|
+
* element has, and `requiredProperties` which it must have — the form builder
|
|
29
|
+
* has been driven by that list all along. Assembly reads it rather than
|
|
30
|
+
* restating it, so an element gaining a property in the builder gains it in the
|
|
31
|
+
* schema too, with no second list to keep in step.
|
|
32
|
+
*
|
|
33
|
+
* Three kinds of listed key are skipped, each for its own reason: editor-only
|
|
34
|
+
* rows that store nothing, members still pending a module, and members the
|
|
35
|
+
* system owns or has deprecated. Anything skipped for a reason not on that list
|
|
36
|
+
* is recorded in {@link UNRESOLVED_VARIANT_KEYS}, which a test asserts is
|
|
37
|
+
* empty — so a key that falls through goes noticed rather than silently missing
|
|
38
|
+
* from every generated form.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* The keys `baseFormColumnInputsSchema` requires of every column, read off the
|
|
42
|
+
* Joi schema itself.
|
|
43
|
+
*
|
|
44
|
+
* `DefaultInputConfig.properties` does NOT list all of them — it lists the rows
|
|
45
|
+
* the settings panel shows, and the builder shows no row for `element` (chosen
|
|
46
|
+
* by dragging) nor usually for `colSize` and `dataType` (taken from the element
|
|
47
|
+
* template). Assembling from `properties` alone therefore produced variants
|
|
48
|
+
* that could never pass `validateForm()`: a generated toggle came out with no
|
|
49
|
+
* `dataType` and was rejected every time.
|
|
50
|
+
*
|
|
51
|
+
* Deriving the set from Joi rather than restating it means the generation
|
|
52
|
+
* schema cannot come to disagree with the validator about what a column must
|
|
53
|
+
* have — the same reason the enums are read at runtime rather than copied.
|
|
54
|
+
*
|
|
55
|
+
* ## Why an empty validate() rather than describe()
|
|
56
|
+
*
|
|
57
|
+
* `describe()` reads this straight off the schema and is the obvious way to do
|
|
58
|
+
* it — but Joi's BROWSER build ships with manifest functionality compiled out,
|
|
59
|
+
* so calling it throws `AssertError: Manifest functionality disabled`. Since
|
|
60
|
+
* this module is evaluated on import, that would take down any Angular
|
|
61
|
+
* consumer of the skillet subpath the moment it loaded.
|
|
62
|
+
*
|
|
63
|
+
* Validating an empty object reaches the same fact through `validate()`, which
|
|
64
|
+
* is core Joi and present in both builds: every unsatisfied `any.required`
|
|
65
|
+
* names a key the schema demands. Verified to return an identical set.
|
|
66
|
+
*/
|
|
67
|
+
export const JOI_MANDATORY_COLUMN_KEYS = (() => {
|
|
68
|
+
const { error } = baseFormColumnInputsSchema.validate({}, { abortEarly: false });
|
|
69
|
+
return (error?.details ?? [])
|
|
70
|
+
.filter((detail) => detail.type === 'any.required')
|
|
71
|
+
.map((detail) => detail.path.join('.'));
|
|
72
|
+
})();
|
|
73
|
+
/**
|
|
74
|
+
* Modelled members `formColumnInputsSchema` does not declare, and which a
|
|
75
|
+
* generated form therefore must not carry.
|
|
76
|
+
*
|
|
77
|
+
* **This set is empty today, and a test asserts it stays that way.** It is kept
|
|
78
|
+
* because it is the guard that made the problem visible in the first place, and
|
|
79
|
+
* because it costs one `validate()` call at load to keep it visible.
|
|
80
|
+
*
|
|
81
|
+
* ## What it caught
|
|
82
|
+
*
|
|
83
|
+
* Joi objects reject unknown keys, so a column carrying an undeclared member
|
|
84
|
+
* does not merely lose it — the whole form fails validation:
|
|
85
|
+
*
|
|
86
|
+
* "Slides > Columns > Placeholder" is not allowed
|
|
87
|
+
*
|
|
88
|
+
* Eleven members were in that state: `placeholder`, `autocomplete`, `disabled`,
|
|
89
|
+
* the four prefix/suffix affixes, `selectUpto`, `showTotalsOf`, and the two
|
|
90
|
+
* table configs. Every one was declared on an interface and most were listed in
|
|
91
|
+
* `DefaultInputConfig.properties`, so the generator offered them and the
|
|
92
|
+
* validator rejected them. They have since been added to
|
|
93
|
+
* `baseFormColumnInputsSchema`, given editor rows, and scoped to the elements
|
|
94
|
+
* that can actually use them.
|
|
95
|
+
*
|
|
96
|
+
* ## Why it is derived rather than listed
|
|
97
|
+
*
|
|
98
|
+
* Nothing here had to be edited when the validator was fixed. The set is
|
|
99
|
+
* computed from the validator at load, so it emptied itself — and it will fill
|
|
100
|
+
* itself again the moment a member is modelled that Joi does not accept. A
|
|
101
|
+
* hand-maintained list would have needed deleting, and would not have been
|
|
102
|
+
* watching afterwards.
|
|
103
|
+
*
|
|
104
|
+
* Probed against `formColumnInputsSchema` rather than the base, because
|
|
105
|
+
* `formInputs` is appended there; probing the base would report a list's own
|
|
106
|
+
* contents as unknown and quietly withdraw every item list.
|
|
107
|
+
*/
|
|
108
|
+
export const JOI_UNKNOWN_COLUMN_KEYS = (() => {
|
|
109
|
+
const candidates = [
|
|
110
|
+
...Object.keys(inputMemberSchemas),
|
|
111
|
+
...Object.keys(draftMemberSchemas),
|
|
112
|
+
...Object.keys(extraInputMemberSchemas),
|
|
113
|
+
...Object.keys(extraDraftMemberSchemas),
|
|
114
|
+
];
|
|
115
|
+
// One probe carrying every candidate at once. Joi reports each unknown key
|
|
116
|
+
// separately, so a single pass names them all — and a single `validate()`
|
|
117
|
+
// keeps this cheap enough to run at module load, the same way
|
|
118
|
+
// JOI_MANDATORY_COLUMN_KEYS does.
|
|
119
|
+
const probe = Object.fromEntries(candidates.map((key) => [key, 'probe']));
|
|
120
|
+
const { error } = formColumnInputsSchema.validate(probe, {
|
|
121
|
+
abortEarly: false,
|
|
122
|
+
});
|
|
123
|
+
return new Set((error?.details ?? [])
|
|
124
|
+
.filter((detail) => detail.type === 'object.unknown')
|
|
125
|
+
.map((detail) => String(detail.path[0])));
|
|
126
|
+
})();
|
|
127
|
+
/**
|
|
128
|
+
* Elements the mandatory base does not apply to.
|
|
129
|
+
*
|
|
130
|
+
* `validateForm()` filters section-title columns out before validating, so Joi
|
|
131
|
+
* never sees one. Forcing `formControlName` and `dataType` onto a heading that
|
|
132
|
+
* captures no value would be inventing a requirement the system does not have.
|
|
133
|
+
*/
|
|
134
|
+
const EXEMPT_FROM_MANDATORY = new Set([
|
|
135
|
+
ElementTypes.SectionTitle,
|
|
136
|
+
]);
|
|
137
|
+
const SKIPPED_KEYS = new Set([
|
|
138
|
+
...EDITOR_ONLY_KEYS,
|
|
139
|
+
...PENDING_EXTRA_MEMBERS,
|
|
140
|
+
...PENDING_INPUT_MEMBERS,
|
|
141
|
+
...SYSTEM_OWNED_INPUT_MEMBERS,
|
|
142
|
+
...DEPRECATED_INPUT_MEMBERS,
|
|
143
|
+
]);
|
|
144
|
+
/**
|
|
145
|
+
* Property keys assembly could not resolve to a node, and could not account for
|
|
146
|
+
* as a deliberate omission. Should always be empty; a test enforces it.
|
|
147
|
+
*/
|
|
148
|
+
export const UNRESOLVED_VARIANT_KEYS = [];
|
|
149
|
+
/**
|
|
150
|
+
* Prose for each element on WHEN to reach for it, and what shape of data it
|
|
151
|
+
* captures.
|
|
152
|
+
*
|
|
153
|
+
* This is the Form Creation Guide's Step 3 decision table ("Choose the control
|
|
154
|
+
* for each field"), restated per element so the choice is made on the shape of
|
|
155
|
+
* the data rather than on what sounds right. Four of the guide's twelve most
|
|
156
|
+
* common mistakes are wrong-element mistakes — a dropdown for a long list, a
|
|
157
|
+
* short custom list left as a dropdown, a number left as text, an API select
|
|
158
|
+
* for something that is really a workflow document — and each of those rules
|
|
159
|
+
* is stated on the element it belongs to.
|
|
160
|
+
*
|
|
161
|
+
* One note in particular changed on the strength of the guide. The
|
|
162
|
+
* autocomplete used to be recommended "over a select when the list is long".
|
|
163
|
+
* The guide draws the line elsewhere: an autocomplete is for a list the user
|
|
164
|
+
* may ADD TO, because it stores whatever is typed, and a merely long list is a
|
|
165
|
+
* paginated selection table. The old sentence would have steered a model to
|
|
166
|
+
* the wrong element for exactly the case the guide calls out.
|
|
167
|
+
*/
|
|
168
|
+
const ELEMENT_NOTES = {
|
|
169
|
+
[ElementTypes.Input]: "A single-line field for text, a number, an email, a phone number, a code or a memorable date. Set type to the data — 'number' for an amount or a quantity, 'email', 'tel' — never leave a number as text.",
|
|
170
|
+
[ElementTypes.Textarea]: 'A few sentences of free text: an explanation, a comment, a reason. Always full width, with a maximum length so the user knows the limit.',
|
|
171
|
+
[ElementTypes.Select]: 'One or several choices from a closed list the user may not add to. Options come from a fixed custom list when policy owns them and from an API when a system owns them; never retype a system list by hand.',
|
|
172
|
+
[ElementTypes.AutoCompleteInput]: 'A text field that suggests known options but stores whatever the user types. Use it when the user may give an answer that is not on the list, or for a lookup key such as a supplier number; not for a value a decision gate reads, since the stored value may be free text. For a merely long list use a paginatedSelectionTable instead.',
|
|
173
|
+
[ElementTypes.Toggle]: "A yes/no state, storing a boolean, with trueLabel and falseLabel naming the two real states. For a review decision its formControlName must be exactly the review step's control name, required and never conditionally hidden.",
|
|
174
|
+
[ElementTypes.DatePicker]: "A single date the user knows or looks up: a delivery date, an invoice date. For a memorable date far in the past, such as a date of birth, use an input of type 'date' instead, because a calendar is slow there. A date the engine schedules on must be required.",
|
|
175
|
+
[ElementTypes.DateRangePicker]: 'A period with a start and an end, stored under two control names of its own.',
|
|
176
|
+
[ElementTypes.DateTimePicker]: 'A single moment — a date AND a time of day — stored as one value: an appointment, a site visit, an inspection, a deadline that falls at a particular hour. Use a plain datePicker when the hour does not matter; the extra time field is friction the user pays for nothing.',
|
|
177
|
+
[ElementTypes.FileUpload]: 'A document, a picture from disk or a photo from the camera. State the accepted types in the hint, and allow several only when several are genuinely expected.',
|
|
178
|
+
[ElementTypes.SectionTitle]: 'A heading that breaks a slide into groups. Captures no value, so it needs no control name. Put one above every group of three to five related fields and carry the guidance for the group in its hintLabel: where the source documents are, or the rule the group must satisfy.',
|
|
179
|
+
[ElementTypes.MultipleInput]: 'A repeatable group of rows of the same shape, for a record that contains a list of records: order lines, attendees, assets, bidders. A row may hold any ordinary field, including a number, a date, a select or an account picker. Show totals for the numeric columns.',
|
|
180
|
+
[ElementTypes.Editor]: 'A rich-text area for formatted prose. Use a plain textarea unless formatting genuinely carries meaning, as in a specification or a resolution.',
|
|
181
|
+
[ElementTypes.PaginatedSelectionTable]: 'A paged, searchable table for choosing from a very large list, or from options that need several columns to tell apart, such as a supplier number, name and status. A plain dropdown for a long list is a common mistake.',
|
|
182
|
+
[ElementTypes.MscoaSelection]: 'Picks an account from the municipal chart of accounts, segment by segment. Stores an account object, never a number, so amounts are always separate number inputs. Only for accounting contexts.',
|
|
183
|
+
[ElementTypes.WorkflowDocumentPicker]: 'Picks an existing transaction of another workflow: an approved budget, the original requisition, a contract. Use it, never a select over an API, whenever the option is a workflow transaction; it already knows the workflow, its columns, its search and its steps.',
|
|
184
|
+
[ElementTypes.WorkflowAdjudication]: 'Captures tender adjudication scoring. Only within an adjudication workflow.',
|
|
185
|
+
[ElementTypes.Signature]: 'Captures a drawn signature. Every review form carries one; elsewhere only where a signature has legal or procedural meaning, since the system already records who submitted.',
|
|
186
|
+
[ElementTypes.ImageCapture]: 'Captures a photo from the device camera.',
|
|
187
|
+
[ElementTypes.Location]: 'Captures a place on a map, stored as an object of coordinates rather than text.',
|
|
188
|
+
[ElementTypes.IconSelect]: 'Chooses an icon from a picker, for an option best recognised by a picture. Rare.',
|
|
189
|
+
};
|
|
190
|
+
/** The elements worth offering before the exotic ones have been proven out. */
|
|
191
|
+
export const COMMON_ELEMENTS = [
|
|
192
|
+
ElementTypes.Input,
|
|
193
|
+
ElementTypes.Textarea,
|
|
194
|
+
ElementTypes.Select,
|
|
195
|
+
ElementTypes.Toggle,
|
|
196
|
+
ElementTypes.DatePicker,
|
|
197
|
+
ElementTypes.DateRangePicker,
|
|
198
|
+
ElementTypes.FileUpload,
|
|
199
|
+
ElementTypes.SectionTitle,
|
|
200
|
+
];
|
|
201
|
+
/**
|
|
202
|
+
* How many levels of nested list contents to offer by default.
|
|
203
|
+
*
|
|
204
|
+
* One level means a form may contain an item list, and that list's rows may
|
|
205
|
+
* contain ordinary fields — which is the shape nearly every real item list has.
|
|
206
|
+
* Two would allow a list inside a list; it is available, but it multiplies the
|
|
207
|
+
* schema by roughly the size of a whole column union each time, so it is not
|
|
208
|
+
* the default.
|
|
209
|
+
*/
|
|
210
|
+
export const DEFAULT_NESTING_DEPTH = 1;
|
|
211
|
+
/** Resolves one listed property key to its node, or explains its absence. */
|
|
212
|
+
function resolveMember(key, ctx) {
|
|
213
|
+
// Checked before anything else: a member the validator will not accept must
|
|
214
|
+
// not reach a generated form even when a registry has a perfectly good node
|
|
215
|
+
// for it. See JOI_UNKNOWN_COLUMN_KEYS.
|
|
216
|
+
if (JOI_UNKNOWN_COLUMN_KEYS.has(key))
|
|
217
|
+
return null;
|
|
218
|
+
// The context-built members are checked first and NEVER fall through to
|
|
219
|
+
// `UNRESOLVED_VARIANT_KEYS`. A null here is a decision — no workflows were
|
|
220
|
+
// offered, or the nesting budget is spent — not a key nobody modelled.
|
|
221
|
+
if (key === AllFormInputPrimaryKeys.MatOptions)
|
|
222
|
+
return ctx.matOptions;
|
|
223
|
+
if (key === AllFormInputPrimaryKeys.Validators)
|
|
224
|
+
return ctx.validators;
|
|
225
|
+
if (key === AllFormInputPrimaryKeys.WorkflowPickerConfig) {
|
|
226
|
+
return ctx.workflowPicker;
|
|
227
|
+
}
|
|
228
|
+
if (key === AllFormInputPrimaryKeys.FormInputs) {
|
|
229
|
+
if (!ctx.nestedColumns)
|
|
230
|
+
return null;
|
|
231
|
+
return s.array('The fields captured for each row of this list, one per column, each with its own control name and column size (a description at 12; quantity, unit price and line total at 4, 4 and 4). Give each one a control name unique within the row; they do not need to be unique across the whole form. A total over these rows that is used anywhere else belongs in a calculated field on the form itself, not only in a roll-up beneath the list.', ctx.nestedColumns, { minItems: 1 });
|
|
232
|
+
}
|
|
233
|
+
const registries = [
|
|
234
|
+
inputMemberSchemas,
|
|
235
|
+
draftMemberSchemas,
|
|
236
|
+
extraInputMemberSchemas,
|
|
237
|
+
extraDraftMemberSchemas,
|
|
238
|
+
];
|
|
239
|
+
for (const registry of registries) {
|
|
240
|
+
const node = registry[key];
|
|
241
|
+
if (node)
|
|
242
|
+
return node;
|
|
243
|
+
}
|
|
244
|
+
if (!SKIPPED_KEYS.has(key))
|
|
245
|
+
UNRESOLVED_VARIANT_KEYS.push(key);
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Whether an element can be offered at all in this context.
|
|
250
|
+
*
|
|
251
|
+
* Each of these withdraws an element rather than emitting a broken one. An
|
|
252
|
+
* element whose `requiredProperties` name a member we cannot supply would
|
|
253
|
+
* otherwise be generated without it and fail `validateForm()` every time — the
|
|
254
|
+
* exact failure the Joi-mandatory keys were introduced to stop.
|
|
255
|
+
*/
|
|
256
|
+
function canOffer(element, ctx, nested) {
|
|
257
|
+
const required = new Set(DefaultInputConfig[element]?.requiredProperties ?? []);
|
|
258
|
+
// `formInputs` is required of an item list. With the nesting budget spent
|
|
259
|
+
// there is nothing to put in one, so the element goes rather than the member.
|
|
260
|
+
if (required.has(AllFormInputPrimaryKeys.FormInputs) && !ctx.nestedColumns) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
// Same for a document picker with no workflow to point at.
|
|
264
|
+
if (required.has(AllFormInputPrimaryKeys.WorkflowPickerConfig) &&
|
|
265
|
+
!ctx.workflowPicker) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
// A section title is exempt from the mandatory base because `validateForm()`
|
|
269
|
+
// strips section titles from a SLIDE before validating. It does not strip
|
|
270
|
+
// them from `formInputs`, which Joi validates through `Joi.link` against the
|
|
271
|
+
// full column schema — so a nested heading would be required to have a
|
|
272
|
+
// `formControlName` and a `dataType` it is deliberately built without, and
|
|
273
|
+
// would fail. Headings therefore only exist at slide level.
|
|
274
|
+
if (nested && element === ElementTypes.SectionTitle)
|
|
275
|
+
return false;
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Builds the object schema(s) for one element.
|
|
280
|
+
*
|
|
281
|
+
* Returns a list because one element can contribute more than one branch — see
|
|
282
|
+
* the calculated-field split below.
|
|
283
|
+
*/
|
|
284
|
+
function buildVariant(element, ctx) {
|
|
285
|
+
const config = DefaultInputConfig[element];
|
|
286
|
+
if (!config)
|
|
287
|
+
return [];
|
|
288
|
+
const mandatory = EXEMPT_FROM_MANDATORY.has(element)
|
|
289
|
+
? []
|
|
290
|
+
: JOI_MANDATORY_COLUMN_KEYS;
|
|
291
|
+
const declared = new Set(config.properties ?? []);
|
|
292
|
+
const listed = [
|
|
293
|
+
...new Set([
|
|
294
|
+
...declared,
|
|
295
|
+
...mandatory,
|
|
296
|
+
...rowRollUps(declared),
|
|
297
|
+
...alwaysShown(element),
|
|
298
|
+
]),
|
|
299
|
+
];
|
|
300
|
+
const required = new Set([
|
|
301
|
+
...(config.requiredProperties ?? []),
|
|
302
|
+
...mandatory,
|
|
303
|
+
]);
|
|
304
|
+
const shape = {};
|
|
305
|
+
for (const key of listed) {
|
|
306
|
+
if (key === AllFormInputPrimaryKeys.Element)
|
|
307
|
+
continue;
|
|
308
|
+
const node = resolveMember(key, ctx);
|
|
309
|
+
if (!node)
|
|
310
|
+
continue;
|
|
311
|
+
shape[key] = required.has(key) ? node : optional(node);
|
|
312
|
+
}
|
|
313
|
+
const note = ELEMENT_NOTES[element];
|
|
314
|
+
const description = note ? `${config.label}. ${note}` : `${config.label}.`;
|
|
315
|
+
if (!canBeCalculated(declared)) {
|
|
316
|
+
return [s.object(description, { ...discriminator(element), ...shape })];
|
|
317
|
+
}
|
|
318
|
+
return calculatedFieldSplit(element, description, shape);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* The `element` discriminator, as a FRESH node each time it is asked for.
|
|
322
|
+
*
|
|
323
|
+
* The whole union hangs off this being a single known value, so it is pinned to
|
|
324
|
+
* a literal rather than reusing the registry's element enum — and, less
|
|
325
|
+
* obviously, it must not be a shared instance. Skillet hoists any node it sees
|
|
326
|
+
* more than once into `$defs` and leaves a `$ref` behind, so reusing one
|
|
327
|
+
* literal across an element's two calculated-field branches turned the
|
|
328
|
+
* discriminator itself into a reference. That is legal JSON Schema and still
|
|
329
|
+
* validates, but it puts the one value a model reads to choose a branch behind
|
|
330
|
+
* an indirection, in the one place indirection is least affordable.
|
|
331
|
+
*/
|
|
332
|
+
function discriminator(element) {
|
|
333
|
+
return { element: s.literal(element) };
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Whether an element can carry a calculated field, from what it lets an author
|
|
337
|
+
* set.
|
|
338
|
+
*
|
|
339
|
+
* ## Why this is not read from `properties`
|
|
340
|
+
*
|
|
341
|
+
* It is the one place assembly departs from `DefaultInputConfig.properties`,
|
|
342
|
+
* so it is worth being exact about why. NO element lists `isCalculatedField` or
|
|
343
|
+
* `calculatedFieldRules` in `properties` — yet the settings panel plainly
|
|
344
|
+
* offers both, through two rows in `ElementEditConfig` named
|
|
345
|
+
* `SpecialElementKeys.Default` and gated only by `additionalTest`:
|
|
346
|
+
* `type === number` for the toggle, `isCalculatedField === true` for the rules.
|
|
347
|
+
*
|
|
348
|
+
* A row named `Default` is not looked up in `properties` at all; its
|
|
349
|
+
* `additionalTest` is the whole of its gate. So `properties` is simply not the
|
|
350
|
+
* list that governs these two, and assembling from it alone left calculated
|
|
351
|
+
* fields unreachable in every generated form — a feature the builder has and
|
|
352
|
+
* the generator silently did not.
|
|
353
|
+
*
|
|
354
|
+
* This is the second time that list has proved incomplete, and the precedent
|
|
355
|
+
* for supplementing it is already here: {@link JOI_MANDATORY_COLUMN_KEYS}
|
|
356
|
+
* exists for exactly the same reason. As there, the supplement is DERIVED
|
|
357
|
+
* rather than hand-listed — an element qualifies when it lets an author set
|
|
358
|
+
* both `type` and `dataType`, because a calculated field must be able to be a
|
|
359
|
+
* number and both must move for that. On today's config that is `input` and
|
|
360
|
+
* `autoCompleteInput`; `datePicker` offers `type` but pins `dataType` to
|
|
361
|
+
* `date` in its template, and so correctly does not qualify.
|
|
362
|
+
*/
|
|
363
|
+
function canBeCalculated(declared) {
|
|
364
|
+
return (declared.has(AllFormInputPrimaryKeys.Type) &&
|
|
365
|
+
declared.has(AllFormInputPrimaryKeys.DataType));
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* The roll-up list an element can carry over its own rows, if it has rows.
|
|
369
|
+
*
|
|
370
|
+
* ## Why this is not read from `properties` either
|
|
371
|
+
*
|
|
372
|
+
* The THIRD time that list has proved incomplete, for the same reason as the
|
|
373
|
+
* two above: `properties` names the rows the settings panel renders, and a
|
|
374
|
+
* roll-up is not authored through a row. It is added by a bespoke control on
|
|
375
|
+
* the item-list editor — `addFunction` on the multiple-input editor, wired at
|
|
376
|
+
* `form-section-stepper.component.ts:147` in `ngx-t-forms` to the store action
|
|
377
|
+
* `addMultipleInputValueCalculationFunction` — and read back at runtime by
|
|
378
|
+
* `getListCalculated.ts`. There is no `ElementEditConfig` entry for
|
|
379
|
+
* `calculateListFunctions` at all, so it could never have appeared in
|
|
380
|
+
* `properties`, and adding it there would make the generic settings panel try
|
|
381
|
+
* to render a row it has no configuration for.
|
|
382
|
+
*
|
|
383
|
+
* So the builder has the feature and, until now, the generator silently did
|
|
384
|
+
* not — the same gap `canBeCalculated` was written to close, in the same shape.
|
|
385
|
+
*
|
|
386
|
+
* Derived rather than hand-listed, as with the other two: an element qualifies
|
|
387
|
+
* when it declares `formInputs`, because a roll-up aggregates a column across
|
|
388
|
+
* that element’s own rows and an element without rows has nothing to total.
|
|
389
|
+
* On today’s config that is `multipleInput` alone.
|
|
390
|
+
*/
|
|
391
|
+
function rowRollUps(declared) {
|
|
392
|
+
return declared.has(AllFormInputPrimaryKeys.FormInputs)
|
|
393
|
+
? [AllFormInputPrimaryKeys.CalculateListFunctions]
|
|
394
|
+
: [];
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* The members every element's settings panel shows regardless of `properties`.
|
|
398
|
+
*
|
|
399
|
+
* The FOURTH supplement to `properties`, and the most general. A row named
|
|
400
|
+
* `SpecialElementKeys.Default` is rendered for every element, gated only by
|
|
401
|
+
* its own tests, and `properties` never lists what such a row edits. So
|
|
402
|
+
* `readonly`, `onlySetTempErrorOnTouch` and `tourContent` were never offered
|
|
403
|
+
* to the model although the builder offers them on every input — and the Form
|
|
404
|
+
* Creation Guide's Step 6.1 treats the first two as settings a designer sets on
|
|
405
|
+
* nearly every field: read-only for a prefilled or fetched value, errors only
|
|
406
|
+
* after the user leaves a field.
|
|
407
|
+
*
|
|
408
|
+
* Derived from the editor config by `ALWAYS_SHOWN_MEMBERS` rather than listed
|
|
409
|
+
* here, for the reason the other three supplements are derived: a hand-kept
|
|
410
|
+
* list is right on the day it is written. Only ungated rows are included; a
|
|
411
|
+
* gated one (`min` needs a numeric type) is conditional on a value, which is
|
|
412
|
+
* what `canBeCalculated` handles structurally and the descriptions handle in
|
|
413
|
+
* prose, and offering it everywhere would put `min` on a toggle.
|
|
414
|
+
*
|
|
415
|
+
* Section titles are exempt, as they are from the mandatory base: their editor
|
|
416
|
+
* shows the rows too, but a heading that captures no value has nothing to make
|
|
417
|
+
* read-only or to validate.
|
|
418
|
+
*/
|
|
419
|
+
function alwaysShown(element) {
|
|
420
|
+
return EXEMPT_FROM_MANDATORY.has(element) ? [] : ALWAYS_SHOWN_MEMBERS;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Splits an element into a plain branch and a calculated branch.
|
|
424
|
+
*
|
|
425
|
+
* ## Encoding the rule instead of describing it
|
|
426
|
+
*
|
|
427
|
+
* Two rules govern calculated fields, and both are conditional in a way Skillet
|
|
428
|
+
* cannot express on a single object:
|
|
429
|
+
*
|
|
430
|
+
* - `calculatedFieldRules` only means anything when `isCalculatedField` is true
|
|
431
|
+
* - `isCalculatedField` only applies when `type` is number
|
|
432
|
+
*
|
|
433
|
+
* Left as prose on a flat object, a model is free to emit rules on a field that
|
|
434
|
+
* is not calculated, or to mark a text field calculated and leave it with no
|
|
435
|
+
* formula. The second is not hypothetical:
|
|
436
|
+
* `validateFormColumnInputsWithRequired` makes `calculatedFieldRules` REQUIRED
|
|
437
|
+
* the moment `isCalculatedField` is true, so a model that sets the flag and
|
|
438
|
+
* stops has produced a column the builder rejects.
|
|
439
|
+
*
|
|
440
|
+
* As two branches of the union the rules stop being advice. A field is either
|
|
441
|
+
* plain — with no rules member existing at all — or calculated, in which case
|
|
442
|
+
* `type` and `dataType` are pinned to number and the rules are mandatory.
|
|
443
|
+
* Neither invalid combination is expressible.
|
|
444
|
+
*
|
|
445
|
+
* ## `isCalculatedField` is derived, not asked for
|
|
446
|
+
*
|
|
447
|
+
* The obvious encoding pins the flag to a literal on each branch, and Skillet
|
|
448
|
+
* cannot do it: `literal<T extends string>` takes strings only, so there is no
|
|
449
|
+
* boolean constant to pin to.
|
|
450
|
+
*
|
|
451
|
+
* That turned out to be the better answer anyway. The flag carries no
|
|
452
|
+
* information the branch does not already carry — it is true exactly when
|
|
453
|
+
* `calculatedFieldRules` is present — so asking a model for it only creates a
|
|
454
|
+
* value that can contradict the rest of the object. It is therefore omitted
|
|
455
|
+
* from the schema entirely and the application sets it during expansion, in the
|
|
456
|
+
* same step that stamps ids: `isCalculatedField = calculatedFieldRules != null`.
|
|
457
|
+
* The interface requires the member (`IFormElementTemplate.isCalculatedField`
|
|
458
|
+
* is not optional), so expansion must set it on EVERY column, plain ones
|
|
459
|
+
* included.
|
|
460
|
+
*
|
|
461
|
+
* ## What this costs, and the one thing it does not enforce
|
|
462
|
+
*
|
|
463
|
+
* It doubles the branch count for the two qualifying elements. That is the
|
|
464
|
+
* whole cost, and it buys the elimination of the most common way a generated
|
|
465
|
+
* calculated field is wrong.
|
|
466
|
+
*
|
|
467
|
+
* `validateForm()` itself does NOT enforce the pairing — `formColumnInputsSchema`
|
|
468
|
+
* marks `calculatedFieldRules` optional, so a flag with no rules passes there.
|
|
469
|
+
* The rule is the builder's, via `validateFormColumnInputsWithRequired`. It is
|
|
470
|
+
* encoded here because a generated form is meant to be indistinguishable from a
|
|
471
|
+
* built one, not merely to clear the looser of the two gates.
|
|
472
|
+
*/
|
|
473
|
+
function calculatedFieldSplit(element, description, shape) {
|
|
474
|
+
const plain = s.object(`${description} The value is entered by the user.`, {
|
|
475
|
+
...discriminator(element),
|
|
476
|
+
...shape,
|
|
477
|
+
});
|
|
478
|
+
const calculated = s.object(`${description} This variant is computed from other fields on the form rather than entered by the user, so it is always a number and always carries its formula.`, {
|
|
479
|
+
...discriminator(element),
|
|
480
|
+
...shape,
|
|
481
|
+
[AllFormInputPrimaryKeys.Type]: s.literal(InputTypes.Number),
|
|
482
|
+
[AllFormInputPrimaryKeys.DataType]: s.literal(InputDataTypes.Number),
|
|
483
|
+
[AllFormInputPrimaryKeys.CalculatedFieldRules]: calculatedFieldRulesDraftSchema,
|
|
484
|
+
});
|
|
485
|
+
return [plain, calculated];
|
|
486
|
+
}
|
|
487
|
+
/** Builds the column union for one nesting level. */
|
|
488
|
+
function buildColumnSchema(options, depth, nested) {
|
|
489
|
+
const requested = options.elements ?? Object.values(ElementTypes);
|
|
490
|
+
// Only unroll the nesting if an item list is actually on offer. Building it
|
|
491
|
+
// unconditionally would double the work — and the emitted schema — for every
|
|
492
|
+
// caller that never asked for one.
|
|
493
|
+
const wantsNesting = requested.includes(ElementTypes.MultipleInput);
|
|
494
|
+
const ctx = {
|
|
495
|
+
matOptions: createMatOptionsSchema(options),
|
|
496
|
+
validators: createValidatorsSchema(options),
|
|
497
|
+
workflowPicker: createWorkflowPickerSchema(options),
|
|
498
|
+
nestedColumns: wantsNesting && depth > 0
|
|
499
|
+
? buildColumnSchema(options, depth - 1, true)
|
|
500
|
+
: null,
|
|
501
|
+
};
|
|
502
|
+
const variants = requested
|
|
503
|
+
.filter((element) => canOffer(element, ctx, nested))
|
|
504
|
+
.flatMap((element) => buildVariant(element, ctx));
|
|
505
|
+
if (!variants.length) {
|
|
506
|
+
throw new Error('createColumnSchema: no elements to offer. Every element passed was either absent from DefaultInputConfig or withdrawn for want of a member it requires.');
|
|
507
|
+
}
|
|
508
|
+
return s.anyOf(variants);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Builds the column schema: a discriminated union over the offered elements.
|
|
512
|
+
*
|
|
513
|
+
* @param options which elements to offer, how deeply item lists may nest, and
|
|
514
|
+
* the endpoints and workflows a `matOptions` source or a document picker may
|
|
515
|
+
* select between. See `members/mat-options.ts` for why those are supplied
|
|
516
|
+
* rather than authored.
|
|
517
|
+
*/
|
|
518
|
+
export function createColumnSchema(options = {}) {
|
|
519
|
+
return buildColumnSchema(options, options.depth ?? DEFAULT_NESTING_DEPTH, false);
|
|
520
|
+
}
|
|
521
|
+
/** Which properties each element contributes, for inspection and testing. */
|
|
522
|
+
export function variantKeys(element, options = {}) {
|
|
523
|
+
const config = DefaultInputConfig[element];
|
|
524
|
+
if (!config)
|
|
525
|
+
return [];
|
|
526
|
+
const ctx = {
|
|
527
|
+
matOptions: createMatOptionsSchema(options),
|
|
528
|
+
validators: createValidatorsSchema(options),
|
|
529
|
+
workflowPicker: createWorkflowPickerSchema(options),
|
|
530
|
+
nestedColumns: null,
|
|
531
|
+
};
|
|
532
|
+
return [
|
|
533
|
+
...new Set([...(config.properties ?? []), ...alwaysShown(element)]),
|
|
534
|
+
].filter((key) => key !== AllFormInputPrimaryKeys.Element &&
|
|
535
|
+
resolveMember(key, ctx) !== null);
|
|
536
|
+
}
|