@abgov/jsonforms-components 2.68.1 → 2.69.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/index.esm.js CHANGED
@@ -6441,7 +6441,6 @@ function collectRequiredPaths(schema, basePath = '') {
6441
6441
  }
6442
6442
  return out;
6443
6443
  }
6444
- // eslint-disable-next-line
6445
6444
  function collectControllerKeys(ifSchema) {
6446
6445
  if (!isObj(ifSchema)) return [];
6447
6446
  const keys = new Set();
@@ -6454,7 +6453,6 @@ function collectControllerKeys(ifSchema) {
6454
6453
  }
6455
6454
  return [...keys];
6456
6455
  }
6457
- // eslint-disable-next-line
6458
6456
  function buildConditionalDeps(rootSchema) {
6459
6457
  const deps = new Map();
6460
6458
  // eslint-disable-next-line
@@ -6489,9 +6487,55 @@ function buildConditionalDeps(rootSchema) {
6489
6487
  return out;
6490
6488
  }
6491
6489
 
6490
+ const sinTitle = 'Social insurance number';
6491
+ const invalidSin = 'Social insurance number is invalid';
6492
+ const DEFAULT_MAX_ITEMS = 50;
6493
+ const REQUIRED_PROPERTY_ERROR = 'is a required property';
6494
+ const ADDRESS_LOOKUP_LABELS = {
6495
+ addressLine1: 'Address line 1',
6496
+ addressLine2: 'Address line 2',
6497
+ municipality: 'City',
6498
+ postalCode: 'Postal code',
6499
+ subdivisionCode: 'Province',
6500
+ country: 'Country'
6501
+ };
6502
+ const VALIDATION_KEYWORDS = {
6503
+ REQUIRED: 'required',
6504
+ MIN_LENGTH: 'minLength',
6505
+ MAX_LENGTH: 'maxLength',
6506
+ FORMAT: 'format',
6507
+ MINIMUM: 'minimum',
6508
+ MAXIMUM: 'maximum',
6509
+ TYPE: 'type',
6510
+ ERROR_MESSAGE: 'errorMessage'
6511
+ };
6512
+ var StepStatus;
6513
+ (function (StepStatus) {
6514
+ StepStatus["NOT_STARTED"] = "NotStarted";
6515
+ StepStatus["IN_PROGRESS"] = "InProgress";
6516
+ StepStatus["COMPLETED"] = "Completed";
6517
+ })(StepStatus || (StepStatus = {}));
6518
+ const getAddressLookupFieldLabel = fieldName => {
6519
+ return ADDRESS_LOOKUP_LABELS[fieldName] || fieldName;
6520
+ };
6521
+
6522
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6523
+ function getValueAtPath(obj, path) {
6524
+ return path.split('.').reduce((acc, key) => acc == null ? void 0 : acc[key], obj);
6525
+ }
6526
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6527
+ function hasMeaningfulValue(val) {
6528
+ if (val === undefined || val === null) return false;
6529
+ if (typeof val === 'string') return val.trim() !== '';
6530
+ if (Array.isArray(val)) return val.length > 0;
6531
+ if (typeof val === 'object') return Object.keys(val).length > 0;
6532
+ return true;
6533
+ }
6534
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6492
6535
  function getSubSchema(schema, path) {
6493
6536
  const parts = path.replace(/^#\//, '').split('/').filter(p => p !== 'properties') // remove 'properties' segments
6494
6537
  .slice(0, -1);
6538
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6495
6539
  let current = schema;
6496
6540
  for (const part of parts) {
6497
6541
  if (!current) return undefined;
@@ -6505,6 +6549,28 @@ function getSubSchema(schema, path) {
6505
6549
  }
6506
6550
  return current;
6507
6551
  }
6552
+ // Checks whether any of the given normalized scopes (dot-path form, e.g. "a.b.c")
6553
+ // resolve to a property name present in the required array. Returns true if at
6554
+ // least one scope's property name is required, false otherwise.
6555
+ function isScopeRequired(normalizedScopes, required) {
6556
+ if (!(normalizedScopes != null && normalizedScopes.length) || !(required != null && required.length)) return false;
6557
+ return normalizedScopes.some(scope => {
6558
+ const propertyName = scope.split('.').pop();
6559
+ return !!propertyName && required.includes(propertyName);
6560
+ });
6561
+ }
6562
+ // Aggregates the required property names applicable to a step's scopes: the
6563
+ // schema's top-level required array plus the required array of each scope's
6564
+ // immediate parent (sub)schema (for nested objects).
6565
+ function getRequiredForScopes(scopes, schema) {
6566
+ const topLevelRequired = schema.required || [];
6567
+ const scopeSets = scopes.reduce((acc, scope) => {
6568
+ const subSchema = getSubSchema(schema, scope);
6569
+ return acc.concat((subSchema == null ? void 0 : subSchema.required) || []);
6570
+ }, topLevelRequired);
6571
+ return Array.from(new Set(scopeSets));
6572
+ }
6573
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6508
6574
  function anyRequiredFieldEmpty(scopes, data, required, schema) {
6509
6575
  for (const scope of scopes) {
6510
6576
  const path = scope.replace(/^#\//, '').split('/').filter(p => p !== 'properties');
@@ -6528,6 +6594,15 @@ function anyRequiredFieldEmpty(scopes, data, required, schema) {
6528
6594
  }
6529
6595
  return false;
6530
6596
  }
6597
+ // Resolve a single schema scope against the provided data and determine whether
6598
+ // it already holds a meaningful value. Used to derive the initial "visited"
6599
+ // state of a step from populated form data.
6600
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6601
+ function hasValueAtScope(data, scope) {
6602
+ const path = normalizeSchemaPath(scope);
6603
+ if (!path) return false;
6604
+ return hasMeaningfulValue(getValueAtPath(data, path));
6605
+ }
6531
6606
  function getStepStatus(opts) {
6532
6607
  const {
6533
6608
  scopes,
@@ -6536,22 +6611,50 @@ function getStepStatus(opts) {
6536
6611
  data,
6537
6612
  visited
6538
6613
  } = opts;
6539
- if (!visited) {
6540
- return 'NotStarted';
6541
- }
6542
6614
  const normalizedScopes = scopes.map(normalizeSchemaPath).filter(Boolean);
6615
+ // A step with no required fields has nothing that must be filled in, so once
6616
+ // the user has visited it (e.g. navigated back to the application overview
6617
+ // page) it is trivially Completed, regardless of whether its optional
6618
+ // fields hold any data.
6619
+ const requiredForScopes = getRequiredForScopes(scopes, schema);
6620
+ const stepHasRequiredFields = isScopeRequired(normalizedScopes, requiredForScopes);
6621
+ // NotStarted is data-driven: if any scoped field has a defined value the step has been started.
6622
+ // For non-standard scopes that cannot be normalised, fall back to the visited flag so those
6623
+ // steps are never stuck in NotStarted when the form is pre-populated or already visited.
6624
+ const stepHasData = normalizedScopes.length > 0 ? normalizedScopes.some(path => hasMeaningfulValue(get$1(data || {}, path))) : visited != null ? visited : false;
6625
+ if (!stepHasRequiredFields && (visited || stepHasData)) {
6626
+ return {
6627
+ status: StepStatus.COMPLETED,
6628
+ hasRequiredFields: stepHasRequiredFields
6629
+ };
6630
+ }
6631
+ if (!stepHasData) {
6632
+ return {
6633
+ status: StepStatus.NOT_STARTED,
6634
+ hasRequiredFields: stepHasRequiredFields
6635
+ };
6636
+ }
6543
6637
  const incompleteInStep = getIncompletePaths(errors, scopes);
6544
6638
  if (incompleteInStep.length > 0) {
6545
- return 'InProgress';
6639
+ return {
6640
+ status: StepStatus.IN_PROGRESS,
6641
+ hasRequiredFields: stepHasRequiredFields
6642
+ };
6546
6643
  }
6547
6644
  const required = schema.required || [];
6548
6645
  if (anyRequiredFieldEmpty(scopes, data, required, schema)) {
6549
- return 'InProgress';
6646
+ return {
6647
+ status: StepStatus.IN_PROGRESS,
6648
+ hasRequiredFields: stepHasRequiredFields
6649
+ };
6550
6650
  }
6551
6651
  const deps = buildConditionalDeps(schema);
6552
6652
  const controllersInStep = normalizedScopes.filter(s => deps.has(s));
6553
6653
  if (controllersInStep.length === 0) {
6554
- return 'Completed';
6654
+ return {
6655
+ status: StepStatus.COMPLETED,
6656
+ hasRequiredFields: stepHasRequiredFields
6657
+ };
6555
6658
  }
6556
6659
  const affected = new Set();
6557
6660
  for (const controller of controllersInStep) {
@@ -6560,27 +6663,38 @@ function getStepStatus(opts) {
6560
6663
  }
6561
6664
  }
6562
6665
  if (affected.size === 0) {
6563
- return 'Completed';
6666
+ return {
6667
+ status: StepStatus.COMPLETED,
6668
+ hasRequiredFields: stepHasRequiredFields
6669
+ };
6564
6670
  }
6565
6671
  const affectedPaths = [...affected];
6566
6672
  for (const err of errors || []) {
6567
6673
  for (const candidate of collectErrorCandidates(err)) {
6568
6674
  if (affectedPaths.some(path => isUnder(candidate, path))) {
6569
- return 'InProgress';
6675
+ return {
6676
+ status: StepStatus.IN_PROGRESS,
6677
+ hasRequiredFields: stepHasRequiredFields
6678
+ };
6570
6679
  }
6571
6680
  }
6572
6681
  }
6573
- return 'Completed';
6682
+ return {
6683
+ status: StepStatus.COMPLETED,
6684
+ hasRequiredFields: stepHasRequiredFields
6685
+ };
6574
6686
  }
6575
6687
  function collectErrorCandidates(e) {
6576
6688
  var _e$params;
6577
6689
  const out = [];
6690
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6578
6691
  const missing = (_e$params = e.params) == null ? void 0 : _e$params.missingProperty;
6579
- if (e.keyword === 'required' && missing) {
6692
+ if (e.keyword === VALIDATION_KEYWORDS.REQUIRED && missing) {
6580
6693
  const base = normalizeInstancePath(e.instancePath || '');
6581
6694
  out.push(base ? `${base}.${missing}` : missing);
6582
6695
  }
6583
6696
  if (e.instancePath) out.push(normalizeInstancePath(e.instancePath));
6697
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6584
6698
  if (e.dataPath) out.push(normalizeInstancePath(e.dataPath));
6585
6699
  return out.filter(Boolean);
6586
6700
  }
@@ -6609,9 +6723,10 @@ function getIncompletePaths(errors, scopePaths) {
6609
6723
  const incomplete = new Set();
6610
6724
  for (const error of errors) {
6611
6725
  var _error$params;
6726
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6612
6727
  const missingProperty = (_error$params = error.params) == null ? void 0 : _error$params.missingProperty;
6613
6728
  const candidates = [];
6614
- if (error.keyword === 'required' && missingProperty) {
6729
+ if (error.keyword === VALIDATION_KEYWORDS.REQUIRED && missingProperty) {
6615
6730
  candidates.push(missingProperty);
6616
6731
  if (error.instancePath) {
6617
6732
  const base = normalizeInstancePath(error.instancePath);
@@ -6672,7 +6787,22 @@ const stepperReducer = (state, action) => {
6672
6787
  switch (action.type) {
6673
6788
  case 'update/uischema':
6674
6789
  {
6675
- return Object.assign({}, action.payload.state);
6790
+ const newState = action.payload.state;
6791
+ // createStepperContextInitData recomputes categories purely from the
6792
+ // current data/schema snapshot, so a category with no data in its scopes
6793
+ // is reported as not visited even if the user already navigated through it
6794
+ // (page/next, page/prev, or setVisited previously marked it visited).
6795
+ // Preserve any previously-tracked isVisited=true so navigation history
6796
+ // isn't lost whenever unrelated form data changes trigger this recompute.
6797
+ const mergedCategories = newState.categories.map(newCategory => {
6798
+ const previousCategory = categories.find(c => c.id === newCategory.id);
6799
+ return Object.assign({}, newCategory, {
6800
+ isVisited: (previousCategory == null ? void 0 : previousCategory.isVisited) || newCategory.isVisited
6801
+ });
6802
+ });
6803
+ return Object.assign({}, newState, {
6804
+ categories: mergedCategories
6805
+ });
6676
6806
  }
6677
6807
  case 'page/next':
6678
6808
  {
@@ -6740,7 +6870,9 @@ const stepperReducer = (state, action) => {
6740
6870
  }
6741
6871
  const filteredErrors = ajv.errors && ajv.errors.filter(error => (error == null ? void 0 : error.data) != null);
6742
6872
  const visited = true;
6743
- const status = getStepStatus({
6873
+ const {
6874
+ status
6875
+ } = getStepStatus({
6744
6876
  scopes: cat.scopes,
6745
6877
  data,
6746
6878
  errors: filteredErrors != null ? filteredErrors : [],
@@ -6748,9 +6880,9 @@ const stepperReducer = (state, action) => {
6748
6880
  visited
6749
6881
  });
6750
6882
  return Object.assign({}, cat, {
6751
- isCompleted: status === 'Completed',
6752
- isValid: status === 'Completed',
6753
- status
6883
+ isCompleted: status === StepStatus.COMPLETED,
6884
+ isValid: status === StepStatus.COMPLETED,
6885
+ status: status
6754
6886
  });
6755
6887
  });
6756
6888
  return Object.assign({}, state, {
@@ -6810,26 +6942,38 @@ const createStepperContextInitData = props => {
6810
6942
  // run validation once, capture errors
6811
6943
  const valid = ajv.validate(schema, data || {});
6812
6944
  const isPage = (uischema == null || (_uischema$options = uischema.options) == null ? void 0 : _uischema$options.variant) === 'pages';
6945
+ //TODO: Determine if cachedStatus is still being used by anything in the library
6946
+ // If not, do a proper clean up.
6813
6947
  const isCacheStatus = (_uischema$options2 = uischema.options) == null ? void 0 : _uischema$options2.cacheStatus;
6814
6948
  isCacheStatus && getIsVisitFromLocalStorage() || [];
6815
6949
  const categories = (_categorization$eleme = categorization.elements) == null ? void 0 : _categorization$eleme.map((c, id) => {
6816
6950
  var _deriveLabelForUISche;
6817
6951
  const scopes = pickPropertyValues(c, 'scope', 'ListWithDetail');
6818
- const visited = false;
6819
- const status = getStepStatus({
6952
+ // Treat a step as visited when its scoped fields already contain data so
6953
+ // that getStepStatus returns the real data-driven status on initial mount initially.
6954
+ let visited = scopes.some(scope => hasValueAtScope(data, scope));
6955
+ const {
6956
+ status,
6957
+ hasRequiredFields
6958
+ } = getStepStatus({
6820
6959
  scopes,
6821
6960
  data,
6822
6961
  errors: filteredErrors != null ? filteredErrors : [],
6823
6962
  schema,
6824
6963
  visited
6825
6964
  });
6965
+ //If the step has all conditional fields, set visited to true so that the step status will be
6966
+ //completed to enable form to be saved.
6967
+ if (!hasRequiredFields) {
6968
+ visited = true;
6969
+ }
6826
6970
  return {
6827
6971
  id,
6828
6972
  label: (_deriveLabelForUISche = deriveLabelForUISchemaElement(c, t)) != null ? _deriveLabelForUISche : `Step ${id + 1}`,
6829
6973
  scopes,
6830
- isCompleted: status === 'Completed',
6831
- isValid: status === 'Completed',
6832
- isVisited: status === 'Completed',
6974
+ isCompleted: status === StepStatus.COMPLETED || visited && !hasRequiredFields,
6975
+ isValid: status === StepStatus.COMPLETED || visited && !hasRequiredFields,
6976
+ isVisited: [StepStatus.COMPLETED, StepStatus.IN_PROGRESS].includes(status),
6833
6977
  status,
6834
6978
  uischema: c,
6835
6979
  isEnabled: isEnabled(c, data, '', ajv, undefined),
@@ -6999,7 +7143,7 @@ const JsonFormsStepperContextProvider = ({
6999
7143
  }
7000
7144
  }
7001
7145
  //eslint-disable-next-line
7002
- }, [JSON.stringify(StepperProps.uischema), JSON.stringify(StepperProps.schema)]);
7146
+ }, [JSON.stringify(StepperProps.uischema), JSON.stringify(StepperProps.schema), JSON.stringify(StepperProps.data)]);
7003
7147
  return jsx(JsonFormsStepperContext.Provider, {
7004
7148
  value: context,
7005
7149
  children: children
@@ -7264,32 +7408,6 @@ fixRegExpWellKnownSymbolLogic$1('match', function (MATCH, nativeMatch, maybeCall
7264
7408
  ];
7265
7409
  });
7266
7410
 
7267
- const sinTitle = 'Social insurance number';
7268
- const invalidSin = 'Social insurance number is invalid';
7269
- const DEFAULT_MAX_ITEMS = 50;
7270
- const REQUIRED_PROPERTY_ERROR = 'is a required property';
7271
- const ADDRESS_LOOKUP_LABELS = {
7272
- addressLine1: 'Address line 1',
7273
- addressLine2: 'Address line 2',
7274
- municipality: 'City',
7275
- postalCode: 'Postal code',
7276
- subdivisionCode: 'Province',
7277
- country: 'Country'
7278
- };
7279
- const VALIDATION_KEYWORDS = {
7280
- REQUIRED: 'required',
7281
- MIN_LENGTH: 'minLength',
7282
- MAX_LENGTH: 'maxLength',
7283
- FORMAT: 'format',
7284
- MINIMUM: 'minimum',
7285
- MAXIMUM: 'maximum',
7286
- TYPE: 'type',
7287
- ERROR_MESSAGE: 'errorMessage'
7288
- };
7289
- const getAddressLookupFieldLabel = fieldName => {
7290
- return ADDRESS_LOOKUP_LABELS[fieldName] || fieldName;
7291
- };
7292
-
7293
7411
  //type DebounceValueType = string | boolean | number | Record<string, unknown>;
7294
7412
  /**
7295
7413
  * A helper util to return a value at a certain delay. The delay is reset if the value arg changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/jsonforms-components",
3
- "version": "2.68.1",
3
+ "version": "2.69.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Government of Alberta - React renderers for JSON Forms based on the design system.",
6
6
  "repository": "https://github.com/GovAlta/adsp-monorepo",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-07-09T20:09:22.767Z",
4
- "sourceCommit": "5d8a7ba85bedb14a77786d3941256a92e2074f6b",
3
+ "generatedAt": "2026-07-22T15:10:21.951Z",
4
+ "sourceCommit": "813cb678891e28cf6ee0066498302eb66248289f",
5
5
  "sourcePath": "libs/jsonforms-components/src/index.ts",
6
6
  "rendererCount": 33,
7
7
  "renderers": [
@@ -2,6 +2,7 @@ import { StepperContextDataType } from './types';
2
2
  import { ErrorObject } from 'ajv';
3
3
  import { Dispatch } from 'react';
4
4
  import Ajv from 'ajv';
5
+ import { JsonSchema } from '@jsonforms/core';
5
6
  export type JsonFormStepperDispatch = Dispatch<StepperAction>;
6
7
  export type StepperAction = {
7
8
  type: 'page/next';
@@ -24,7 +25,7 @@ export type StepperAction = {
24
25
  errors?: ErrorObject[];
25
26
  id: number;
26
27
  ajv: Ajv;
27
- schema: any;
28
+ schema: JsonSchema;
28
29
  data: any;
29
30
  };
30
31
  } | {
@@ -1,4 +1,5 @@
1
1
  import { Categorization, Category } from '@jsonforms/core';
2
+ import { StepStatusType } from '../../../common/Constants';
2
3
  export interface CategoryInternalState {
3
4
  isCompleted?: boolean;
4
5
  isVisited?: boolean;
@@ -16,6 +17,15 @@ export interface CategoryConfig {
16
17
  }
17
18
  export type CategoryState = CategoryInternalState & CategoryConfig;
18
19
  export type CategoriesState = Array<CategoryState>;
20
+ export interface StepConfig {
21
+ id: string;
22
+ label: string;
23
+ scopePaths: string[];
24
+ }
25
+ export interface StepStatusData {
26
+ status: StepStatusType;
27
+ hasRequiredFields: boolean;
28
+ }
19
29
  export interface StepperContextDataType {
20
30
  categories: CategoriesState;
21
31
  activeId: number;
@@ -1,12 +1,16 @@
1
+ import { JsonSchema } from '@jsonforms/core';
1
2
  import type { ErrorObject } from 'ajv';
3
+ import { StepStatusData } from './types';
2
4
  export declare function hasMeaningfulValue(val: any): boolean;
5
+ export declare function isScopeRequired(normalizedScopes: string[], required: string[]): boolean;
6
+ export declare function hasValueAtScope(data: any, scope: string): boolean;
3
7
  export declare function getStepStatus(opts: {
4
8
  scopes: string[];
5
9
  data: any;
6
10
  errors: AjvError[];
7
- schema: any;
8
- visited: boolean;
9
- }): 'Completed' | 'InProgress' | 'NotStarted';
11
+ schema: JsonSchema;
12
+ visited?: boolean;
13
+ }): StepStatusData;
10
14
  export declare const isErrorPathIncluded: (errorPaths: string[], path: string) => boolean;
11
15
  type AjvError = ErrorObject & {
12
16
  dataPath?: string;
@@ -14,12 +18,6 @@ type AjvError = ErrorObject & {
14
18
  export declare function normalizeSchemaPath(schemaPath: string): string;
15
19
  export declare function normalizeInstancePath(instancePath: string): string;
16
20
  export declare function getIncompletePaths(errors: AjvError[] | null | undefined, scopePaths: string[]): string[];
17
- export type StepStatus = 'not-started' | 'in-progress' | 'completed';
18
- export interface StepConfig {
19
- id: string;
20
- label: string;
21
- scopePaths: string[];
22
- }
23
21
  export declare const subErrorInParent: (error: ErrorObject, paths: string[]) => boolean;
24
22
  export declare const getErrorsInScopes: (errors: ErrorObject[] | null | undefined, scopes: string[]) => ErrorObject[];
25
23
  export declare const hasDataInScopes: (data: object, scopes: string[]) => boolean;
@@ -1 +1,2 @@
1
- export declare function buildConditionalDeps(rootSchema: any): Map<string, string[]>;
1
+ import { JsonSchema } from '@jsonforms/core';
2
+ export declare function buildConditionalDeps(rootSchema: JsonSchema): Map<string, string[]>;
@@ -20,4 +20,10 @@ export declare const VALIDATION_KEYWORDS: {
20
20
  TYPE: string;
21
21
  ERROR_MESSAGE: string;
22
22
  };
23
+ export declare enum StepStatus {
24
+ NOT_STARTED = "NotStarted",
25
+ IN_PROGRESS = "InProgress",
26
+ COMPLETED = "Completed"
27
+ }
28
+ export type StepStatusType = StepStatus.COMPLETED | StepStatus.NOT_STARTED | StepStatus.IN_PROGRESS;
23
29
  export declare const getAddressLookupFieldLabel: (fieldName: string) => string;