@genesislcap/foundation-forms 14.496.2-alpha-da9ccbd.0 → 14.497.0
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/custom-elements.json +341 -1
- package/dist/dts/filters/filters.d.ts +0 -1
- package/dist/dts/filters/filters.d.ts.map +1 -1
- package/dist/dts/form.d.ts +94 -1
- package/dist/dts/form.d.ts.map +1 -1
- package/dist/dts/form.styles.d.ts.map +1 -1
- package/dist/dts/form.template.d.ts.map +1 -1
- package/dist/dts/index.d.ts +3 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/jsonforms/renderers/CategorizationWrapperRenderer.d.ts.map +1 -1
- package/dist/dts/react.d.ts +10 -0
- package/dist/dts/review.d.ts +34 -0
- package/dist/dts/review.d.ts.map +1 -0
- package/dist/dts/review.styles.d.ts +3 -0
- package/dist/dts/review.styles.d.ts.map +1 -0
- package/dist/dts/review.template.d.ts +6 -0
- package/dist/dts/review.template.d.ts.map +1 -0
- package/dist/dts/utils/index.d.ts +1 -0
- package/dist/dts/utils/index.d.ts.map +1 -1
- package/dist/dts/utils/review-utils.d.ts +82 -0
- package/dist/dts/utils/review-utils.d.ts.map +1 -0
- package/dist/dts/utils/schema-utils.d.ts +12 -0
- package/dist/dts/utils/schema-utils.d.ts.map +1 -1
- package/dist/esm/form.js +132 -3
- package/dist/esm/form.styles.js +9 -2
- package/dist/esm/form.template.js +63 -25
- package/dist/esm/index.js +3 -0
- package/dist/esm/jsonforms/renderers/CategorizationWrapperRenderer.js +1 -45
- package/dist/esm/review.js +63 -0
- package/dist/esm/review.styles.js +93 -0
- package/dist/esm/review.template.js +39 -0
- package/dist/esm/utils/index.js +1 -0
- package/dist/esm/utils/review-utils.js +241 -0
- package/dist/esm/utils/schema-utils.js +48 -0
- package/dist/foundation-forms.api.json +428 -0
- package/dist/foundation-forms.d.ts +224 -2
- package/dist/react.cjs +7 -0
- package/dist/react.mjs +6 -0
- package/package.json +15 -15
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/review.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,WAAW,EAAwB,MAAM,yBAAyB,CAAC;AAGjG,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAEhF;;;;GAIG;AACH,qBAMa,MAAO,SAAQ,WAAW;IACQ,MAAM,EAAE,MAAM,CAAW;IAEtE;;;OAGG;IACG,OAAO,EAAE,MAAM,CAAY;IAEjC;;;OAGG;IAC+B,QAAQ,EAAE,MAAM,CAAC;IAEnD;;;OAGG;IACS,QAAQ,EAAE,aAAa,EAAE,CAAM;IAE3C;;;;OAIG;IACS,UAAU,EAAE,MAAM,CAAM;IAEpC,gBAAgB;IAChB,IACI,gBAAgB,IAAI,aAAa,EAAE,CAEtC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.styles.d.ts","sourceRoot":"","sources":["../../src/review.styles.ts"],"names":[],"mappings":"AAKA,gBAAgB;AAChB,eAAO,MAAM,0BAA0B,iDAyFtC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Review } from './review';
|
|
2
|
+
/** @internal */
|
|
3
|
+
export declare const getPrefixedReview: (prefix: any) => import("@microsoft/fast-element").ViewTemplate<Review, any>;
|
|
4
|
+
/** @internal */
|
|
5
|
+
export declare const foundationFormReviewTemplate: import("@microsoft/fast-element").ViewTemplate<Review, any>;
|
|
6
|
+
//# sourceMappingURL=review.template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.template.d.ts","sourceRoot":"","sources":["../../src/review.template.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGvC,gBAAgB;AAChB,eAAO,MAAM,iBAAiB,GAAI,WAAM,gEA2CvC,CAAC;AAEF,gBAAgB;AAChB,eAAO,MAAM,4BAA4B,6DAExC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** @public */
|
|
2
|
+
export type ReviewField = {
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
};
|
|
6
|
+
/** @public */
|
|
7
|
+
export type ReviewSection = {
|
|
8
|
+
title: string | null;
|
|
9
|
+
fields: ReviewField[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Builds the review panel's fields grouped into sections by their nearest `Group` layout.
|
|
13
|
+
* Sections are formed from consecutive runs of the same group title as fields are
|
|
14
|
+
* encountered in the UI schema — not a global title lookup — so two fields that happen to
|
|
15
|
+
* share a title (including `null`, for ungrouped fields) don't get merged into one section
|
|
16
|
+
* unless they're actually adjacent. Otherwise an ungrouped field appearing after a Group
|
|
17
|
+
* would jump back up next to earlier ungrouped fields instead of staying where it sits in
|
|
18
|
+
* the form, and two distinct Groups with the same label would collapse into one. Falls back
|
|
19
|
+
* to a flat, single ungrouped section when no UI schema is available.
|
|
20
|
+
*
|
|
21
|
+
* Async because connected-select fields backed by `options.allOptionsResourceName` (rather
|
|
22
|
+
* than a static `options.data`) resolve their display label with a criteria-filtered fetch
|
|
23
|
+
* — see `resolveResourceOptionLabel`. The UI schema is walked synchronously to collect one
|
|
24
|
+
* value-resolution promise per field (preserving encounter order), then all promises settle
|
|
25
|
+
* in parallel via `Promise.all` before fields are added to their sections — so a slow fetch
|
|
26
|
+
* for one field can't reorder fields that resolved faster.
|
|
27
|
+
*
|
|
28
|
+
* This is the single walker of the UI schema for review purposes — `Form` calls it directly
|
|
29
|
+
* to populate `Review.sections`. Don't reimplement this traversal elsewhere; the two
|
|
30
|
+
* previously did drift (a since-removed `collectReviewFields` resolved labels via
|
|
31
|
+
* `resolveOptionLabel` / `resolveEnumLabel` only, silently skipping `resolveResourceOptionLabel`
|
|
32
|
+
* and showing raw ids for connected-select fields).
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildReviewSections(data: Record<string, any>, uiSchema: any, schema: any): Promise<ReviewSection[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Filters review sections down to fields whose label or value contains `query`
|
|
38
|
+
* (case-insensitive). Sections left with no matching fields are dropped entirely.
|
|
39
|
+
* Returns `sections` unchanged when `query` is blank.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export declare function filterReviewSections(sections: ReviewSection[], query: string): ReviewSection[];
|
|
43
|
+
/**
|
|
44
|
+
* Looks up the display label for a raw value from `options.data` (used by segmented,
|
|
45
|
+
* radio group, connected-select, and enum-with-explicit-data renderers). Honors
|
|
46
|
+
* `options.valueField`/`options.labelField` when set, defaulting to `value`/`label`.
|
|
47
|
+
* Returns `null` when no match found.
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveOptionLabel(raw: any, options: any): string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Resolves the display label for a connected-select field backed by
|
|
53
|
+
* `options.allOptionsResourceName` (a Data Server/Request Server resource) rather than a
|
|
54
|
+
* static `options.data` array — e.g. `ConnectedSelectControlRenderer` in async/remote mode.
|
|
55
|
+
* Fetches a single row via a criteria-filtered snapshot matching `valueField === raw`,
|
|
56
|
+
* then reads `labelField` off the returned row. Returns `null` when there's no resource
|
|
57
|
+
* to query, the resource can't be reached, or no row matches.
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolveResourceOptionLabel(raw: any, options: any): Promise<string | null>;
|
|
61
|
+
/**
|
|
62
|
+
* For plain enum combobox fields (no options.data), applies the same `capitalCase`
|
|
63
|
+
* transform the renderer uses — e.g. `SEMI_ANNUAL` → `Semi Annual`.
|
|
64
|
+
* Navigates the JSON schema via the fieldName path to confirm the field is an enum
|
|
65
|
+
* before transforming. Returns `null` for non-enum fields.
|
|
66
|
+
* @internal
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveEnumLabel(raw: any, fieldName: string, schema: any): string | null;
|
|
69
|
+
/**
|
|
70
|
+
* Navigates a `/properties/`-separated path in data to support nested object scopes
|
|
71
|
+
* such as `ADDRESS/properties/STREET` produced by Group layout UI schemas.
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
export declare function getDataValueAtPath(data: any, fieldName: string): any;
|
|
75
|
+
/**
|
|
76
|
+
* Converts a raw field value to a display string for the review panel.
|
|
77
|
+
* Arrays of primitives are comma-joined; arrays of objects and plain objects
|
|
78
|
+
* are rendered as readable `key: value` lines.
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
export declare function formatValueForReview(value: any): string;
|
|
82
|
+
//# sourceMappingURL=review-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-utils.d.ts","sourceRoot":"","sources":["../../../src/utils/review-utils.ts"],"names":[],"mappings":"AAIA,cAAc;AACd,MAAM,MAAM,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,cAAc;AACd,MAAM,MAAM,aAAa,GAAG;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,QAAQ,EAAE,GAAG,EACb,MAAM,EAAE,GAAG,GACV,OAAO,CAAC,aAAa,EAAE,CAAC,CAuE1B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,CAc9F;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,CAMxE;AAED;;;;;;;;GAQG;AACH,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA8B/F;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,CAiBxF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,CAEpE;AAYD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,CAwBvD"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ErrorObject } from 'ajv';
|
|
1
2
|
import type { UiSchema, UiSchemaElement } from '../types';
|
|
2
3
|
/**
|
|
3
4
|
* Extracted control info for grid layout rendering.
|
|
@@ -36,6 +37,17 @@ export declare function withGridView(uiSchema: UiSchema): UiSchema;
|
|
|
36
37
|
* @internal
|
|
37
38
|
*/
|
|
38
39
|
export declare function isBulkUiSchema(uischema: UiSchema): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Converts AJV validation errors to the JSON Schema scope strings they correspond to
|
|
42
|
+
* (e.g. `#/properties/ADDRESS/properties/STREET`). Used to match errors back to UI
|
|
43
|
+
* schema controls for features like categorization-tab error indicators.
|
|
44
|
+
*/
|
|
45
|
+
export declare function extractErrorScopes(errors: ErrorObject[]): string[];
|
|
46
|
+
/**
|
|
47
|
+
* Returns true when `uiSchema` (or any of its descendants) has a scope that appears
|
|
48
|
+
* in `errorScopes`. Walks both `elements` and `options.childElements`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function uiSchemaHasAnyError(uiSchema: UiSchemaElement | UiSchema, errorScopes: string[]): boolean;
|
|
39
51
|
/**
|
|
40
52
|
* Generates a UI schema suitable for bulk insert mode with array control.
|
|
41
53
|
* @param userProvidedUiSchema - Optional user-provided UI schema to use as childUiSchema
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-utils.d.ts","sourceRoot":"","sources":["../../../src/utils/schema-utils.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"schema-utils.d.ts","sourceRoot":"","sources":["../../../src/utils/schema-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAClC,OAAO,KAAK,EAAiC,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEzF;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,QAAQ,EAAE,eAAe,CAAC;IAC1B,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,SAAS,EAAE,MAAM,CAAC;CACnB;AAaD;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,QAAQ,GAAG,gBAAgB,EAAE,CA6ClF;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAiBzD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAK1D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,EAAE,CAelE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,GAAG,QAAQ,EACpC,WAAW,EAAE,MAAM,EAAE,GACpB,OAAO,CAmBT;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,oBAAoB,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,QAAQ,CAoBrF"}
|
package/dist/esm/form.js
CHANGED
|
@@ -2,7 +2,7 @@ import { __awaiter, __decorate, __rest } from "tslib";
|
|
|
2
2
|
import { Connect } from '@genesislcap/foundation-comms';
|
|
3
3
|
import { LifecycleMixin } from '@genesislcap/foundation-utils';
|
|
4
4
|
import JsonSchemaDereferencer from '@json-schema-tools/dereferencer';
|
|
5
|
-
import { isControl, isStringControl, mapStateToControlProps, rankWith } from '@jsonforms/core';
|
|
5
|
+
import { isControl, isStringControl, mapStateToControlProps, rankWith, } from '@jsonforms/core';
|
|
6
6
|
import { attr, customElement, DOM, observable, Observable, volatile, } from '@microsoft/fast-element';
|
|
7
7
|
import { FoundationElement } from '@microsoft/fast-foundation';
|
|
8
8
|
import { foundationFormStyles as styles } from './form.styles';
|
|
@@ -29,7 +29,7 @@ import { NumberControlRendererEntry } from './jsonforms/renderers/NumberControlR
|
|
|
29
29
|
import { SegmentedControlRendererEntry } from './jsonforms/renderers/SegmentedControlRenderer';
|
|
30
30
|
import { StringArrayEntry } from './jsonforms/renderers/StringArrayControlRenderer';
|
|
31
31
|
import { StringControlRendererTemplate } from './jsonforms/renderers/StringControlRenderer';
|
|
32
|
-
import { downloadCsvTemplate as downloadCsvFile, extractFieldsFromUiSchema, generateCsvTemplate, logger, mapCsvToSchema, mapGenesisFieldErrorsToAdditionalErrors, parseCsv, } from './utils';
|
|
32
|
+
import { buildReviewSections, downloadCsvTemplate as downloadCsvFile, extractFieldsFromUiSchema, generateCsvTemplate, getDataValueAtPath, logger, mapCsvToSchema, mapGenesisFieldErrorsToAdditionalErrors, parseCsv, } from './utils';
|
|
33
33
|
import { findModalParent, showConfirmationDialog } from './utils/confirmation-dialog-utils';
|
|
34
34
|
import { removeDataPropertiesNotInSchema } from './utils/form-utils';
|
|
35
35
|
import { generateBulkUiSchema, isBulkUiSchema } from './utils/schema-utils';
|
|
@@ -149,17 +149,55 @@ let Form = class Form extends LifecycleMixin(FoundationElement) {
|
|
|
149
149
|
* @public
|
|
150
150
|
*/
|
|
151
151
|
this.data = {};
|
|
152
|
+
/**
|
|
153
|
+
* Text to appear on submit button
|
|
154
|
+
* @default Submit
|
|
155
|
+
* @public
|
|
156
|
+
*/
|
|
157
|
+
this.submitButtonText = 'Submit';
|
|
158
|
+
/**
|
|
159
|
+
* Text to appear on submit button in bulk insert mode
|
|
160
|
+
* @default Submit AllS
|
|
161
|
+
* @public
|
|
162
|
+
*/
|
|
163
|
+
this.bulkSubmitButtonText = 'Submit All';
|
|
164
|
+
/**
|
|
165
|
+
* Text to appear on reset button
|
|
166
|
+
* @default Submit
|
|
167
|
+
* @public
|
|
168
|
+
*/
|
|
169
|
+
this.resetButtonText = 'Reset';
|
|
152
170
|
/**
|
|
153
171
|
* Minimum number of items required in bulk insert mode.
|
|
154
172
|
* @public
|
|
155
173
|
*/
|
|
156
174
|
this.bulkInsertMinItems = 1;
|
|
175
|
+
/**
|
|
176
|
+
* Text to appear on reset button
|
|
177
|
+
* @public
|
|
178
|
+
*/
|
|
179
|
+
this.reviewButtonText = 'Review';
|
|
180
|
+
/**
|
|
181
|
+
* Heading shown at the top of the review panel.
|
|
182
|
+
* @public
|
|
183
|
+
*/
|
|
184
|
+
this.reviewHeading = 'Review';
|
|
157
185
|
/**
|
|
158
186
|
* Tracks the submission status for each row in bulk insert mode.
|
|
159
187
|
* Key is the row index, value is the status object.
|
|
160
188
|
* @public
|
|
161
189
|
*/
|
|
162
190
|
this.rowSubmitStatuses = new Map();
|
|
191
|
+
/**
|
|
192
|
+
* Whether the form is currently showing the submit-review preview panel.
|
|
193
|
+
* @internal
|
|
194
|
+
*/
|
|
195
|
+
this.reviewMode = false;
|
|
196
|
+
/**
|
|
197
|
+
* Field label/value pairs rendered in the review panel, grouped into sections.
|
|
198
|
+
* @internal
|
|
199
|
+
*/
|
|
200
|
+
this.reviewSections = [];
|
|
163
201
|
}
|
|
164
202
|
resourceNameChanged() {
|
|
165
203
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -258,6 +296,17 @@ let Form = class Form extends LifecycleMixin(FoundationElement) {
|
|
|
258
296
|
const { renderers: defaultRenderers = [], additionalRenderers = [] } = this;
|
|
259
297
|
this.mergedRenderers = [...defaultRenderers, ...additionalRenderers];
|
|
260
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Value of `reviewIdField` resolved from the current form data, if set.
|
|
301
|
+
* @internal
|
|
302
|
+
*/
|
|
303
|
+
get reviewRecordId() {
|
|
304
|
+
if (!this.reviewIdField) {
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
const value = getDataValueAtPath(this.data, this.reviewIdField);
|
|
308
|
+
return value === undefined || value === null ? undefined : String(value);
|
|
309
|
+
}
|
|
261
310
|
/**
|
|
262
311
|
* @internal
|
|
263
312
|
*/
|
|
@@ -287,11 +336,62 @@ let Form = class Form extends LifecycleMixin(FoundationElement) {
|
|
|
287
336
|
yield this._submitBulk();
|
|
288
337
|
return;
|
|
289
338
|
}
|
|
339
|
+
// Show review preview if enabled and the form has no validation errors.
|
|
340
|
+
if (this.showSubmitReview && !this.hasValidationErrors()) {
|
|
341
|
+
this.reviewSections = yield buildReviewSections(this.data || {}, this.userProvidedUiSchema || this.uischema, this.originalDetailsSchema || this.jsonSchema);
|
|
342
|
+
this.reviewMode = true;
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
yield this._performSubmit();
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Called from the review panel to go back to the form.
|
|
350
|
+
* @internal
|
|
351
|
+
*/
|
|
352
|
+
backFromReview() {
|
|
353
|
+
this.reviewMode = false;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Called from the review panel submit button to perform the actual submission.
|
|
357
|
+
* Intentionally skips `confirmationMessage` — that gate already ran once in `_submit()`
|
|
358
|
+
* before review mode was entered, and re-confirming here would ask the user twice.
|
|
359
|
+
* @internal
|
|
360
|
+
*/
|
|
361
|
+
_submitFromReview() {
|
|
362
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
363
|
+
this.reviewMode = false;
|
|
364
|
+
yield this._performSubmit();
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Whether the form currently has validation errors. Reads live errors directly from the
|
|
369
|
+
* json-forms element when it's mounted, so we always use the current validation state
|
|
370
|
+
* (this.errors can lag in edge cases such as required fields with empty-string values not
|
|
371
|
+
* yet reported via data-change). Falls back to this.errors when json-forms isn't mounted
|
|
372
|
+
* (e.g. while the read-only review panel is showing), which is safe since no interactive
|
|
373
|
+
* editing happens during review.
|
|
374
|
+
* @internal
|
|
375
|
+
*/
|
|
376
|
+
hasValidationErrors() {
|
|
377
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
378
|
+
const jfEl = this.jsonFormsEl;
|
|
379
|
+
return jfEl
|
|
380
|
+
? ((_d = (_c = (_b = (_a = jfEl.jsonforms) === null || _a === void 0 ? void 0 : _a.core) === null || _b === void 0 ? void 0 : _b.errors) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0 ||
|
|
381
|
+
((_h = (_g = (_f = (_e = jfEl.jsonforms) === null || _e === void 0 ? void 0 : _e.core) === null || _f === void 0 ? void 0 : _f.additionalErrors) === null || _g === void 0 ? void 0 : _g.length) !== null && _h !== void 0 ? _h : 0) > 0
|
|
382
|
+
: this.errors.length > 0;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Performs the actual API submission after review (or directly when review is disabled).
|
|
386
|
+
* @internal
|
|
387
|
+
*/
|
|
388
|
+
_performSubmit() {
|
|
389
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
290
390
|
const payload = Object.assign({}, this.data);
|
|
291
391
|
const commitPayload = this.buildCommitPayload(payload);
|
|
292
392
|
logger.debug({ payload, errors: this.errors });
|
|
293
393
|
this.$emit('submit', { payload, errors: this.errors });
|
|
294
|
-
if (Object.entries(payload).length !== 0 && !this.
|
|
394
|
+
if (Object.entries(payload).length !== 0 && !this.hasValidationErrors() && this.resourceName) {
|
|
295
395
|
this.submitting = true;
|
|
296
396
|
const formSubmitReq = yield this.connect.commitEvent(this.resourceName, commitPayload);
|
|
297
397
|
this.submitting = false;
|
|
@@ -638,6 +738,8 @@ let Form = class Form extends LifecycleMixin(FoundationElement) {
|
|
|
638
738
|
*/
|
|
639
739
|
reset(clearData = true) {
|
|
640
740
|
this.submitted = false;
|
|
741
|
+
this.reviewMode = false;
|
|
742
|
+
this.reviewSections = [];
|
|
641
743
|
this.clearRowSubmitStatuses();
|
|
642
744
|
this.serverFieldErrors = [];
|
|
643
745
|
if (clearData) {
|
|
@@ -902,6 +1004,15 @@ __decorate([
|
|
|
902
1004
|
__decorate([
|
|
903
1005
|
attr({ attribute: 'bulk-insert', mode: 'boolean' })
|
|
904
1006
|
], Form.prototype, "bulkInsert", void 0);
|
|
1007
|
+
__decorate([
|
|
1008
|
+
attr({ attribute: 'submit-button-text' })
|
|
1009
|
+
], Form.prototype, "submitButtonText", void 0);
|
|
1010
|
+
__decorate([
|
|
1011
|
+
attr({ attribute: 'bulk-submit-button-text' })
|
|
1012
|
+
], Form.prototype, "bulkSubmitButtonText", void 0);
|
|
1013
|
+
__decorate([
|
|
1014
|
+
attr({ attribute: 'reset-button-text' })
|
|
1015
|
+
], Form.prototype, "resetButtonText", void 0);
|
|
905
1016
|
__decorate([
|
|
906
1017
|
attr({
|
|
907
1018
|
attribute: 'bulk-insert-min-items',
|
|
@@ -920,9 +1031,27 @@ __decorate([
|
|
|
920
1031
|
},
|
|
921
1032
|
})
|
|
922
1033
|
], Form.prototype, "bulkInsertMaxItems", void 0);
|
|
1034
|
+
__decorate([
|
|
1035
|
+
attr({ attribute: 'show-submit-review', mode: 'boolean' })
|
|
1036
|
+
], Form.prototype, "showSubmitReview", void 0);
|
|
1037
|
+
__decorate([
|
|
1038
|
+
attr({ attribute: 'review-button-text' })
|
|
1039
|
+
], Form.prototype, "reviewButtonText", void 0);
|
|
1040
|
+
__decorate([
|
|
1041
|
+
attr({ attribute: 'review-heading' })
|
|
1042
|
+
], Form.prototype, "reviewHeading", void 0);
|
|
1043
|
+
__decorate([
|
|
1044
|
+
attr({ attribute: 'review-id-field' })
|
|
1045
|
+
], Form.prototype, "reviewIdField", void 0);
|
|
923
1046
|
__decorate([
|
|
924
1047
|
observable
|
|
925
1048
|
], Form.prototype, "rowSubmitStatuses", void 0);
|
|
1049
|
+
__decorate([
|
|
1050
|
+
observable
|
|
1051
|
+
], Form.prototype, "reviewMode", void 0);
|
|
1052
|
+
__decorate([
|
|
1053
|
+
observable
|
|
1054
|
+
], Form.prototype, "reviewSections", void 0);
|
|
926
1055
|
__decorate([
|
|
927
1056
|
volatile
|
|
928
1057
|
], Form.prototype, "isSubmitHidden", null);
|
package/dist/esm/form.styles.js
CHANGED
|
@@ -22,10 +22,17 @@ export const foundationFormStyles = css `
|
|
|
22
22
|
min-width: 64px;
|
|
23
23
|
width: 100%;
|
|
24
24
|
height: 100%;
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
box-sizing: border-box;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
foundation-form json-forms {
|
|
32
|
+
flex: 1;
|
|
25
33
|
overflow-y: auto;
|
|
26
|
-
|
|
34
|
+
min-height: 0;
|
|
27
35
|
scrollbar-color: var(--neutral-stroke-rest) transparent;
|
|
28
|
-
box-sizing: border-box;
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
foundation-form .actions {
|
|
@@ -6,11 +6,12 @@ import { CategorizationWrapper } from './jsonforms/renderers/CategorizationWrapp
|
|
|
6
6
|
import { DispatchRenderer } from './jsonforms/renderers/dispatch-renderer';
|
|
7
7
|
import { StepperWrapper } from './jsonforms/renderers/StepperWrapperRenderer';
|
|
8
8
|
import { VerticalCategorizationWrapper } from './jsonforms/renderers/VerticalCategorizationWrapperRenderer';
|
|
9
|
-
|
|
9
|
+
import { Review } from './review';
|
|
10
|
+
avoidTreeShaking(JSONForms, ArrayListWrapper, CategorizationWrapper, VerticalCategorizationWrapper, DispatchRenderer, StepperWrapper, Review);
|
|
10
11
|
/** @internal */
|
|
11
12
|
export const getPrefixedForm = (prefix) => html `
|
|
12
13
|
<template>
|
|
13
|
-
${when((x) => x.bulkInsert, html `
|
|
14
|
+
${when((x) => !x.reviewMode && x.bulkInsert, html `
|
|
14
15
|
<div class="csv-upload-section" part="csv-upload">
|
|
15
16
|
<input
|
|
16
17
|
type="file"
|
|
@@ -39,43 +40,80 @@ export const getPrefixedForm = (prefix) => html `
|
|
|
39
40
|
</${prefix}-button>
|
|
40
41
|
</div>
|
|
41
42
|
`)}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
43
|
+
${when((x) => !x.reviewMode, html `
|
|
44
|
+
<json-forms
|
|
45
|
+
${ref('jsonFormsEl')}
|
|
46
|
+
@submit-button-clicked=${(x) => x._submit()}
|
|
47
|
+
@submit-part=${(x, c) => x.submitPart(c.event)}
|
|
48
|
+
@submit-single-row=${(x, c) => x.submitSingleRow(c.event.detail.index)}
|
|
49
|
+
@reset-form=${(x) => x.reset(false)}
|
|
50
|
+
?readonly=${(x) => x.readonly}
|
|
51
|
+
:submitted=${(x) => x.submitted}
|
|
52
|
+
:renderers=${(x) => x.mergedRenderers}
|
|
53
|
+
:uischema=${(x) => x.uischema}
|
|
54
|
+
:schema=${(x) => x.jsonSchema}
|
|
55
|
+
:data=${(x) => x.data}
|
|
56
|
+
:serverFieldErrors=${(x) => x.serverFieldErrors}
|
|
57
|
+
:prefix=${(x) => x.prefix}
|
|
58
|
+
:rowSubmitStatuses=${(x) => x.rowSubmitStatuses}
|
|
59
|
+
:bulkInsert=${(x) => x.bulkInsert}
|
|
60
|
+
@data-change=${(x, c) => x.onChange(c.event)}
|
|
61
|
+
></json-forms>
|
|
62
|
+
`)}
|
|
63
|
+
${when((x) => x.reviewMode, html `
|
|
64
|
+
<foundation-form-review
|
|
65
|
+
part="review-panel"
|
|
66
|
+
design-system-prefix=${(x) => x.prefix}
|
|
67
|
+
heading=${(x) => x.reviewHeading}
|
|
68
|
+
:recordId=${(x) => x.reviewRecordId}
|
|
69
|
+
:sections=${(x) => x.reviewSections}
|
|
70
|
+
></foundation-form-review>
|
|
71
|
+
`)}
|
|
72
|
+
${when((x) => x.reviewMode || x.isSubmitHidden || x.isResetVisible, html `
|
|
60
73
|
<slot name="userActions" part="userActions" class="actions">
|
|
61
|
-
${when((x) => x.isResetVisible, html `
|
|
74
|
+
${when((x) => !x.reviewMode && x.isResetVisible, html `
|
|
62
75
|
<${prefix}-button
|
|
63
76
|
@click="${(x) => x.handleResetClicked()}"
|
|
64
77
|
class="reset-button"
|
|
65
78
|
appearance="neutral"
|
|
66
79
|
data-test-id="reset-button"
|
|
67
80
|
>
|
|
68
|
-
|
|
81
|
+
${(x) => x.resetButtonText}
|
|
82
|
+
</${prefix}-button>
|
|
83
|
+
`)}
|
|
84
|
+
${when((x) => x.reviewMode, html `
|
|
85
|
+
<${prefix}-button
|
|
86
|
+
@click="${(x) => x.backFromReview()}"
|
|
87
|
+
class="reset-button"
|
|
88
|
+
appearance="neutral"
|
|
89
|
+
data-test-id="review-back-button"
|
|
90
|
+
>
|
|
91
|
+
Back
|
|
69
92
|
</${prefix}-button>
|
|
70
93
|
`)}
|
|
71
|
-
${when((x) => x.isSubmitHidden, html `
|
|
94
|
+
${when((x) => !x.reviewMode && x.isSubmitHidden, html `
|
|
72
95
|
<${prefix}-button
|
|
73
96
|
@click="${(x) => x._submit()}"
|
|
74
|
-
?
|
|
97
|
+
?disabled=${(x) => x.submitting}
|
|
98
|
+
class="submit-button"
|
|
99
|
+
appearance="accent"
|
|
100
|
+
>
|
|
101
|
+
${(x) => x.showSubmitReview
|
|
102
|
+
? x.reviewButtonText
|
|
103
|
+
: x.bulkInsert
|
|
104
|
+
? x.bulkSubmitButtonText
|
|
105
|
+
: x.submitButtonText}
|
|
106
|
+
</${prefix}-button>
|
|
107
|
+
`)}
|
|
108
|
+
${when((x) => x.reviewMode, html `
|
|
109
|
+
<${prefix}-button
|
|
110
|
+
@click="${(x) => x._submitFromReview()}"
|
|
111
|
+
?disabled=${(x) => x.submitting}
|
|
75
112
|
class="submit-button"
|
|
76
113
|
appearance="accent"
|
|
114
|
+
data-test-id="review-submit-button"
|
|
77
115
|
>
|
|
78
|
-
${(x) => (x.bulkInsert ?
|
|
116
|
+
${(x) => (x.bulkInsert ? x.bulkSubmitButtonText : x.submitButtonText)}
|
|
79
117
|
</${prefix}-button>
|
|
80
118
|
`)}
|
|
81
119
|
</slot>
|
package/dist/esm/index.js
CHANGED
|
@@ -2,6 +2,9 @@ export * from './filters';
|
|
|
2
2
|
export * from './form';
|
|
3
3
|
export * from './form.styles';
|
|
4
4
|
export * from './form.template';
|
|
5
|
+
export * from './review';
|
|
6
|
+
export * from './review.styles';
|
|
7
|
+
export * from './review.template';
|
|
5
8
|
export { MulticolumnDropdownControlRendererEntry, MulticolumnDropdownControlRendererTemplate, } from './jsonforms/renderers/MulticolumnDropdownControlRenderer';
|
|
6
9
|
export { LayoutVerticalCategorizationRendererEntry } from './jsonforms/renderers/LayoutVerticalCategorizationRenderer';
|
|
7
10
|
export { controlWrapperTemplate, controlWrapperStyles,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
2
|
import { html, css, observable, customElement, FASTElement, repeat, when, } from '@microsoft/fast-element';
|
|
3
3
|
import { classNames } from '@microsoft/fast-web-utilities';
|
|
4
|
+
import { extractErrorScopes, uiSchemaHasAnyError } from '../../utils/schema-utils';
|
|
4
5
|
/**
|
|
5
6
|
* Resolves the `activeCategory` option (the zero-based index of the category
|
|
6
7
|
* tab that should open on first render) to a valid tab index. Out-of-bounds or
|
|
@@ -160,48 +161,3 @@ CategorizationWrapper = __decorate([
|
|
|
160
161
|
})
|
|
161
162
|
], CategorizationWrapper);
|
|
162
163
|
export { CategorizationWrapper };
|
|
163
|
-
function uiSchemaHasAnyError(uiSchema, errorScopes) {
|
|
164
|
-
// Direct scope match
|
|
165
|
-
if (typeof uiSchema.scope === 'string' &&
|
|
166
|
-
errorScopes.includes(uiSchema.scope)) {
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
// Check composite children via options.childElements
|
|
170
|
-
const options = uiSchema.options;
|
|
171
|
-
const childElements = options === null || options === void 0 ? void 0 : options.childElements;
|
|
172
|
-
if (Array.isArray(childElements)) {
|
|
173
|
-
for (const child of childElements) {
|
|
174
|
-
if (uiSchemaHasAnyError(child, errorScopes)) {
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
// Check layout children (elements)
|
|
180
|
-
if (Array.isArray(uiSchema.elements)) {
|
|
181
|
-
for (const el of uiSchema.elements) {
|
|
182
|
-
if (uiSchemaHasAnyError(el, errorScopes)) {
|
|
183
|
-
return true;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
function extractErrorScopes(errors) {
|
|
190
|
-
var _a;
|
|
191
|
-
const scopes = new Set();
|
|
192
|
-
for (const error of errors) {
|
|
193
|
-
if (error.keyword === 'required' && ((_a = error.params) === null || _a === void 0 ? void 0 : _a.missingProperty)) {
|
|
194
|
-
const basePath = error.instancePath || '';
|
|
195
|
-
const prop = error.params.missingProperty;
|
|
196
|
-
// Normalize the base path
|
|
197
|
-
const fullPath = basePath === '' ? `#/properties/${prop}` : `#${basePath}/properties/${prop}`;
|
|
198
|
-
scopes.add(fullPath);
|
|
199
|
-
}
|
|
200
|
-
else if (error.instancePath) {
|
|
201
|
-
// Remove leading slash and convert to JSON Pointer path
|
|
202
|
-
const instancePath = `#${error.instancePath.replace(/\//g, '/properties/')}`;
|
|
203
|
-
scopes.add(instancePath);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return Array.from(scopes);
|
|
207
|
-
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { attr, customElement, FASTElement, observable, volatile } from '@microsoft/fast-element';
|
|
3
|
+
import { foundationFormReviewStyles as styles } from './review.styles';
|
|
4
|
+
import { foundationFormReviewTemplate as template } from './review.template';
|
|
5
|
+
import { filterReviewSections } from './utils/review-utils';
|
|
6
|
+
/**
|
|
7
|
+
* Read-only preview panel shown by `<foundation-form>` when `show-submit-review` is enabled.
|
|
8
|
+
* Renders the reviewed data grouped into sections with a header and a filter field.
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
let Review = class Review extends FASTElement {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(...arguments);
|
|
14
|
+
this.prefix = 'rapid';
|
|
15
|
+
/**
|
|
16
|
+
* Title shown in the review header.
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
this.heading = 'Review';
|
|
20
|
+
/**
|
|
21
|
+
* Sections of label/value pairs to render, grouped by their nearest UI schema `Group`.
|
|
22
|
+
* @public
|
|
23
|
+
*/
|
|
24
|
+
this.sections = [];
|
|
25
|
+
/**
|
|
26
|
+
* Free-text query filtering fields by label or value. Sections with no matching
|
|
27
|
+
* fields are hidden.
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
this.filterText = '';
|
|
31
|
+
}
|
|
32
|
+
/** @internal */
|
|
33
|
+
get filteredSections() {
|
|
34
|
+
return filterReviewSections(this.sections, this.filterText);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
__decorate([
|
|
38
|
+
attr({ attribute: 'design-system-prefix' })
|
|
39
|
+
], Review.prototype, "prefix", void 0);
|
|
40
|
+
__decorate([
|
|
41
|
+
attr
|
|
42
|
+
], Review.prototype, "heading", void 0);
|
|
43
|
+
__decorate([
|
|
44
|
+
attr({ attribute: 'record-id' })
|
|
45
|
+
], Review.prototype, "recordId", void 0);
|
|
46
|
+
__decorate([
|
|
47
|
+
observable
|
|
48
|
+
], Review.prototype, "sections", void 0);
|
|
49
|
+
__decorate([
|
|
50
|
+
observable
|
|
51
|
+
], Review.prototype, "filterText", void 0);
|
|
52
|
+
__decorate([
|
|
53
|
+
volatile
|
|
54
|
+
], Review.prototype, "filteredSections", null);
|
|
55
|
+
Review = __decorate([
|
|
56
|
+
customElement({
|
|
57
|
+
name: 'foundation-form-review',
|
|
58
|
+
template,
|
|
59
|
+
styles,
|
|
60
|
+
shadowOptions: null,
|
|
61
|
+
})
|
|
62
|
+
], Review);
|
|
63
|
+
export { Review };
|