@openg2p/registry-widgets 1.1.2-dev.6 → 1.1.2-dev.8

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.
Files changed (48) hide show
  1. package/dist/components/SectionBuilder/schemas.d.ts +112 -0
  2. package/dist/components/SectionBuilder/schemas.d.ts.map +1 -1
  3. package/dist/components/SectionRenderer.d.ts.map +1 -1
  4. package/dist/components/WidgetFieldLabel.d.ts +11 -0
  5. package/dist/components/WidgetFieldLabel.d.ts.map +1 -0
  6. package/dist/hooks/useBaseWidget.d.ts +2 -0
  7. package/dist/hooks/useBaseWidget.d.ts.map +1 -1
  8. package/dist/index.d.ts +52 -15
  9. package/dist/index.esm.js +1038 -414
  10. package/dist/index.esm.js.map +1 -1
  11. package/dist/index.js +1046 -413
  12. package/dist/index.js.map +1 -1
  13. package/dist/registry/defaultWidgets.d.ts.map +1 -1
  14. package/dist/types/index.d.ts +11 -1
  15. package/dist/types/index.d.ts.map +1 -1
  16. package/dist/utils/conditions.d.ts +17 -11
  17. package/dist/utils/conditions.d.ts.map +1 -1
  18. package/dist/utils/dataSource.d.ts +6 -0
  19. package/dist/utils/dataSource.d.ts.map +1 -1
  20. package/dist/utils/geoHierarchy.d.ts +9 -0
  21. package/dist/utils/geoHierarchy.d.ts.map +1 -1
  22. package/dist/utils/schemaNamespace.d.ts.map +1 -1
  23. package/dist/utils/schemaTranslation.d.ts.map +1 -1
  24. package/dist/utils/sectionRevert.d.ts +24 -0
  25. package/dist/utils/sectionRevert.d.ts.map +1 -0
  26. package/dist/utils/sectionValidate.d.ts.map +1 -1
  27. package/dist/widgets/ArrayWidget.d.ts.map +1 -1
  28. package/dist/widgets/BooleanWidget.d.ts.map +1 -1
  29. package/dist/widgets/CheckboxWidget.d.ts.map +1 -1
  30. package/dist/widgets/CurrencyInputWidget.d.ts.map +1 -1
  31. package/dist/widgets/DateInputWidget.d.ts.map +1 -1
  32. package/dist/widgets/DateTimeInputWidget.d.ts.map +1 -1
  33. package/dist/widgets/DialogTableWidget.d.ts.map +1 -1
  34. package/dist/widgets/FileInputWidget.d.ts.map +1 -1
  35. package/dist/widgets/IterableAccordionWidget.d.ts.map +1 -1
  36. package/dist/widgets/MultiSelectWidget.d.ts +7 -0
  37. package/dist/widgets/MultiSelectWidget.d.ts.map +1 -0
  38. package/dist/widgets/NumberInputWidget.d.ts.map +1 -1
  39. package/dist/widgets/PhoneInputWidget.d.ts.map +1 -1
  40. package/dist/widgets/RadioWidget.d.ts.map +1 -1
  41. package/dist/widgets/RegisterLookupWidget.d.ts.map +1 -1
  42. package/dist/widgets/SelectWidget.d.ts.map +1 -1
  43. package/dist/widgets/TableWidget.d.ts.map +1 -1
  44. package/dist/widgets/TextAreaWidget.d.ts.map +1 -1
  45. package/dist/widgets/TextInputWidget.d.ts.map +1 -1
  46. package/dist/widgets/index.d.ts +1 -0
  47. package/dist/widgets/index.d.ts.map +1 -1
  48. package/package.json +1 -1
package/dist/index.esm.js CHANGED
@@ -402,6 +402,18 @@ const createZodSchema = (validation, required = false) => {
402
402
  return schema;
403
403
  };
404
404
 
405
+ const normalizeBooleanLike = (val) => {
406
+ if (val === true || val === 1)
407
+ return true;
408
+ if (val === false || val === 0 || val === null || val === undefined || val === '') {
409
+ return false;
410
+ }
411
+ if (typeof val === 'string') {
412
+ const normalized = val.trim().toLowerCase();
413
+ return normalized === 'true' || normalized === 'yes' || normalized === '1';
414
+ }
415
+ return Boolean(val);
416
+ };
405
417
  /**
406
418
  * Evaluate condition against field value
407
419
  */
