@openg2p/registry-widgets 1.1.0-dev.12 → 1.1.0-dev.14

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.
@@ -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,4CAkVxB,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
@@ -145,6 +145,8 @@ interface WidgetFormat {
145
145
  booleanControlType?: BooleanControlType;
146
146
  booleanTrueLabel?: string;
147
147
  booleanFalseLabel?: string;
148
+ /** Label for null/unset when allowUnset (radio, toggle); defaults to "Not set" */
149
+ booleanUnsetLabel?: string;
148
150
  allowUnset?: boolean;
149
151
  layout?: 'vertical' | 'horizontal' | 'grid';
150
152
  sortOptions?: boolean;
package/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createSlice, configureStore } from '@reduxjs/toolkit';
2
- import React, { useContext, createContext, useMemo, useEffect, useRef, useCallback, useState } from 'react';
2
+ import React, { useContext, createContext, useMemo, useEffect, useRef, useCallback, useState, useId } from 'react';
3
3
  import { Provider, useDispatch, useSelector, useStore } from 'react-redux';
4
4
  import { z } from 'zod';
5
5
  import { createPortal } from 'react-dom';
@@ -1077,16 +1077,12 @@ const getApiDataSource = async (dataSource, allValues, dataSourceRequestHandler,
1077
1077
  console.error('[getApiDataSource] API data source missing endpoint. Use "endpoint" field to specify the operation (e.g., "get_g2p_geo_level_values")');
1078
1078
  return [];
1079
1079
  }
1080
- let response;
1081
- try {
1082
- response = await dataSourceRequestHandler(service, endpoint, method, requestParams, {
1083
- headers: dataSource.headers,
1084
- });
1085
- }
1086
- catch (error) {
1087
- console.error('[getApiDataSource] Handler error:', error);
1088
- throw error;
1089
- }
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
+ });
1090
1086
  // Handle OpenG2P response format (response_body.response_payload)
