@openg2p/registry-widgets 1.1.0-dev.4 → 1.1.0-dev.5

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
@@ -3849,6 +3849,52 @@ const sectionValidate = (section, currentSchemaData, dispatch) => {
3849
3849
  return isValid;
3850
3850
  };
3851
3851
 
3852
+ /** Table-style widgets that bind to an array path in the store / schema. */
3853
+ function isTableLikeWidget(widget) {
3854
+ const w = widget.widget;
3855
+ /** widget-type union in types omits legacy values like simple-table still used at runtime */
3856
+ const t = widget['widget-type'];
3857
+ return (w === 'table' ||
3858
+ w === 'dialog-table' ||
3859
+ w === 'simple-table' ||
3860
+ t === 'table' ||
3861
+ t === 'simple-table');
3862
+ }
3863
+ /**
3864
+ * Resolve `records` for section save payloads.
3865
+ * - Back-compat: path ending in `.records` (e.g. `regId.records`)
3866
+ * - Else: first array snapshot at a string `widget-data-path` on a table-like widget
3867
+ * (e.g. `household.members` for dialog-table)
3868
+ */
3869
+ function extractTableRecordsFromSnapshot(snapshot, sectionWidgets) {
3870
+ const convention = Object.entries(snapshot).find(([key, value]) => key.endsWith('.records') && Array.isArray(value));
3871
+ if (convention) {
3872
+ return convention[1];
3873
+ }
3874
+ const tablePaths = [];
3875
+ sectionWidgets.forEach((widget) => {
3876
+ if (!isTableLikeWidget(widget))
3877
+ return;
3878
+ const p = widget['widget-data-path'];
3879
+ if (typeof p === 'string' && p.length > 0) {
3880
+ tablePaths.push(p);
3881
+ }
3882
+ else if (p && typeof p === 'object') {
3883
+ Object.values(p).forEach((sub) => {
3884
+ if (typeof sub === 'string' && sub.length > 0)
3885
+ tablePaths.push(sub);
3886
+ });
3887
+ }
3888
+ });
3889
+ for (const path of tablePaths) {
3890
+ const val = snapshot[path];
3891
+ if (Array.isArray(val)) {
3892
+ return val;
3893
+ }
3894
+ }
3895
+ return [];
3896
+ }
3897
+
3852
3898
  /** Root class on readonly label/value rows; SectionRenderer scopes overflow/ellipsis rules here. */
3853
3899
  const READONLY_VALUE_ROW_ROOT_CLASSES = [
3854
3900
  'TextDisplayWidget',
@@ -4214,9 +4260,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4214
4260
  const widgetPath = widget['widget-data-path'];
4215
4261
  if (!widgetPath)
4216
4262
  return;
4217
- if (widget['widget-type'] === 'table' ||
4218
- widget['widget-type'] === 'simple-table' ||
4219
- widget['widget'] === 'table') {
4263
+ if (isTableLikeWidget(widget)) {
4220
4264
  hasTable = true;
4221
4265
  }
4222
4266
  if (typeof widgetPath === 'object') {
@@ -4251,8 +4295,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4251
4295
  },
4252
4296
  ];
4253
4297
  }
4254
- const tableEntry = Object.entries(snapshot).find(([key, value]) => key.endsWith('.records') && Array.isArray(value));
4255
- return tableEntry ? tableEntry[1] : [];
4298
+ return extractTableRecordsFromSnapshot(snapshot, widgets);
4256
4299
  };
4257
4300
  // Get original section (without namespace) for building snapshots
4258
4301
  // This ensures we use the original data paths when saving
