@openg2p/registry-widgets 1.1.0-dev.8 → 1.1.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.
@@ -38,6 +38,12 @@ export interface SectionRendererProps {
38
38
  onPreviousSection?: (index: number) => void;
39
39
  /** IntakeForm mode: when true or undefined, sections are editable; when false, sections are readonly */
40
40
  isDraft?: boolean;
41
+ /**
42
+ * IntakeForm mode: when true the accordion header is clickable so the user can open/close this
43
+ * section freely. False (default) locks the header — navigation is only via Next/Previous.
44
+ * SectionsContainer sets this to true for every visited section plus the one immediately after.
45
+ */
46
+ isAccessible?: boolean;
41
47
  /** RegistryView: called when this section enters or exits edit mode. Used by SectionsContainer to enforce single-edit. */
42
48
  onEditModeChange?: (sectionId: string, editing: boolean) => void;
43
49
  /** RegistryView: when true, forces this section out of edit mode (another section took over). */
@@ -51,5 +57,5 @@ export interface SectionRendererProps {
51
57
  * - Panels wrap when they exceed available width
52
58
  * - Sections can sit side-by-side if there's space
53
59
  */
54
- export declare const SectionRenderer: ({ section, dataSourceRequestHandler: propDataSourceRequestHandler, schemaData, onValueChange, gridColumnSpan, onSectionSave, hideEditButton, mode, namespace, changeRequestType, showChangeRequestLabel, dbSectionId, sectionRegisterId, onSectionDirtyChange, sectionIndex, sectionCount, expandedSectionIndex, onExpandSection, onSectionSaveSuccess, onPreviousSection, isDraft, onEditModeChange, forceExitEdit, }: SectionRendererProps) => import("react/jsx-runtime").JSX.Element;
60
+ export declare const SectionRenderer: ({ section, dataSourceRequestHandler: propDataSourceRequestHandler, schemaData, onValueChange, gridColumnSpan, onSectionSave, hideEditButton, mode, namespace, changeRequestType, showChangeRequestLabel, dbSectionId, sectionRegisterId, onSectionDirtyChange, sectionIndex, sectionCount, expandedSectionIndex, onExpandSection, onSectionSaveSuccess, onPreviousSection, isDraft, isAccessible, onEditModeChange, forceExitEdit, }: SectionRendererProps) => import("react/jsx-runtime").JSX.Element;
55
61
  //# sourceMappingURL=SectionRenderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SectionRenderer.d.ts","sourceRoot":"","sources":["../../src/components/SectionRenderer.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAyC,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAQ9D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AA2ClD,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,CAAC;IACvB,wBAAwB,CAAC,EAAE,oBAAoB,CAAC,0BAA0B,CAAC,CAAC;IAC5E,UAAU,CAAC,EAAE,oBAAoB,CAAC,YAAY,CAAC,CAAC;IAChD,aAAa,CAAC,EAAE,oBAAoB,CAAC,eAAe,CAAC,CAAC;IACtD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAClC,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAGjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,2GAA2G;IAC3G,oBAAoB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAErE,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,oGAAoG;IACpG,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,uGAAuG;IACvG,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,4FAA4F;IAC5F,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,wGAAwG;IACxG,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,0HAA0H;IAC1H,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACjE,iGAAiG;IACjG,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAGD;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,wZAwB7B,oBAAoB,4CA68CtB,CAAC"}
1
+ {"version":3,"file":"SectionRenderer.d.ts","sourceRoot":"","sources":["../../src/components/SectionRenderer.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAyC,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAQ9D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AA2ClD,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,CAAC;IACvB,wBAAwB,CAAC,EAAE,oBAAoB,CAAC,0BAA0B,CAAC,CAAC;IAC5E,UAAU,CAAC,EAAE,oBAAoB,CAAC,YAAY,CAAC,CAAC;IAChD,aAAa,CAAC,EAAE,oBAAoB,CAAC,eAAe,CAAC,CAAC;IACtD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAClC,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAGjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,2GAA2G;IAC3G,oBAAoB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAErE,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,oGAAoG;IACpG,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,uGAAuG;IACvG,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,4FAA4F;IAC5F,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,wGAAwG;IACxG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB,0HAA0H;IAC1H,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACjE,iGAAiG;IACjG,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAGD;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,saAyB7B,oBAAoB,4CAo+CtB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"SectionsContainer.d.ts","sourceRoot":"","sources":["../../src/components/SectionsContainer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAmB,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAOpE,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,QAAQ,GAAG,YAAY,CAAC;AAEnE,uHAAuH;AACvH,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,qEAAqE;IACrE,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,qFAAqF;IACrF,kBAAkB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAClD;;;OAGG;IACD,iBAAiB,IAAI,cAAc,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,wBAAwB,CAAC,EAAE,wBAAwB,CAAC;IACpD,UAAU,CAAC,EAAE,oBAAoB,CAAC,YAAY,CAAC,CAAC;IAChD,aAAa,CAAC,EAAE,oBAAoB,CAAC,eAAe,CAAC,CAAC;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,wGAAwG;IACxG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IAEpE,wHAAwH;IACxH,oBAAoB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrE,0JAA0J;IAC1J,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACpD;AAoFD;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,yMAa/B,sBAAsB,4CAwSxB,CAAC"}
1
+ {"version":3,"file":"SectionsContainer.d.ts","sourceRoot":"","sources":["../../src/components/SectionsContainer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAmB,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAOpE,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,QAAQ,GAAG,YAAY,CAAC;AAEnE,uHAAuH;AACvH,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,qEAAqE;IACrE,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,qFAAqF;IACrF,kBAAkB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAClD;;;OAGG;IACD,iBAAiB,IAAI,cAAc,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,wBAAwB,CAAC,EAAE,wBAAwB,CAAC;IACpD,UAAU,CAAC,EAAE,oBAAoB,CAAC,YAAY,CAAC,CAAC;IAChD,aAAa,CAAC,EAAE,oBAAoB,CAAC,eAAe,CAAC,CAAC;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,wGAAwG;IACxG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IAEpE,wHAAwH;IACxH,oBAAoB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrE,0JAA0J;IAC1J,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACpD;AAoFD;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,yMAa/B,sBAAsB,4CAqUxB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useBaseWidget.d.ts","sourceRoot":"","sources":["../../src/hooks/useBaseWidget.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAgBtE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,gBAAgB,CAAC;IACzB,wBAAwB,CAAC,EAAE,wBAAwB,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;CACxD;AAMD,eAAO,MAAM,aAAa,GAAI,SAAS,oBAAoB;;;;;;;;;yBAsM5C,GAAG,aAAY,OAAO;;uBA2Td,MAAM,EAAE;;;;;;;0BAhMpB,MAAM;;;CAqMhB,CAAC"}
1
+ {"version":3,"file":"useBaseWidget.d.ts","sourceRoot":"","sources":["../../src/hooks/useBaseWidget.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAgBtE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,gBAAgB,CAAC;IACzB,wBAAwB,CAAC,EAAE,wBAAwB,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;CACxD;AAMD,eAAO,MAAM,aAAa,GAAI,SAAS,oBAAoB;;;;;;;;;yBAsM5C,GAAG,aAAY,OAAO;;uBAgUd,MAAM,EAAE;;;;;;;0BArMpB,MAAM;;;CA0MhB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -707,6 +707,12 @@ interface SectionRendererProps {
707
707
  onPreviousSection?: (index: number) => void;
708
708
  /** IntakeForm mode: when true or undefined, sections are editable; when false, sections are readonly */
709
709
  isDraft?: boolean;
710
+ /**
711
+ * IntakeForm mode: when true the accordion header is clickable so the user can open/close this
712
+ * section freely. False (default) locks the header — navigation is only via Next/Previous.
713
+ * SectionsContainer sets this to true for every visited section plus the one immediately after.
714
+ */
715
+ isAccessible?: boolean;
710
716
  /** RegistryView: called when this section enters or exits edit mode. Used by SectionsContainer to enforce single-edit. */
711
717
  onEditModeChange?: (sectionId: string, editing: boolean) => void;
712
718
  /** RegistryView: when true, forces this section out of edit mode (another section took over). */
@@ -720,7 +726,7 @@ interface SectionRendererProps {
720
726
  * - Panels wrap when they exceed available width
721
727
  * - Sections can sit side-by-side if there's space
722
728
  */
723
- declare const SectionRenderer: ({ section, dataSourceRequestHandler: propDataSourceRequestHandler, schemaData, onValueChange, gridColumnSpan, onSectionSave, hideEditButton, mode, namespace, changeRequestType, showChangeRequestLabel, dbSectionId, sectionRegisterId, onSectionDirtyChange, sectionIndex, sectionCount, expandedSectionIndex, onExpandSection, onSectionSaveSuccess, onPreviousSection, isDraft, onEditModeChange, forceExitEdit, }: SectionRendererProps) => react_jsx_runtime.JSX.Element;
729
+ declare const SectionRenderer: ({ section, dataSourceRequestHandler: propDataSourceRequestHandler, schemaData, onValueChange, gridColumnSpan, onSectionSave, hideEditButton, mode, namespace, changeRequestType, showChangeRequestLabel, dbSectionId, sectionRegisterId, onSectionDirtyChange, sectionIndex, sectionCount, expandedSectionIndex, onExpandSection, onSectionSaveSuccess, onPreviousSection, isDraft, isAccessible, onEditModeChange, forceExitEdit, }: SectionRendererProps) => react_jsx_runtime.JSX.Element;
724
730
 
725
731
  interface SectionBuilderProps {
726
732
  initialSection?: SectionConfig;
@@ -1523,9 +1529,13 @@ declare const getWidgetValue: (values: Record<string, any>, dataPath: string | R
1523
1529
  declare const setWidgetValue: (currentValues: Record<string, any>, dataPath: string | Record<string, string> | undefined, widgetId: string, value: any) => Record<string, any>;
1524
1530
 
1525
1531
  /**
1526
- * Validate value against validation rules
1532
+ * Validate value against validation rules.
1533
+ *
1534
+ * @param skipRequired - When true, required-field checks are skipped (used by
1535
+ * per-section Save/Next buttons so the user can move between sections without
1536
+ * filling every mandatory field; only format/range checks still run).
1527
1537
  */
1528
- declare const validateWidget: (value: any, validation: WidgetValidation | undefined, required?: boolean) => string[];
1538
+ declare const validateWidget: (value: any, validation: WidgetValidation | undefined, required?: boolean, skipRequired?: boolean) => string[];
1529
1539
  /**
1530
1540
  * Create Zod schema from validation config
1531
1541
  */
package/dist/index.esm.js CHANGED
@@ -245,24 +245,27 @@ const getValidationPattern = (validationType) => {
245
245
  };
246
246
 
247
247
  /**
248
- * Validate value against validation rules
248
+ * Validate value against validation rules.
249
+ *
250
+ * @param skipRequired - When true, required-field checks are skipped (used by
251
+ * per-section Save/Next buttons so the user can move between sections without
252
+ * filling every mandatory field; only format/range checks still run).
249
253
  */
250
- const validateWidget = (value, validation, required = false) => {
254
+ const validateWidget = (value, validation, required = false, skipRequired = false) => {
251
255
  const errors = [];
252
256
  if (!validation && !required) {
253
257
  return errors;
254
258
  }
255
- // Check required
256
- const isRequired = validation?.required ?? required;
259
+ // Check required (skipped when navigating between sections)
260
+ const isRequired = !skipRequired && (validation?.required ?? required);
257
261
  // For boolean, false is a valid value, so only check for null/undefined/empty string
258
262
  const isEmpty = value === null || value === undefined || value === '';
259
263
  if (isRequired && isEmpty) {
260
264
  errors.push('This field is required');
261
265
  return errors; // Return early if required field is empty
262
266
  }
263
- // Skip other validations if value is empty and not required
264
- // Note: For boolean, false is a valid value, so we only skip if truly empty
265
- if (isEmpty && !isRequired) {
267
+ // Skip format/range validations if value is empty
268
+ if (isEmpty) {
266
269
  return errors;
267
270
  }
268
271
  if (!validation) {
@@ -1074,16 +1077,12 @@ const getApiDataSource = async (dataSource, allValues, dataSourceRequestHandler,
1074
1077
  console.error('[getApiDataSource] API data source missing endpoint. Use "endpoint" field to specify the operation (e.g., "get_g2p_geo_level_values")');
1075
1078
  return [];
1076
1079
  }
1077
- let response;
1078
- try {
1079
- response = await dataSourceRequestHandler(service, endpoint, method, requestParams, {
1080
- headers: dataSource.headers,
1081
- });
1082
- }
1083
- catch (error) {
1084
- console.error('[getApiDataSource] Handler error:', error);
1085
- throw error;
1086
- }
1080
+ // Call handler — let any throw propagate to the outer catch so it is logged once
1081
+ // by useBaseWidget rather than double-logged here (which can cascade when
1082
+ // intercept-console-error.js converts console.error calls into thrown errors).
1083
+ const response = await dataSourceRequestHandler(service, endpoint, method, requestParams, {
1084
+ headers: dataSource.headers,
1085
+ });
1087
1086
  // Handle OpenG2P response format (response_body.response_payload)
1088
1087
  if (response && typeof response === 'object') {
1089
1088
  if (response.response_body?.response_payload && Array.isArray(response.response_body.response_payload)) {
@@ -1106,8 +1105,8 @@ const getApiDataSource = async (dataSource, allValues, dataSourceRequestHandler,
1106
1105
  return [];
1107
1106
  }
1108
1107
  catch (error) {
1109
- console.error('Error fetching API data source:', error);
1110
- return [];
1108
+ // Rethrow so useBaseWidget's catch can log it with full widget context
1109
+ throw error;
1111
1110
  }
1112
1111
  };
1113
1112
  /**
@@ -2301,7 +2300,7 @@ const useBaseWidget = (options) => {
2301
2300
  dispatch(setDataSource({ widgetId, data: transformed }));
2302
2301
  }
2303
2302
  catch (error) {
2304
- console.error(`[useBaseWidget] ERROR loading data source for ${widgetId}:`, error);
2303
+ console.error(`[useBaseWidget] ERROR loading data source for widget "${widgetId}" (type="${dataSource.type}"):`, error, '\nWidget config:', config, '\ndataSourceRequestHandler provided:', Boolean(dataSourceRequestHandler));
2305
2304
  dispatch(setDataSource({ widgetId, data: [] }));
2306
2305
  }
2307
2306
  finally {
@@ -3806,7 +3805,15 @@ const collectWidgets = (panels) => {
3806
3805
  });
3807
3806
  return widgets;
3808
3807
  };
3809
- const sectionValidate = (section, currentSchemaData, dispatch) => {
3808
+ /**
3809
+ * Validate all widgets in a section and dispatch errors to the store.
3810
+ *
3811
+ * @param skipRequired - When true, required-field checks (widget-required,
3812
+ * validation.required, document-required) are skipped. Use this for
3813
+ * per-section Save/Next navigation so the user can advance without filling
3814
+ * every mandatory field; only format/range errors are reported.
3815
+ */
3816
+ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = false) => {
3810
3817
  const allWidgets = collectWidgets(section.panels);
3811
3818
  let isValid = true;
3812
3819
  for (const widget of allWidgets) {
@@ -3815,7 +3822,7 @@ const sectionValidate = (section, currentSchemaData, dispatch) => {
3815
3822
  continue;
3816
3823
  const widgetId = widget['widget-id'];
3817
3824
  const value = getWidgetValue(currentSchemaData, widget['widget-data-path'], widgetId);
3818
- const errors = validateWidget(value, widget['widget-data-validation'], widget['widget-required']);
3825
+ const errors = validateWidget(value, widget['widget-data-validation'], widget['widget-required'], skipRequired);
3819
3826
  if (errors.length > 0) {
3820
3827
  isValid = false;
3821
3828
  dispatch(setTouched({ widgetId, touched: true }));
@@ -3826,10 +3833,10 @@ const sectionValidate = (section, currentSchemaData, dispatch) => {
3826
3833
  dispatch(setError({ widgetId, errors: [] }));
3827
3834
  }
3828
3835
  }
3829
- // Supporting Documents
3836
+ // Supporting Documents — only check required when not skipping required validation
3830
3837
  section['section-supporting-documents']?.forEach((doc, index) => {
3831
3838
  const widgetId = `supporting-doc-${section['section-id']}-${index}`;
3832
- if (doc['document-required']) {
3839
+ if (!skipRequired && doc['document-required']) {
3833
3840
  const file = getValueByPath(currentSchemaData, doc['document-data-path']);
3834
3841
  if (!file) {
3835
3842
  isValid = false;
@@ -3935,7 +3942,7 @@ function scopedClassSelectors(sectionClassId, classNames) {
3935
3942
  * - Panels wrap when they exceed available width
3936
3943
  * - Sections can sit side-by-side if there's space
3937
3944
  */
3938
- const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequestHandler, schemaData, onValueChange, gridColumnSpan, onSectionSave, hideEditButton = false, mode = 'RegistryView', namespace, changeRequestType, showChangeRequestLabel = true, dbSectionId, sectionRegisterId, onSectionDirtyChange, sectionIndex, sectionCount, expandedSectionIndex, onExpandSection, onSectionSaveSuccess, onPreviousSection, isDraft, onEditModeChange, forceExitEdit, }) => {
3945
+ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequestHandler, schemaData, onValueChange, gridColumnSpan, onSectionSave, hideEditButton = false, mode = 'RegistryView', namespace, changeRequestType, showChangeRequestLabel = true, dbSectionId, sectionRegisterId, onSectionDirtyChange, sectionIndex, sectionCount, expandedSectionIndex, onExpandSection, onSectionSaveSuccess, onPreviousSection, isDraft, isAccessible = false, onEditModeChange, forceExitEdit, }) => {
3939
3946
  const { translateConfig, translate } = useWidgetTranslation();
3940
3947
  const resolvedTheme = useWidgetTheme();
3941
3948
  const portalCSSVariables = useMemo(() => themeToCSSVariables(resolvedTheme), [resolvedTheme]);
@@ -4001,16 +4008,25 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4001
4008
  const isExpandedFromContainer = typeof sectionIndex === 'number' && expandedSectionIndex === sectionIndex;
4002
4009
  const isExpandedStandalone = sectionIndex === undefined && standaloneExpanded;
4003
4010
  const isExpanded = mode === 'IntakeForm' && (isExpandedFromContainer || isExpandedStandalone);
4011
+ // IntakeForm only: tracks whether the user has clicked Next on this section at least once.
4012
+ // Used to unlock the accordion header so the user can navigate back to a visited section.
4013
+ const [hasBeenSavedByUser, setHasBeenSavedByUser] = useState(false);
4014
+ // Accordion header click behaviour in IntakeForm mode:
4015
+ // - Standalone (no sectionIndex): always toggleable.
4016
+ // - Managed by SectionsContainer: toggleable only when isAccessible is true
4017
+ // (i.e. the section has been visited OR is the immediate next one).
4018
+ // Sections beyond that remain locked.
4004
4019
  const handleAccordionToggle = useCallback(() => {
4005
4020
  if (mode !== 'IntakeForm')
4006
4021
  return;
4007
- if (typeof sectionIndex === 'number' && onExpandSection) {
4008
- onExpandSection(sectionIndex);
4009
- }
4010
- else if (sectionIndex === undefined) {
4022
+ if (sectionIndex === undefined) {
4011
4023
  setStandaloneExpanded(prev => !prev);
4012
4024
  }
4013
- }, [mode, sectionIndex, onExpandSection]);
4025
+ else if (isAccessible && onExpandSection) {
4026
+ onExpandSection(sectionIndex);
4027
+ }
4028
+ // Intentionally no-op for locked sections (isAccessible === false)
4029
+ }, [mode, sectionIndex, isAccessible, onExpandSection]);
4014
4030
  // Recursively count all vertical panels, especially those nested inside horizontal panels
4015
4031
  // Typically: horizontal panels at first level contain vertical panels at second level
4016
4032
  // Accounts for panel-column-span: a panel with column-span 3 counts as 3 columns
@@ -4345,8 +4361,6 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4345
4361
  const baselineSnapshotRef = useRef(null);
4346
4362
  // IntakeForm only: increment when baseline is updated after save - forces badge to update (refs don't trigger re-renders)
4347
4363
  const [intakeFormBaselineTrigger, setIntakeFormBaselineTrigger] = useState(0);
4348
- // IntakeForm only: tracks whether the user has actually saved this section (prevents "Saved" badge on initial load)
4349
- const [hasBeenSavedByUser, setHasBeenSavedByUser] = useState(false);
4350
4364
  // IntakeForm: treat as edit mode for dirty tracking when isDraft. RegistryView: use isEditMode.
4351
4365
  const effectiveEditModeForDirty = mode === 'IntakeForm' ? (isDraft !== false) : isEditMode;
4352
4366
  // Compute isDirty: compare current store state to baseline (only when in edit mode)
@@ -4464,7 +4478,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4464
4478
  const sectionWidgets = collectWidgets(originalSection.panels);
4465
4479
  const currentState = store.getState().widget;
4466
4480
  const currentSchemaData = currentState.values || {};
4467
- const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch);
4481
+ const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch, true);
4468
4482
  if (!isSectionValid) {
4469
4483
  return;
4470
4484
  }
@@ -4530,7 +4544,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4530
4544
  const sectionWidgets = collectWidgets(originalSection.panels);
4531
4545
  const currentState = store.getState().widget;
4532
4546
  const currentSchemaData = currentState.values || {};
4533
- const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch);
4547
+ const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch, true);
4534
4548
  if (!isSectionValid)
4535
4549
  return;
4536
4550
  const oldSchemaData = schemaData || contextSchemaData;
@@ -4579,6 +4593,11 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4579
4593
  }
4580
4594
  onSectionDirtyChange?.(sectionId, false);
4581
4595
  }
4596
+ else if (mode === 'IntakeForm') {
4597
+ // No onSectionSave provided, but still mark section as visited so the
4598
+ // user can navigate back to it by clicking the accordion header.
4599
+ setHasBeenSavedByUser(true);
4600
+ }
4582
4601
  // Always navigate to the next section
4583
4602
  onSectionSaveSuccess?.(sectionIndex);
4584
4603
  }, [store, onSectionSave, onSectionSaveSuccess, sectionIndex, originalSection, schemaData, contextSchemaData, namespace, hasSupportingDocuments, dbSectionId, sectionRegisterId, dispatch, buildSectionSnapshot, sectionId, onSectionDirtyChange, mode, isDraft]);
@@ -4797,13 +4816,17 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4797
4816
  .${sectionClassId}.intake-form-accordion-item .intake-form-accordion-header h2 {
4798
4817
  color: var(--owt-color-primary-dark, #F07B1A);
4799
4818
  }
4800
- .${sectionClassId}.intake-form-accordion-item .intake-form-accordion-header:hover {
4819
+ /* Hover / focus only shown when the header is actually interactive (standalone mode) */
4820
+ .${sectionClassId}.intake-form-accordion-item .intake-form-accordion-header[data-interactive="true"]:hover {
4801
4821
  opacity: 0.85;
4802
4822
  }
4803
- .${sectionClassId}.intake-form-accordion-item .intake-form-accordion-header:focus-visible {
4823
+ .${sectionClassId}.intake-form-accordion-item .intake-form-accordion-header[data-interactive="true"]:focus-visible {
4804
4824
  outline: 2px solid var(--owt-color-primary, #F5BB1A);
4805
4825
  outline-offset: 2px;
4806
4826
  }
4827
+ .${sectionClassId}.intake-form-accordion-item .intake-form-accordion-header[data-interactive="false"]:focus-visible {
4828
+ outline: none;
4829
+ }
4807
4830
  .${sectionClassId}.intake-form-accordion-item .intake-form-accordion-content {
4808
4831
  padding-top: 8px;
4809
4832
  padding-bottom: 0px;
@@ -4846,7 +4869,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4846
4869
  }),
4847
4870
  }, children: mode === 'IntakeForm' ? (
4848
4871
  /* IntakeForm: accordion layout - header always visible, content only when expanded */
4849
- jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsxs("button", { type: "button", id: `intake-form-accordion-header-${sectionId}`, className: "intake-form-accordion-header", onClick: handleAccordionToggle, "aria-expanded": isExpanded, "aria-controls": isExpanded ? `intake-form-accordion-content-${sectionId}` : undefined, style: {
4872
+ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsxs("button", { type: "button", id: `intake-form-accordion-header-${sectionId}`, className: "intake-form-accordion-header", onClick: handleAccordionToggle, "aria-expanded": isExpanded, "aria-controls": isExpanded ? `intake-form-accordion-content-${sectionId}` : undefined, "data-interactive": sectionIndex === undefined || isAccessible ? 'true' : 'false', style: {
4850
4873
  width: '100%',
4851
4874
  display: 'flex',
4852
4875
  alignItems: 'flex-start',
@@ -4856,7 +4879,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4856
4879
  marginBottom: 0,
4857
4880
  background: 'none',
4858
4881
  border: 'none',
4859
- cursor: 'pointer',
4882
+ cursor: sectionIndex === undefined || isAccessible ? 'pointer' : 'default',
4860
4883
  textAlign: 'left',
4861
4884
  fontFamily: 'Roboto, sans-serif',
4862
4885
  }, children: [jsxRuntimeExports.jsxs("div", { style: { flex: 1, display: 'flex', alignItems: 'center', gap: '12px', minWidth: 0 }, children: [jsxRuntimeExports.jsx("h2", { className: "text-xl font-semibold", style: { margin: 0 }, children: sectionToRender['section-title']
@@ -5154,6 +5177,11 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
5154
5177
  const dataSourceRequestHandler = propDataSourceRequestHandler || contextDataSourceRequestHandler;
5155
5178
  // IntakeForm mode: accordion state - which section is expanded (null = none; first expanded by default)
5156
5179
  const [expandedSectionIndex, setExpandedSectionIndex] = useState(0);
5180
+ // IntakeForm mode: high-water mark of the furthest section the user has clicked Next on.
5181
+ // A section at index i is accessible when i <= maxVisitedIndex + 1
5182
+ // (i.e. every visited section plus the one immediately after it).
5183
+ // Starts at -1 so only section 0 is accessible before any Next is clicked.
5184
+ const [maxVisitedIndex, setMaxVisitedIndex] = useState(-1);
5157
5185
  // RegistryView: track which section is currently in edit mode (by section-id); null = none
5158
5186
  const [editingSectionId, setEditingSectionId] = useState(null);
5159
5187
  const handleEditModeChange = useCallback((sectionId, editing) => {
@@ -5164,6 +5192,10 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
5164
5192
  // Stable ref for namespace so formHandle useMemo doesn't depend on the (possibly inline) function identity
5165
5193
  const namespaceRef = useRef(namespace);
5166
5194
  namespaceRef.current = namespace;
5195
+ // Stable refs so formHandle closure can access current mode and accordion setter without stale captures
5196
+ const modeRef = useRef(mode);
5197
+ modeRef.current = mode;
5198
+ const setExpandedSectionIndexRef = useRef(setExpandedSectionIndex);
5167
5199
  // Track dirty (unsaved changes) per section for form handle validation
5168
5200
  const sectionDirtyMapRef = useRef({});
5169
5201
  const handleSectionDirtyChange = useCallback((sectionId, isDirty) => {
@@ -5174,8 +5206,9 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
5174
5206
  const handleExpandSection = useCallback((index) => {
5175
5207
  setExpandedSectionIndex(prev => (prev === index ? null : index));
5176
5208
  }, []);
5177
- // IntakeForm mode: called after section save - collapse current, expand next
5209
+ // IntakeForm mode: called after section save - advance high-water mark, collapse current, expand next
5178
5210
  const handleSectionSaveSuccess = useCallback((index) => {
5211
+ setMaxVisitedIndex(prev => Math.max(prev, index));
5179
5212
  if (index + 1 < safeSections.length) {
5180
5213
  setExpandedSectionIndex(index + 1);
5181
5214
  }
@@ -5225,33 +5258,52 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
5225
5258
  };
5226
5259
  return {
5227
5260
  validate: async () => {
5228
- checkNoUnsavedChanges();
5261
+ if (modeRef.current !== 'IntakeForm')
5262
+ checkNoUnsavedChanges();
5229
5263
  const values = getValues();
5230
5264
  let allValid = true;
5265
+ let firstInvalidIndex = null;
5231
5266
  for (let i = 0; i < safeSections.length; i++) {
5232
5267
  const section = safeSections[i];
5233
5268
  const ns = getNamespace(section, i);
5234
5269
  const sectionToValidate = ns ? namespaceSectionConfig(section, ns) : section;
5235
5270
  const valid = sectionValidate(sectionToValidate, values, dispatch);
5236
- if (!valid)
5271
+ if (!valid) {
5272
+ if (firstInvalidIndex === null)
5273
+ firstInvalidIndex = i;
5237
5274
  allValid = false;
5275
+ }
5276
+ }
5277
+ if (!allValid && modeRef.current === 'IntakeForm' && firstInvalidIndex !== null) {
5278
+ setExpandedSectionIndexRef.current(firstInvalidIndex);
5238
5279
  }
5239
5280
  return allValid;
5240
5281
  },
5241
5282
  getFormData: () => getValues(),
5242
5283
  validateAndGetData: async () => {
5243
- checkNoUnsavedChanges();
5284
+ if (modeRef.current !== 'IntakeForm')
5285
+ checkNoUnsavedChanges();
5244
5286
  const values = getValues();
5245
5287
  const results = [];
5288
+ let firstInvalidIndex = null;
5246
5289
  for (let i = 0; i < safeSections.length; i++) {
5247
5290
  const section = safeSections[i];
5248
5291
  const ns = getNamespace(section, i);
5249
5292
  const sectionToValidate = ns ? namespaceSectionConfig(section, ns) : section;
5250
5293
  const valid = sectionValidate(sectionToValidate, values, dispatch);
5251
5294
  if (!valid) {
5252
- throw new Error('Validation failed');
5295
+ if (firstInvalidIndex === null)
5296
+ firstInvalidIndex = i;
5297
+ }
5298
+ else {
5299
+ results.push(buildSectionChanges(section, values, ns));
5253
5300
  }
5254
- results.push(buildSectionChanges(section, values, ns));
5301
+ }
5302
+ if (firstInvalidIndex !== null) {
5303
+ if (modeRef.current === 'IntakeForm') {
5304
+ setExpandedSectionIndexRef.current(firstInvalidIndex);
5305
+ }
5306
+ throw new Error('Validation failed. Please fix the errors and try again.');
5255
5307
  }
5256
5308
  return results;
5257
5309
  },
@@ -5350,6 +5402,8 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
5350
5402
  onSectionSaveSuccess: handleSectionSaveSuccess,
5351
5403
  onPreviousSection: handlePreviousSection,
5352
5404
  isDraft,
5405
+ // Accessible = every visited section + the one immediately after
5406
+ isAccessible: index <= maxVisitedIndex + 1,
5353
5407
  }
5354
5408
  : {};
5355
5409
  // RegistryView: single-edit coordination props
@@ -7332,7 +7386,7 @@ const NumberInputWidget = ({ config }) => {
7332
7386
  if (parsed === null) {
7333
7387
  // Allow empty input or partial input (e.g., "-", ".")
7334
7388
  if (inputValue === '' || inputValue === '-' || inputValue === '.') {
7335
- onChange('');
7389
+ onChange(null);
7336
7390
  }
7337
7391
  // Don't update if invalid - let user continue typing
7338
7392
  return;
@@ -9696,10 +9750,13 @@ const TextAreaWidget = ({ config }) => {
9696
9750
  // For readonly mode, render as preformatted text using <pre> tag
9697
9751
  if (isReadonly) {
9698
9752
  const displayValue = getStringValue() || '-';
9699
- return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] TextAreaDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("pre", { className: "text-base text-gray-900 font-medium whitespace-pre-wrap", title: String(displayValue), style: {
9753
+ return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] TextAreaDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { title: String(displayValue), className: "text-base text-gray-900 font-medium overflow-y-auto whitespace-pre-wrap break-words", style: {
9700
9754
  fontFamily: 'Roboto, sans-serif',
9701
- margin: 0,
9702
- padding: 0,
9755
+ height: '56px',
9756
+ minHeight: '56px',
9757
+ maxHeight: '56px',
9758
+ lineHeight: '20px',
9759
+ padding: '8px 0',
9703
9760
  backgroundColor: 'transparent',
9704
9761
  border: 'none',
9705
9762
  }, children: displayValue }) })] }));