1091
1087
  if (response && typeof response === 'object') {
1092
1088
  if (response.response_body?.response_payload && Array.isArray(response.response_body.response_payload)) {
@@ -1109,8 +1105,8 @@ const getApiDataSource = async (dataSource, allValues, dataSourceRequestHandler,
1109
1105
  return [];
1110
1106
  }
1111
1107
  catch (error) {
1112
- console.error('Error fetching API data source:', error);
1113
- return [];
1108
+ // Rethrow so useBaseWidget's catch can log it with full widget context
1109
+ throw error;
1114
1110
  }
1115
1111
  };
1116
1112
  /**
@@ -2304,7 +2300,7 @@ const useBaseWidget = (options) => {
2304
2300
  dispatch(setDataSource({ widgetId, data: transformed }));
2305
2301
  }
2306
2302
  catch (error) {
2307
- 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));
2308
2304
  dispatch(setDataSource({ widgetId, data: [] }));
2309
2305
  }
2310
2306
  finally {
@@ -3797,6 +3793,116 @@ const namespaceSectionConfig = (section, namespace) => {
3797
3793
  return namespaced;
3798
3794
  };
3799
3795
 
3796
+ /** Table-style widgets that bind to an array path in the store / schema. */
3797
+ function isTableLikeWidget(widget) {
3798
+ const w = widget.widget;
3799
+ /** widget-type union in types omits legacy values like simple-table still used at runtime */
3800
+ const t = widget['widget-type'];
3801
+ return (w === 'table' ||
3802
+ w === 'dialog-table' ||
3803
+ w === 'simple-table' ||
3804
+ t === 'table' ||
3805
+ t === 'simple-table');
3806
+ }
3807
+ /**
3808
+ * Resolve `records` for section save payloads.
3809
+ * - Back-compat: path ending in `.records` (e.g. `regId.records`)
3810
+ * - Else: first array snapshot at a string `widget-data-path` on a table-like widget
3811
+ * (e.g. `household.members` for dialog-table)
3812
+ */
3813
+ function extractTableRecordsFromSnapshot(snapshot, sectionWidgets) {
3814
+ const convention = Object.entries(snapshot).find(([key, value]) => key.endsWith('.records') && Array.isArray(value));
3815
+ if (convention) {
3816
+ return convention[1];
3817
+ }
3818
+ const tablePaths = [];
3819
+ sectionWidgets.forEach((widget) => {
3820
+ if (!isTableLikeWidget(widget))
3821
+ return;
3822
+ const p = widget['widget-data-path'];
3823
+ if (typeof p === 'string' && p.length > 0) {
3824
+ tablePaths.push(p);
3825
+ }
3826
+ else if (p && typeof p === 'object') {
3827
+ Object.values(p).forEach((sub) => {
3828
+ if (typeof sub === 'string' && sub.length > 0)
3829
+ tablePaths.push(sub);
3830
+ });
3831
+ }
3832
+ });
3833
+ for (const path of tablePaths) {
3834
+ const val = snapshot[path];
3835
+ if (Array.isArray(val)) {
3836
+ return val;
3837
+ }
3838
+ }
3839
+ return [];
3840
+ }
3841
+
3842
+ const isColumnRequired = (column, skipRequired) => {
3843
+ if (skipRequired)
3844
+ return false;
3845
+ const validation = column['widget-data-validation'];
3846
+ return !!(column['widget-required'] || validation?.required);
3847
+ };
3848
+ const validateTableLikeWidget = (widget, currentSchemaData, dispatch, skipRequired) => {
3849
+ const widgetId = widget['widget-id'];
3850
+ if (!widgetId)
3851
+ return true;
3852
+ const columns = (widget['widget-data-columns'] || []);
3853
+ const value = getWidgetValue(currentSchemaData, widget['widget-data-path'], widgetId);
3854
+ const rows = Array.isArray(value)
3855
+ ? value
3856
+ : [];
3857
+ const activeRows = rows.filter((row) => row?.edit_action !== 'DELETE');
3858
+ const rowErrors = [];
3859
+ let isValid = true;
3860
+ if (!skipRequired && widget['widget-required'] && activeRows.length === 0) {
3861
+ dispatch(setTouched({ widgetId, touched: true }));
3862
+ dispatch(setError({ widgetId, errors: ['At least one record is required'] }));
3863
+ return false;
3864
+ }
3865
+ const hasRequiredColumns = columns.some((col) => isColumnRequired(col, skipRequired));
3866
+ if (!skipRequired && hasRequiredColumns && activeRows.length === 0) {
3867
+ dispatch(setTouched({ widgetId, touched: true }));
3868
+ dispatch(setError({
3869
+ widgetId,
3870
+ errors: ['Add at least one record and fill all required fields'],
3871
+ }));
3872
+ return false;
3873
+ }
3874
+ activeRows.forEach((row, rowIndex) => {
3875
+ columns.forEach((col) => {
3876
+ if (col['widget-readonly'])
3877
+ return;
3878
+ const key = col['column-key'];
3879
+ if (!key)
3880
+ return;
3881
+ const required = isColumnRequired(col, skipRequired);
3882
+ const cellValue = row[key];
3883
+ const errors = validateWidget(cellValue, col['widget-data-validation'], required, skipRequired);
3884
+ if (errors.length > 0) {
3885
+ isValid = false;
3886
+ const label = col['widget-label'] || key;
3887
+ rowErrors.push(`Row ${rowIndex + 1}, ${label}: ${errors[0]}`);
3888
+ }
3889
+ });
3890
+ });
3891
+ if (!isValid) {
3892
+ dispatch(setTouched({ widgetId, touched: true }));
3893
+ dispatch(setError({
3894
+ widgetId,
3895
+ errors: rowErrors.length > 0
3896
+ ? rowErrors.slice(0, 5)
3897
+ : ['Please fix required fields in the table'],
3898
+ }));
3899
+ }
3900
+ else {
3901
+ dispatch(setTouched({ widgetId, touched: false }));
3902
+ dispatch(setError({ widgetId, errors: [] }));
3903
+ }
3904
+ return isValid;
3905
+ };
3800
3906
  const collectWidgets = (panels) => {
3801
3907
  let widgets = [];
3802
3908
  panels.forEach((panel) => {
@@ -3825,6 +3931,13 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
3825
3931
  if (!isVisible)
3826
3932
  continue;
3827
3933
  const widgetId = widget['widget-id'];
3934
+ if (isTableLikeWidget(widget)) {
3935
+ const tableValid = validateTableLikeWidget(widget, currentSchemaData, dispatch, skipRequired);
3936
+ if (!tableValid) {
3937
+ isValid = false;
3938
+ }
3939
+ continue;
3940
+ }
3828
3941
  const value = getWidgetValue(currentSchemaData, widget['widget-data-path'], widgetId);
3829
3942
  const errors = validateWidget(value, widget['widget-data-validation'], widget['widget-required'], skipRequired);
3830
3943
  if (errors.length > 0) {
@@ -3859,52 +3972,6 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
3859
3972
  return isValid;
3860
3973
  };
3861
3974
 
3862
- /** Table-style widgets that bind to an array path in the store / schema. */
3863
- function isTableLikeWidget(widget) {
3864
- const w = widget.widget;
3865
- /** widget-type union in types omits legacy values like simple-table still used at runtime */
3866
- const t = widget['widget-type'];
3867
- return (w === 'table' ||
3868
- w === 'dialog-table' ||
3869
- w === 'simple-table' ||
3870
- t === 'table' ||
3871
- t === 'simple-table');
3872
- }
3873
- /**
3874
- * Resolve `records` for section save payloads.
3875
- * - Back-compat: path ending in `.records` (e.g. `regId.records`)
3876
- * - Else: first array snapshot at a string `widget-data-path` on a table-like widget
3877
- * (e.g. `household.members` for dialog-table)
3878
- */
3879
- function extractTableRecordsFromSnapshot(snapshot, sectionWidgets) {
3880
- const convention = Object.entries(snapshot).find(([key, value]) => key.endsWith('.records') && Array.isArray(value));
3881
- if (convention) {
3882
- return convention[1];
3883
- }
3884
- const tablePaths = [];
3885
- sectionWidgets.forEach((widget) => {
3886
- if (!isTableLikeWidget(widget))
3887
- return;
3888
- const p = widget['widget-data-path'];
3889
- if (typeof p === 'string' && p.length > 0) {
3890
- tablePaths.push(p);
3891
- }
3892
- else if (p && typeof p === 'object') {
3893
- Object.values(p).forEach((sub) => {
3894
- if (typeof sub === 'string' && sub.length > 0)
3895
- tablePaths.push(sub);
3896
- });
3897
- }
3898
- });
3899
- for (const path of tablePaths) {
3900
- const val = snapshot[path];
3901
- if (Array.isArray(val)) {
3902
- return val;
3903
- }
3904
- }
3905
- return [];
3906
- }
3907
-
3908
3975
  /** Root class on readonly label/value rows; SectionRenderer scopes overflow/ellipsis rules here. */
3909
3976
  const READONLY_VALUE_ROW_ROOT_CLASSES = [
3910
3977
  'TextDisplayWidget',
@@ -5196,11 +5263,6 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
5196
5263
  // Stable ref for namespace so formHandle useMemo doesn't depend on the (possibly inline) function identity
5197
5264
  const namespaceRef = useRef(namespace);
5198
5265
  namespaceRef.current = namespace;
5199
- // Stable ref for sections so formHandle useMemo doesn't depend on the (possibly unstable) array reference.
5200
- // Without this, an inline sections={[...]} prop would recreate formHandle every render, causing
5201
- // onFormReady to fire every render → host setState → infinite re-render loop.
5202
- const safeSectionsRef = useRef(safeSections);
5203
- safeSectionsRef.current = safeSections;
5204
5266
  // Stable refs so formHandle closure can access current mode and accordion setter without stale captures
5205
5267
  const modeRef = useRef(mode);
5206
5268
  modeRef.current = mode;
@@ -5249,9 +5311,6 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
5249
5311
  }, [mode, safeSections.length, expandedSectionIndex]);
5250
5312
  const UNSAVED_CHANGES_ERROR = 'Unsaved changes detected. Please save all sections before submitting.';
5251
5313
  // Form handle for onFormReady - allows host to validate and get all section data from its own Submit button
5252
- // IMPORTANT: safeSections is intentionally accessed via safeSectionsRef.current (not closed over directly)
5253
- // so that this memo never depends on the sections array reference. This prevents an infinite re-render
5254
- // loop when the host passes an unstable sections prop (e.g. inline array or derived state).
5255
5314
  const formHandle = useMemo(() => {
5256
5315
  const getValues = () => store.getState().widget?.values || {};
5257
5316
  const getNamespace = (section, index) => {
@@ -5273,11 +5332,10 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
5273
5332
  if (modeRef.current !== 'IntakeForm')
5274
5333
  checkNoUnsavedChanges();
5275
5334
  const values = getValues();
5276
- const sections = safeSectionsRef.current;
5277
5335
  let allValid = true;
5278
5336
  let firstInvalidIndex = null;
5279
- for (let i = 0; i < sections.length; i++) {
5280
- const section = sections[i];
5337
+ for (let i = 0; i < safeSections.length; i++) {
5338
+ const section = safeSections[i];
5281
5339
  const ns = getNamespace(section, i);
5282
5340
  const sectionToValidate = ns ? namespaceSectionConfig(section, ns) : section;
5283
5341
  const valid = sectionValidate(sectionToValidate, values, dispatch);
@@ -5297,11 +5355,10 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
5297
5355
  if (modeRef.current !== 'IntakeForm')
5298
5356
  checkNoUnsavedChanges();
5299
5357
  const values = getValues();
5300
- const sections = safeSectionsRef.current;
5301
5358
  const results = [];
5302
5359
  let firstInvalidIndex = null;
5303
- for (let i = 0; i < sections.length; i++) {
5304
- const section = sections[i];
5360
+ for (let i = 0; i < safeSections.length; i++) {
5361
+ const section = safeSections[i];
5305
5362
  const ns = getNamespace(section, i);
5306
5363
  const sectionToValidate = ns ? namespaceSectionConfig(section, ns) : section;
5307
5364
  const valid = sectionValidate(sectionToValidate, values, dispatch);
@@ -5323,18 +5380,16 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
5323
5380
  },
5324
5381
  getStructuredData: () => {
5325
5382
  const values = getValues();
5326
- const sections = safeSectionsRef.current;
5327
5383
  const results = [];
5328
- for (let i = 0; i < sections.length; i++) {
5329
- const section = sections[i];
5384
+ for (let i = 0; i < safeSections.length; i++) {
5385
+ const section = safeSections[i];
5330
5386
  const ns = getNamespace(section, i);
5331
5387
  results.push(buildSectionChanges(section, values, ns));
5332
5388
  }
5333
5389
  return results;
5334
5390
  },
5335
5391
  };
5336
- // eslint-disable-next-line react-hooks/exhaustive-deps
5337
- }, [store, dispatch]); // safeSections intentionally omitted — accessed via safeSectionsRef
5392
+ }, [store, dispatch, safeSections]);
5338
5393
  // Call onFormReady when form is ready (sections loaded)
5339
5394
  useEffect(() => {
5340
5395
  if (onFormReady && safeSections.length > 0) {
@@ -7402,7 +7457,7 @@ const NumberInputWidget = ({ config }) => {
7402
7457
  if (parsed === null) {
7403
7458
  // Allow empty input or partial input (e.g., "-", ".")
7404
7459
  if (inputValue === '' || inputValue === '-' || inputValue === '.') {
7405
- onChange('');
7460
+ onChange(null);
7406
7461
  }
7407
7462
  // Don't update if invalid - let user continue typing
7408
7463
  return;
@@ -7526,6 +7581,8 @@ const BooleanWidget = ({ config }) => {
7526
7581
  return labels[representation];
7527
7582
  }, [representation, formatConfig, translateConfig]);
7528
7583
  const { trueLabel, falseLabel } = getLabels();
7584
+ const unsetLabel = useMemo(() => translateConfig(formatConfig?.booleanUnsetLabel || 'Not set'), [formatConfig?.booleanUnsetLabel, translateConfig]);
7585
+ const radioGroupName = `${widgetConfig['widget-id'] ?? 'boolean'}__${useId().replace(/:/g, '')}`;
7529
7586
  // Determine current value (handle null/undefined)
7530
7587
  const currentValue = useMemo(() => {
7531
7588
  if (value === null || value === undefined) {
@@ -7557,7 +7614,7 @@ const BooleanWidget = ({ config }) => {
7557
7614
  const label = translateConfig(widgetConfig['widget-label']);
7558
7615
  let displayValue = '';
7559
7616
  if (currentValue === null) {
7560
- displayValue = '-';
7617
+ displayValue = '';
7561
7618
  }
7562
7619
  else if (currentValue === true) {
7563
7620
  displayValue = trueLabel;
@@ -7569,18 +7626,20 @@ const BooleanWidget = ({ config }) => {
7569
7626
  }
7570
7627
  // Render based on control type
7571
7628
  if (controlType === 'checkbox') {
7572
- return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "flex items-center cursor-pointer", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: currentValue === true, onChange: handleCheckboxChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: currentValue === true ? trueLabel : (currentValue === false ? falseLabel : '-') })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
7629
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "inline-flex items-baseline cursor-pointer gap-2", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: currentValue === true, onChange: handleCheckboxChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), (currentValue === true || currentValue === false) && (jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: currentValue === true ? trueLabel : falseLabel }))] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
7573
7630
  }
7574
7631
  if (controlType === 'radio') {
7575
7632
  const containerClass = orientation === 'horizontal'
7576
- ? 'flex flex-row space-x-4'
7577
- : 'flex flex-col space-y-2';
7578
- return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: containerClass, onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsxs("label", { className: `flex items-center cursor-pointer ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: widgetConfig['widget-id'], checked: currentValue === null, onChange: () => handleRadioChange(null), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: "-" })] })), jsxRuntimeExports.jsxs("label", { className: `flex items-center cursor-pointer ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: widgetConfig['widget-id'], checked: currentValue === true, onChange: () => handleRadioChange(true), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: trueLabel })] }), jsxRuntimeExports.jsxs("label", { className: `flex items-center cursor-pointer ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: widgetConfig['widget-id'], checked: currentValue === false, onChange: () => handleRadioChange(false), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: falseLabel })] })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
7633
+ ? 'flex flex-row flex-wrap items-baseline gap-x-4 gap-y-2'
7634
+ : 'flex flex-col items-start gap-2';
7635
+ const radioDisabled = !isEnabled || widgetConfig['widget-readonly'];
7636
+ const optionDisabledClass = radioDisabled ? 'opacity-50 cursor-not-allowed' : '';
7637
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: containerClass, onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === null, onChange: () => handleRadioChange(null), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: unsetLabel })] })), jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === true, onChange: () => handleRadioChange(true), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: trueLabel })] }), jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === false, onChange: () => handleRadioChange(false), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: falseLabel })] })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
7579
7638
  }
7580
7639
  // Toggle/switch control type
7581
- return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 sm:min-w-[150px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-3", onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(null), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === null
7640
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 sm:min-w-[150px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-3", onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(null), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === null
7582
7641
  ? 'bg-blue-600 text-white border-blue-600'
7583
- : 'bg-white text-gray-700 border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50'}`, style: { borderRadius: '15px' }, children: "-" })), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(true), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === true
7642
+ : 'bg-white text-gray-700 border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50'}`, style: { borderRadius: '15px' }, children: unsetLabel })), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(true), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === true
7584
7643
  ? 'bg-blue-600 text-white border-blue-600'
7585
7644
  : 'bg-white text-gray-700 border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50'}`, style: { borderRadius: '15px' }, children: trueLabel }), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(false), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === false
7586
7645
  ? 'bg-blue-600 text-white border-blue-600'
@@ -8291,7 +8350,7 @@ const CheckboxWidget = ({ config }) => {
8291
8350
  const displayValue = isChecked ? 'Yes' : 'No';
8292
8351
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] CheckboxDisplayWidget 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", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
8293
8352
  }
8294
- return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "flex items-center cursor-pointer", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: isChecked, onChange: (e) => onChange(e.target.checked), onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: isChecked ? 'Yes' : 'No' })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8353
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "inline-flex cursor-pointer items-baseline gap-2", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: isChecked, onChange: (e) => onChange(e.target.checked), onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-base leading-normal text-gray-700", children: isChecked ? 'Yes' : 'No' })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8295
8354
  }