@@ -4962,10 +5005,7 @@ function buildSectionChanges(section, storeValues, namespace, options) {
4962
5005
  const widgetPath = widget['widget-data-path'];
4963
5006
  if (!widgetPath)
4964
5007
  return;
4965
- const widgetType = widget['widget-type'];
4966
- if (widgetType === 'table' ||
4967
- widgetType === 'simple-table' ||
4968
- widget.widget === 'table') {
5008
+ if (isTableLikeWidget(widget)) {
4969
5009
  hasTable = true;
4970
5010
  }
4971
5011
  if (typeof widgetPath === 'object') {
@@ -5003,8 +5043,7 @@ function buildSectionChanges(section, storeValues, namespace, options) {
5003
5043
  ];
5004
5044
  }
5005
5045
  else {
5006
- const tableEntry = Object.entries(snapshot).find(([key, value]) => key.endsWith('.records') && Array.isArray(value));
5007
- records = tableEntry ? tableEntry[1] : [];
5046
+ records = extractTableRecordsFromSnapshot(snapshot, sectionWidgets);
5008
5047
  }
5009
5048
  const files = [];
5010
5049
  const supportingDocs = section['section-supporting-documents'] || [];
@@ -5030,7 +5069,9 @@ const hasTableWidget = (panels) => {
5030
5069
  // Check widgets in this panel
5031
5070
  if (panel.widgets) {
5032
5071
  for (const widget of panel.widgets) {
5033
- if (widget.widget === 'table' || widget['widget-type'] === 'table') {
5072
+ if (widget.widget === 'table' ||
5073
+ widget.widget === 'dialog-table' ||
5074
+ widget['widget-type'] === 'table') {
5034
5075
  return true;
5035
5076
  }
5036
5077
  }
@@ -5052,7 +5093,9 @@ const getTableWidgetColumnSpan = (panels) => {
5052
5093
  // Check widgets in this panel
5053
5094
  if (panel.widgets) {
5054
5095
  for (const widget of panel.widgets) {
5055
- if (widget.widget === 'table' || widget['widget-type'] === 'table') {
5096
+ if (widget.widget === 'table' ||
5097
+ widget.widget === 'dialog-table' ||
5098
+ widget['widget-type'] === 'table') {
5056
5099
  // Return the widget's column span if specified, otherwise null
5057
5100
  return widget['widget-column-span'] || null;
5058
5101
  }
@@ -9189,6 +9232,8 @@ const SelectDisplayValue = ({ config, value }) => {
9189
9232
  const DialogTableWidget = ({ config }) => {
9190
9233
  const { value, error, touched, isEnabled, onChange, config: widgetConfig } = useBaseWidget({ config });
9191
9234
  const { translate, translateConfig } = useWidgetTranslation();
9235
+ const dispatch = reactRedux.useDispatch();
9236
+ const storeValues = reactRedux.useSelector((state) => state.widget?.values ?? {});
9192
9237
  const rows = Array.isArray(value) ? value : [];
9193
9238
  const columns = widgetConfig['widget-data-columns'] || [];
9194
9239
  const operations = widgetConfig['widget-data-operations'] || {};
@@ -9207,6 +9252,9 @@ const DialogTableWidget = ({ config }) => {
9207
9252
  const [dialogMode, setDialogMode] = React.useState('add');
9208
9253
  const [activeRowIndex, setActiveRowIndex] = React.useState(null);
9209
9254
  const [formData, setFormData] = React.useState({});
9255
+ /** Unique per dialog open so Redux widget ids don't reuse stale values across rows/add sessions */
9256
+ const dialogSessionRef = React.useRef(0);
9257
+ const [dialogSessionId, setDialogSessionId] = React.useState(0);
9210
9258
  const addDialogTitle = translateConfig(widgetConfig['widget-data-dialog-title-add']) ||
9211
9259
  translate('table.addRecordDialog') ||
9212
9260
  'Add record';
@@ -9221,13 +9269,32 @@ const DialogTableWidget = ({ config }) => {
9221
9269
  });
9222
9270
  return emptyRow;
9223
9271
  }, [columns]);
9272
+ const dialogFieldWidgetId = React.useCallback((columnKey) => `${widgetConfig['widget-id']}-dlg-${dialogSessionId}-${columnKey}`, [widgetConfig, dialogSessionId]);
9273
+ const resetDialogWidgets = React.useCallback((sessionId) => {
9274
+ if (sessionId <= 0)
9275
+ return;
9276
+ columns.forEach((col) => {
9277
+ const wid = `${widgetConfig['widget-id']}-dlg-${sessionId}-${col['column-key']}`;
9278
+ dispatch(resetWidget(wid));
9279
+ });
9280
+ }, [columns, widgetConfig, dispatch]);
9281
+ const beginDialogSession = React.useCallback(() => {
9282
+ dialogSessionRef.current += 1;
9283
+ const nextSession = dialogSessionRef.current;
9284
+ setDialogSessionId(nextSession);
9285
+ return nextSession;
9286
+ }, []);
9224
9287
  const openAddDialog = React.useCallback(() => {
9288
+ resetDialogWidgets(dialogSessionId);
9289
+ beginDialogSession();
9225
9290
  setDialogMode('add');
9226
9291
  setActiveRowIndex(null);
9227
9292
  setFormData(buildEmptyRow());
9228
9293
  setDialogOpen(true);
9229
- }, [buildEmptyRow]);
9294
+ }, [buildEmptyRow, beginDialogSession, resetDialogWidgets, dialogSessionId]);
9230
9295
  const openEditDialog = React.useCallback((rowIndex) => {
9296
+ resetDialogWidgets(dialogSessionId);
9297
+ beginDialogSession();
9231
9298
  const row = rows[rowIndex] || {};
9232
9299
  const nextFormData = buildEmptyRow();
9233
9300
  columns.forEach((col) => {
@@ -9239,18 +9306,33 @@ const DialogTableWidget = ({ config }) => {
9239
9306
  setActiveRowIndex(rowIndex);
9240
9307
  setFormData(nextFormData);
9241
9308
  setDialogOpen(true);
9242
- }, [rows, columns, buildEmptyRow]);
9309
+ }, [rows, columns, buildEmptyRow, resetDialogWidgets, dialogSessionId, beginDialogSession]);
9243
9310
  const closeDialog = React.useCallback(() => {
9311
+ const sessionToClear = dialogSessionId;
9244
9312
  setDialogOpen(false);
9245
9313
  setActiveRowIndex(null);
9246
9314
  setFormData({});
9247
- }, []);
9315
+ resetDialogWidgets(sessionToClear);
9316
+ setDialogSessionId(0);
9317
+ }, [dialogSessionId, resetDialogWidgets]);
9248
9318
  const updateField = React.useCallback((columnKey, newValue) => {
9249
9319
  setFormData((prev) => ({ ...prev, [columnKey]: newValue }));
9250
9320
  }, []);
9321
+ const collectMergedRowPayload = React.useCallback(() => {
9322
+ const merged = { ...formData };
9323
+ columns.forEach((col) => {
9324
+ const k = col['column-key'];
9325
+ const wid = dialogFieldWidgetId(k);
9326
+ const fromStore = storeValues[wid];
9327
+ if (fromStore !== undefined)
9328
+ merged[k] = fromStore;
9329
+ });
9330
+ return merged;
9331
+ }, [formData, columns, storeValues, dialogFieldWidgetId]);
9251
9332
  const saveDialog = React.useCallback(() => {
9333
+ const payload = collectMergedRowPayload();
9252
9334
  if (dialogMode === 'add') {
9253
- const savedRow = { ...formData, edit_action: 'ADD' };
9335
+ const savedRow = { ...payload, edit_action: 'ADD' };
9254
9336
  onChange([...rows, savedRow]);
9255
9337
  closeDialog();
9256
9338
  return;
@@ -9260,11 +9342,11 @@ const DialogTableWidget = ({ config }) => {
9260
9342
  const currentRow = newRows[activeRowIndex] || {};
9261
9343
  const wasDeleted = currentRow.edit_action === 'DELETE';
9262
9344
  const editAction = wasDeleted ? 'UPDATE' : (currentRow.edit_action ?? 'UPDATE');
9263
- newRows[activeRowIndex] = { ...currentRow, ...formData, edit_action: editAction };
9345
+ newRows[activeRowIndex] = { ...currentRow, ...payload, edit_action: editAction };
9264
9346
  onChange(newRows);
9265
9347
  closeDialog();
9266
9348
  }
9267
- }, [dialogMode, formData, onChange, rows, closeDialog, activeRowIndex]);
9349
+ }, [collectMergedRowPayload, dialogMode, onChange, rows, closeDialog, activeRowIndex]);
9268
9350
  const deleteRow = React.useCallback((rowIndex) => {
9269
9351
  const newRows = rows.filter((_, i) => i !== rowIndex);
9270
9352
  onChange(newRows);
@@ -9352,7 +9434,8 @@ const DialogTableWidget = ({ config }) => {
9352
9434
  }, "aria-label": "Close", children: "\u00D7" })] }), jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", style: { maxHeight: '70vh', overflow: 'auto' }, children: columns.map((col) => {
9353
9435
  const key = col['column-key'];
9354
9436
  const widgetType = col.widget || 'text';
9355
- const cellWidgetId = `${widgetConfig['widget-id']}-dialog-${dialogMode}-${key}`;
9437
+ const cellWidgetId = dialogFieldWidgetId(key);
9438
+ const initialValue = formData[key] ?? col['widget-data-default'] ?? '';
9356
9439
  const fieldConfig = {
9357
9440
  ...col,
9358
9441
  widget: widgetType,
@@ -9361,10 +9444,10 @@ const DialogTableWidget = ({ config }) => {
9361
9444
  'widget-label': col['widget-label'],
9362
9445
  'widget-readonly': isReadonly || col['widget-readonly'] === true,
9363
9446
  'widget-data-path': undefined,
9364
- 'widget-data-default': formData[key] ?? col['widget-data-default'] ?? '',
9447
+ 'widget-data-default': initialValue,
9365
9448
  };
9366
- return (jsxRuntimeExports.jsx("div", { className: "min-w-0", children: jsxRuntimeExports.jsx(WidgetRenderer, { config: fieldConfig, schemaData: { [cellWidgetId]: formData[key] ?? col['widget-data-default'] ?? '' }, onValueChange: (_widgetId, newValue) => updateField(key, newValue) }) }, key));
9367
- }) }), jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-3 mt-6", children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: closeDialog, className: "px-4 py-2 text-sm font-medium", style: {
9449
+ return (jsxRuntimeExports.jsx("div", { className: "min-w-0", children: jsxRuntimeExports.jsx(WidgetRenderer, { config: fieldConfig, schemaData: { [cellWidgetId]: initialValue }, onValueChange: (_widgetId, newValue) => updateField(key, newValue) }) }, `${dialogSessionId}-${key}`));
9450
+ }) }, `dialog-fields-${dialogSessionId}`), jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-3 mt-6", children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: closeDialog, className: "px-4 py-2 text-sm font-medium", style: {
9368
9451
  borderRadius: 'var(--owt-btn-border-radius, 10px)',
9369
9452
  border: '1px solid var(--owt-btn-secondary-border, #C4C4C4)',
9370
9453
  backgroundColor: 'var(--owt-btn-secondary-bg, #FFFFFF)',