@genesislcap/foundation-forms 14.491.0 → 14.492.0-FUI-2571.4

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 (37) hide show
  1. package/dist/custom-elements.json +341 -1
  2. package/dist/dts/form.d.ts +87 -0
  3. package/dist/dts/form.d.ts.map +1 -1
  4. package/dist/dts/form.styles.d.ts.map +1 -1
  5. package/dist/dts/form.template.d.ts.map +1 -1
  6. package/dist/dts/index.d.ts +3 -0
  7. package/dist/dts/index.d.ts.map +1 -1
  8. package/dist/dts/jsonforms/renderers/CategorizationWrapperRenderer.d.ts.map +1 -1
  9. package/dist/dts/react.d.ts +10 -0
  10. package/dist/dts/review.d.ts +34 -0
  11. package/dist/dts/review.d.ts.map +1 -0
  12. package/dist/dts/review.styles.d.ts +3 -0
  13. package/dist/dts/review.styles.d.ts.map +1 -0
  14. package/dist/dts/review.template.d.ts +6 -0
  15. package/dist/dts/review.template.d.ts.map +1 -0
  16. package/dist/dts/utils/index.d.ts +1 -0
  17. package/dist/dts/utils/index.d.ts.map +1 -1
  18. package/dist/dts/utils/review-utils.d.ts +82 -0
  19. package/dist/dts/utils/review-utils.d.ts.map +1 -0
  20. package/dist/dts/utils/schema-utils.d.ts +12 -0
  21. package/dist/dts/utils/schema-utils.d.ts.map +1 -1
  22. package/dist/esm/form.js +129 -2
  23. package/dist/esm/form.styles.js +9 -2
  24. package/dist/esm/form.template.js +59 -25
  25. package/dist/esm/index.js +3 -0
  26. package/dist/esm/jsonforms/renderers/CategorizationWrapperRenderer.js +1 -45
  27. package/dist/esm/review.js +63 -0
  28. package/dist/esm/review.styles.js +93 -0
  29. package/dist/esm/review.template.js +38 -0
  30. package/dist/esm/utils/index.js +1 -0
  31. package/dist/esm/utils/review-utils.js +241 -0
  32. package/dist/esm/utils/schema-utils.js +48 -0
  33. package/dist/foundation-forms.api.json +428 -0
  34. package/dist/foundation-forms.d.ts +217 -0
  35. package/dist/react.cjs +7 -0
  36. package/dist/react.mjs +6 -0
  37. package/package.json +15 -15