8296
8355
  // Multiple checkboxes (with data source) - for array values
8297
8356
  // Process and sort options if needed
@@ -8334,7 +8393,7 @@ const CheckboxWidget = ({ config }) => {
8334
8393
  switch (layout) {
8335
8394
  case 'horizontal':
8336
8395
  return {
8337
- className: 'flex flex-row flex-wrap gap-4',
8396
+ className: 'flex flex-row flex-wrap items-baseline gap-4',
8338
8397
  style: undefined,
8339
8398
  };
8340
8399
  case 'grid':
@@ -8347,7 +8406,7 @@ const CheckboxWidget = ({ config }) => {
8347
8406
  case 'vertical':
8348
8407
  default:
8349
8408
  return {
8350
- className: 'flex flex-col space-y-2',
8409
+ className: 'flex flex-col gap-2',
8351
8410
  style: undefined,
8352
8411
  };
8353
8412
  }
@@ -8361,7 +8420,7 @@ const CheckboxWidget = ({ config }) => {
8361
8420
  : '-';
8362
8421
  return (jsxRuntimeExports.jsxs("div", { className: "mb-3 CheckboxDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-sm text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
8363
8422
  }
8364
- return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("div", { className: layoutConfig.className, style: layoutConfig.style, onBlur: onBlur, children: loading ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500", children: translate('common.loading') })) : (processedOptions.map((option) => (jsxRuntimeExports.jsxs("label", { className: `flex items-center cursor-pointer ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "checkbox", value: option.value, checked: selectedValues.includes(option.value), onChange: (e) => handleCheckboxChange(option.value, e.target.checked), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: translateConfig(option.label) })] }, option.value)))) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8423
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("div", { className: layoutConfig.className, style: layoutConfig.style, onBlur: onBlur, children: loading ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500", children: translate('common.loading') })) : (processedOptions.map((option) => (jsxRuntimeExports.jsxs("label", { className: `inline-flex cursor-pointer items-baseline gap-2 ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "checkbox", value: option.value, checked: selectedValues.includes(option.value), onChange: (e) => handleCheckboxChange(option.value, e.target.checked), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-base leading-normal text-gray-700", children: translateConfig(option.label) })] }, option.value)))) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8365
8424
  };