@@ -410,6 +422,9 @@ const evaluateCondition = (condition, allValues) => {
410
422
  const { operator, value } = condition;
411
423
  switch (operator) {
412
424
  case 'equals':
425
+ if (typeof value === 'boolean' || typeof fieldValue === 'boolean') {
426
+ return normalizeBooleanLike(fieldValue) === normalizeBooleanLike(value);
427
+ }
413
428
  return fieldValue === value;
414
429
  case 'notEquals':
415
430
  return fieldValue !== value;
@@ -442,37 +457,62 @@ const evaluateCondition = (condition, allValues) => {
442
457
  }
443
458
  };
444
459
  /**
445
- * Check if widget should be visible based on conditions
460
+ * Normalize widget-data-options into a sequential list of action rules.
461
+ * Supports legacy single { action, condition } and new { actions: [...] }.
446
462
  */
447
- const shouldShowWidget = (options, allValues) => {
448
- if (!options?.condition) {
449
- return true;
463
+ const normalizeOptionRules = (options) => {
464
+ if (!options) {
465
+ return [];
450
466
  }
451
- const conditionResult = evaluateCondition(options.condition, allValues);
452
- if (options.action === 'show') {
453
- return conditionResult;
467
+ if (Array.isArray(options.actions) && options.actions.length > 0) {
468
+ return options.actions.filter((rule) => !!rule?.action);
454
469
  }
455
- if (options.action === 'hide') {
456
- return !conditionResult;
470
+ if (options.action && options.condition) {
471
+ return [{ action: options.action, condition: options.condition }];
457
472
  }
458
- return true;
473
+ return [];
474
+ };
475
+ const hasVisibilityRules = (options) => {
476
+ return normalizeOptionRules(options).some((rule) => rule.action === 'show' || rule.action === 'hide');
459
477
  };
460
478
  /**
461
- * Check if widget should be enabled based on conditions
479
+ * Evaluate widget-data-options rules sequentially.
480
+ * show/hide and enable/disable only affect visibility and enabled state.
481
+ * require is independent: required = widget-required OR require-condition-match.
462
482
  */
463
- const shouldEnableWidget = (options, allValues) => {
464
- if (!options?.condition) {
465
- return true;
466
- }
467
- const conditionResult = evaluateCondition(options.condition, allValues);
468
- if (options.action === 'enable') {
469
- return conditionResult;
470
- }
471
- if (options.action === 'disable') {
472
- return !conditionResult;
483
+ const evaluateWidgetConditions = (options, allValues, baseRequired = false) => {
484
+ let visible = true;
485
+ let enabled = true;
486
+ let required = baseRequired;
487
+ const rules = normalizeOptionRules(options);
488
+ for (const rule of rules) {
489
+ if (!rule.condition) {
490
+ continue;
491
+ }
492
+ const match = evaluateCondition(rule.condition, allValues);
493
+ switch (rule.action) {
494
+ case 'show':
495
+ visible = match;
496
+ break;
497
+ case 'hide':
498
+ visible = !match;
499
+ break;
500
+ case 'enable':
501
+ enabled = match;
502
+ break;
503
+ case 'disable':
504
+ enabled = !match;
505
+ break;
506
+ case 'require':
507
+ required = baseRequired || match;
508
+ break;
509
+ }
473
510
  }
474
- return true;
511
+ return { visible, enabled, required };
475
512
  };
513
+ const shouldShowWidget = (options, allValues) => evaluateWidgetConditions(options, allValues).visible;
514
+ const shouldEnableWidget = (options, allValues) => evaluateWidgetConditions(options, allValues).enabled;
515
+ const shouldRequireWidget = (options, allValues, baseRequired = false) => evaluateWidgetConditions(options, allValues, baseRequired).required;
476
516
 
477
517
  /**
478
518
  * Format number with thousand and decimal separators
@@ -1042,6 +1082,67 @@ const formatValue = (value, format, widgetType) => {
1042
1082
  return value?.toString() || '';
1043
1083
  };
1044
1084
 
1085
+ const apiDataSourceCache = new Map();
1086
+ const apiDataSourceInflight = new Map();
1087
+ function buildApiRequestContext(dataSource, allValues, levelId) {
1088
+ let depValue = null;
1089
+ if (dataSource.dependsOn) {
1090
+ if (dataSource.dependsOn.includes('.')) {
1091
+ depValue = getValueByPath(allValues, dataSource.dependsOn);
1092
+ }
1093
+ else {
1094
+ depValue = resolveWidgetIdValue(allValues, dataSource.dependsOn);
1095
+ }
1096
+ if (depValue === null || depValue === undefined || depValue === '') {
1097
+ return null;
1098
+ }
1099
+ }
1100
+ const method = dataSource.method || 'GET';
1101
+ const staticParams = { ...dataSource.params };
1102
+ const standardFields = ['type', 'service', 'endpoint', 'url', 'method', 'dependsOn', 'valueKey', 'labelKey', 'headers', 'body', 'params', 'level_id'];
1103
+ for (const [key, value] of Object.entries(dataSource)) {
1104
+ if (!standardFields.includes(key) && value !== undefined && value !== null) {
1105
+ staticParams[key] = value;
1106
+ }
1107
+ }
1108
+ if (levelId) {
1109
+ staticParams.level_id = levelId;
1110
+ }
1111
+ const requestParams = { ...staticParams };
1112
+ if (dataSource.dependsOn && depValue !== null && depValue !== undefined) {
1113
+ const parentValueId = typeof depValue === 'object' && depValue !== null
1114
+ ? (depValue.level_value_id || depValue.id || depValue.value || depValue)
1115
+ : depValue;
1116
+ if (staticParams.level_id) {
1117
+ requestParams.parent_level_value_id = parentValueId;
1118
+ }
1119
+ else {
1120
+ const paramKey = dataSource.dependsOn.split('.').pop() || 'filter';
1121
+ requestParams[paramKey] = parentValueId;
1122
+ }
1123
+ }
1124
+ else if (staticParams.level_id) {
1125
+ requestParams.parent_level_value_id = '';
1126
+ }
1127
+ const service = dataSource.service;
1128
+ const endpoint = dataSource.endpoint;
1129
+ if (!service || !endpoint) {
1130
+ return null;
1131
+ }
1132
+ return { service, endpoint, method, requestParams };
1133
+ }
1134
+ function buildApiDataSourceCacheKey(service, endpoint, method, requestParams) {
1135
+ return `${service}|${endpoint}|${method}|${JSON.stringify(requestParams)}`;
1136
+ }
1137
+ /** Return cached API options when already fetched (e.g. duplicate table cells). */
1138
+ function getCachedApiDataSource(dataSource, allValues, levelId) {
1139
+ const context = buildApiRequestContext(dataSource, allValues, levelId);
1140
+ if (!context) {
1141
+ return undefined;
1142
+ }
1143
+ const cacheKey = buildApiDataSourceCacheKey(context.service, context.endpoint, context.method, context.requestParams);
1144
+ return apiDataSourceCache.get(cacheKey);
1145
+ }
1045
1146
  /**
1046
1147
  * Get static data source options
1047
1148
  */
@@ -1059,98 +1160,33 @@ const getApiDataSource = async (dataSource, allValues, dataSourceRequestHandler,
1059
1160
  return [];
1060
1161
  }
1061
1162
  try {
1062
- // Get dependency value if exists
1063
- // dependsOn can be either a data path (e.g., "person.address") or a widget-id
1064
- let depValue = null;
1065
- if (dataSource.dependsOn) {
1066
- if (dataSource.dependsOn.includes('.')) {
1067
- depValue = getValueByPath(allValues, dataSource.dependsOn);
1068
- }
1069
- else {
1070
- depValue = resolveWidgetIdValue(allValues, dataSource.dependsOn);
1071
- }
1072
- if (depValue === null || depValue === undefined || depValue === '') {
1073
- // If dependency is empty, return empty array
1074
- return [];
1075
- }
1076
- }
1077
- // Build request parameters
1078
- const method = dataSource.method || 'GET';
1079
- // Extract static params from dataSource
1080
- // Include explicit params object and any additional fields (like level_id)
1081
- const staticParams = { ...dataSource.params };
1082
- // Extract additional fields that aren't part of the standard ApiDataSource interface
1083
- // These are fields like level_id that might be directly on the dataSource
1084
- // BUT: level_id should come from widget-geo-config.level, not from dataSource
1085
- const standardFields = ['type', 'service', 'endpoint', 'url', 'method', 'dependsOn', 'valueKey', 'labelKey', 'headers', 'body', 'params', 'level_id'];
1086
- for (const [key, value] of Object.entries(dataSource)) {
1087
- if (!standardFields.includes(key) && value !== undefined && value !== null) {
1088
- staticParams[key] = value;
1089
- }
1090
- }
1091
- // If levelId is provided (from widget-geo-config.level), use it instead of any level_id in dataSource
1092
- if (levelId) {
1093
- staticParams.level_id = levelId;
1094
- }
1095
- // Build request params object
1096
- const requestParams = { ...staticParams };
1097
- // Add dependency value to params
1098
- if (dataSource.dependsOn && depValue !== null && depValue !== undefined) {
1099
- // Extract the actual value ID if depValue is an object
1100
- const parentValueId = typeof depValue === 'object' && depValue !== null
1101
- ? (depValue.level_value_id || depValue.id || depValue.value || depValue)
1102
- : depValue;
1103
- // For geo APIs, use parent_level_value_id
1104
- if (staticParams.level_id) {
1105
- requestParams.parent_level_value_id = parentValueId;
1106
- }
1107
- else {
1108
- // For other APIs, use the dependency field name as param key
1109
- const paramKey = dataSource.dependsOn.split('.').pop() || 'filter';
1110
- requestParams[paramKey] = parentValueId;
1111
- }
1112
- }
1113
- else if (staticParams.level_id) {
1114
- // First level has no parent, send empty string as many OpenG2P APIs expect it
1115
- requestParams.parent_level_value_id = "";
1116
- }
1117
- // Get service mnemonic and endpoint (required)
1118
- const service = dataSource.service;
1119
- const endpoint = dataSource.endpoint;
1120
- if (!service) {
1121
- console.error('[getApiDataSource] API data source missing service mnemonic. Use "service" field instead of "url"');
1122
- return [];
1123
- }
1124
- if (!endpoint) {
1125
- console.error('[getApiDataSource] API data source missing endpoint. Use "endpoint" field to specify the operation (e.g., "get_g2p_geo_level_values")');
1163
+ const context = buildApiRequestContext(dataSource, allValues, levelId);
1164
+ if (!context) {
1126
1165
  return [];
1127
1166
  }
1128
- // Call handler let any throw propagate to the outer catch so it is logged once
1129
- // by useBaseWidget rather than double-logged here (which can cascade when
1130
- // intercept-console-error.js converts console.error calls into thrown errors).
1131
- const response = await dataSourceRequestHandler(service, endpoint, method, requestParams, {
1132
- headers: dataSource.headers,
1133
- });
1134
- // Handle OpenG2P response format (response_body.response_payload)
1135
- if (response && typeof response === 'object') {
1136
- if (response.response_body?.response_payload && Array.isArray(response.response_body.response_payload)) {
1137
- return response.response_body.response_payload;
1138
- }
1139
- }
1140
- // Handle array response
1141
- if (Array.isArray(response)) {
1142
- return response;
1167
+ const { service, endpoint, method, requestParams } = context;
1168
+ const cacheKey = buildApiDataSourceCacheKey(service, endpoint, method, requestParams);
1169
+ const cached = apiDataSourceCache.get(cacheKey);
1170
+ if (cached) {
1171
+ return cached;
1172
+ }
1173
+ const inflight = apiDataSourceInflight.get(cacheKey);
1174
+ if (inflight) {
1175
+ return inflight;
1176
+ }
1177
+ const fetchPromise = (async () => {
1178
+ const response = await dataSourceRequestHandler(service, endpoint, method, requestParams, { headers: dataSource.headers });
1179
+ const parsed = Array.isArray(response) ? response : [];
1180
+ apiDataSourceCache.set(cacheKey, parsed);
1181
+ return parsed;
1182
+ })();
1183
+ apiDataSourceInflight.set(cacheKey, fetchPromise);
1184
+ try {
1185
+ return await fetchPromise;
1143
1186
  }
1144
- // Handle object response (extract array from common keys)
1145
- if (response && typeof response === 'object') {
1146
- if (response.data && Array.isArray(response.data)) {
1147
- return response.data;
1148
- }
1149
- if (response.results && Array.isArray(response.results)) {
1150
- return response.results;
1151
- }
1187
+ finally {
1188
+ apiDataSourceInflight.delete(cacheKey);
1152
1189
  }
1153
- return [];
1154
1190
  }
1155
1191
  catch (error) {
1156
1192
  // Rethrow so useBaseWidget's catch can log it with full widget context
@@ -1936,6 +1972,98 @@ const WidgetProvider = ({ store, dataSourceRequestHandler, schemaData, translate
1936
1972
  return content;
1937
1973
  };
1938
1974
 
1975
+ /**
1976
+ * Custom hook for widget translations
1977
+ * Provides translation function with widget-specific namespace and fallback support
1978
+ */
1979
+ const useWidgetTranslation = () => {
1980
+ const { translate: translateFunction } = useWidgetContext();
1981
+ /**
1982
+ * Translate a key with flexible namespace support
1983
+ * Supports translation keys in various formats and direct strings
1984
+ *
1985
+ * Translation key formats supported:
1986
+ * - "widgets:common.addItem" - Namespaced key (for widget-specific translations)
1987
+ * - "Name" - Direct string (will be looked up in flat translation structure)
1988
+ * - "sections.personalDetails" - Nested key (for backward compatibility)
1989
+ *
1990
+ * With flat translation structure, direct strings like "Name" are automatically
1991
+ * translated by looking them up in the translation resources.
1992
+ *
1993
+ * @param keyOrString - Translation key (e.g., "widgets:common.addItem") or direct string (e.g., "Name")
1994
+ * @param options - Translation options (interpolation values, default value, etc.)
1995
+ * @returns Translated string or original string if translation not found
1996
+ */
1997
+ const translate = (keyOrString, options) => {
1998
+ if (!keyOrString) {
1999
+ return options?.defaultValue || '';
2000
+ }
2001
+ // Use the provided translation function or fallback to the key
2002
+ if (translateFunction) {
2003
+ return translateFunction(keyOrString, options) || options?.defaultValue || keyOrString;
2004
+ }
2005
+ // Fallback to key if no translation function available
2006
+ return options?.defaultValue || keyOrString;
2007
+ };
2008
+ /**
2009
+ * Translate widget config property
2010
+ * Attempts to translate the value, but if translation is not found,
2011
+ * returns the original value as-is (graceful fallback)
2012
+ *
2013
+ * This function will:
2014
+ * - Try to translate any string value
2015
+ * - If translation exists, use the translated value
2016
+ * - If translation doesn't exist (returns same value or throws), use original value
2017
+ * - This prevents errors when literal strings like "XXXX-XXXX-XXXX" are used
2018
+ */
2019
+ const translateConfig = (value, fallback) => {
2020
+ if (!value) {
2021
+ return fallback || '';
2022
+ }
2023
+ // Try to translate the value
2024
+ if (translateFunction) {
2025
+ try {
2026
+ // Pass defaultValue to ensure we get the original value if translation fails
2027
+ const translated = translateFunction(value, { defaultValue: value });
2028
+ // If translation returns empty, null, undefined, or the exact same value,
2029
+ // it means no translation was found - return the original value
2030
+ if (!translated || translated === value) {
2031
+ return value;
2032
+ }
2033
+ // Translation found, return it
2034
+ return translated;
2035
+ }
2036
+ catch (error) {
2037
+ // If translation throws an error (e.g., missing key warning), return original value
2038
+ return value;
2039
+ }
2040
+ }
2041
+ // No translation function available, return value as-is
2042
+ return value;
2043
+ };
2044
+ // No need of this getLanguage and changeLanguage functions
2045
+ /**
2046
+ * Get current language
2047
+ */
2048
+ // const getLanguage = (): string => {
2049
+ // return i18n.language || 'en';
2050
+ // };
2051
+ /**
2052
+ * Change language
2053
+ */
2054
+ // const changeLanguage = (lng: string): Promise<void> => {
2055
+ // return i18n.changeLanguage(lng).then(() => undefined);
2056
+ // };
2057
+ return {
2058
+ t: translate,
2059
+ translate,
2060
+ translateConfig,
2061
+ // getLanguage,
2062
+ // changeLanguage,
2063
+ // i18n: null,
2064
+ };
2065
+ };
2066
+
1939
2067
  /**
1940
2068
  * Geo Hierarchy Builder
1941
2069
  * Manages geo hierarchy state and builds hierarchy JSON structure
@@ -2135,6 +2263,42 @@ function isUpstreamGeoAncestor(changedWidgetId, widgetId, immediateParentWidgetI
2135
2263
  }
2136
2264
  return false;
2137
2265
  }
2266
+ /** Group id for geo widgets sharing the same register prefix (e.g. `{registerId}`). */
2267
+ function getGeoGroupId(dataPath) {
2268
+ if (typeof dataPath === 'string' && dataPath.includes('.')) {
2269
+ return dataPath.split('.').slice(0, -1).join('.');
2270
+ }
2271
+ return 'default';
2272
+ }
2273
+ /**
2274
+ * Resolve the human-readable label for a geo level from persisted hierarchy JSON.
2275
+ * Used in readonly mode when API options are not loaded.
2276
+ */
2277
+ function resolveGeoWidgetLevelLabel(values, widgetId, dataPath, geoConfig) {
2278
+ if (!dataPath || typeof dataPath !== 'string') {
2279
+ return undefined;
2280
+ }
2281
+ const stored = getWidgetValue(values, dataPath, widgetId);
2282
+ const hierarchy = stored?.hierarchy || stored?.geo_code_hierarchy_json?.hierarchy;
2283
+ if (!Array.isArray(hierarchy)) {
2284
+ return undefined;
2285
+ }
2286
+ const levelData = hierarchy.find((l) => l.level === geoConfig.level);
2287
+ if (levelData?.level_value_mnemonic) {
2288
+ return String(levelData.level_value_mnemonic);
2289
+ }
2290
+ return undefined;
2291
+ }
2292
+ /** All registered geo widgets that are descendants of ancestorWidgetId. */
2293
+ function getGeoDescendantWidgetIds(ancestorWidgetId) {
2294
+ const descendants = [];
2295
+ for (const [childId, parentId] of geoWidgetParentRegistry.entries()) {
2296
+ if (isUpstreamGeoAncestor(ancestorWidgetId, childId, parentId)) {
2297
+ descendants.push(childId);
2298
+ }
2299
+ }
2300
+ return descendants;
2301
+ }
2138
2302
  function readStoredHierarchyLevels(values, dataPath, widgetId) {
2139
2303
  const stored = getWidgetValue(values, dataPath, widgetId);
2140
2304
  const hierarchy = stored?.hierarchy || stored?.geo_code_hierarchy_json?.hierarchy;
@@ -2185,6 +2349,7 @@ const useBaseWidget = (options) => {
2185
2349
  const dispatch = useDispatch();
2186
2350
  const context = useWidgetContext();
2187
2351
  const eventBus = useWidgetEventBus();
2352
+ const { translateConfig } = useWidgetTranslation();
2188
2353
  const widgetId = config['widget-id'];
2189
2354
  // Fall back to WidgetContext for dataSourceRequestHandler
2190
2355
  const dataSourceRequestHandler = propHandler || context.dataSourceRequestHandler;
@@ -2355,6 +2520,15 @@ const useBaseWidget = (options) => {
2355
2520
  }
2356
2521
  // eslint-disable-next-line react-hooks/exhaustive-deps
2357
2522
  }, [isLayoutWidget]); // Only run once on mount
2523
+ const resolveIsRequired = useCallback((currentValues) => {
2524
+ if (isLayoutWidget) {
2525
+ return false;
2526
+ }
2527
+ if (config['widget-readonly']) {
2528
+ return false;
2529
+ }
2530
+ return evaluateWidgetConditions(config['widget-data-options'], currentValues, config['widget-required'] ?? false).required;
2531
+ }, [config, isLayoutWidget]);
2358
2532
  // Handle value change
2359
2533
  // CRITICAL: Don't include 'values' in dependency array - it causes the callback to be recreated
2360
2534
  // every time values change, which can lead to stale closures and double dispatches
@@ -2398,29 +2572,26 @@ const useBaseWidget = (options) => {
2398
2572
  lastDispatchedValueRef.current = newValue;
2399
2573
  dispatch(setValue({ widgetId, value: newValue }));
2400
2574
  }
2575
+ else if (config['widget-geo-config']) {
2576
+ // Geo widgets: hierarchy dataPath is managed by useGeoWidgetCascade
2577
+ getGeoDescendantWidgetIds(widgetId).forEach((descendantId) => {
2578
+ dispatch(setValue({ widgetId: descendantId, value: GEO_LEVEL_CLEARED }));
2579
+ dispatch(setDataSource({ widgetId: descendantId, data: [] }));
2580
+ });
2581
+ dispatch(setValue({ widgetId, value: newValue }));
2582
+ }
2401
2583
  else {
2402
- // Has dataPath: update both widgetId and dataPath
2403
- // CRITICAL: For geo widgets, we do NOT want to overwrite the shared hierarchy dataPath
2404
- // with a primitive value (the selected ID). The hierarchy object is managed by useGeoWidgetCascade.
2405
- if (config['widget-geo-config']) {
2406
- dispatch(setValue({ widgetId, value: newValue }));
2407
- return;
2408
- }
2409
- // For non-geo widgets, update both widgetId and dataPath
2410
- // CRITICAL: Create updated values object with newValue already set
2411
- // This prevents setWidgetValue from reading stale values
2584
+ // Non-geo widgets: update both widgetId and dataPath
2412
2585
  const currentValuesWithUpdate = {
2413
2586
  ...valuesRef.current,
2414
- [widgetId]: newValue, // Ensure widgetId has the new value
2587
+ [widgetId]: newValue,
2415
2588
  };
2416
2589
  const updatedValues = setWidgetValue(currentValuesWithUpdate, config['widget-data-path'], widgetId, newValue);
2417
- // setWidgetValue returns the complete updated structure with all existing data preserved
2418
- // Use setValues to update the entire state with deep merge
2419
2590
  dispatch(setValues(updatedValues));
2420
2591
  }
2421
2592
  // Validate if needed
2422
2593
  if (validate) {
2423
- const validationErrors = validateWidget(newValue, config['widget-data-validation'], config['widget-required']);
2594
+ const validationErrors = validateWidget(newValue, config['widget-data-validation'], resolveIsRequired(currentValues));
2424
2595
  dispatch(setError({ widgetId, errors: validationErrors }));
2425
2596
  }
2426
2597
  // Call custom onChange if provided
@@ -2439,13 +2610,12 @@ const useBaseWidget = (options) => {
2439
2610
  timestamp: Date.now(),
2440
2611
  });
2441
2612
  }
2442
- }, [config, widgetId, dispatch, onValueChange, eventBus] // Removed 'values' to prevent stale closures
2613
+ }, [config, widgetId, dispatch, onValueChange, eventBus, resolveIsRequired] // Removed 'values' to prevent stale closures
2443
2614
  );
2444
2615
  // Handle blur
2445
2616
  const handleBlur = useCallback(() => {
2446
2617
  dispatch(setTouched({ widgetId, touched: true }));
2447
- // Validate on blur
2448
- const validationErrors = validateWidget(currentValue, config['widget-data-validation'], config['widget-required']);
2618
+ const validationErrors = validateWidget(currentValue, config['widget-data-validation'], resolveIsRequired(valuesRef.current));
2449
2619
  dispatch(setError({ widgetId, errors: validationErrors }));
2450
2620
  // Publish widget:blur event
2451
2621
  if (eventBus) {
@@ -2456,7 +2626,7 @@ const useBaseWidget = (options) => {
2456
2626
  timestamp: Date.now(),
2457
2627
  });
2458
2628
  }
2459
- }, [currentValue, config, widgetId, dispatch, eventBus]);
2629
+ }, [currentValue, config, widgetId, dispatch, eventBus, resolveIsRequired]);
2460
2630
  // Get field value helper
2461
2631
  const getFieldValue = useCallback((path) => {
2462
2632
  return getWidgetValue(values, path, '');
@@ -2464,7 +2634,7 @@ const useBaseWidget = (options) => {
2464
2634
  // Conditional visibility and enablement
2465
2635
  const isVisible = useMemo(() => {
2466
2636
  // Layout widgets are always visible unless explicitly hidden
2467
- if (isLayoutWidget && !config['widget-data-options']?.condition) {
2637
+ if (isLayoutWidget && !hasVisibilityRules(config['widget-data-options'])) {
2468
2638
  return true;
2469
2639
  }
2470
2640
  return shouldShowWidget(config['widget-data-options'], values);
@@ -2479,6 +2649,7 @@ const useBaseWidget = (options) => {
2479
2649
  }
2480
2650
  return shouldEnableWidget(config['widget-data-options'], values);
2481
2651
  }, [config['widget-readonly'], config['widget-data-options'], values, isLayoutWidget]);
2652
+ const isRequired = useMemo(() => resolveIsRequired(values), [resolveIsRequired, values]);
2482
2653
  // Format value for display
2483
2654
  const formattedValue = useMemo(() => {
2484
2655
  if (!config['widget-data-format']) {
@@ -2525,10 +2696,9 @@ const useBaseWidget = (options) => {
2525
2696
  if (!dataSource) {
2526
2697
  return;
2527
2698
  }
2528
- // For API data sources, check if widget is readonly
2529
- // According to PRD: "Level 1 geo widgets load on widget mount or when entering edit mode"
2530
- // So we should only load API data sources when widget is NOT readonly
2531
- if (dataSource.type === 'api' && isReadonly) {
2699
+ const loadApiInReadonly = !!geoConfig ||
2700
+ ['select', 'radio', 'checkbox', 'multi-select'].includes(config.widget);
2701
+ if (dataSource.type === 'api' && isReadonly && !loadApiInReadonly) {
2532
2702
  return;
2533
2703
  }
2534
2704
  // For widgets with dependencies, check if dependency value exists
@@ -2570,6 +2740,30 @@ const useBaseWidget = (options) => {
2570
2740
  // React will call this effect again when the handler is ready
2571
2741
  return;
2572
2742
  }
2743
+ const resolveOptionKeys = () => {
2744
+ if (dataSource.type === 'static') {
2745
+ return { valueKey: undefined, labelKey: undefined };
2746
+ }
2747
+ if (geoConfig) {
2748
+ return {
2749
+ valueKey: dataSource.valueKey || 'level_value_id',
2750
+ labelKey: dataSource.labelKey || 'level_value_mnemonic',
2751
+ };
2752
+ }
2753
+ return { valueKey: dataSource.valueKey, labelKey: dataSource.labelKey };
2754
+ };
2755
+ if (dataSource.type === 'api') {
2756
+ const levelId = geoConfig?.level;
2757
+ const cached = getCachedApiDataSource(dataSource, valuesRef.current, levelId);
2758
+ if (cached) {
2759
+ const { valueKey, labelKey } = resolveOptionKeys();
2760
+ dispatch(setDataSource({
2761
+ widgetId,
2762
+ data: transformDataSourceOptions(cached, valueKey, labelKey),
2763
+ }));
2764
+ return;
2765
+ }
2766
+ }
2573
2767
  dispatch(setLoading({ widgetId, loading: true }));
2574
2768
  let data = [];
2575
2769
  if (dataSource.type === 'static') {
@@ -2582,31 +2776,13 @@ const useBaseWidget = (options) => {
2582
2776
  dispatch(setDataSource({ widgetId, data: [] }));
2583
2777
  return;
2584
2778
  }
2585
- // Extract level_id from widget-geo-config.level if available
2586
2779
  const levelId = geoConfig?.level;
2587
2780
  data = await getApiDataSource(dataSource, valuesRef.current, currentHandler, levelId);
2588
2781
  }
2589
2782
  else if (dataSource.type === 'schema') {
2590
2783
  data = getSchemaDataSource(dataSource, schemaData || {});
2591
2784
  }
2592
- // Transform to { value, label } format
2593
- // For geo widgets, default to level_value_id and level_value_mnemonic
2594
- let valueKey;
2595
- let labelKey;
2596
- if (dataSource.type === 'static') {
2597
- valueKey = undefined;
2598
- labelKey = undefined;
2599
- }
2600
- else if (geoConfig) {
2601
- // Geo widgets: default to level_value_id and level_value_mnemonic
2602
- valueKey = dataSource.valueKey || 'level_value_id';
2603
- labelKey = dataSource.labelKey || 'level_value_mnemonic';
2604
- }
2605
- else {
2606
- // Non-geo widgets: use specified keys or undefined
2607
- valueKey = dataSource.valueKey;
2608
- labelKey = dataSource.labelKey;
2609
- }
2785
+ const { valueKey, labelKey } = resolveOptionKeys();
2610
2786
  const transformed = transformDataSourceOptions(data, valueKey, labelKey);
2611
2787
  dispatch(setDataSource({ widgetId, data: transformed }));
2612
2788
  }
@@ -2622,15 +2798,24 @@ const useBaseWidget = (options) => {
2622
2798
  // Use configKey and dependencyValue to ensure effect runs only when relevant state changes
2623
2799
  // eslint-disable-next-line react-hooks/exhaustive-deps
2624
2800
  }, [configKey, dependencyValue, dataSourceRequestHandler, schemaData, widgetId, dispatch]);
2801
+ const geoDisplayLabel = useMemo(() => {
2802
+ if (!geoConfig) {
2803
+ return undefined;
2804
+ }
2805
+ const rawLabel = resolveGeoWidgetLevelLabel(values, widgetId, config['widget-data-path'], geoConfig);
2806
+ return rawLabel ? translateConfig(rawLabel) : undefined;
2807
+ }, [values, widgetId, config, geoConfig, translateConfig]);
2625
2808
  return {
2626
2809
  widgetId,
2627
2810
  value: currentValue,
2811
+ geoDisplayLabel,
2628
2812
  formattedValue,
2629
2813
  error: errors,
2630
2814
  touched,
2631
2815
  loading,
2632
2816
  isVisible,
2633
2817
  isEnabled,
2818
+ isRequired,
2634
2819
  onChange: handleChange,
2635
2820
  onBlur: handleBlur,
2636
2821
  setError: (errors) => dispatch(setError({ widgetId, errors })),
@@ -2721,6 +2906,7 @@ const useGeoWidgetCascade = (options) => {
2721
2906
  const valuesRef = useRef(values);
2722
2907
  const handlerRef = useRef(dataSourceRequestHandler);
2723
2908
  const lastCascadePublishRef = useRef(undefined);
2909
+ const lastDirectParentValueRef = useRef(undefined);
2724
2910
  // Keep refs updated
2725
2911
  useEffect(() => {
2726
2912
  valuesRef.current = values;
@@ -2790,6 +2976,13 @@ const useGeoWidgetCascade = (options) => {
2790
2976
  event.value === null ||
2791
2977
  event.value === '' ||
2792
2978
  event.value === GEO_LEVEL_CLEARED;
2979
+ const isFirstParentEvent = lastDirectParentValueRef.current === undefined;
2980
+ const parentValueChanged = !isFirstParentEvent &&
2981
+ lastDirectParentValueRef.current !== event.value;
2982
+ lastDirectParentValueRef.current = event.value;
2983
+ if (!parentCleared && !parentValueChanged && !isFirstParentEvent) {
2984
+ return;
2985
+ }
2793
2986
  let parentValue = event.value;
2794
2987
  if (!parentCleared && (parentValue === undefined || parentValue === null)) {
2795
2988
  parentValue = currentValues[parentWidgetId];
@@ -2897,17 +3090,7 @@ const useGeoWidgetCascade = (options) => {
2897
3090
  if (dataPath && geoHierarchyBuilder.buildHierarchyJson(groupId)) {
2898
3091
  dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
2899
3092
  }
2900
- // Notify descendants when this level changes via hierarchy/rehydration (handleChange may not run).
2901
- if (!isLastLevel && eventBus && lastCascadePublishRef.current !== level_value_id) {
2902
- lastCascadePublishRef.current = level_value_id;
2903
- eventBus.publish({
2904
- type: 'widget:change',
2905
- widgetId,
2906
- value: level_value_id,
2907
- timestamp: Date.now(),
2908
- });
2909
- }
2910
- }, [geoConfig, currentValue, widgetId, dataPath, dispatch, dataSourceOptions, eventBus, groupId]);
3093
+ }, [geoConfig, currentValue, widgetId, dataPath, dispatch, dataSourceOptions, groupId]);
2911
3094
  };
2912
3095
 
2913
3096
  class WidgetRegistry {
@@ -2984,144 +3167,52 @@ class WidgetRegistry {
2984
3167
  config,
2985
3168
  ...context,
2986
3169
  ...entry.defaultProps,
2987
- });
2988
- }
2989
- }
2990
- // Singleton instance
2991
- const widgetRegistry = new WidgetRegistry();
2992
-
2993
- const WidgetRenderer = ({ config, dataSourceRequestHandler: propDataSourceRequestHandler, schemaData: propSchemaData, onValueChange, defaultComponent, }) => {
2994
- // Use context values as fallback
2995
- const context = useWidgetContext();
2996
- const dataSourceRequestHandler = propDataSourceRequestHandler || context.dataSourceRequestHandler;
2997
- const schemaData = propSchemaData || context.schemaData;
2998
- // Warn if dataSourceRequestHandler is missing for API data sources, but don't break rendering
2999
- // This allows widgets to render in read-only or static modes (e.g., CRView)
3000
- if (!dataSourceRequestHandler && config['widget-data-source']?.type === 'api') {
3001
- console.warn(`[WidgetRenderer] dataSourceRequestHandler is not provided for widget ${config['widget-id']} with API data source. ` +
3002
- `The widget will render but API data source functionality will be disabled.`);
3003
- }
3004
- // Get values from Redux for cascade hooks
3005
- const values = useSelector((state) => state.widget.values);
3006
- const widgetContext = useBaseWidget({
3007
- config,
3008
- dataSourceRequestHandler,
3009
- schemaData,
3010
- onValueChange,
3011
- });
3012
- // Apply cascade hooks if configured (only if handler is available)
3013
- if (dataSourceRequestHandler) {
3014
- useWidgetCascade({
3015
- config,
3016
- dataSourceRequestHandler,
3017
- values,
3018
- });
3019
- useGeoWidgetCascade({
3020
- config,
3021
- dataSourceRequestHandler,
3022
- values,
3023
- });
3024
- }
3025
- // Don't render if not visible
3026
- if (!widgetContext.isVisible) {
3027
- return null;
3028
- }
3029
- // Render widget using registry
3030
- // Don't use key based on readonly state - it causes remounting which resets userHasSetValueRef
3031
- // The readonly state is already handled in the widget components themselves
3032
- return (jsxRuntimeExports.jsx("div", { className: "widget-container", "data-widget-id": widgetContext.widgetId, style: { marginBottom: 0 }, children: widgetRegistry.render(config, widgetContext, defaultComponent) }));
3033
- };
3034
-
3035
- /**
3036
- * Custom hook for widget translations
3037
- * Provides translation function with widget-specific namespace and fallback support
3038
- */
3039
- const useWidgetTranslation = () => {
3040
- const { translate: translateFunction } = useWidgetContext();
3041
- /**
3042
- * Translate a key with flexible namespace support
3043
- * Supports translation keys in various formats and direct strings
3044
- *
3045
- * Translation key formats supported:
3046
- * - "widgets:common.addItem" - Namespaced key (for widget-specific translations)
3047
- * - "Name" - Direct string (will be looked up in flat translation structure)
3048
- * - "sections.personalDetails" - Nested key (for backward compatibility)
3049
- *
3050
- * With flat translation structure, direct strings like "Name" are automatically
3051
- * translated by looking them up in the translation resources.
3052
- *
3053
- * @param keyOrString - Translation key (e.g., "widgets:common.addItem") or direct string (e.g., "Name")
3054
- * @param options - Translation options (interpolation values, default value, etc.)
3055
- * @returns Translated string or original string if translation not found
3056
- */
3057
- const translate = (keyOrString, options) => {
3058
- if (!keyOrString) {
3059
- return options?.defaultValue || '';
3060
- }
3061
- // Use the provided translation function or fallback to the key
3062
- if (translateFunction) {
3063
- return translateFunction(keyOrString, options) || options?.defaultValue || keyOrString;
3064
- }
3065
- // Fallback to key if no translation function available
3066
- return options?.defaultValue || keyOrString;
3067
- };
3068
- /**
3069
- * Translate widget config property
3070
- * Attempts to translate the value, but if translation is not found,
3071
- * returns the original value as-is (graceful fallback)
3072
- *
3073
- * This function will:
3074
- * - Try to translate any string value
3075
- * - If translation exists, use the translated value
3076
- * - If translation doesn't exist (returns same value or throws), use original value
3077
- * - This prevents errors when literal strings like "XXXX-XXXX-XXXX" are used
3078
- */
3079
- const translateConfig = (value, fallback) => {
3080
- if (!value) {
3081
- return fallback || '';
3082
- }
3083
- // Try to translate the value
3084
- if (translateFunction) {
3085
- try {
3086
- // Pass defaultValue to ensure we get the original value if translation fails
3087
- const translated = translateFunction(value, { defaultValue: value });
3088
- // If translation returns empty, null, undefined, or the exact same value,
3089
- // it means no translation was found - return the original value
3090
- if (!translated || translated === value) {
3091
- return value;
3092
- }
3093
- // Translation found, return it
3094
- return translated;
3095
- }
3096
- catch (error) {
3097
- // If translation throws an error (e.g., missing key warning), return original value
3098
- return value;
3099
- }
3100
- }
3101
- // No translation function available, return value as-is
3102
- return value;
3103
- };
3104
- // No need of this getLanguage and changeLanguage functions
3105
- /**
3106
- * Get current language
3107
- */
3108
- // const getLanguage = (): string => {
3109
- // return i18n.language || 'en';
3110
- // };
3111
- /**
3112
- * Change language
3113
- */
3114
- // const changeLanguage = (lng: string): Promise<void> => {
3115
- // return i18n.changeLanguage(lng).then(() => undefined);
3116
- // };
3117
- return {
3118
- t: translate,
3119
- translate,
3120
- translateConfig,
3121
- // getLanguage,
3122
- // changeLanguage,
3123
- // i18n: null,
3124
- };
3170
+ });
3171
+ }
3172
+ }
3173
+ // Singleton instance
3174
+ const widgetRegistry = new WidgetRegistry();
3175
+
3176
+ const WidgetRenderer = ({ config, dataSourceRequestHandler: propDataSourceRequestHandler, schemaData: propSchemaData, onValueChange, defaultComponent, }) => {
3177
+ // Use context values as fallback
3178
+ const context = useWidgetContext();
3179
+ const dataSourceRequestHandler = propDataSourceRequestHandler || context.dataSourceRequestHandler;
3180
+ const schemaData = propSchemaData || context.schemaData;
3181
+ // Warn if dataSourceRequestHandler is missing for API data sources, but don't break rendering
3182
+ // This allows widgets to render in read-only or static modes (e.g., CRView)
3183
+ if (!dataSourceRequestHandler && config['widget-data-source']?.type === 'api') {
3184
+ console.warn(`[WidgetRenderer] dataSourceRequestHandler is not provided for widget ${config['widget-id']} with API data source. ` +
3185
+ `The widget will render but API data source functionality will be disabled.`);
3186
+ }
3187
+ // Get values from Redux for cascade hooks
3188
+ const values = useSelector((state) => state.widget.values);
3189
+ const widgetContext = useBaseWidget({
3190
+ config,
3191
+ dataSourceRequestHandler,
3192
+ schemaData,
3193
+ onValueChange,
3194
+ });
3195
+ // Apply cascade hooks if configured (only if handler is available)
3196
+ if (dataSourceRequestHandler) {
3197
+ useWidgetCascade({
3198
+ config,
3199
+ dataSourceRequestHandler,
3200
+ values,
3201
+ });
3202
+ useGeoWidgetCascade({
3203
+ config,
3204
+ dataSourceRequestHandler,
3205
+ values,
3206
+ });
3207
+ }
3208
+ // Don't render if not visible
3209
+ if (!widgetContext.isVisible) {
3210
+ return null;
3211
+ }
3212
+ // Render widget using registry
3213
+ // Don't use key based on readonly state - it causes remounting which resets userHasSetValueRef
3214
+ // The readonly state is already handled in the widget components themselves
3215
+ return (jsxRuntimeExports.jsx("div", { className: "widget-container", "data-widget-id": widgetContext.widgetId, style: { marginBottom: 0 }, children: widgetRegistry.render(config, widgetContext, defaultComponent) }));
3125
3216
  };
3126
3217
 
3127
3218
  /**
@@ -3242,6 +3333,16 @@ const PanelRenderer = ({ panel, dataSourceRequestHandler, schemaData, onValueCha
3242
3333
  return (jsxRuntimeExports.jsx("div", { className: `panel panel-${orientation}`, "data-panel-id": panel['panel-id'], style: orientation === 'horizontal' ? { width: '100%' } : {}, children: content }));
3243
3334
  };
3244
3335
 
3336
+ /**
3337
+ * Field label: long text truncates with ellipsis; required asterisk always stays visible.
3338
+ */
3339
+ const WidgetFieldLabel = ({ label, required = false, className = '', title, }) => {
3340
+ const { translateConfig } = useWidgetTranslation();
3341
+ const translatedLabel = translateConfig(label);
3342
+ const tooltip = title !== undefined ? translateConfig(title) : translatedLabel;
3343
+ return (jsxRuntimeExports.jsxs("label", { className: `flex items-baseline min-w-0 max-w-full ${className}`, style: { fontFamily: 'Roboto, sans-serif' }, title: tooltip, children: [jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate", children: translatedLabel }), required && jsxRuntimeExports.jsx("span", { className: "ml-1 shrink-0 text-red-500", children: "*" })] }));
3344
+ };
3345
+
3245
3346
  /**
3246
3347
  * Utility functions for file preview functionality
3247
3348
  */
@@ -3591,7 +3692,7 @@ const deserializeValue = (value) => {
3591
3692
  };
3592
3693
 
3593
3694
  const FileInputWidget = ({ config }) => {
3594
- const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
3695
+ const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
3595
3696
  const { translate, translateConfig } = useWidgetTranslation();
3596
3697
  const accept = widgetConfig['widget-data-options']?.accept;
3597
3698
  const multiple = widgetConfig['widget-data-options']?.multiple || false;
@@ -3831,7 +3932,7 @@ const FileInputWidget = ({ config }) => {
3831
3932
  setPreviewFile(null);
3832
3933
  } })] }));
3833
3934
  }
3834
- return (jsxRuntimeExports.jsxs("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: "flex flex-col sm:flex-row sm:items-center gap-2 sm:space-x-4", children: [jsxRuntimeExports.jsxs("label", { className: `cursor-pointer inline-flex items-center justify-between gap-2 border border-gray-300 shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${!isEnabled
3935
+ return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center gap-2 sm:space-x-4", children: [jsxRuntimeExports.jsxs("label", { className: `cursor-pointer inline-flex items-center justify-between gap-2 border border-gray-300 shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${!isEnabled
3835
3936
  ? 'opacity-50 cursor-not-allowed'
3836
3937
  : ''}`, style: {
3837
3938
  width: '100%',
@@ -3892,6 +3993,13 @@ const namespaceWidgetConfig = (widgetConfig, namespace) => {
3892
3993
  if (namespaced['widget-data-path']) {
3893
3994
  namespaced['widget-data-path'] = namespaceDataPath(namespaced['widget-data-path'], namespace);
3894
3995
  }
3996
+ // Namespace geo parent references so cascade events match namespaced widget-id
3997
+ if (namespaced['widget-geo-config']?.parentWidgetId) {
3998
+ namespaced['widget-geo-config'] = {
3999
+ ...namespaced['widget-geo-config'],
4000
+ parentWidgetId: `${namespace}__${namespaced['widget-geo-config'].parentWidgetId}`,
4001
+ };
4002
+ }
3895
4003
  // Recursively namespace nested widgets (for layout widgets)
3896
4004
  if (namespaced.widgets && Array.isArray(namespaced.widgets)) {
3897
4005
  namespaced.widgets = namespaced.widgets.map((widget) => namespaceWidgetConfig(widget, namespace));
@@ -4096,6 +4204,9 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
4096
4204
  const isVisible = shouldShowWidget(widget['widget-data-options'], currentSchemaData);
4097
4205
  if (!isVisible)
4098
4206
  continue;
4207
+ const isEnabled = shouldEnableWidget(widget['widget-data-options'], currentSchemaData);
4208
+ if (!isEnabled)
4209
+ continue;
4099
4210
  const widgetId = widget['widget-id'];
4100
4211
  if (isTableLikeWidget(widget)) {
4101
4212
  const tableValid = validateTableLikeWidget(widget, currentSchemaData, dispatch, skipRequired);
@@ -4105,7 +4216,8 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
4105
4216
  continue;
4106
4217
  }
4107
4218
  const value = getWidgetValue(currentSchemaData, widget['widget-data-path'], widgetId);
4108
- const errors = validateWidget(value, widget['widget-data-validation'], widget['widget-required'], skipRequired);
4219
+ const isRequired = shouldRequireWidget(widget['widget-data-options'], currentSchemaData, widget['widget-required'] ?? false);
4220
+ const errors = validateWidget(value, widget['widget-data-validation'], isRequired, skipRequired);
4109
4221
  if (errors.length > 0) {
4110
4222
  isValid = false;
4111
4223
  dispatch(setTouched({ widgetId, touched: true }));
@@ -4138,6 +4250,108 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
4138
4250
  return isValid;
4139
4251
  };
4140
4252
 
4253
+ const cloneValue = (value) => {
4254
+ if (value === undefined) {
4255
+ return undefined;
4256
+ }
4257
+ try {
4258
+ return structuredClone(value);
4259
+ }
4260
+ catch {
4261
+ return JSON.parse(JSON.stringify(value));
4262
+ }
4263
+ };
4264
+ const resolveNamespacedWidgetId = (widgetId, namespace) => namespace ? `${namespace}__${widgetId}` : widgetId;
4265
+ const resolveStoreDataPath = (dataPath, namespace) => {
4266
+ if (!dataPath) {
4267
+ return dataPath;
4268
+ }
4269
+ if (!namespace) {
4270
+ return dataPath;
4271
+ }
4272
+ if (typeof dataPath === 'string') {
4273
+ return `${namespace}.${dataPath}`;
4274
+ }
4275
+ return Object.fromEntries(Object.entries(dataPath).map(([key, path]) => [key, `${namespace}.${path}`]));
4276
+ };
4277
+ /**
4278
+ * Capture Redux widget values for a section at edit entry.
4279
+ * Used to restore exact pre-edit state on Cancel (schemaData may be stale or shared with Redux).
4280
+ */
4281
+ function captureSectionEditSnapshot(values, section, options) {
4282
+ const { namespace, sectionId, supportingDocuments = [] } = options ?? {};
4283
+ const dataPaths = [];
4284
+ const processedPaths = new Set();
4285
+ const widgetIds = {};
4286
+ const addPath = (path) => {
4287
+ if (!path || processedPaths.has(path)) {
4288
+ return;
4289
+ }
4290
+ processedPaths.add(path);
4291
+ dataPaths.push({
4292
+ path,
4293
+ value: cloneValue(getValueByPath(values, path)),
4294
+ });
4295
+ if (path.endsWith('.geo_code_hierarchy_json')) {
4296
+ const prefix = path.slice(0, -'.geo_code_hierarchy_json'.length);
4297
+ addPath(`${prefix}.geo_lowest_level_value_id`);
4298
+ }
4299
+ };
4300
+ collectWidgets(section.panels).forEach((widget) => {
4301
+ const widgetId = resolveNamespacedWidgetId(widget['widget-id'], namespace);
4302
+ const storeDataPath = resolveStoreDataPath(widget['widget-data-path'], namespace);
4303
+ if (Object.prototype.hasOwnProperty.call(values, widgetId)) {
4304
+ widgetIds[widgetId] = { present: true, value: cloneValue(values[widgetId]) };
4305
+ }
4306
+ else {
4307
+ widgetIds[widgetId] = { present: false };
4308
+ }
4309
+ if (typeof storeDataPath === 'string') {
4310
+ addPath(storeDataPath);
4311
+ }
4312
+ else if (storeDataPath && typeof storeDataPath === 'object') {
4313
+ Object.values(storeDataPath).forEach((path) => {
4314
+ if (typeof path === 'string') {
4315
+ addPath(path);
4316
+ }
4317
+ });
4318
+ }
4319
+ });
4320
+ supportingDocuments.forEach((doc, index) => {
4321
+ const widgetId = `supporting-doc-${sectionId ?? 'section'}-${index}`;
4322
+ const storeDataPath = namespace && doc['document-data-path']
4323
+ ? `${namespace}.${doc['document-data-path']}`
4324
+ : doc['document-data-path'];
4325
+ if (Object.prototype.hasOwnProperty.call(values, widgetId)) {
4326
+ widgetIds[widgetId] = { present: true, value: cloneValue(values[widgetId]) };
4327
+ }
4328
+ else {
4329
+ widgetIds[widgetId] = { present: false };
4330
+ }
4331
+ if (typeof storeDataPath === 'string') {
4332
+ addPath(storeDataPath);
4333
+ }
4334
+ });
4335
+ return { dataPaths, widgetIds };
4336
+ }
4337
+ /** Apply a section edit snapshot back onto the full Redux values object. */
4338
+ function applySectionEditSnapshot(currentValues, snapshot) {
4339
+ let result = currentValues;
4340
+ for (const { path, value } of snapshot.dataPaths) {
4341
+ result = setValueByPath(result, path, cloneValue(value));
4342
+ }
4343
+ for (const [widgetId, entry] of Object.entries(snapshot.widgetIds)) {
4344
+ if (entry.present) {
4345
+ result = { ...result, [widgetId]: cloneValue(entry.value) };
4346
+ }
4347
+ else {
4348
+ const { [widgetId]: _removed, ...rest } = result;
4349
+ result = rest;
4350
+ }
4351
+ }
4352
+ return result;
4353
+ }
4354
+
4141
4355
  /** Root class on readonly label/value rows; SectionRenderer scopes overflow/ellipsis rules here. */
4142
4356
  const READONLY_VALUE_ROW_ROOT_CLASSES = [
4143
4357
  'TextDisplayWidget',
@@ -4349,6 +4563,8 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4349
4563
  }, [forceExitEdit]); // eslint-disable-line react-hooks/exhaustive-deps
4350
4564
  const [isDocumentsExpanded, setIsDocumentsExpanded] = useState(true);
4351
4565
  const sectionRef = useRef(null);
4566
+ const baselineSnapshotRef = useRef(null);
4567
+ const editEntrySnapshotRef = useRef(null);
4352
4568
  const [sectionHeight, setSectionHeight] = useState(null);
4353
4569
  const [editSectionPosition, setEditSectionPosition] = useState(null);
4354
4570
  // Capture section position when entering edit mode and update on scroll
@@ -4413,6 +4629,18 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4413
4629
  panels: makePanelsEditable(sectionToRender.panels, widgetsEditable),
4414
4630
  };
4415
4631
  }, [sectionToRender, widgetsEditable]);
4632
+ const effectiveHideEditButton = hideEditButton ||
4633
+ section['section-hide-edit-button'] === true ||
4634
+ !collectWidgets(section.panels || []).some((w) => section['section-editable'] === true || w['widget-readonly'] !== true);
4635
+ const captureEditEntrySnapshot = useCallback(() => {
4636
+ const currentValues = store.getState().widget.values;
4637
+ const supportingDocuments = section['section-supporting-documents'] || [];
4638
+ editEntrySnapshotRef.current = captureSectionEditSnapshot(currentValues, section, {
4639
+ namespace,
4640
+ sectionId,
4641
+ supportingDocuments: hasSupportingDocuments ? supportingDocuments : [],
4642
+ });
4643
+ }, [store, section, namespace, sectionId, hasSupportingDocuments]);
4416
4644
  // Handle edit button click
4417
4645
  const handleEdit = () => {
4418
4646
  // Capture height BEFORE entering edit mode to preserve space
@@ -4420,6 +4648,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4420
4648
  const height = sectionRef.current.offsetHeight;
4421
4649
  setSectionHeight(height);
4422
4650
  }
4651
+ captureEditEntrySnapshot();
4423
4652
  setIsEditMode(true);
4424
4653
  onEditModeChange?.(originalSectionId, true);
4425
4654
  };
@@ -4594,8 +4823,6 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4594
4823
  }
4595
4824
  return { records, files };
4596
4825
  }, [originalSection, hasSupportingDocuments]);
4597
- // Capture baseline when entering edit mode (used for isDirty comparison)
4598
- const baselineSnapshotRef = useRef(null);
4599
4826
  // IntakeForm only: increment when baseline is updated after save - forces badge to update (refs don't trigger re-renders)
4600
4827
  const [intakeFormBaselineTrigger, setIntakeFormBaselineTrigger] = useState(0);
4601
4828
  // IntakeForm: treat as edit mode for dirty tracking when isDraft. RegistryView: use isEditMode.
@@ -4613,6 +4840,9 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4613
4840
  // Set baseline when entering edit mode; clear when leaving (baseline captured only on entry)
4614
4841
  useEffect(() => {
4615
4842
  if (effectiveEditModeForDirty) {
4843
+ if (!editEntrySnapshotRef.current) {
4844
+ captureEditEntrySnapshot();
4845
+ }
4616
4846
  const oldSchemaData = schemaData || contextSchemaData || {};
4617
4847
  if (namespace) {
4618
4848
  const namespacedSchema = getValueByPath(oldSchemaData, namespace);
@@ -4621,11 +4851,12 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4621
4851
  : buildSectionSnapshot(oldSchemaData);
4622
4852
  }
4623
4853
  else {
4624
- baselineSnapshotRef.current = buildSectionSnapshot(oldSchemaData);
4854
+ baselineSnapshotRef.current = buildSectionSnapshot(store.getState().widget.values, namespace);
4625
4855
  }
4626
4856
  }
4627
4857
  else {
4628
4858
  baselineSnapshotRef.current = null;
4859
+ editEntrySnapshotRef.current = null;
4629
4860
  onSectionDirtyChange?.(sectionId, false);
4630
4861
  }
4631
4862
  // eslint-disable-next-line react-hooks/exhaustive-deps -- baseline must be captured only when effectiveEditModeForDirty toggles
@@ -4651,56 +4882,103 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4651
4882
  // and handleCancel.
4652
4883
  const revertToOriginalValues = useCallback(() => {
4653
4884
  const sectionWidgets = collectWidgets(originalSection.panels);
4654
- const oldSchemaData = schemaData || contextSchemaData;
4655
4885
  const currentStoreValues = store.getState().widget.values;
4886
+ const snapshot = editEntrySnapshotRef.current;
4656
4887
  let newStoreValues = currentStoreValues;
4657
- sectionWidgets.forEach(widget => {
4658
- const originalWidgetId = widget['widget-id'];
4659
- const namespacedWidgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
4660
- const widgetId = namespacedWidgetId;
4661
- const originalDataPath = widget['widget-data-path'];
4662
- const storeDataPath = namespace && originalDataPath
4663
- ? (typeof originalDataPath === 'string'
4664
- ? `${namespace}.${originalDataPath}`
4665
- : Object.fromEntries(Object.entries(originalDataPath).map(([key, path]) => [key, `${namespace}.${path}`])))
4666
- : originalDataPath;
4667
- if (widgetId && originalDataPath) {
4668
- let oldValue;
4669
- if (typeof originalDataPath === 'object') {
4670
- oldValue = {};
4671
- Object.entries(originalDataPath).forEach(([key, path]) => {
4672
- if (typeof path === 'string') {
4673
- oldValue[key] = getValueByPath(oldSchemaData, path);
4888
+ if (snapshot) {
4889
+ newStoreValues = applySectionEditSnapshot(currentStoreValues, snapshot);
4890
+ }
4891
+ else {
4892
+ const oldSchemaData = schemaData || contextSchemaData;
4893
+ const processedGeoGroups = new Set();
4894
+ sectionWidgets.forEach((widget) => {
4895
+ const originalWidgetId = widget['widget-id'];
4896
+ const namespacedWidgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
4897
+ const widgetId = namespacedWidgetId;
4898
+ const originalDataPath = widget['widget-data-path'];
4899
+ const storeDataPath = namespace && originalDataPath
4900
+ ? (typeof originalDataPath === 'string'
4901
+ ? `${namespace}.${originalDataPath}`
4902
+ : Object.fromEntries(Object.entries(originalDataPath).map(([key, path]) => [key, `${namespace}.${path}`])))
4903
+ : originalDataPath;
4904
+ const geoConfig = widget['widget-geo-config'];
4905
+ if (widgetId && originalDataPath) {
4906
+ let oldValue;
4907
+ if (typeof originalDataPath === 'object') {
4908
+ oldValue = {};
4909
+ Object.entries(originalDataPath).forEach(([key, path]) => {
4910
+ if (typeof path === 'string') {
4911
+ oldValue[key] = getValueByPath(oldSchemaData, path);
4912
+ }
4913
+ });
4914
+ }
4915
+ else if (typeof originalDataPath === 'string') {
4916
+ oldValue = getValueByPath(oldSchemaData, originalDataPath);
4917
+ }
4918
+ if (oldValue !== undefined) {
4919
+ newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
4920
+ if (geoConfig && typeof storeDataPath === 'string') {
4921
+ const groupId = getGeoGroupId(storeDataPath);
4922
+ const levelValue = resolveGeoWidgetLevelValue(newStoreValues, widgetId, storeDataPath, geoConfig);
4923
+ if (levelValue !== undefined && levelValue !== null && levelValue !== '') {
4924
+ newStoreValues = { ...newStoreValues, [widgetId]: levelValue };
4925
+ }
4926
+ else {
4927
+ const { [widgetId]: _removed, ...rest } = newStoreValues;
4928
+ newStoreValues = rest;
4929
+ }
4930
+ if (!processedGeoGroups.has(groupId)) {
4931
+ resetAndSeedGeoHierarchyFromValues(newStoreValues, storeDataPath, widgetId, groupId);
4932
+ processedGeoGroups.add(groupId);
4933
+ }
4934
+ if (geoConfig.parentWidgetId) {
4935
+ dispatch(setDataSource({ widgetId, data: [] }));
4936
+ }
4674
4937
  }
4675
- });
4676
- }
4677
- else if (typeof originalDataPath === 'string') {
4678
- oldValue = getValueByPath(oldSchemaData, originalDataPath);
4938
+ else {
4939
+ newStoreValues = { ...newStoreValues, [widgetId]: oldValue };
4940
+ }
4941
+ }
4679
4942
  }
4680
- if (oldValue !== undefined) {
4943
+ });
4944
+ if (hasSupportingDocuments) {
4945
+ const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
4946
+ originalSupportingDocuments.forEach((doc, index) => {
4947
+ const widgetId = `supporting-doc-${sectionId}-${index}`;
4948
+ const originalDataPath = doc['document-data-path'];
4949
+ const storeDataPath = namespace && originalDataPath
4950
+ ? `${namespace}.${originalDataPath}`
4951
+ : originalDataPath;
4952
+ const oldValue = getValueByPath(oldSchemaData, originalDataPath);
4681
4953
  newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
4682
- // Also revert the widgetId-based entry — useBaseWidget.handleChange
4683
- // sets values[widgetId] during editing, and useBaseWidget.currentValue
4684
- // reads values[widgetId] first before falling through to the dataPath.
4685
- newStoreValues = { ...newStoreValues, [widgetId]: oldValue };
4686
- }
4954
+ });
4687
4955
  }
4688
- });
4689
- if (hasSupportingDocuments) {
4690
- const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
4691
- originalSupportingDocuments.forEach((doc, index) => {
4692
- const widgetId = `supporting-doc-${sectionId}-${index}`;
4693
- const originalDataPath = doc['document-data-path'];
4694
- const storeDataPath = namespace && originalDataPath
4695
- ? `${namespace}.${originalDataPath}`
4696
- : originalDataPath;
4697
- const oldValue = getValueByPath(oldSchemaData, originalDataPath);
4698
- newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
4699
- });
4700
- }
4701
- if (newStoreValues !== currentStoreValues) {
4702
- dispatch(setValues(newStoreValues));
4703
4956
  }
4957
+ const processedGeoGroups = new Set();
4958
+ sectionWidgets.forEach((widget) => {
4959
+ const geoConfig = widget['widget-geo-config'];
4960
+ if (!geoConfig) {
4961
+ return;
4962
+ }
4963
+ const originalWidgetId = widget['widget-id'];
4964
+ const widgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
4965
+ const originalDataPath = widget['widget-data-path'];
4966
+ const storeDataPath = namespace && typeof originalDataPath === 'string'
4967
+ ? `${namespace}.${originalDataPath}`
4968
+ : originalDataPath;
4969
+ if (typeof storeDataPath !== 'string') {
4970
+ return;
4971
+ }
4972
+ const groupId = getGeoGroupId(storeDataPath);
4973
+ if (!processedGeoGroups.has(groupId)) {
4974
+ resetAndSeedGeoHierarchyFromValues(newStoreValues, storeDataPath, widgetId, groupId);
4975
+ processedGeoGroups.add(groupId);
4976
+ }
4977
+ if (geoConfig.parentWidgetId) {
4978
+ dispatch(setDataSource({ widgetId, data: [] }));
4979
+ }
4980
+ });
4981
+ dispatch(setValues(newStoreValues));
4704
4982
  }, [originalSection, schemaData, contextSchemaData, store, namespace, hasSupportingDocuments, sectionId, dispatch]);
4705
4983
  // Handle save button click
4706
4984
  const handleSave = async () => {
@@ -5196,7 +5474,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
5196
5474
  color: changeRequestType === 'new' ? 'var(--owt-color-bg, #FFFFFF)' : 'var(--owt-color-error, #B91C1C)',
5197
5475
  whiteSpace: 'nowrap',
5198
5476
  boxShadow: changeRequestType === 'new' ? '0 2px 4px rgba(40, 167, 69, 0.3)' : 'none',
5199
- }, children: changeRequestType === 'new' ? 'New' : 'Old' }))] })), jsxRuntimeExports.jsxs("div", { id: gridId, className: "section-panels", style: mode === 'RegistryView' && hideEditButton ? { paddingBottom: '30px' } : {}, children: [editableSection.panels.map((panel, index) => (jsxRuntimeExports.jsx("div", { className: "panel-wrapper", children: jsxRuntimeExports.jsx(PanelRenderer, { panel: panel, dataSourceRequestHandler: dataSourceRequestHandler, schemaData: namespacedSchemaData, onValueChange: onValueChange }) }, panel['panel-id'] || `section-panel-${index}`))), mode === 'CRView' && crViewData && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("hr", { className: "w-full", style: { height: '1px', marginTop: '20px', marginBottom: '0px', border: 'none', backgroundColor: 'var(--owt-color-border, #C4C4C4)' } }), jsxRuntimeExports.jsxs("div", { className: "cr-view-container", style: {
5477
+ }, children: changeRequestType === 'new' ? 'New' : 'Old' }))] })), jsxRuntimeExports.jsxs("div", { id: gridId, className: "section-panels", style: mode === 'RegistryView' && effectiveHideEditButton ? { paddingBottom: '30px' } : {}, children: [editableSection.panels.map((panel, index) => (jsxRuntimeExports.jsx("div", { className: "panel-wrapper", children: jsxRuntimeExports.jsx(PanelRenderer, { panel: panel, dataSourceRequestHandler: dataSourceRequestHandler, schemaData: namespacedSchemaData, onValueChange: onValueChange }) }, panel['panel-id'] || `section-panel-${index}`))), mode === 'CRView' && crViewData && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("hr", { className: "w-full", style: { height: '1px', marginTop: '20px', marginBottom: '0px', border: 'none', backgroundColor: 'var(--owt-color-border, #C4C4C4)' } }), jsxRuntimeExports.jsxs("div", { className: "cr-view-container", style: {
5200
5478
  marginTop: '20px',
5201
5479
  paddingBottom: '30px',
5202
5480
  display: 'flex',
@@ -5244,7 +5522,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
5244
5522
  fontSize: '14px',
5245
5523
  color: 'var(--owt-color-text, #011627)',
5246
5524
  fontWeight: 'normal',
5247
- }, children: crViewData.approvedDate }))] })] })] })), mode === 'RegistryView' && !hideEditButton && (jsxRuntimeExports.jsx("hr", { className: "w-full", style: { height: '1px', marginTop: !isEditMode ? '10px' : 0, marginBottom: '14px', border: 'none', backgroundColor: 'var(--owt-color-border, #C4C4C4)' } })), mode === 'RegistryView' && !isEditMode && !hideEditButton && (jsxRuntimeExports.jsx("div", { className: "flex justify-center items-center", style: { marginBottom: '20px' }, children: jsxRuntimeExports.jsxs("button", { onClick: handleEdit, className: "font-normal inline-flex items-center gap-2 bg-transparent border-0 p-0 cursor-pointer hover:opacity-80", style: {
5525
+ }, children: crViewData.approvedDate }))] })] })] })), mode === 'RegistryView' && !effectiveHideEditButton && (jsxRuntimeExports.jsx("hr", { className: "w-full", style: { height: '1px', marginTop: !isEditMode ? '10px' : 0, marginBottom: '14px', border: 'none', backgroundColor: 'var(--owt-color-border, #C4C4C4)' } })), mode === 'RegistryView' && !isEditMode && !effectiveHideEditButton && (jsxRuntimeExports.jsx("div", { className: "flex justify-center items-center", style: { marginBottom: '20px' }, children: jsxRuntimeExports.jsxs("button", { onClick: handleEdit, className: "font-normal inline-flex items-center gap-2 bg-transparent border-0 p-0 cursor-pointer hover:opacity-80", style: {
5248
5526
  fontFamily: 'Roboto, sans-serif',
5249
5527
  fontSize: '16px',
5250
5528
  color: 'var(--owt-color-text-muted, #727474)',
@@ -6050,7 +6328,7 @@ const JSONEditorPanel = ({ section, onChange, onReset, context = 'section', }) =
6050
6328
  'widget-data-format.dateConstraint': ['any', 'past-only', 'future-only'],
6051
6329
  'widget-data-format.dateTimeConstraint': ['any', 'past-only', 'future-only'],
6052
6330
  // Widget options
6053
- 'widget-data-options.action': ['show', 'hide', 'enable', 'disable'],
6331
+ 'widget-data-options.action': ['show', 'hide', 'enable', 'disable', 'require'],
6054
6332
  'widget-data-options.condition.operator': CONDITION_OPERATORS,
6055
6333
  };
6056
6334
  }, []);
@@ -7435,7 +7713,7 @@ const removeMask = (value, mask) => {
7435
7713
  };
7436
7714
 
7437
7715
  const TextInputWidget = ({ config }) => {
7438
- const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
7716
+ const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
7439
7717
  const { translate, translateConfig } = useWidgetTranslation();
7440
7718
  // Track raw value separately for masking (to preserve unmasked value internally)
7441
7719
  const formatConfig = widgetConfig['widget-data-format'];
@@ -7572,7 +7850,7 @@ const TextInputWidget = ({ config }) => {
7572
7850
  const label = translateConfig(widgetConfig['widget-label']);
7573
7851
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] TextDisplayWidget 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 }) })] }));
7574
7852
  }
7575
- 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: "flex items-center gap-2 mb-1", children: [jsxRuntimeExports.jsx("input", { type: getInputType(), value: displayValue, onChange: handleChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, maxLength: formatConfig?.mask ? undefined : maxLength, inputMode: formatConfig?.currency
7853
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntimeExports.jsx("input", { type: getInputType(), value: displayValue, onChange: handleChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, maxLength: formatConfig?.mask ? undefined : maxLength, inputMode: formatConfig?.currency
7576
7854
  ? 'decimal'
7577
7855
  : formatConfig?.characterType === 'numeric' || formatConfig?.characterType === 'numeric-decimal'
7578
7856
  ? 'numeric'
@@ -7595,7 +7873,7 @@ const NumberInputWidget = ({ config }) => {
7595
7873
  }
7596
7874
  return { ...config, 'widget-data-default': normalizedDefault };
7597
7875
  }, [config]);
7598
- const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config: resolvedConfig });
7876
+ const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config: resolvedConfig });
7599
7877
  const { translate, translateConfig } = useWidgetTranslation();
7600
7878
  const formatConfig = widgetConfig['widget-data-format'];
7601
7879
  const validationConfig = widgetConfig['widget-data-validation'];
@@ -7726,7 +8004,7 @@ const NumberInputWidget = ({ config }) => {
7726
8004
  const display = numValue !== null ? formatNumber(numValue, formatConfig) : '';
7727
8005
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] NumberDisplayWidget 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 ${textAlignClass}`, title: String(display ?? ''), children: display }) })] }));
7728
8006
  }