@@ -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,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE1D;;;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;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,oBAAoB,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,QAAQ,CAoBrF"}
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
@@ -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,60 @@ 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
+ * @internal
358
+ */
359
+ _submitFromReview() {
360
+ return __awaiter(this, void 0, void 0, function* () {
361
+ this.reviewMode = false;
362
+ yield this._performSubmit();
363
+ });
364
+ }
365
+ /**
366
+ * Whether the form currently has validation errors. Reads live errors directly from the
367
+ * json-forms element when it's mounted, so we always use the current validation state
368
+ * (this.errors can lag in edge cases such as required fields with empty-string values not
369
+ * yet reported via data-change). Falls back to this.errors when json-forms isn't mounted
370
+ * (e.g. while the read-only review panel is showing), which is safe since no interactive
371
+ * editing happens during review.
372
+ * @internal
373
+ */
374
+ hasValidationErrors() {
375
+ var _a, _b, _c, _d, _e, _f, _g, _h;
376
+ const jfEl = this.jsonFormsEl;
377
+ return jfEl
378
+ ? ((_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 ||
379
+ ((_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
380
+ : this.errors.length > 0;
381
+ }
382
+ /**
383
+ * Performs the actual API submission after review (or directly when review is disabled).
384
+ * @internal
385
+ */
386
+ _performSubmit() {
387
+ return __awaiter(this, void 0, void 0, function* () {
290
388
  const payload = Object.assign({}, this.data);
291
389
  const commitPayload = this.buildCommitPayload(payload);
292
390
  logger.debug({ payload, errors: this.errors });
293
391
  this.$emit('submit', { payload, errors: this.errors });
294
- if (Object.entries(payload).length !== 0 && !this.errors.length && this.resourceName) {
392
+ if (Object.entries(payload).length !== 0 && !this.hasValidationErrors() && this.resourceName) {
295
393
  this.submitting = true;
296
394
  const formSubmitReq = yield this.connect.commitEvent(this.resourceName, commitPayload);
297
395
  this.submitting = false;
@@ -638,6 +736,8 @@ let Form = class Form extends LifecycleMixin(FoundationElement) {
638
736
  */
639
737
  reset(clearData = true) {
640
738
  this.submitted = false;
739
+ this.reviewMode = false;
740
+ this.reviewSections = [];
641
741
  this.clearRowSubmitStatuses();
642
742
  this.serverFieldErrors = [];
643
743
  if (clearData) {
@@ -902,6 +1002,15 @@ __decorate([
902
1002
  __decorate([
903
1003
  attr({ attribute: 'bulk-insert', mode: 'boolean' })
904
1004
  ], Form.prototype, "bulkInsert", void 0);
1005
+ __decorate([
1006
+ attr({ attribute: 'submit-button-text' })
1007
+ ], Form.prototype, "submitButtonText", void 0);
1008
+ __decorate([
1009
+ attr({ attribute: 'bulk-submit-button-text' })
1010
+ ], Form.prototype, "bulkSubmitButtonText", void 0);
1011
+ __decorate([
1012
+ attr({ attribute: 'reset-button-text' })
1013
+ ], Form.prototype, "resetButtonText", void 0);
905
1014
  __decorate([
906
1015
  attr({
907
1016
  attribute: 'bulk-insert-min-items',
@@ -920,9 +1029,27 @@ __decorate([
920
1029
  },
921
1030
  })
922
1031
  ], Form.prototype, "bulkInsertMaxItems", void 0);
1032
+ __decorate([
1033
+ attr({ attribute: 'show-submit-review', mode: 'boolean' })
1034
+ ], Form.prototype, "showSubmitReview", void 0);
1035
+ __decorate([
1036
+ attr({ attribute: 'review-button-text' })
1037
+ ], Form.prototype, "reviewButtonText", void 0);
1038
+ __decorate([
1039
+ attr({ attribute: 'review-heading' })
1040
+ ], Form.prototype, "reviewHeading", void 0);
1041
+ __decorate([
1042
+ attr({ attribute: 'review-id-field' })
1043
+ ], Form.prototype, "reviewIdField", void 0);
923
1044
  __decorate([
924
1045
  observable
925
1046
  ], Form.prototype, "rowSubmitStatuses", void 0);
1047
+ __decorate([
1048
+ observable
1049
+ ], Form.prototype, "reviewMode", void 0);
1050
+ __decorate([
1051
+ observable
1052
+ ], Form.prototype, "reviewSections", void 0);
926
1053
  __decorate([
927
1054
  volatile
928
1055
  ], Form.prototype, "isSubmitHidden", null);
@@ -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
- display: inline-block;
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
- avoidTreeShaking(JSONForms, ArrayListWrapper, CategorizationWrapper, VerticalCategorizationWrapper, DispatchRenderer, StepperWrapper);
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,76 @@ export const getPrefixedForm = (prefix) => html `
39
40
  </${prefix}-button>
40
41
  </div>
41
42
  `)}
42
- <json-forms
43
- @submit-button-clicked=${(x) => x._submit()}
44
- @submit-part=${(x, c) => x.submitPart(c.event)}
45
- @submit-single-row=${(x, c) => x.submitSingleRow(c.event.detail.index)}
46
- @reset-form=${(x) => x.reset(false)}
47
- ?readonly=${(x) => x.readonly}
48
- :submitted=${(x) => x.submitted}
49
- :renderers=${(x) => x.mergedRenderers}
50
- :uischema=${(x) => x.uischema}
51
- :schema=${(x) => x.jsonSchema}
52
- :data=${(x) => x.data}
53
- :serverFieldErrors=${(x) => x.serverFieldErrors}
54
- :prefix=${(x) => x.prefix}
55
- :rowSubmitStatuses=${(x) => x.rowSubmitStatuses}
56
- :bulkInsert=${(x) => x.bulkInsert}
57
- @data-change=${(x, c) => x.onChange(c.event)}
58
- ></json-forms>
59
- ${when((x) => x.isSubmitHidden || x.isResetVisible, html `
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
- Reset
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
- ?disable=${(x) => !x.submitting}
97
+ ?disabled=${(x) => !x.submitting}
98
+ class="submit-button"
99
+ appearance="accent"
100
+ >
101
+ ${(x) => (x.bulkInsert ? x.bulkSubmitButtonText : x.submitButtonText)}
102
+ </${prefix}-button>
103
+ `)}
104
+ ${when((x) => x.reviewMode, html `
105
+ <${prefix}-button
106
+ @click="${(x) => x._submitFromReview()}"
107
+ ?disabled=${(x) => x.submitting}
75
108
  class="submit-button"
76
109
  appearance="accent"
110
+ data-test-id="review-submit-button"
77
111
  >
78
- ${(x) => (x.bulkInsert ? 'Submit All' : 'Submit')}
112
+ ${(x) => (x.showSubmitReview ? x.reviewButtonText : x.submitButtonText)}
79
113
  </${prefix}-button>
80
114
  `)}
81
115
  </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 };