8366
8425
 
8367
8426
  const SimpleTableWidget = ({ config }) => {
@@ -9400,6 +9459,27 @@ const DialogTableWidget = ({ config }) => {
9400
9459
  }, [formData, columns, storeValues, dialogFieldWidgetId]);
9401
9460
  const saveDialog = useCallback(() => {
9402
9461
  const payload = collectMergedRowPayload();
9462
+ let hasErrors = false;
9463
+ columns.forEach((col) => {
9464
+ const key = col['column-key'];
9465
+ const cellWidgetId = dialogFieldWidgetId(key);
9466
+ const isColReadonly = isReadonly || col['widget-readonly'] === true;
9467
+ if (isColReadonly)
9468
+ return;
9469
+ const cellValue = payload[key];
9470
+ const validationErrors = validateWidget(cellValue, col['widget-data-validation'], col['widget-required']);
9471
+ if (validationErrors && validationErrors.length > 0) {
9472
+ hasErrors = true;
9473
+ dispatch(setError({ widgetId: cellWidgetId, errors: validationErrors }));
9474
+ dispatch(setTouched({ widgetId: cellWidgetId, touched: true }));
9475
+ }
9476
+ else {
9477
+ dispatch(setError({ widgetId: cellWidgetId, errors: [] }));
9478
+ }
9479
+ });
9480
+ if (hasErrors) {
9481
+ return;
9482
+ }
9403
9483
  if (dialogMode === 'add') {
9404
9484
  const savedRow = { ...payload, edit_action: 'ADD' };
9405
9485
  onChange([...rows, savedRow]);
@@ -9415,7 +9495,7 @@ const DialogTableWidget = ({ config }) => {
9415
9495
  onChange(newRows);
9416
9496
  closeDialog();
9417
9497
  }
9418
- }, [collectMergedRowPayload, dialogMode, onChange, rows, closeDialog, activeRowIndex]);
9498
+ }, [collectMergedRowPayload, dialogMode, onChange, rows, closeDialog, activeRowIndex, columns, dialogFieldWidgetId, isReadonly, dispatch]);
9419
9499
  const deleteRow = useCallback((rowIndex) => {
9420
9500
  const newRows = rows.filter((_, i) => i !== rowIndex);
9421
9501
  onChange(newRows);
@@ -9766,10 +9846,13 @@ const TextAreaWidget = ({ config }) => {
9766
9846
  // For readonly mode, render as preformatted text using <pre> tag
9767
9847
  if (isReadonly) {
9768
9848
  const displayValue = getStringValue() || '-';
9769
- 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: {
9849
+ 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: {
9770
9850
  fontFamily: 'Roboto, sans-serif',
9771
- margin: 0,
9772
- padding: 0,
9851
+ height: '56px',
9852
+ minHeight: '56px',
9853
+ maxHeight: '56px',
9854
+ lineHeight: '20px',
9855
+ padding: '8px 0',
9773
9856
  backgroundColor: 'transparent',
9774
9857
  border: 'none',
9775
9858
  }, children: displayValue }) })] }));
