@openg2p/registry-widgets 1.1.0-dev.10 → 1.1.0-dev.12
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/components/SectionRenderer.d.ts +7 -1
- package/dist/components/SectionRenderer.d.ts.map +1 -1
- package/dist/components/SectionsContainer.d.ts.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.esm.js +49 -18
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +49 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3947,7 +3947,7 @@ function scopedClassSelectors(sectionClassId, classNames) {
|
|
|
3947
3947
|
* - Panels wrap when they exceed available width
|
|
3948
3948
|
* - Sections can sit side-by-side if there's space
|
|
3949
3949
|
*/
|
|
3950
|
-
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, }) => {
|
|
3950
|
+
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, }) => {
|
|
3951
3951
|
const { translateConfig, translate } = useWidgetTranslation();
|
|
3952
3952
|
const resolvedTheme = useWidgetTheme();
|
|
3953
3953
|
const portalCSSVariables = React.useMemo(() => themeToCSSVariables(resolvedTheme), [resolvedTheme]);
|
|
@@ -4013,17 +4013,25 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4013
4013
|
const isExpandedFromContainer = typeof sectionIndex === 'number' && expandedSectionIndex === sectionIndex;
|
|
4014
4014
|
const isExpandedStandalone = sectionIndex === undefined && standaloneExpanded;
|
|
4015
4015
|
const isExpanded = mode === 'IntakeForm' && (isExpandedFromContainer || isExpandedStandalone);
|
|
4016
|
-
//
|
|
4017
|
-
//
|
|
4018
|
-
|
|
4016
|
+
// IntakeForm only: tracks whether the user has clicked Next on this section at least once.
|
|
4017
|
+
// Used to unlock the accordion header so the user can navigate back to a visited section.
|
|
4018
|
+
const [hasBeenSavedByUser, setHasBeenSavedByUser] = React.useState(false);
|
|
4019
|
+
// Accordion header click behaviour in IntakeForm mode:
|
|
4020
|
+
// - Standalone (no sectionIndex): always toggleable.
|
|
4021
|
+
// - Managed by SectionsContainer: toggleable only when isAccessible is true
|
|
4022
|
+
// (i.e. the section has been visited OR is the immediate next one).
|
|
4023
|
+
// Sections beyond that remain locked.
|
|
4019
4024
|
const handleAccordionToggle = React.useCallback(() => {
|
|
4020
4025
|
if (mode !== 'IntakeForm')
|
|
4021
4026
|
return;
|
|
4022
4027
|
if (sectionIndex === undefined) {
|
|
4023
4028
|
setStandaloneExpanded(prev => !prev);
|
|
4024
4029
|
}
|
|
4025
|
-
|
|
4026
|
-
|
|
4030
|
+
else if (isAccessible && onExpandSection) {
|
|
4031
|
+
onExpandSection(sectionIndex);
|
|
4032
|
+
}
|
|
4033
|
+
// Intentionally no-op for locked sections (isAccessible === false)
|
|
4034
|
+
}, [mode, sectionIndex, isAccessible, onExpandSection]);
|
|
4027
4035
|
// Recursively count all vertical panels, especially those nested inside horizontal panels
|
|
4028
4036
|
// Typically: horizontal panels at first level contain vertical panels at second level
|
|
4029
4037
|
// Accounts for panel-column-span: a panel with column-span 3 counts as 3 columns
|
|
@@ -4358,8 +4366,6 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4358
4366
|
const baselineSnapshotRef = React.useRef(null);
|
|
4359
4367
|
// IntakeForm only: increment when baseline is updated after save - forces badge to update (refs don't trigger re-renders)
|
|
4360
4368
|
const [intakeFormBaselineTrigger, setIntakeFormBaselineTrigger] = React.useState(0);
|
|
4361
|
-
// IntakeForm only: tracks whether the user has actually saved this section (prevents "Saved" badge on initial load)
|
|
4362
|
-
const [hasBeenSavedByUser, setHasBeenSavedByUser] = React.useState(false);
|
|
4363
4369
|
// IntakeForm: treat as edit mode for dirty tracking when isDraft. RegistryView: use isEditMode.
|
|
4364
4370
|
const effectiveEditModeForDirty = mode === 'IntakeForm' ? (isDraft !== false) : isEditMode;
|
|
4365
4371
|
// Compute isDirty: compare current store state to baseline (only when in edit mode)
|
|
@@ -4592,6 +4598,11 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4592
4598
|
}
|
|
4593
4599
|
onSectionDirtyChange?.(sectionId, false);
|
|
4594
4600
|
}
|
|
4601
|
+
else if (mode === 'IntakeForm') {
|
|
4602
|
+
// No onSectionSave provided, but still mark section as visited so the
|
|
4603
|
+
// user can navigate back to it by clicking the accordion header.
|
|
4604
|
+
setHasBeenSavedByUser(true);
|
|
4605
|
+
}
|
|
4595
4606
|
// Always navigate to the next section
|
|
4596
4607
|
onSectionSaveSuccess?.(sectionIndex);
|
|
4597
4608
|
}, [store, onSectionSave, onSectionSaveSuccess, sectionIndex, originalSection, schemaData, contextSchemaData, namespace, hasSupportingDocuments, dbSectionId, sectionRegisterId, dispatch, buildSectionSnapshot, sectionId, onSectionDirtyChange, mode, isDraft]);
|
|
@@ -4863,7 +4874,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4863
4874
|
}),
|
|
4864
4875
|
}, children: mode === 'IntakeForm' ? (
|
|
4865
4876
|
/* IntakeForm: accordion layout - header always visible, content only when expanded */
|
|
4866
|
-
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 ? 'true' : 'false', style: {
|
|
4877
|
+
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: {
|
|
4867
4878
|
width: '100%',
|
|
4868
4879
|
display: 'flex',
|
|
4869
4880
|
alignItems: 'flex-start',
|
|
@@ -4873,7 +4884,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4873
4884
|
marginBottom: 0,
|
|
4874
4885
|
background: 'none',
|
|
4875
4886
|
border: 'none',
|
|
4876
|
-
cursor: sectionIndex === undefined ? 'pointer' : 'default',
|
|
4887
|
+
cursor: sectionIndex === undefined || isAccessible ? 'pointer' : 'default',
|
|
4877
4888
|
textAlign: 'left',
|
|
4878
4889
|
fontFamily: 'Roboto, sans-serif',
|
|
4879
4890
|
}, 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']
|
|
@@ -5171,6 +5182,11 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
5171
5182
|
const dataSourceRequestHandler = propDataSourceRequestHandler || contextDataSourceRequestHandler;
|
|
5172
5183
|
// IntakeForm mode: accordion state - which section is expanded (null = none; first expanded by default)
|
|
5173
5184
|
const [expandedSectionIndex, setExpandedSectionIndex] = React.useState(0);
|
|
5185
|
+
// IntakeForm mode: high-water mark of the furthest section the user has clicked Next on.
|
|
5186
|
+
// A section at index i is accessible when i <= maxVisitedIndex + 1
|
|
5187
|
+
// (i.e. every visited section plus the one immediately after it).
|
|
5188
|
+
// Starts at -1 so only section 0 is accessible before any Next is clicked.
|
|
5189
|
+
const [maxVisitedIndex, setMaxVisitedIndex] = React.useState(-1);
|
|
5174
5190
|
// RegistryView: track which section is currently in edit mode (by section-id); null = none
|
|
5175
5191
|
const [editingSectionId, setEditingSectionId] = React.useState(null);
|
|
5176
5192
|
const handleEditModeChange = React.useCallback((sectionId, editing) => {
|
|
@@ -5181,6 +5197,11 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
5181
5197
|
// Stable ref for namespace so formHandle useMemo doesn't depend on the (possibly inline) function identity
|
|
5182
5198
|
const namespaceRef = React.useRef(namespace);
|
|
5183
5199
|
namespaceRef.current = namespace;
|
|
5200
|
+
// Stable ref for sections so formHandle useMemo doesn't depend on the (possibly unstable) array reference.
|
|
5201
|
+
// Without this, an inline sections={[...]} prop would recreate formHandle every render, causing
|
|
5202
|
+
// onFormReady to fire every render → host setState → infinite re-render loop.
|
|
5203
|
+
const safeSectionsRef = React.useRef(safeSections);
|
|
5204
|
+
safeSectionsRef.current = safeSections;
|
|
5184
5205
|
// Stable refs so formHandle closure can access current mode and accordion setter without stale captures
|
|
5185
5206
|
const modeRef = React.useRef(mode);
|
|
5186
5207
|
modeRef.current = mode;
|
|
@@ -5195,8 +5216,9 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
5195
5216
|
const handleExpandSection = React.useCallback((index) => {
|
|
5196
5217
|
setExpandedSectionIndex(prev => (prev === index ? null : index));
|
|
5197
5218
|
}, []);
|
|
5198
|
-
// IntakeForm mode: called after section save - collapse current, expand next
|
|
5219
|
+
// IntakeForm mode: called after section save - advance high-water mark, collapse current, expand next
|
|
5199
5220
|
const handleSectionSaveSuccess = React.useCallback((index) => {
|
|
5221
|
+
setMaxVisitedIndex(prev => Math.max(prev, index));
|
|
5200
5222
|
if (index + 1 < safeSections.length) {
|
|
5201
5223
|
setExpandedSectionIndex(index + 1);
|
|
5202
5224
|
}
|
|
@@ -5228,6 +5250,9 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
5228
5250
|
}, [mode, safeSections.length, expandedSectionIndex]);
|
|
5229
5251
|
const UNSAVED_CHANGES_ERROR = 'Unsaved changes detected. Please save all sections before submitting.';
|
|
5230
5252
|
// Form handle for onFormReady - allows host to validate and get all section data from its own Submit button
|
|
5253
|
+
// IMPORTANT: safeSections is intentionally accessed via safeSectionsRef.current (not closed over directly)
|
|
5254
|
+
// so that this memo never depends on the sections array reference. This prevents an infinite re-render
|
|
5255
|
+
// loop when the host passes an unstable sections prop (e.g. inline array or derived state).
|
|
5231
5256
|
const formHandle = React.useMemo(() => {
|
|
5232
5257
|
const getValues = () => store.getState().widget?.values || {};
|
|
5233
5258
|
const getNamespace = (section, index) => {
|
|
@@ -5249,10 +5274,11 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
5249
5274
|
if (modeRef.current !== 'IntakeForm')
|
|
5250
5275
|
checkNoUnsavedChanges();
|
|
5251
5276
|
const values = getValues();
|
|
5277
|
+
const sections = safeSectionsRef.current;
|
|
5252
5278
|
let allValid = true;
|
|
5253
5279
|
let firstInvalidIndex = null;
|
|
5254
|
-
for (let i = 0; i <
|
|
5255
|
-
const section =
|
|
5280
|
+
for (let i = 0; i < sections.length; i++) {
|
|
5281
|
+
const section = sections[i];
|
|
5256
5282
|
const ns = getNamespace(section, i);
|
|
5257
5283
|
const sectionToValidate = ns ? namespaceSectionConfig(section, ns) : section;
|
|
5258
5284
|
const valid = sectionValidate(sectionToValidate, values, dispatch);
|
|
@@ -5272,10 +5298,11 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
5272
5298
|
if (modeRef.current !== 'IntakeForm')
|
|
5273
5299
|
checkNoUnsavedChanges();
|
|
5274
5300
|
const values = getValues();
|
|
5301
|
+
const sections = safeSectionsRef.current;
|
|
5275
5302
|
const results = [];
|
|
5276
5303
|
let firstInvalidIndex = null;
|
|
5277
|
-
for (let i = 0; i <
|
|
5278
|
-
const section =
|
|
5304
|
+
for (let i = 0; i < sections.length; i++) {
|
|
5305
|
+
const section = sections[i];
|
|
5279
5306
|
const ns = getNamespace(section, i);
|
|
5280
5307
|
const sectionToValidate = ns ? namespaceSectionConfig(section, ns) : section;
|
|
5281
5308
|
const valid = sectionValidate(sectionToValidate, values, dispatch);
|
|
@@ -5297,16 +5324,18 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
5297
5324
|
},
|
|
5298
5325
|
getStructuredData: () => {
|
|
5299
5326
|
const values = getValues();
|
|
5327
|
+
const sections = safeSectionsRef.current;
|
|
5300
5328
|
const results = [];
|
|
5301
|
-
for (let i = 0; i <
|
|
5302
|
-
const section =
|
|
5329
|
+
for (let i = 0; i < sections.length; i++) {
|
|
5330
|
+
const section = sections[i];
|
|
5303
5331
|
const ns = getNamespace(section, i);
|
|
5304
5332
|
results.push(buildSectionChanges(section, values, ns));
|
|
5305
5333
|
}
|
|
5306
5334
|
return results;
|
|
5307
5335
|
},
|
|
5308
5336
|
};
|
|
5309
|
-
|
|
5337
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5338
|
+
}, [store, dispatch]); // safeSections intentionally omitted — accessed via safeSectionsRef
|
|
5310
5339
|
// Call onFormReady when form is ready (sections loaded)
|
|
5311
5340
|
React.useEffect(() => {
|
|
5312
5341
|
if (onFormReady && safeSections.length > 0) {
|
|
@@ -5390,6 +5419,8 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
5390
5419
|
onSectionSaveSuccess: handleSectionSaveSuccess,
|
|
5391
5420
|
onPreviousSection: handlePreviousSection,
|
|
5392
5421
|
isDraft,
|
|
5422
|
+
// Accessible = every visited section + the one immediately after
|
|
5423
|
+
isAccessible: index <= maxVisitedIndex + 1,
|
|
5393
5424
|
}
|
|
5394
5425
|
: {};
|
|
5395
5426
|
// RegistryView: single-edit coordination props
|