7729
- 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: "flex items-center justify-between mb-1", children: [jsxRuntimeExports.jsx("input", { type: "text", inputMode: "decimal", value: displayValue, onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, onKeyDown: handleKeyDown, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, maxLength: maxLength, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${textAlignClass} ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (value === null || value === undefined || value === ''))
8007
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-1", children: [jsxRuntimeExports.jsx("input", { type: "text", inputMode: "decimal", value: displayValue, onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, onKeyDown: handleKeyDown, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, maxLength: maxLength, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${textAlignClass} ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (value === null || value === undefined || value === ''))
7730
8008
  ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
7731
8009
  : 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), maxLength && (jsxRuntimeExports.jsxs("span", { className: `text-xs ml-2 flex-shrink-0 ${currentLength > maxLength
7732
8010
  ? 'text-red-500'
@@ -7734,7 +8012,7 @@ const NumberInputWidget = ({ config }) => {
7734
8012
  };
7735
8013
 
7736
8014
  const BooleanWidget = ({ config }) => {
7737
- const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
8015
+ const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
7738
8016
  const { translate, translateConfig } = useWidgetTranslation();
7739
8017
  const formatConfig = widgetConfig['widget-data-format'];
7740
8018
  const representation = formatConfig?.booleanRepresentation || 'true-false';
@@ -7803,7 +8081,7 @@ const BooleanWidget = ({ config }) => {
7803
8081
  }
7804
8082
  // Render based on control type
7805
8083
  if (controlType === 'checkbox') {
7806
- 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] }))] })] }) }));
8084
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { 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", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), 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] }))] })] }) }));
7807
8085
  }
7808
8086
  if (controlType === 'radio') {
7809
8087
  const containerClass = orientation === 'horizontal'
@@ -7811,10 +8089,10 @@ const BooleanWidget = ({ config }) => {
7811
8089
  : 'flex flex-col items-start gap-2';
7812
8090
  const radioDisabled = !isEnabled || widgetConfig['widget-readonly'];
7813
8091
  const optionDisabledClass = radioDisabled ? 'opacity-50 cursor-not-allowed' : '';
7814
- 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] }))] })] }) }));
8092
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { 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", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), 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] }))] })] }) }));
7815
8093
  }
7816
8094
  // Toggle/switch control type
7817
- 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
8095
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { 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", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), 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
7818
8096
  ? 'bg-blue-600 text-white border-blue-600'
7819
8097
  : '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
7820
8098
  ? 'bg-blue-600 text-white border-blue-600'
@@ -7824,7 +8102,7 @@ const BooleanWidget = ({ config }) => {
7824
8102
  };
7825
8103
 
7826
8104
  const DateInputWidget = ({ config }) => {
7827
- const { value, error, touched, isEnabled, onChange, onBlur, setError, config: widgetConfig, } = useBaseWidget({ config });
8105
+ const { value, error, touched, isEnabled, isRequired, onChange, onBlur, setError, config: widgetConfig, } = useBaseWidget({ config });
7828
8106
  const formValues = useSelector((state) => state.widget.values);
7829
8107
  const { translateConfig } = useWidgetTranslation();
7830
8108
  const formatConfig = widgetConfig['widget-data-format'];
@@ -8025,7 +8303,7 @@ const DateInputWidget = ({ config }) => {
8025
8303
  }
8026
8304
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DateDisplayWidget 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 }) })] }));
8027
8305
  }
8028
- 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("input", { type: inputType, value: getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, min: inputMethod === 'picker' ? effectiveMinDate : undefined, max: inputMethod === 'picker' ? effectiveMaxDate : undefined, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${showValidationError || showRequiredError
8306
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: inputType, value: getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, min: inputMethod === 'picker' ? effectiveMinDate : undefined, max: inputMethod === 'picker' ? effectiveMaxDate : undefined, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${showValidationError || showRequiredError
8029
8307
  ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
8030
8308
  : 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), showValidationError && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })] }) }));
8031
8309
  };
@@ -8315,7 +8593,7 @@ const validateDateTimeConstraints = (date, minDateTime, maxDateTime, constraint)
8315
8593
  };
8316
8594
 
8317
8595
  const DateTimeInputWidget = ({ config }) => {
8318
- const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
8596
+ const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
8319
8597
  const { translate, translateConfig } = useWidgetTranslation();
8320
8598
  const formatConfig = widgetConfig['widget-data-format'];
8321
8599
  const optionsConfig = widgetConfig['widget-data-options'];
@@ -8468,29 +8746,33 @@ const DateTimeInputWidget = ({ config }) => {
8468
8746
  }
8469
8747
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DateTimeDisplayWidget 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 }) })] }));
8470
8748
  }
8471
- 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("input", { type: inputType, value: getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, min: inputMethod === 'picker' ? effectiveMinDateTime : undefined, max: inputMethod === 'picker' ? effectiveMaxDateTime : undefined, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (!value || value === ''))
8749
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: inputType, value: getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, min: inputMethod === 'picker' ? effectiveMinDateTime : undefined, max: inputMethod === 'picker' ? effectiveMaxDateTime : undefined, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (!value || value === ''))
8472
8750
  ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
8473
8751
  : 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8474
8752
  };
8475
8753
 
8476
8754
  const SelectWidget = ({ config }) => {
8477
- const { value, error, touched, isEnabled, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
8755
+ const { value, geoDisplayLabel, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
8478
8756
  const { translate, translateConfig } = useWidgetTranslation();
8479
8757
  // For readonly mode, render as display text showing only the selected label
8480
8758
  if (widgetConfig['widget-readonly']) {
8481
8759
  const label = translateConfig(widgetConfig['widget-label']);
8482
8760
  // Find the selected option's label
8483
- const selectedOption = dataSourceOptions.find((option) => option.value === value);
8484
- const displayValue = selectedOption ? selectedOption.label : (value || '-');
8761
+ const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
8762
+ const displayValue = selectedOption
8763
+ ? translateConfig(selectedOption.label)
8764
+ : loading
8765
+ ? (geoDisplayLabel || '-')
8766
+ : (geoDisplayLabel || (value != null && value !== '' ? translateConfig(String(value)) : '-'));
8485
8767
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] SelectDisplayWidget 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 }) })] }));
8486
8768
  }
8487
- 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("select", { value: value || '', onChange: (e) => onChange(e.target.value === '' ? undefined : e.target.value), onBlur: onBlur, disabled: !isEnabled || loading || widgetConfig['widget-readonly'], className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (!value || value === ''))
8769
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("select", { value: value || '', onChange: (e) => onChange(e.target.value === '' ? undefined : e.target.value), onBlur: onBlur, disabled: !isEnabled || loading || widgetConfig['widget-readonly'], className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (!value || value === ''))
8488
8770
  ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
8489
- : 'border-gray-300'} ${!isEnabled || loading || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']), children: [jsxRuntimeExports.jsx("option", { value: "", children: translate('common.select') }), dataSourceOptions.map((option) => (jsxRuntimeExports.jsx("option", { value: option.value, children: option.label }, option.value)))] }), loading && (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 mt-1", children: translate('common.loadingOptions') })), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8771
+ : 'border-gray-300'} ${!isEnabled || loading || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']), children: [jsxRuntimeExports.jsx("option", { value: "", children: translate('common.select') }), dataSourceOptions.map((option) => (jsxRuntimeExports.jsx("option", { value: option.value, children: translateConfig(option.label) }, option.value)))] }), loading && (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 mt-1", children: translate('common.loadingOptions') })), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8490
8772
  };
8491
8773
 
8492
8774
  const RadioWidget = ({ config }) => {
8493
- const { value, error, touched, isEnabled, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
8775
+ const { value, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
8494
8776
  const { translate, translateConfig } = useWidgetTranslation();
8495
8777
  const formatConfig = widgetConfig['widget-data-format'];
8496
8778
  const layout = formatConfig?.layout || widgetConfig['widget-orientation'] || 'vertical';
@@ -8553,14 +8835,16 @@ const RadioWidget = ({ config }) => {
8553
8835
  if (widgetConfig['widget-readonly']) {
8554
8836
  const label = translateConfig(widgetConfig['widget-label']);
8555
8837
  const selectedOption = processedOptions.find(opt => opt.value === currentValue);
8556
- const displayValue = selectedOption ? selectedOption.label : (allowUnset && currentValue === null ? '-' : '');
8838
+ const displayValue = selectedOption
8839
+ ? translateConfig(selectedOption.label)
8840
+ : (allowUnset && currentValue === null ? '-' : '');
8557
8841
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] RadioDisplayWidget 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 }) })] }));
8558
8842
  }
8559
- 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') })) : (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { 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: handleUnset, 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: "-" })] })), 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: "radio", name: widgetConfig['widget-id'], value: option.value, checked: currentValue === option.value, onChange: (e) => handleChange(option.value), 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: translateConfig(option.label) })] }, option.value)))] })) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8843
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), 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') })) : (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { 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: handleUnset, 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: "-" })] })), 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: "radio", name: widgetConfig['widget-id'], value: option.value, checked: currentValue === option.value, onChange: (e) => handleChange(option.value), 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: translateConfig(option.label) })] }, option.value)))] })) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8560
8844
  };
8561
8845
 
8562
8846
  const CheckboxWidget = ({ config }) => {
8563
- const { value, error, touched, isEnabled, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
8847
+ const { value, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
8564
8848
  const { translate, translateConfig } = useWidgetTranslation();
8565
8849
  const hasDataSource = !!widgetConfig['widget-data-source'];
8566
8850
  const formatConfig = widgetConfig['widget-data-format'];
@@ -8575,7 +8859,7 @@ const CheckboxWidget = ({ config }) => {
8575
8859
  const displayValue = isChecked ? 'Yes' : 'No';
8576
8860
  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 }) })] }));
8577
8861
  }
8578
- 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] }))] })] }) }));
8862
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { 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", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), 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] }))] })] }) }));
8579
8863
  }
8580
8864
  // Multiple checkboxes (with data source) - for array values
8581
8865
  // Process and sort options if needed
@@ -8645,7 +8929,7 @@ const CheckboxWidget = ({ config }) => {
8645
8929
  : '-';
8646
8930
  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 }) })] }));
8647
8931
  }
8648
- 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] }))] })] }) }));
8932
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { 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", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), 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] }))] })] }) }));
8649
8933
  };
8650
8934
 
8651
8935
  const SimpleTableWidget = ({ config }) => {
@@ -8696,7 +8980,7 @@ const SimpleTableWidget = ({ config }) => {
8696
8980
  };
8697
8981
 
8698
8982
  const ArrayWidget = ({ config }) => {
8699
- const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
8983
+ const { value, error, touched, isEnabled, isRequired, onChange, config: widgetConfig, } = useBaseWidget({ config });
8700
8984
  const { translate, translateConfig } = useWidgetTranslation();
8701
8985
  const items = Array.isArray(value) ? value : [];
8702
8986
  const itemConfig = widgetConfig['widget-item'];
@@ -8720,7 +9004,7 @@ const ArrayWidget = ({ config }) => {
8720
9004
  newItems[index] = newValue;
8721
9005
  onChange(newItems);
8722
9006
  };
8723
- return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start mb-2", 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 flex justify-between items-center", children: [jsxRuntimeExports.jsx("div", { className: "flex-1" }), operations.add && !isReadonly && isEnabled && (jsxRuntimeExports.jsx("button", { type: "button", onClick: addItem, className: "px-3 py-1 text-sm bg-blue-500 text-white hover:bg-blue-600", style: { borderRadius: '15px' }, children: addLabel }))] })] }), items.length === 0 ? (jsxRuntimeExports.jsxs("div", { className: "text-gray-500 text-sm py-4 text-center border border-gray-300 rounded", children: [translate('common.noItems'), ". ", operations.add && !isReadonly && translate('common.clickToAdd', { label: addLabel })] })) : (jsxRuntimeExports.jsx("div", { className: "space-y-2", children: items.map((itemValue, index) => {
9007
+ return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start mb-2", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0 flex justify-between items-center", children: [jsxRuntimeExports.jsx("div", { className: "flex-1" }), operations.add && !isReadonly && isEnabled && (jsxRuntimeExports.jsx("button", { type: "button", onClick: addItem, className: "px-3 py-1 text-sm bg-blue-500 text-white hover:bg-blue-600", style: { borderRadius: '15px' }, children: addLabel }))] })] }), items.length === 0 ? (jsxRuntimeExports.jsxs("div", { className: "text-gray-500 text-sm py-4 text-center border border-gray-300 rounded", children: [translate('common.noItems'), ". ", operations.add && !isReadonly && translate('common.clickToAdd', { label: addLabel })] })) : (jsxRuntimeExports.jsx("div", { className: "space-y-2", children: items.map((itemValue, index) => {
8724
9008
  ({
8725
9009
  ...itemConfig,
8726
9010
  'widget-id': `${widgetConfig['widget-id']}-item-${index}`,
@@ -8731,7 +9015,7 @@ const ArrayWidget = ({ config }) => {
8731
9015
  };
8732
9016
 
8733
9017
  const IterableAccordionWidget = ({ config }) => {
8734
- const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
9018
+ const { value, error, touched, isEnabled, isRequired, onChange, config: widgetConfig, } = useBaseWidget({ config });
8735
9019
  const { translate, translateConfig } = useWidgetTranslation();
8736
9020
  const items = Array.isArray(value) ? value : [];
8737
9021
  const itemConfig = widgetConfig['widget-item'];
@@ -8780,7 +9064,7 @@ const IterableAccordionWidget = ({ config }) => {
8780
9064
  newItems[index] = newValue;
8781
9065
  onChange(newItems);
8782
9066
  };
8783
- return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start mb-2", 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 flex justify-between items-center", children: [jsxRuntimeExports.jsx("div", { className: "flex-1" }), operations.add && !isReadonly && isEnabled && (jsxRuntimeExports.jsx("button", { type: "button", onClick: addItem, className: "px-3 py-1 text-sm bg-blue-500 text-white hover:bg-blue-600", style: { borderRadius: '15px' }, children: addLabel }))] })] }), items.length === 0 ? (jsxRuntimeExports.jsxs("div", { className: "text-gray-500 text-sm py-4 text-center border border-gray-300 rounded", children: [translate('common.noItems'), ". ", operations.add && !isReadonly && translate('common.clickToAdd', { label: addLabel })] })) : (jsxRuntimeExports.jsx("div", { className: "space-y-2", children: items.map((itemValue, index) => {
9067
+ return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start mb-2", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0 flex justify-between items-center", children: [jsxRuntimeExports.jsx("div", { className: "flex-1" }), operations.add && !isReadonly && isEnabled && (jsxRuntimeExports.jsx("button", { type: "button", onClick: addItem, className: "px-3 py-1 text-sm bg-blue-500 text-white hover:bg-blue-600", style: { borderRadius: '15px' }, children: addLabel }))] })] }), items.length === 0 ? (jsxRuntimeExports.jsxs("div", { className: "text-gray-500 text-sm py-4 text-center border border-gray-300 rounded", children: [translate('common.noItems'), ". ", operations.add && !isReadonly && translate('common.clickToAdd', { label: addLabel })] })) : (jsxRuntimeExports.jsx("div", { className: "space-y-2", children: items.map((itemValue, index) => {
8784
9068
  const isCollapsed = collapsedItems[index] ?? defaultCollapsed;
8785
9069
  const parentPath = widgetConfig['widget-data-path'];
8786
9070
  const childPath = itemConfig['widget-data-path'];
@@ -8819,7 +9103,7 @@ const IterableAccordionWidget = ({ config }) => {
8819
9103
  };
8820
9104
 
8821
9105
  const PhoneInputWidget = ({ config }) => {
8822
- const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
9106
+ const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
8823
9107
  const { translate, translateConfig } = useWidgetTranslation();
8824
9108
  // Use formatted value if available, otherwise raw value
8825
9109
  const displayValue = formattedValue !== undefined && formattedValue !== value
@@ -8830,13 +9114,13 @@ const PhoneInputWidget = ({ config }) => {
8830
9114
  const label = translateConfig(widgetConfig['widget-label']);
8831
9115
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] PhoneDisplayWidget 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 || '-' }) })] }));
8832
9116
  }
8833
- 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("input", { type: "tel", value: displayValue, onChange: (e) => onChange(e.target.value), onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: translateConfig(widgetConfig['widget-data-placeholder']), className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (!value || value === ''))
9117
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: "tel", value: displayValue, onChange: (e) => onChange(e.target.value), onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: translateConfig(widgetConfig['widget-data-placeholder']), className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (!value || value === ''))
8834
9118
  ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
8835
9119
  : 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8836
9120
  };
8837
9121
 
8838
9122
  const CurrencyInputWidget = ({ config }) => {
8839
- const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
9123
+ const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
8840
9124
  const { translate, translateConfig } = useWidgetTranslation();
8841
9125
  // For input, use raw numeric value; formatted value is for display only
8842
9126
  const numericValue = typeof value === 'number' ? value : (value ? parseFloat(String(value)) : '');
@@ -8858,7 +9142,7 @@ const CurrencyInputWidget = ({ config }) => {
8858
9142
  const display = formattedValue || (value !== null && value !== undefined ? String(value) : '-');
8859
9143
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] CurrencyDisplayWidget 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(display ?? ''), children: display }) })] }));
8860
9144
  }
8861
- 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: "relative", children: [jsxRuntimeExports.jsx("input", { type: "text", inputMode: "decimal", value: numericValue, onChange: handleChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: translateConfig(widgetConfig['widget-data-placeholder']), className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (value === null || value === undefined || value === ''))
9145
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "relative", children: [jsxRuntimeExports.jsx("input", { type: "text", inputMode: "decimal", value: numericValue, onChange: handleChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: translateConfig(widgetConfig['widget-data-placeholder']), className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (value === null || value === undefined || value === ''))
8862
9146
  ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
8863
9147
  : 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), formattedValue && formattedValue !== String(value) && (jsxRuntimeExports.jsx("span", { className: "absolute right-3 top-2 text-gray-500 text-sm", children: formattedValue }))] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8864
9148
  };
@@ -8978,7 +9262,7 @@ const SelectDisplayValue$1 = ({ config, value }) => {
8978
9262
  if (value === null || value === undefined || value === '') {
8979
9263
  return jsxRuntimeExports.jsx("span", { children: "-" });
8980
9264
  }
8981
- const selectedOption = dataSourceOptions.find((option) => option.value === value);
9265
+ const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
8982
9266
  return jsxRuntimeExports.jsx("span", { children: selectedOption ? selectedOption.label : String(value) });
8983
9267
  };
8984
9268
  const TableCellText = ({ config, value, onValueChange }) => {
@@ -9701,6 +9985,7 @@ const TableWidget = ({ config }) => {
9701
9985
  }, children: translate('common.cancel') || 'Cancel' })] }) })] }))] })] }) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-sm mt-1", style: { color: 'var(--owt-widget-error-color, #B91C1C)' }, children: error[0] }))] })] }));
9702
9986
  };
9703
9987
 
9988
+ const isUnsetRowValue = (value) => value === null || value === undefined || value === '';
9704
9989
  // Display select value label in view mode
9705
9990
  const SelectDisplayValue = ({ config, value }) => {
9706
9991
  const { dataSourceOptions, loading } = useBaseWidget({ config });
@@ -9708,7 +9993,7 @@ const SelectDisplayValue = ({ config, value }) => {
9708
9993
  return jsxRuntimeExports.jsx("span", { children: "-" });
9709
9994
  if (value === null || value === undefined || value === '')
9710
9995
  return jsxRuntimeExports.jsx("span", { children: "-" });
9711
- const selectedOption = dataSourceOptions.find((option) => option.value === value);
9996
+ const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
9712
9997
  return jsxRuntimeExports.jsx("span", { children: selectedOption ? selectedOption.label : String(value) });
9713
9998
  };
9714
9999
  /**
@@ -9733,7 +10018,6 @@ const DialogTableWidget = ({ config }) => {
9733
10018
  const { value, error, touched, isEnabled, onChange, config: widgetConfig } = useBaseWidget({ config });
9734
10019
  const { translate, translateConfig } = useWidgetTranslation();
9735
10020
  const dispatch = useDispatch();
9736
- const storeValues = useSelector((state) => state.widget?.values ?? {});
9737
10021
  const rows = Array.isArray(value) ? value : [];
9738
10022
  const columns = widgetConfig['widget-data-columns'] || [];
9739
10023
  const operations = widgetConfig['widget-data-operations'] || {};
@@ -9765,7 +10049,12 @@ const DialogTableWidget = ({ config }) => {
9765
10049
  const emptyRow = {};
9766
10050
  columns.forEach((col) => {
9767
10051
  const key = col['column-key'];
9768
- emptyRow[key] = col['widget-data-default'] ?? '';
10052
+ if (col['widget-data-default'] !== undefined) {
10053
+ emptyRow[key] = col['widget-data-default'];
10054
+ }
10055
+ else if (col.widget === 'checkbox') {
10056
+ emptyRow[key] = false;
10057
+ }
9769
10058
  });
9770
10059
  return emptyRow;
9771
10060
  }, [columns]);
@@ -9818,17 +10107,48 @@ const DialogTableWidget = ({ config }) => {
9818
10107
  const updateField = useCallback((columnKey, newValue) => {
9819
10108
  setFormData((prev) => ({ ...prev, [columnKey]: newValue }));
9820
10109
  }, []);
9821
- const collectMergedRowPayload = useCallback(() => {
9822
- const merged = { ...formData };
10110
+ const membersWidgetId = widgetConfig['widget-id'];
10111
+ const dialogStoreValues = useSelector((state) => {
10112
+ if (dialogSessionId <= 0) {
10113
+ return {};
10114
+ }
10115
+ const values = state.widget?.values ?? {};
10116
+ const row = {};
10117
+ columns.forEach((col) => {
10118
+ const k = col['column-key'];
10119
+ const wid = `${membersWidgetId}-dlg-${dialogSessionId}-${k}`;
10120
+ if (values[wid] !== undefined) {
10121
+ row[k] = values[wid];
10122
+ }
10123
+ });
10124
+ return row;
10125
+ }, (a, b) => JSON.stringify(a) === JSON.stringify(b));
10126
+ const buildDialogRowValues = useCallback((storeSlice) => {
10127
+ const row = { ...formData };
9823
10128
  columns.forEach((col) => {
9824
10129
  const k = col['column-key'];
9825
- const wid = dialogFieldWidgetId(k);
9826
- const fromStore = storeValues[wid];
9827
- if (fromStore !== undefined)
9828
- merged[k] = fromStore;
10130
+ if (storeSlice[k] !== undefined) {
10131
+ row[k] = storeSlice[k];
10132
+ }
10133
+ });
10134
+ return row;
10135
+ }, [formData, columns]);
10136
+ const dialogRowValues = useMemo(() => buildDialogRowValues(dialogStoreValues), [buildDialogRowValues, dialogStoreValues]);
10137
+ const collectMergedRowPayload = useCallback(() => buildDialogRowValues(dialogStoreValues), [buildDialogRowValues, dialogStoreValues]);
10138
+ const finalizeDialogRowPayload = useCallback((raw) => {
10139
+ const result = {};
10140
+ columns.forEach((col) => {
10141
+ const key = col['column-key'];
10142
+ if (!shouldShowWidget(col['widget-data-options'], raw)) {
10143
+ return;
10144
+ }
10145
+ const val = raw[key];
10146
+ if (!isUnsetRowValue(val)) {
10147
+ result[key] = val;
10148
+ }
9829
10149
  });
9830
- return merged;
9831
- }, [formData, columns, storeValues, dialogFieldWidgetId]);
10150
+ return result;
10151
+ }, [columns]);
9832
10152
  const saveDialog = useCallback(() => {
9833
10153
  const payload = collectMergedRowPayload();
9834
10154
  let hasErrors = false;
@@ -9838,8 +10158,11 @@ const DialogTableWidget = ({ config }) => {
9838
10158
  const isColReadonly = isReadonly || col['widget-readonly'] === true;
9839
10159
  if (isColReadonly)
9840
10160
  return;
10161
+ if (!shouldShowWidget(col['widget-data-options'], payload))
10162
+ return;
9841
10163
  const cellValue = payload[key];
9842
- const validationErrors = validateWidget(cellValue, col['widget-data-validation'], col['widget-required']);
10164
+ const isRequired = shouldRequireWidget(col['widget-data-options'], payload, col['widget-required']);
10165
+ const validationErrors = validateWidget(cellValue, col['widget-data-validation'], isRequired);
9843
10166
  if (validationErrors && validationErrors.length > 0) {
9844
10167
  hasErrors = true;
9845
10168
  dispatch(setError({ widgetId: cellWidgetId, errors: validationErrors }));
@@ -9852,8 +10175,9 @@ const DialogTableWidget = ({ config }) => {
9852
10175
  if (hasErrors) {
9853
10176
  return;
9854
10177
  }
10178
+ const cleaned = finalizeDialogRowPayload(payload);
9855
10179
  if (dialogMode === 'add') {
9856
- const savedRow = { ...payload, edit_action: 'ADD' };
10180
+ const savedRow = { ...cleaned, edit_action: 'ADD' };
9857
10181
  onChange([...rows, savedRow]);
9858
10182
  closeDialog();
9859
10183
  return;
@@ -9863,11 +10187,18 @@ const DialogTableWidget = ({ config }) => {
9863
10187
  const currentRow = newRows[activeRowIndex] || {};
9864
10188
  const wasDeleted = currentRow.edit_action === 'DELETE';
9865
10189
  const editAction = wasDeleted ? 'UPDATE' : (currentRow.edit_action ?? 'UPDATE');
9866
- newRows[activeRowIndex] = { ...currentRow, ...payload, edit_action: editAction };
10190
+ const merged = { ...currentRow, ...cleaned, edit_action: editAction };
10191
+ columns.forEach((col) => {
10192
+ const key = col['column-key'];
10193
+ if (!(key in cleaned)) {
10194
+ delete merged[key];
10195
+ }
10196
+ });
10197
+ newRows[activeRowIndex] = merged;
9867
10198
  onChange(newRows);
9868
10199
  closeDialog();
9869
10200
  }
9870
- }, [collectMergedRowPayload, dialogMode, onChange, rows, closeDialog, activeRowIndex, columns, dialogFieldWidgetId, isReadonly, dispatch]);
10201
+ }, [collectMergedRowPayload, finalizeDialogRowPayload, dialogMode, onChange, rows, closeDialog, activeRowIndex, columns, dialogFieldWidgetId, isReadonly, dispatch]);
9871
10202
  const deleteRow = useCallback((rowIndex) => {
9872
10203
  const newRows = rows.filter((_, i) => i !== rowIndex);
9873
10204
  onChange(newRows);
@@ -9954,9 +10285,12 @@ const DialogTableWidget = ({ config }) => {
9954
10285
  lineHeight: 1,
9955
10286
  }, "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) => {
9956
10287
  const key = col['column-key'];
10288
+ if (!shouldShowWidget(col['widget-data-options'], dialogRowValues)) {
10289
+ return null;
10290
+ }
9957
10291
  const widgetType = col.widget || 'text';
9958
10292
  const cellWidgetId = dialogFieldWidgetId(key);
9959
- const initialValue = formData[key] ?? col['widget-data-default'] ?? '';
10293
+ const initialValue = formData[key] ?? col['widget-data-default'];
9960
10294
  const fieldConfig = {
9961
10295
  ...col,
9962
10296
  widget: widgetType,
@@ -9966,6 +10300,8 @@ const DialogTableWidget = ({ config }) => {
9966
10300
  'widget-readonly': isReadonly || col['widget-readonly'] === true,
9967
10301
  'widget-data-path': undefined,
9968
10302
  'widget-data-default': initialValue,
10303
+ 'widget-data-options': undefined,
10304
+ 'widget-required': shouldRequireWidget(col['widget-data-options'], dialogRowValues, col['widget-required']),
9969
10305
  };
9970
10306
  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}`));
9971
10307
  }) }, `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: {
@@ -10163,7 +10499,7 @@ const ProfileWidget = ({ config }) => {
10163
10499
  const TextAreaWidget = ({ config }) => {
10164
10500
  // Check readonly early from original config
10165
10501
  const isReadonly = config['widget-readonly'] || false;
10166
- const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
10502
+ const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
10167
10503
  const { translate, translateConfig } = useWidgetTranslation();
10168
10504
  const formatConfig = widgetConfig['widget-data-format'] || {};
10169
10505
  const validationConfig = widgetConfig['widget-data-validation'] || {};
@@ -10211,7 +10547,6 @@ const TextAreaWidget = ({ config }) => {
10211
10547
  ? translateConfig(widgetConfig['widget-label'])
10212
10548
  : '';
10213
10549
  // Check if required
10214
- const isRequired = widgetConfig['widget-required'] || false;
10215
10550
  // Error display
10216
10551
  const hasError = touched && error && error.length > 0;
10217
10552
  const errorMessage = hasError ? error[0] : '';
@@ -10229,7 +10564,7 @@ const TextAreaWidget = ({ config }) => {
10229
10564
  border: 'none',
10230
10565
  }, children: displayValue }) })] }));
10231
10566
  }
10232
- 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: [label, isRequired && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { style: { position: 'relative' }, children: [jsxRuntimeExports.jsx("textarea", { id: widgetConfig['widget-id'], rows: rows, value: getStringValue(), onChange: handleChange, onBlur: onBlur, disabled: !isEnabled, placeholder: placeholder, className: `w-full px-3 py-2 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${hasError
10567
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: label, required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { style: { position: 'relative' }, children: [jsxRuntimeExports.jsx("textarea", { id: widgetConfig['widget-id'], rows: rows, value: getStringValue(), onChange: handleChange, onBlur: onBlur, disabled: !isEnabled, placeholder: placeholder, className: `w-full px-3 py-2 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${hasError
10233
10568
  ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
10234
10569
  : 'border-gray-300'} ${!isEnabled ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: {
10235
10570
  borderRadius: '10px',
@@ -11707,7 +12042,7 @@ const ResultsTable = ({ rows, selectedRowKey, onRowClick, onRowDoubleClick, }) =
11707
12042
  }) })] }) }));
11708
12043
  };
11709
12044
  const RegisterLookupWidget = ({ config }) => {
11710
- const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
12045
+ const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
11711
12046
  const { translate, translateConfig } = useWidgetTranslation();
11712
12047
  const { dataSourceRequestHandler } = useWidgetContext();
11713
12048
  const dataSource = widgetConfig['widget-data-source'];
@@ -11870,7 +12205,7 @@ const RegisterLookupWidget = ({ config }) => {
11870
12205
  onChange(null);
11871
12206
  setAppliedRecord(null);
11872
12207
  setPendingRow(null);
11873
- }, className: "text-sm underline text-red-500 p-0 border-0 bg-transparent cursor-pointer focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-1 rounded", children: translate('common.remove', { defaultValue: 'Remove' }) })] })), !isReadonly && touched && error.length > 0 && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })) : !isReadonly ? (jsxRuntimeExports.jsxs("div", { className: "w-full min-w-0", children: [jsxRuntimeExports.jsxs("button", { type: "button", disabled: !isEnabled, onClick: openLookup, title: actionLabel, className: `flex items-center gap-2 w-full sm:w-[180px] max-w-full px-3 h-[30px] text-sm border rounded-[10px] shadow-sm transition-colors ${hasError ? 'border-red-500 text-gray-700' : 'border-gray-300 text-gray-700'} ${!isEnabled ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white cursor-pointer'}`, children: [jsxRuntimeExports.jsx("img", { src: img$g, alt: "", className: "w-4 h-4 opacity-50 flex-shrink-0" }), jsxRuntimeExports.jsx("span", { className: "truncate", children: actionLabel }), widgetConfig['widget-required'] && jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" })] }), touched && error.length > 0 && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })) : null, !isReadonly && isOpen && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50", style: { backgroundColor: 'rgba(0,0,0,0.5)' }, onClick: () => { setIsOpen(false); onBlur(); } }), jsxRuntimeExports.jsxs("div", { className: "flex flex-col overflow-hidden", style: {
12208
+ }, className: "text-sm underline text-red-500 p-0 border-0 bg-transparent cursor-pointer focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-1 rounded", children: translate('common.remove', { defaultValue: 'Remove' }) })] })), !isReadonly && touched && error.length > 0 && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })) : !isReadonly ? (jsxRuntimeExports.jsxs("div", { className: "w-full min-w-0", children: [jsxRuntimeExports.jsxs("button", { type: "button", disabled: !isEnabled, onClick: openLookup, title: actionLabel, className: `flex items-center gap-2 w-full sm:w-[180px] max-w-full px-3 h-[30px] text-sm border rounded-[10px] shadow-sm transition-colors ${hasError ? 'border-red-500 text-gray-700' : 'border-gray-300 text-gray-700'} ${!isEnabled ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white cursor-pointer'}`, children: [jsxRuntimeExports.jsx("img", { src: img$g, alt: "", className: "w-4 h-4 opacity-50 flex-shrink-0" }), jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate", children: actionLabel }), isRequired && jsxRuntimeExports.jsx("span", { className: "shrink-0 text-red-500", children: "*" })] }), touched && error.length > 0 && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })) : null, !isReadonly && isOpen && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50", style: { backgroundColor: 'rgba(0,0,0,0.5)' }, onClick: () => { setIsOpen(false); onBlur(); } }), jsxRuntimeExports.jsxs("div", { className: "flex flex-col overflow-hidden", style: {
11874
12209
  position: 'fixed',
11875
12210
  top: modalPos.y,
11876
12211
  left: modalPos.x,
@@ -11899,6 +12234,288 @@ const RegisterLookupWidget = ({ config }) => {
11899
12234
  : translate('common.searchHint', { defaultValue: 'Type and press Enter or click search' }) })) : (jsxRuntimeExports.jsx(ResultsTable, { rows: searchResults, selectedRowKey: pendingRow?.internal_record_id ?? null, onRowClick: setPendingRow, onRowDoubleClick: applySelection })) }), jsxRuntimeExports.jsxs("div", { className: `flex-shrink-0 flex flex-wrap items-center gap-3 px-5 py-3 border-t border-gray-200 ${totalCount !== null ? 'justify-between' : 'justify-end'}`, children: [totalCount !== null && (jsxRuntimeExports.jsx(PaginationFooter, { embedded: true, currentPage: currentPage, totalPages: totalPages, totalCount: totalCount, pageSize: pageSize, onPageChange: (page) => runSearch(searchText, page), onPrev: () => currentPage > 1 && runSearch(searchText, currentPage - 1), onNext: () => currentPage < totalPages && runSearch(searchText, currentPage + 1), translate: translate })), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => pendingRow && applySelection(pendingRow), disabled: !pendingRow, className: "px-4 h-9 text-sm font-medium rounded-[10px] text-white disabled:opacity-40 disabled:cursor-not-allowed flex-shrink-0", style: { backgroundColor: 'var(--owt-color-info, #2563eb)' }, children: selectRecordLabel })] })] })] }))] }));