@@ -10238,20 +10321,29 @@ const HeaderSectionWidget = ({ config }) => {
10238
10321
  .${cls} .hdr-field-row {
10239
10322
  display: flex;
10240
10323
  align-items: flex-start;
10241
- gap: 0.5rem;
10242
10324
  font-size: 1rem;
10243
10325
  line-height: 1.6;
10244
10326
  }
10245
10327
 
10246
10328
  .${cls} .hdr-field-label {
10329
+ width: 50%;
10330
+ flex: 0 0 50%;
10247
10331
  color: rgba(0, 0, 0, 0.5);
10248
10332
  font-weight: 400;
10249
10333
  white-space: nowrap;
10334
+ overflow: hidden;
10335
+ text-overflow: ellipsis;
10336
+ padding-right: 4px;
10250
10337
  }
10251
10338
 
10252
10339
  .${cls} .hdr-field-value {
10340
+ width: 50%;
10341
+ flex: 0 0 50%;
10253
10342
  color: var(--owt-color-text, #111827);
10254
10343
  font-weight: 500;
10344
+ white-space: nowrap;
10345
+ overflow: hidden;
10346
+ text-overflow: ellipsis;
10255
10347
  }
10256
10348
 
10257
10349
  .${cls} .hdr-status-badge {
@@ -10261,24 +10353,38 @@ const HeaderSectionWidget = ({ config }) => {
10261
10353
  font-size: 0.75rem;
10262
10354
  font-weight: 600;
10263
10355
  color: #fff;
10356
+ max-width: 100%;
10357
+ overflow: hidden;
10358
+ text-overflow: ellipsis;
10359
+ white-space: nowrap;
10264
10360
  }
10265
10361
 
10266
10362
  .${cls} .hdr-meta-row {
10267
10363
  display: flex;
10268
10364
  align-items: baseline;
10269
- gap: 0.35rem;
10270
10365
  font-size: 1rem;
10271
10366
  line-height: 1.6;
10272
10367
  }
10273
10368
 
10274
10369
  .${cls} .hdr-meta-label {
10370
+ width: 50%;
10371
+ flex: 0 0 50%;
10275
10372
  color: rgba(0, 0, 0, 0.5);
10276
10373
  font-weight: 400;
10374
+ white-space: nowrap;
10375
+ overflow: hidden;
10376
+ text-overflow: ellipsis;
10377
+ padding-right: 4px;
10277
10378
  }
10278
10379
 
10279
10380
  .${cls} .hdr-meta-value {
10381
+ width: 50%;
10382
+ flex: 0 0 50%;
10280
10383
  color: var(--owt-color-text, #111827);
10281
10384
  font-weight: 500;
10385
+ white-space: nowrap;
10386
+ overflow: hidden;
10387
+ text-overflow: ellipsis;
10282
10388
  }
10283
10389
 
10284
10390
  .${cls} .hdr-select {
@@ -10342,7 +10448,7 @@ const HeaderSectionWidget = ({ config }) => {
10342
10448
  .parentElement?.querySelector('.hdr-avatar-placeholder');
10343
10449
  if (placeholder)
10344
10450
  placeholder.style.display = 'flex';
10345
- } })) : null, jsxRuntimeExports.jsx("div", { className: "hdr-avatar-placeholder", style: { display: displayImageUrl ? 'none' : 'flex' }, children: jsxRuntimeExports.jsx("img", { src: img$f, alt: "Profile Placeholder" }) }), !isReadonly && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsxs("div", { className: "hdr-avatar-overlay", children: [jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action", onClick: () => fileInputRef.current?.click(), children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), jsxRuntimeExports.jsx("polyline", { points: "17 8 12 3 7 8" }), jsxRuntimeExports.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })] }), "Upload"] }), jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action hdr-avatar-action--delete", onClick: handleImageDelete, children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("polyline", { points: "3 6 5 6 21 6" }), jsxRuntimeExports.jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })] }), "Delete"] })] }), jsxRuntimeExports.jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: handleImageUpload })] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-info", children: [displayName && jsxRuntimeExports.jsx("div", { className: "hdr-name", children: displayName }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", children: [getLabel('functionalId'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: functionalId || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsx("span", { className: "hdr-field-label", children: getLabel('status') }), isReadonly ? (statusLabel ? (jsxRuntimeExports.jsx("span", { className: "hdr-status-badge", style: { backgroundColor: statusColor }, children: statusLabel })) : (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: "-" }))) : (jsxRuntimeExports.jsxs("select", { className: "hdr-select", value: statusValue, onChange: (e) => updateFieldValue('status', e.target.value), children: [jsxRuntimeExports.jsx("option", { value: "", children: getLabel('select') }), statusOptions.map((opt) => (jsxRuntimeExports.jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", children: [getLabel('statusReason'), " :"] }), isReadonly ? (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: statusReason || '-' })) : (jsxRuntimeExports.jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [jsxRuntimeExports.jsx("input", { type: "text", className: `hdr-input ${(!isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing) ? 'hdr-input--error' : ''}`, value: statusReason, placeholder: getLabel('enterReason'), required: isStatusChanged, "aria-required": isStatusChanged, "aria-invalid": !isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing, onBlur: () => {
10451
+ } })) : null, jsxRuntimeExports.jsx("div", { className: "hdr-avatar-placeholder", style: { display: displayImageUrl ? 'none' : 'flex' }, children: jsxRuntimeExports.jsx("img", { src: img$f, alt: "Profile Placeholder" }) }), !isReadonly && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsxs("div", { className: "hdr-avatar-overlay", children: [jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action", onClick: () => fileInputRef.current?.click(), children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), jsxRuntimeExports.jsx("polyline", { points: "17 8 12 3 7 8" }), jsxRuntimeExports.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })] }), "Upload"] }), jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action hdr-avatar-action--delete", onClick: handleImageDelete, children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("polyline", { points: "3 6 5 6 21 6" }), jsxRuntimeExports.jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })] }), "Delete"] })] }), jsxRuntimeExports.jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: handleImageUpload })] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-info", children: [displayName && jsxRuntimeExports.jsx("div", { className: "hdr-name", children: displayName }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", title: `${getLabel('functionalId')} :`, children: [getLabel('functionalId'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-field-value", title: functionalId || '-', children: functionalId || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsx("span", { className: "hdr-field-label", title: getLabel('status'), children: getLabel('status') }), isReadonly ? (statusLabel ? (jsxRuntimeExports.jsx("span", { className: "hdr-status-badge", style: { backgroundColor: statusColor }, title: statusLabel, children: statusLabel })) : (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", title: "-", children: "-" }))) : (jsxRuntimeExports.jsxs("select", { className: "hdr-select", value: statusValue, onChange: (e) => updateFieldValue('status', e.target.value), children: [jsxRuntimeExports.jsx("option", { value: "", children: getLabel('select') }), statusOptions.map((opt) => (jsxRuntimeExports.jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", title: `${getLabel('statusReason')} :`, children: [getLabel('statusReason'), " :"] }), isReadonly ? (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", title: statusReason || '-', children: statusReason || '-' })) : (jsxRuntimeExports.jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [jsxRuntimeExports.jsx("input", { type: "text", className: `hdr-input ${(!isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing) ? 'hdr-input--error' : ''}`, value: statusReason, placeholder: getLabel('enterReason'), required: isStatusChanged, "aria-required": isStatusChanged, "aria-invalid": !isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing, onBlur: () => {
10346
10452
  if (isReasonMissing)
10347
10453
  setShowReasonRequired(true);
10348
10454
  }, onChange: (e) => {
@@ -10350,7 +10456,7 @@ const HeaderSectionWidget = ({ config }) => {
10350
10456
  if (showReasonRequired && String(e.target.value || '').trim().length > 0) {
10351
10457
  setShowReasonRequired(false);
10352
10458
  }
10353
- } }), !isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing ? (jsxRuntimeExports.jsx("div", { className: "hdr-error-text", children: getLabel('enterReason') })) : null] }))] })] })] }), jsxRuntimeExports.jsx("div", { className: "hdr-right", children: jsxRuntimeExports.jsxs("div", { className: "hdr-right-top", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-col", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('createdBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: createdBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('createdAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: createdAt || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('lastApprovedBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: lastApprovedBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('lastApprovedAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: lastApprovedAt || '-' })] })] }), score ? (jsxRuntimeExports.jsx("div", { className: "hdr-score-ring", style: { ['--pct']: score.percent }, "aria-label": `Completion score ${score.completionDisplay} of ${score.idealDisplay} (${score.percent}%)`, title: `${score.completionDisplay} / ${score.idealDisplay} (${score.percent}%)`, children: jsxRuntimeExports.jsx("div", { className: "hdr-score-value", children: String(score.completionDisplay) }) })) : null] }) })] })] }));
10459
+ } }), !isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing ? (jsxRuntimeExports.jsx("div", { className: "hdr-error-text", children: getLabel('enterReason') })) : null] }))] })] })] }), jsxRuntimeExports.jsx("div", { className: "hdr-right", children: jsxRuntimeExports.jsxs("div", { className: "hdr-right-top", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-col", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", title: `${getLabel('createdBy')} :`, children: [getLabel('createdBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", title: createdBy || '-', children: createdBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", title: `${getLabel('createdAt')} :`, children: [getLabel('createdAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", title: createdAt || '-', children: createdAt || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", title: `${getLabel('lastApprovedBy')} :`, children: [getLabel('lastApprovedBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", title: lastApprovedBy || '-', children: lastApprovedBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", title: `${getLabel('lastApprovedAt')} :`, children: [getLabel('lastApprovedAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", title: lastApprovedAt || '-', children: lastApprovedAt || '-' })] })] }), score ? (jsxRuntimeExports.jsx("div", { className: "hdr-score-ring", style: { ['--pct']: score.percent }, "aria-label": `Completion score ${score.completionDisplay} of ${score.idealDisplay} (${score.percent}%)`, title: `${score.completionDisplay} / ${score.idealDisplay} (${score.percent}%)`, children: jsxRuntimeExports.jsx("div", { className: "hdr-score-value", children: String(score.completionDisplay) }) })) : null] }) })] })] }));
10354
10460
  };
10355
10461
 
10356
10462
  function getValueByPathOrKey(obj, path) {