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