11900
12235
  };
11901
12236
 
12237
+ const MultiSelectWidget = ({ config }) => {
12238
+ const { value, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
12239
+ const { translate, translateConfig } = useWidgetTranslation();
12240
+ const [isOpen, setIsOpen] = useState(false);
12241
+ const [isListPopupOpen, setIsListPopupOpen] = useState(false);
12242
+ const [searchQuery, setSearchQuery] = useState('');
12243
+ const [dropdownPosition, setDropdownPosition] = useState(null);
12244
+ const [listPopupPosition, setListPopupPosition] = useState(null);
12245
+ const [mounted, setMounted] = useState(false);
12246
+ const containerRef = useRef(null);
12247
+ const triggerRef = useRef(null);
12248
+ const dropdownRef = useRef(null);
12249
+ const listPopupRef = useRef(null);
12250
+ const moreButtonRef = useRef(null);
12251
+ const searchInputRef = useRef(null);
12252
+ const formatConfig = widgetConfig['widget-data-format'];
12253
+ const sortOptions = formatConfig?.sortOptions ?? false;
12254
+ useEffect(() => {
12255
+ setMounted(true);
12256
+ }, []);
12257
+ const updateDropdownPosition = useCallback(() => {
12258
+ const trigger = triggerRef.current;
12259
+ if (!trigger)
12260
+ return;
12261
+ const rect = trigger.getBoundingClientRect();
12262
+ const gap = 4;
12263
+ const spaceBelow = window.innerHeight - rect.bottom - gap;
12264
+ const spaceAbove = rect.top - gap;
12265
+ const openDown = spaceBelow >= 160 || spaceBelow >= spaceAbove;
12266
+ const availableSpace = openDown ? spaceBelow : spaceAbove;
12267
+ const maxHeight = Math.min(320, Math.max(160, availableSpace - 8));
12268
+ setDropdownPosition(openDown
12269
+ ? {
12270
+ top: rect.bottom + gap,
12271
+ left: rect.left,
12272
+ width: rect.width,
12273
+ maxHeight,
12274
+ placement: 'bottom',
12275
+ }
12276
+ : {
12277
+ bottom: window.innerHeight - rect.top + gap,
12278
+ left: rect.left,
12279
+ width: rect.width,
12280
+ maxHeight,
12281
+ placement: 'top',
12282
+ });
12283
+ }, []);
12284
+ const updateListPopupPosition = useCallback(() => {
12285
+ const anchor = moreButtonRef.current;
12286
+ if (!anchor)
12287
+ return;
12288
+ const rect = anchor.getBoundingClientRect();
12289
+ const gap = 4;
12290
+ const spaceBelow = window.innerHeight - rect.bottom - gap;
12291
+ const spaceAbove = rect.top - gap;
12292
+ const openDown = spaceBelow >= 120 || spaceBelow >= spaceAbove;
12293
+ const availableSpace = openDown ? spaceBelow : spaceAbove;
12294
+ const maxHeight = Math.min(280, Math.max(120, availableSpace - 8));
12295
+ setListPopupPosition(openDown
12296
+ ? {
12297
+ top: rect.bottom + gap,
12298
+ left: rect.left,
12299
+ width: Math.max(rect.width, 220),
12300
+ maxHeight,
12301
+ placement: 'bottom',
12302
+ }
12303
+ : {
12304
+ bottom: window.innerHeight - rect.top + gap,
12305
+ left: rect.left,
12306
+ width: Math.max(rect.width, 220),
12307
+ maxHeight,
12308
+ placement: 'top',
12309
+ });
12310
+ }, []);
12311
+ useEffect(() => {
12312
+ if (!isOpen) {
12313
+ setDropdownPosition(null);
12314
+ setSearchQuery('');
12315
+ return;
12316
+ }
12317
+ updateDropdownPosition();
12318
+ const handleResize = () => updateDropdownPosition();
12319
+ window.addEventListener('resize', handleResize);
12320
+ return () => {
12321
+ window.removeEventListener('resize', handleResize);
12322
+ };
12323
+ }, [isOpen, updateDropdownPosition]);
12324
+ useEffect(() => {
12325
+ if (!isListPopupOpen) {
12326
+ setListPopupPosition(null);
12327
+ return;
12328
+ }
12329
+ updateListPopupPosition();
12330
+ const handleResize = () => updateListPopupPosition();
12331
+ window.addEventListener('resize', handleResize);
12332
+ return () => {
12333
+ window.removeEventListener('resize', handleResize);
12334
+ };
12335
+ }, [isListPopupOpen, updateListPopupPosition]);
12336
+ useEffect(() => {
12337
+ if (!isOpen && !isListPopupOpen)
12338
+ return;
12339
+ const handleScroll = (event) => {
12340
+ const target = event.target;
12341
+ if (dropdownRef.current?.contains(target))
12342
+ return;
12343
+ if (listPopupRef.current?.contains(target))
12344
+ return;
12345
+ if (isOpen)
12346
+ setIsOpen(false);
12347
+ if (isListPopupOpen)
12348
+ setIsListPopupOpen(false);
12349
+ };
12350
+ window.addEventListener('scroll', handleScroll, true);
12351
+ return () => window.removeEventListener('scroll', handleScroll, true);
12352
+ }, [isOpen, isListPopupOpen]);
12353
+ useEffect(() => {
12354
+ if (!isOpen && !isListPopupOpen)
12355
+ return;
12356
+ const handleClickOutside = (event) => {
12357
+ const target = event.target;
12358
+ if (isOpen) {
12359
+ if (containerRef.current?.contains(target))
12360
+ return;
12361
+ if (dropdownRef.current?.contains(target))
12362
+ return;
12363
+ setIsOpen(false);
12364
+ }
12365
+ if (isListPopupOpen) {
12366
+ if (listPopupRef.current?.contains(target))
12367
+ return;
12368
+ if (moreButtonRef.current?.contains(target))
12369
+ return;
12370
+ setIsListPopupOpen(false);
12371
+ }
12372
+ };
12373
+ document.addEventListener('mousedown', handleClickOutside);
12374
+ return () => document.removeEventListener('mousedown', handleClickOutside);
12375
+ }, [isOpen, isListPopupOpen]);
12376
+ useEffect(() => {
12377
+ if (isOpen && searchInputRef.current) {
12378
+ searchInputRef.current.focus();
12379
+ }
12380
+ }, [isOpen]);
12381
+ const processedOptions = useMemo(() => {
12382
+ let options = dataSourceOptions.map((opt) => {
12383
+ const rawLabel = String(opt.label ?? opt.value ?? '');
12384
+ return {
12385
+ value: opt.value,
12386
+ label: translateConfig(rawLabel),
12387
+ rawLabel,
12388
+ };
12389
+ });
12390
+ if (sortOptions) {
12391
+ options.sort((a, b) => a.label.localeCompare(b.label));
12392
+ }
12393
+ return options;
12394
+ }, [dataSourceOptions, sortOptions, translateConfig]);
12395
+ const filteredOptions = useMemo(() => {
12396
+ if (!searchQuery.trim())
12397
+ return processedOptions;
12398
+ const q = searchQuery.trim().toLowerCase();
12399
+ return processedOptions.filter((opt) => opt.label.toLowerCase().includes(q) ||
12400
+ opt.rawLabel.toLowerCase().includes(q));
12401
+ }, [processedOptions, searchQuery]);
12402
+ const selectedValues = useMemo(() => {
12403
+ if (value === null || value === undefined)
12404
+ return [];
12405
+ if (Array.isArray(value))
12406
+ return value;
12407
+ return [value];
12408
+ }, [value]);
12409
+ const allFilteredSelected = useMemo(() => {
12410
+ if (filteredOptions.length === 0)
12411
+ return false;
12412
+ return filteredOptions.every((opt) => selectedValues.includes(opt.value));
12413
+ }, [filteredOptions, selectedValues]);
12414
+ const handleToggle = useCallback((optionValue, checked) => {
12415
+ if (checked) {
12416
+ onChange([...selectedValues, optionValue]);
12417
+ }
12418
+ else {
12419
+ onChange(selectedValues.filter((v) => v !== optionValue));
12420
+ }
12421
+ }, [selectedValues, onChange]);
12422
+ const handleSelectAll = useCallback(() => {
12423
+ const filteredVals = filteredOptions.map((o) => o.value);
12424
+ const merged = Array.from(new Set([...selectedValues, ...filteredVals]));
12425
+ onChange(merged);
12426
+ }, [filteredOptions, selectedValues, onChange]);
12427
+ const handleClearAll = useCallback(() => {
12428
+ onChange([]);
12429
+ }, [onChange]);
12430
+ const selectedLabels = useMemo(() => {
12431
+ return selectedValues.map((val) => {
12432
+ const opt = processedOptions.find((o) => o.value === val);
12433
+ return opt ? opt.label : translateConfig(String(val));
12434
+ });
12435
+ }, [selectedValues, processedOptions, translateConfig]);
12436
+ const fullSelectionText = selectedLabels.join(', ');
12437
+ const visibleLabels = selectedLabels.slice(0, 5);
12438
+ const overflowCount = Math.max(0, selectedLabels.length - 10);
12439
+ const disabled = !isEnabled || loading || widgetConfig['widget-readonly'];
12440
+ const renderSelectedLabels = (options) => {
12441
+ if (selectedLabels.length === 0)
12442
+ return null;
12443
+ const readonly = options?.readonly ?? false;
12444
+ return (jsxRuntimeExports.jsxs("div", { className: `flex flex-wrap gap-1 ${readonly ? '' : 'mt-1.5'}`, children: [visibleLabels.map((label, index) => (jsxRuntimeExports.jsxs("span", { className: "inline-flex max-w-full items-center gap-1 rounded-md bg-blue-50 px-2 py-0.5 text-xs text-blue-800", title: label, children: [jsxRuntimeExports.jsx("span", { className: "truncate", children: label }), !readonly && !disabled && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleToggle(selectedValues[index], false), className: "shrink-0 text-blue-600 hover:text-blue-900 focus:outline-none", "aria-label": translate('common.removeItem', {
12445
+ label,
12446
+ defaultValue: `Remove ${label}`,
12447
+ }), children: "\u00D7" }))] }, `${selectedValues[index]}-${label}`))), overflowCount > 0 && (jsxRuntimeExports.jsx("button", { ref: moreButtonRef, type: "button", onClick: () => setIsListPopupOpen((prev) => !prev), className: "inline-flex items-center rounded-md bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-1 focus:ring-blue-500", children: translate('common.moreSelected', {
12448
+ count: overflowCount,
12449
+ defaultValue: `+${overflowCount} more`,
12450
+ }) }))] }));
12451
+ };
12452
+ const listPopupPanel = isListPopupOpen && listPopupPosition && mounted ? (jsxRuntimeExports.jsxs("div", { ref: listPopupRef, className: "fixed z-[201] bg-white border border-gray-300 shadow-lg", style: {
12453
+ ...(listPopupPosition.placement === 'bottom'
12454
+ ? { top: listPopupPosition.top }
12455
+ : { bottom: listPopupPosition.bottom }),
12456
+ left: listPopupPosition.left,
12457
+ width: listPopupPosition.width,
12458
+ maxWidth: '320px',
12459
+ maxHeight: listPopupPosition.maxHeight,
12460
+ borderRadius: '10px',
12461
+ display: 'flex',
12462
+ flexDirection: 'column',
12463
+ }, children: [jsxRuntimeExports.jsx("div", { className: "px-3 py-2 text-xs font-semibold text-gray-500 shrink-0", style: { borderBottom: '1px solid #e5e7eb' }, children: translate('common.allSelected', {
12464
+ count: selectedLabels.length,
12465
+ defaultValue: `All selected (${selectedLabels.length})`,
12466
+ }) }), jsxRuntimeExports.jsx("div", { className: "overflow-y-auto flex-1 min-h-0 py-1 overscroll-contain", children: selectedLabels.map((label, index) => (jsxRuntimeExports.jsx("div", { className: "px-3 py-1.5 text-sm text-gray-700", title: label, children: label }, `${selectedValues[index]}-${label}`))) })] })) : null;
12467
+ if (widgetConfig['widget-readonly']) {
12468
+ const fieldLabel = widgetConfig['widget-label'];
12469
+ return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] MultiSelectDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [fieldLabel && (jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", label: fieldLabel })), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [selectedLabels.length === 0 ? (jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", children: "-" })) : (renderSelectedLabels({ readonly: true })), mounted && listPopupPanel && typeof document !== 'undefined'
12470
+ ? createPortal(listPopupPanel, document.body)
12471
+ : null] })] }));
12472
+ }
12473
+ const optionsMaxHeight = dropdownPosition
12474
+ ? Math.min(280, dropdownPosition.maxHeight - 100)
12475
+ : 280;
12476
+ const dropdownPanel = isOpen && dropdownPosition && mounted ? (jsxRuntimeExports.jsxs("div", { ref: dropdownRef, className: "fixed z-[200] bg-white border border-gray-300 shadow-lg", style: {
12477
+ ...(dropdownPosition.placement === 'bottom'
12478
+ ? { top: dropdownPosition.top }
12479
+ : { bottom: dropdownPosition.bottom }),
12480
+ left: dropdownPosition.left,
12481
+ width: dropdownPosition.width,
12482
+ maxWidth: '280px',
12483
+ maxHeight: dropdownPosition.maxHeight,
12484
+ borderRadius: '10px',
12485
+ display: 'flex',
12486
+ flexDirection: 'column',
12487
+ }, children: [jsxRuntimeExports.jsx("div", { className: "px-3 pt-2 pb-1 shrink-0", style: { borderBottom: '1px solid #e5e7eb' }, children: jsxRuntimeExports.jsx("input", { ref: searchInputRef, type: "text", placeholder: translate('common.searchPlaceholder', { defaultValue: 'Search...' }), value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "w-full h-[28px] px-2 text-sm border border-gray-300 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500", style: { borderRadius: '6px' } }) }), jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-3 py-1 shrink-0", style: { borderBottom: '1px solid #e5e7eb' }, children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: allFilteredSelected ? () => {
12488
+ const filteredVals = new Set(filteredOptions.map((o) => o.value));
12489
+ onChange(selectedValues.filter((v) => !filteredVals.has(v)));
12490
+ } : handleSelectAll, className: "text-xs font-medium text-blue-600 hover:text-blue-800 focus:outline-none", children: allFilteredSelected
12491
+ ? translate('common.deselectAll', { defaultValue: 'Deselect All' })
12492
+ : translate('common.selectAll', { defaultValue: 'Select All' }) }), selectedValues.length > 0 && (jsxRuntimeExports.jsx("button", { type: "button", onClick: handleClearAll, className: "text-xs font-medium text-red-500 hover:text-red-700 focus:outline-none", children: translate('common.clearAll', { defaultValue: 'Clear All' }) }))] }), jsxRuntimeExports.jsx("div", { className: "overflow-y-auto flex-1 min-h-0 py-1 overscroll-contain", style: { maxHeight: `${Math.max(80, optionsMaxHeight)}px` }, children: loading ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 px-3 py-2", children: translate('common.loading') })) : filteredOptions.length === 0 ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-400 px-3 py-2", children: translate('common.noOptionsFound', { defaultValue: 'No options found' }) })) : (filteredOptions.map((option) => {
12493
+ const isChecked = selectedValues.includes(option.value);
12494
+ return (jsxRuntimeExports.jsxs("label", { className: `flex items-center gap-2 px-3 py-1 cursor-pointer hover:bg-blue-50 ${isChecked ? 'bg-blue-50/60' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: isChecked, onChange: (e) => handleToggle(option.value, e.target.checked), className: "h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700 leading-normal select-none", children: option.label })] }, option.value));
12495
+ })) })] })) : null;
12496
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: widgetConfig['widget-label'] ?? '', required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", ref: containerRef, children: [jsxRuntimeExports.jsxs("button", { ref: triggerRef, type: "button", onClick: () => {
12497
+ if (!disabled)
12498
+ setIsOpen((prev) => !prev);
12499
+ }, onBlur: () => {
12500
+ if (!isOpen)
12501
+ onBlur();
12502
+ }, disabled: disabled, className: `w-full sm:w-[280px] max-w-full h-[30px] px-3 border shadow-sm text-left flex items-center justify-between gap-2 ${(touched && error.length > 0) ||
12503
+ (widgetConfig['widget-required'] && selectedValues.length === 0)
12504
+ ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
12505
+ : 'border-gray-300'} ${disabled ? 'bg-gray-100 cursor-not-allowed' : 'bg-white cursor-pointer'} focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500`, style: { borderRadius: '10px' }, title: selectedValues.length > 0
12506
+ ? fullSelectionText
12507
+ : translateConfig(widgetConfig['widget-data-tooltip']), children: [jsxRuntimeExports.jsx("span", { className: `truncate text-sm ${selectedValues.length === 0 ? 'text-gray-400' : 'text-gray-900'}`, children: selectedLabels.length === 0
12508
+ ? translate('common.select', { defaultValue: 'Select...' })
12509
+ : translate('common.selectedCount', {
12510
+ count: selectedLabels.length,
12511
+ defaultValue: `${selectedLabels.length} selected`,
12512
+ }) }), jsxRuntimeExports.jsx("svg", { className: `w-4 h-4 flex-shrink-0 text-gray-500 transition-transform ${isOpen ? 'rotate-180' : ''}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })] }), mounted && dropdownPanel && typeof document !== 'undefined'
12513
+ ? createPortal(dropdownPanel, document.body)
12514
+ : null, selectedLabels.length > 0 && renderSelectedLabels(), mounted && listPopupPanel && typeof document !== 'undefined'
12515
+ ? createPortal(listPopupPanel, document.body)
12516
+ : null, touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })), loading && (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 mt-1", children: translate('common.loadingOptions') }))] })] }) }));
12517
+ };
12518
+
11902
12519
  /**
11903
12520
  * Register all default/generic widgets
11904
12521
  * This is called automatically when the package is imported
@@ -11948,6 +12565,8 @@ const registerDefaultWidgets = () => {
11948
12565
  widgetRegistry.register({ widget: 'id-authentication', component: IdAuthenticationWidget });
11949
12566
  // Register lookup widget — searchable popup to select a record from any register
11950
12567
  widgetRegistry.register({ widget: 'register-lookup', component: RegisterLookupWidget });
12568
+ // Multi-select widget — searchable dropdown with checkbox-style options, select all, and clear all
12569
+ widgetRegistry.register({ widget: 'multi-select', component: MultiSelectWidget });
11951
12570
  };
11952
12571
  // Auto-register on import
11953
12572
  registerDefaultWidgets();
@@ -12011,6 +12630,14 @@ var enTranslations = {
12011
12630
  "common.sectionModified": "Modified and not saved",
12012
12631
  "common.supportedDocuments": "Supported Documents",
12013
12632
  "common.searchPlaceholder": "Search...",
12633
+ "common.selectAll": "Select All",
12634
+ "common.deselectAll": "Deselect All",
12635
+ "common.clearAll": "Clear All",
12636
+ "common.noOptionsFound": "No options found",
12637
+ "common.allSelected": "All selected ({{count}})",
12638
+ "common.moreSelected": "+{{count}} more",
12639
+ "common.selectedCount": "{{count}} selected",
12640
+ "common.removeItem": "Remove {{label}}",
12014
12641
  "common.selectAction": "Select {{label}}",
12015
12642
  "common.selectTitle": "Select {{label}}",
12016
12643
  "common.change": "Change",
@@ -12263,13 +12890,10 @@ const translateWidgetConfig = (widgetConfig, translate) => {
12263
12890
  ...dataSource,
12264
12891
  options: dataSource.options.map((option) => {
12265
12892
  if (option.label && typeof option.label === 'string') {
12266
- const optionLabel = option.label;
12267
- if (isTranslationKey(optionLabel)) {
12268
- return {
12269
- ...option,
12270
- label: translate(optionLabel, { defaultValue: optionLabel }),
12271
- };
12272
- }
12893
+ return {
12894
+ ...option,
12895
+ label: translate(option.label, { defaultValue: option.label }),
12896
+ };
12273
12897
  }
12274
12898
  return option;
12275
12899
  }),
@@ -12322,5 +12946,5 @@ const translateUISchema = (schema, translate) => {
12322
12946
  };
12323
12947
  };
12324
12948
 
12325
- export { ArrayWidget, BooleanWidget, CheckboxWidget, CurrencyInputWidget, DateInputWidget, DateTimeInputWidget, DialogTableWidget, DisplayWidget, FileInputWidget, GEO_LEVEL_CLEARED, HeaderSectionWidget, IdAuthenticationWidget, IterableAccordionWidget, JSONEditorPanel, NumberInputWidget, PanelRenderer, PhoneInputWidget, ProfileWidget, PropertyEditor, RadioWidget, RegisterLookupWidget, ScoresDisplayWidget, SectionBuilder, SectionRenderer, SectionTree, SectionsContainer, SelectWidget, SimpleTableWidget, TableWidget, TextAreaWidget, TextInputWidget, VisualBuilderPanel, WidgetEventBus, WidgetProvider, WidgetRenderer, applyCaseControl, applyDecimalPrecision, applyMask, applySharedGeoHierarchyToValues, createWidgetStore, createZodSchema, defaultTheme, evaluateCondition, filterByCharacterType, formatCurrency, formatDate, formatNumber, formatPhone, formatValue, geoHierarchyBuilder, getApiDataSource, getFormattedNumberLength, getSchemaDataSource, getStaticDataSource, getValueByPath, getWidgetValue, initI18n, isAllowedKey, isUpstreamGeoAncestor, normalizeNumericDefault, parseDataPath, parseNumber, registerDefaultWidgets, registerGeoWidgetParent, removeMask, resetAll, resetAndSeedGeoHierarchyFromValues, resetWidget, resolveGeoWidgetLevelValue, resolveTheme, resolveWidgetIdValue, seedGeoHierarchyFromValues, setDataSource, setError, setLoading, setTouched, setValue, setValueByPath, setValues, setWidgetValue, shouldEnableWidget, shouldShowWidget, transformDataSourceOptions, translatePanelConfig, translateUISchema, translateWidgetConfig, unregisterGeoWidgetParent, useBaseWidget, useGeoWidgetCascade, useWidgetCascade, useWidgetContext, useWidgetEventBus, useWidgetTheme, useWidgetTranslation, validateNumericValue, validateWidget, widgetRegistry };
12949
+ export { ArrayWidget, BooleanWidget, CheckboxWidget, CurrencyInputWidget, DateInputWidget, DateTimeInputWidget, DialogTableWidget, DisplayWidget, FileInputWidget, GEO_LEVEL_CLEARED, HeaderSectionWidget, IdAuthenticationWidget, IterableAccordionWidget, JSONEditorPanel, MultiSelectWidget, NumberInputWidget, PanelRenderer, PhoneInputWidget, ProfileWidget, PropertyEditor, RadioWidget, RegisterLookupWidget, ScoresDisplayWidget, SectionBuilder, SectionRenderer, SectionTree, SectionsContainer, SelectWidget, SimpleTableWidget, TableWidget, TextAreaWidget, TextInputWidget, VisualBuilderPanel, WidgetEventBus, WidgetProvider, WidgetRenderer, applyCaseControl, applyDecimalPrecision, applyMask, applySharedGeoHierarchyToValues, createWidgetStore, createZodSchema, defaultTheme, evaluateCondition, evaluateWidgetConditions, filterByCharacterType, formatCurrency, formatDate, formatNumber, formatPhone, formatValue, geoHierarchyBuilder, getApiDataSource, getCachedApiDataSource, getFormattedNumberLength, getGeoDescendantWidgetIds, getGeoGroupId, getSchemaDataSource, getStaticDataSource, getValueByPath, getWidgetValue, hasVisibilityRules, initI18n, isAllowedKey, isUpstreamGeoAncestor, normalizeNumericDefault, normalizeOptionRules, parseDataPath, parseNumber, registerDefaultWidgets, registerGeoWidgetParent, removeMask, resetAll, resetAndSeedGeoHierarchyFromValues, resetWidget, resolveGeoWidgetLevelLabel, resolveGeoWidgetLevelValue, resolveTheme, resolveWidgetIdValue, seedGeoHierarchyFromValues, setDataSource, setError, setLoading, setTouched, setValue, setValueByPath, setValues, setWidgetValue, shouldEnableWidget, shouldRequireWidget, shouldShowWidget, transformDataSourceOptions, translatePanelConfig, translateUISchema, translateWidgetConfig, unregisterGeoWidgetParent, useBaseWidget, useGeoWidgetCascade, useWidgetCascade, useWidgetContext, useWidgetEventBus, useWidgetTheme, useWidgetTranslation, validateNumericValue, validateWidget, widgetRegistry };
12326
12950
  //# sourceMappingURL=index.esm.js.map