@openg2p/registry-widgets 1.1.0-dev.8 → 1.1.0-dev.9

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/index.js CHANGED
@@ -246,24 +246,27 @@ const getValidationPattern = (validationType) => {
246
246
  };
247
247
 
248
248
  /**
249
- * Validate value against validation rules
249
+ * Validate value against validation rules.
250
+ *
251
+ * @param skipRequired - When true, required-field checks are skipped (used by
252
+ * per-section Save/Next buttons so the user can move between sections without
253
+ * filling every mandatory field; only format/range checks still run).
250
254
  */
251
- const validateWidget = (value, validation, required = false) => {
255
+ const validateWidget = (value, validation, required = false, skipRequired = false) => {
252
256
  const errors = [];
253
257
  if (!validation && !required) {
254
258
  return errors;
255
259
  }
256
- // Check required
257
- const isRequired = validation?.required ?? required;
260
+ // Check required (skipped when navigating between sections)
261
+ const isRequired = !skipRequired && (validation?.required ?? required);
258
262
  // For boolean, false is a valid value, so only check for null/undefined/empty string
259
263
  const isEmpty = value === null || value === undefined || value === '';
260
264
  if (isRequired && isEmpty) {
261
265
  errors.push('This field is required');
262
266
  return errors; // Return early if required field is empty
263
267
  }
264
- // Skip other validations if value is empty and not required
265
- // Note: For boolean, false is a valid value, so we only skip if truly empty
266
- if (isEmpty && !isRequired) {
268
+ // Skip format/range validations if value is empty
269
+ if (isEmpty) {
267
270
  return errors;
268
271
  }
269
272
  if (!validation) {
@@ -3807,7 +3810,15 @@ const collectWidgets = (panels) => {
3807
3810
  });
3808
3811
  return widgets;
3809
3812
  };
3810
- const sectionValidate = (section, currentSchemaData, dispatch) => {
3813
+ /**
3814
+ * Validate all widgets in a section and dispatch errors to the store.
3815
+ *
3816
+ * @param skipRequired - When true, required-field checks (widget-required,
3817
+ * validation.required, document-required) are skipped. Use this for
3818
+ * per-section Save/Next navigation so the user can advance without filling
3819
+ * every mandatory field; only format/range errors are reported.
3820
+ */
3821
+ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = false) => {
3811
3822
  const allWidgets = collectWidgets(section.panels);
3812
3823
  let isValid = true;
3813
3824
  for (const widget of allWidgets) {
@@ -3816,7 +3827,7 @@ const sectionValidate = (section, currentSchemaData, dispatch) => {
3816
3827
  continue;
3817
3828
  const widgetId = widget['widget-id'];
3818
3829
  const value = getWidgetValue(currentSchemaData, widget['widget-data-path'], widgetId);
3819
- const errors = validateWidget(value, widget['widget-data-validation'], widget['widget-required']);
3830
+ const errors = validateWidget(value, widget['widget-data-validation'], widget['widget-required'], skipRequired);
3820
3831
  if (errors.length > 0) {
3821
3832
  isValid = false;
3822
3833
  dispatch(setTouched({ widgetId, touched: true }));
@@ -3827,10 +3838,10 @@ const sectionValidate = (section, currentSchemaData, dispatch) => {
3827
3838
  dispatch(setError({ widgetId, errors: [] }));
3828
3839
  }
3829
3840
  }
3830
- // Supporting Documents
3841
+ // Supporting Documents — only check required when not skipping required validation
3831
3842
  section['section-supporting-documents']?.forEach((doc, index) => {
3832
3843
  const widgetId = `supporting-doc-${section['section-id']}-${index}`;
3833
- if (doc['document-required']) {
3844
+ if (!skipRequired && doc['document-required']) {
3834
3845
  const file = getValueByPath(currentSchemaData, doc['document-data-path']);
3835
3846
  if (!file) {
3836
3847
  isValid = false;
@@ -4465,7 +4476,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4465
4476
  const sectionWidgets = collectWidgets(originalSection.panels);
4466
4477
  const currentState = store.getState().widget;
4467
4478
  const currentSchemaData = currentState.values || {};
4468
- const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch);
4479
+ const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch, true);
4469
4480
  if (!isSectionValid) {
4470
4481
  return;
4471
4482
  }
@@ -4531,7 +4542,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4531
4542
  const sectionWidgets = collectWidgets(originalSection.panels);
4532
4543
  const currentState = store.getState().widget;
4533
4544
  const currentSchemaData = currentState.values || {};
4534
- const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch);
4545
+ const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch, true);
4535
4546
  if (!isSectionValid)
4536
4547
  return;
4537
4548
  const oldSchemaData = schemaData || contextSchemaData;
@@ -5165,6 +5176,10 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
5165
5176
  // Stable ref for namespace so formHandle useMemo doesn't depend on the (possibly inline) function identity
5166
5177
  const namespaceRef = React.useRef(namespace);
5167
5178
  namespaceRef.current = namespace;
5179
+ // Stable refs so formHandle closure can access current mode and accordion setter without stale captures
5180
+ const modeRef = React.useRef(mode);
5181
+ modeRef.current = mode;
5182
+ const setExpandedSectionIndexRef = React.useRef(setExpandedSectionIndex);
5168
5183
  // Track dirty (unsaved changes) per section for form handle validation
5169
5184
  const sectionDirtyMapRef = React.useRef({});
5170
5185
  const handleSectionDirtyChange = React.useCallback((sectionId, isDirty) => {
@@ -5226,33 +5241,52 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
5226
5241
  };
5227
5242
  return {
5228
5243
  validate: async () => {
5229
- checkNoUnsavedChanges();
5244
+ if (modeRef.current !== 'IntakeForm')
5245
+ checkNoUnsavedChanges();
5230
5246
  const values = getValues();
5231
5247
  let allValid = true;
5248
+ let firstInvalidIndex = null;
5232
5249
  for (let i = 0; i < safeSections.length; i++) {
5233
5250
  const section = safeSections[i];
5234
5251
  const ns = getNamespace(section, i);
5235
5252
  const sectionToValidate = ns ? namespaceSectionConfig(section, ns) : section;
5236
5253
  const valid = sectionValidate(sectionToValidate, values, dispatch);
5237
- if (!valid)
5254
+ if (!valid) {
5255
+ if (firstInvalidIndex === null)
5256
+ firstInvalidIndex = i;
5238
5257
  allValid = false;
5258
+ }
5259
+ }
5260
+ if (!allValid && modeRef.current === 'IntakeForm' && firstInvalidIndex !== null) {
5261
+ setExpandedSectionIndexRef.current(firstInvalidIndex);
5239
5262
  }
5240
5263
  return allValid;
5241
5264
  },
5242
5265
  getFormData: () => getValues(),
5243
5266
  validateAndGetData: async () => {
5244
- checkNoUnsavedChanges();
5267
+ if (modeRef.current !== 'IntakeForm')
5268
+ checkNoUnsavedChanges();
5245
5269
  const values = getValues();
5246
5270
  const results = [];
5271
+ let firstInvalidIndex = null;
5247
5272
  for (let i = 0; i < safeSections.length; i++) {
5248
5273
  const section = safeSections[i];
5249
5274
  const ns = getNamespace(section, i);
5250
5275
  const sectionToValidate = ns ? namespaceSectionConfig(section, ns) : section;
5251
5276
  const valid = sectionValidate(sectionToValidate, values, dispatch);
5252
5277
  if (!valid) {
5253
- throw new Error('Validation failed');
5278
+ if (firstInvalidIndex === null)
5279
+ firstInvalidIndex = i;
5254
5280
  }
5255
- results.push(buildSectionChanges(section, values, ns));
5281
+ else {
5282
+ results.push(buildSectionChanges(section, values, ns));
5283
+ }
5284
+ }
5285
+ if (firstInvalidIndex !== null) {
5286
+ if (modeRef.current === 'IntakeForm') {
5287
+ setExpandedSectionIndexRef.current(firstInvalidIndex);
5288
+ }
5289
+ throw new Error('Validation failed. Please fix the errors and try again.');
5256
5290
  }
5257
5291
  return results;
5258
5292
  },