@openg2p/registry-widgets 1.1.2-dev.0 → 1.1.2-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -174,15 +174,33 @@ const parseDataPath = (dataPath) => {
174
174
  /**
175
175
  * Get value from widget state using data path
176
176
  */
177
+ /**
178
+ * Resolve a widget-id reference in Redux values.
179
+ * Supports namespaced ids (e.g. "rv-section-0__region_code" when ref is "region_code").
180
+ */
181
+ const resolveWidgetIdValue = (values, ref) => {
182
+ if (!ref) {
183
+ return undefined;
184
+ }
185
+ if (ref.includes('.')) {
186
+ return getValueByPath(values, ref);
187
+ }
188
+ if (Object.prototype.hasOwnProperty.call(values, ref)) {
189
+ return values[ref];
190
+ }
191
+ const suffix = `__${ref}`;
192
+ for (const [key, val] of Object.entries(values)) {
193
+ if (key.endsWith(suffix)) {
194
+ return val;
195
+ }
196
+ }
197
+ return undefined;
198
+ };
177
199
  const getWidgetValue = (values, dataPath, widgetId) => {
178
200
  if (!dataPath) {
179
201
  // Fallback to widget-id if no data path
180
202
  return values[widgetId];
181
203
  }
182
- if (widgetId == "user-profile") {
183
- console.log('values', values);
184
- console.log('dataPath', dataPath);
185
- }
186
204
  if (typeof dataPath === 'string') {
187
205
  return getValueByPath(values, dataPath);
188
206
  }
@@ -1046,24 +1064,11 @@ const getApiDataSource = async (dataSource, allValues, dataSourceRequestHandler,
1046
1064
  // dependsOn can be either a data path (e.g., "person.address") or a widget-id
1047
1065
  let depValue = null;
1048
1066
  if (dataSource.dependsOn) {
1049
- // First try as data path
1050
- depValue = getValueByPath(allValues, dataSource.dependsOn);
1051
- // If not found and doesn't contain dots, try as widget-id
1052
- if ((depValue === null || depValue === undefined) && !dataSource.dependsOn.includes('.')) {
1053
- depValue = allValues[dataSource.dependsOn];
1054
- // Smart resolution: If not found at top level, try to find the dependency in the same nested object
1055
- // by looking for other keys in allValues that might contain the dependency.
1056
- // We look for objects that contain both the current widget's path (if we can guess it) and the dependency.
1057
- // But since we don't know the current widget's path here, we search for any object that has this dependency key.
1058
- if (depValue === null || depValue === undefined || depValue === '') {
1059
- for (const val of Object.values(allValues)) {
1060
- if (val && typeof val === 'object' && !Array.isArray(val) && dataSource.dependsOn in val) {
1061
- depValue = val[dataSource.dependsOn];
1062
- if (depValue !== null && depValue !== undefined && depValue !== '')
1063
- break;
1064
- }
1065
- }
1066
- }
1067
+ if (dataSource.dependsOn.includes('.')) {
1068
+ depValue = getValueByPath(allValues, dataSource.dependsOn);
1069
+ }
1070
+ else {
1071
+ depValue = resolveWidgetIdValue(allValues, dataSource.dependsOn);
1067
1072
  }
1068
1073
  if (depValue === null || depValue === undefined || depValue === '') {
1069
1074
  // If dependency is empty, return empty array
@@ -1932,6 +1937,247 @@ const WidgetProvider = ({ store, dataSourceRequestHandler, schemaData, translate
1932
1937
  return content;
1933
1938
  };
1934
1939
 
1940
+ /**
1941
+ * Geo Hierarchy Builder
1942
+ * Manages geo hierarchy state and builds hierarchy JSON structure
1943
+ */
1944
+ function extractLevelValueFromStored(value, geoConfig) {
1945
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1946
+ return value;
1947
+ }
1948
+ const hierarchy = value.hierarchy || value.geo_code_hierarchy_json?.hierarchy;
1949
+ if (Array.isArray(hierarchy)) {
1950
+ const levelData = hierarchy.find((l) => l.level === geoConfig.level);
1951
+ if (levelData) {
1952
+ return levelData.level_value_id;
1953
+ }
1954
+ // Level absent from hierarchy (e.g. cleared by upstream cascade) — do not use lowest-level fallback
1955
+ return undefined;
1956
+ }
1957
+ if ('geo_lowest_level_value_id' in value) {
1958
+ return value.geo_lowest_level_value_id;
1959
+ }
1960
+ if ('lowest_level_value_id' in value) {
1961
+ return value.lowest_level_value_id;
1962
+ }
1963
+ if (value.geo_code_hierarchy_json?.lowest_level_value_id) {
1964
+ return value.geo_code_hierarchy_json.lowest_level_value_id;
1965
+ }
1966
+ if (value.geo_code_hierarchy_json?.geo_lowest_level_value_id) {
1967
+ return value.geo_code_hierarchy_json.geo_lowest_level_value_id;
1968
+ }
1969
+ return undefined;
1970
+ }
1971
+ /**
1972
+ * Resolve the display value for a geo level widget.
1973
+ * When widgetId is explicitly set in Redux (including cleared undefined/null), do not
1974
+ * fall back to shared hierarchy dataPath — that stale path was keeping grandchildren visible.
1975
+ */
1976
+ function resolveGeoWidgetLevelValue(values, widgetId, dataPath, geoConfig) {
1977
+ if (Object.prototype.hasOwnProperty.call(values, widgetId)) {
1978
+ let value = values[widgetId];
1979
+ if (value === undefined || value === null || value === '') {
1980
+ return value;
1981
+ }
1982
+ if (typeof value === 'object' && !Array.isArray(value)) {
1983
+ return extractLevelValueFromStored(value, geoConfig);
1984
+ }
1985
+ return value;
1986
+ }
1987
+ if (!dataPath) {
1988
+ return undefined;
1989
+ }
1990
+ let value = getWidgetValue(values, dataPath, widgetId);
1991
+ if (value !== null && value !== undefined && typeof value === 'object' && !Array.isArray(value)) {
1992
+ value = extractLevelValueFromStored(value, geoConfig);
1993
+ }
1994
+ return value;
1995
+ }
1996
+ /**
1997
+ * Write the in-memory geo hierarchy builder state into Redux at the shared dataPath.
1998
+ */
1999
+ function applySharedGeoHierarchyToValues(baseValues, groupId, dataPath, widgetId) {
2000
+ const hierarchyJson = geoHierarchyBuilder.buildHierarchyJson(groupId);
2001
+ if (!dataPath || typeof dataPath !== 'string') {
2002
+ return baseValues;
2003
+ }
2004
+ const inner = hierarchyJson?.geo_code_hierarchy_json;
2005
+ const lowestId = hierarchyJson?.geo_lowest_level_value_id;
2006
+ if (dataPath.endsWith('.geo_code_hierarchy_json')) {
2007
+ const prefix = dataPath.substring(0, dataPath.lastIndexOf('.'));
2008
+ let finalUpdatedValues = setWidgetValue(baseValues, dataPath, widgetId, inner);
2009
+ finalUpdatedValues = setWidgetValue(finalUpdatedValues, `${prefix}.geo_lowest_level_value_id`, widgetId, lowestId);
2010
+ return finalUpdatedValues;
2011
+ }
2012
+ return setWidgetValue(baseValues, dataPath, widgetId, inner);
2013
+ }
2014
+ class GeoHierarchyBuilder {
2015
+ constructor() {
2016
+ this.hierarchies = new Map();
2017
+ }
2018
+ /**
2019
+ * Get or create hierarchy state for a group
2020
+ */
2021
+ getHierarchy(groupId = 'default') {
2022
+ if (!this.hierarchies.has(groupId)) {
2023
+ this.hierarchies.set(groupId, {
2024
+ levels: new Map(),
2025
+ order: [],
2026
+ });
2027
+ }
2028
+ return this.hierarchies.get(groupId);
2029
+ }
2030
+ /**
2031
+ * Add a level to the hierarchy
2032
+ */
2033
+ addLevel(level, level_value_id, level_value_mnemonic, groupId = 'default') {
2034
+ const hierarchy = this.getHierarchy(groupId);
2035
+ // If level already exists, remove it and everything after it
2036
+ const existingIndex = hierarchy.order.indexOf(level);
2037
+ if (existingIndex >= 0) {
2038
+ // Remove this level and all subsequent levels
2039
+ const levelsToRemove = hierarchy.order.slice(existingIndex);
2040
+ levelsToRemove.forEach((l) => {
2041
+ hierarchy.levels.delete(l);
2042
+ hierarchy.order = hierarchy.order.filter((o) => o !== l);
2043
+ });
2044
+ }
2045
+ // Add new level
2046
+ hierarchy.levels.set(level, {
2047
+ level,
2048
+ level_value_id,
2049
+ level_value_mnemonic,
2050
+ });
2051
+ hierarchy.order.push(level);
2052
+ }
2053
+ /**
2054
+ * Remove a level and all levels below it
2055
+ */
2056
+ removeLevelAndBelow(level, groupId = 'default') {
2057
+ const hierarchy = this.getHierarchy(groupId);
2058
+ const index = hierarchy.order.indexOf(level);
2059
+ if (index >= 0) {
2060
+ // Remove this level and all subsequent levels
2061
+ const levelsToRemove = hierarchy.order.slice(index);
2062
+ levelsToRemove.forEach((l) => {
2063
+ hierarchy.levels.delete(l);
2064
+ hierarchy.order = hierarchy.order.filter((o) => o !== l);
2065
+ });
2066
+ }
2067
+ }
2068
+ /**
2069
+ * Build hierarchy JSON structure
2070
+ */
2071
+ buildHierarchyJson(groupId = 'default') {
2072
+ const hierarchy = this.getHierarchy(groupId);
2073
+ if (hierarchy.order.length === 0) {
2074
+ return null;
2075
+ }
2076
+ const hierarchyArray = hierarchy.order.map((level) => {
2077
+ const data = hierarchy.levels.get(level);
2078
+ return {
2079
+ level: data.level,
2080
+ level_value_id: data.level_value_id,
2081
+ level_value_mnemonic: data.level_value_mnemonic,
2082
+ };
2083
+ });
2084
+ const lowestLevel = hierarchy.order[hierarchy.order.length - 1];
2085
+ const lowestLevelData = hierarchy.levels.get(lowestLevel);
2086
+ return {
2087
+ geo_lowest_level_value_id: lowestLevelData.level_value_id,
2088
+ geo_code_hierarchy_json: {
2089
+ hierarchy: hierarchyArray,
2090
+ lowest_level_value_id: lowestLevelData.level_value_id,
2091
+ },
2092
+ };
2093
+ }
2094
+ /**
2095
+ * Clear hierarchy for a group
2096
+ */
2097
+ clear(groupId = 'default') {
2098
+ this.hierarchies.delete(groupId);
2099
+ }
2100
+ /**
2101
+ * Clear all hierarchies
2102
+ */
2103
+ clearAll() {
2104
+ this.hierarchies.clear();
2105
+ }
2106
+ /**
2107
+ * Get current levels for a group
2108
+ */
2109
+ getLevels(groupId = 'default') {
2110
+ const hierarchy = this.getHierarchy(groupId);
2111
+ return hierarchy.order.map((level) => hierarchy.levels.get(level));
2112
+ }
2113
+ }
2114
+ // Singleton instance
2115
+ const geoHierarchyBuilder = new GeoHierarchyBuilder();
2116
+ /** Sentinel written to Redux when a geo level is cleared by cascade (distinct from "never set"). */
2117
+ const GEO_LEVEL_CLEARED = null;
2118
+ const geoWidgetParentRegistry = new Map();
2119
+ function registerGeoWidgetParent(widgetId, parentWidgetId) {
2120
+ geoWidgetParentRegistry.set(widgetId, parentWidgetId || null);
2121
+ }
2122
+ function unregisterGeoWidgetParent(widgetId) {
2123
+ geoWidgetParentRegistry.delete(widgetId);
2124
+ }
2125
+ /** True when changedWidgetId is any upstream geo parent of widgetId (not only immediate parent). */
2126
+ function isUpstreamGeoAncestor(changedWidgetId, widgetId, immediateParentWidgetId) {
2127
+ if (changedWidgetId === widgetId) {
2128
+ return false;
2129
+ }
2130
+ let cursor = immediateParentWidgetId;
2131
+ while (cursor) {
2132
+ if (cursor === changedWidgetId) {
2133
+ return true;
2134
+ }
2135
+ cursor = geoWidgetParentRegistry.get(cursor) ?? null;
2136
+ }
2137
+ return false;
2138
+ }
2139
+ function readStoredHierarchyLevels(values, dataPath, widgetId) {
2140
+ const stored = getWidgetValue(values, dataPath, widgetId);
2141
+ const hierarchy = stored?.hierarchy || stored?.geo_code_hierarchy_json?.hierarchy;
2142
+ if (!Array.isArray(hierarchy)) {
2143
+ return [];
2144
+ }
2145
+ return hierarchy.filter((entry) => entry?.level && entry.level_value_id);
2146
+ }
2147
+ function builderMatchesStored(groupId, storedLevels) {
2148
+ const builderLevels = geoHierarchyBuilder.getLevels(groupId);
2149
+ if (builderLevels.length !== storedLevels.length) {
2150
+ return false;
2151
+ }
2152
+ return storedLevels.every((stored, index) => {
2153
+ const built = builderLevels[index];
2154
+ return (built.level === stored.level &&
2155
+ String(built.level_value_id) === String(stored.level_value_id));
2156
+ });
2157
+ }
2158
+ /** Seed in-memory builder from persisted hierarchy JSON (edit-mode rehydration). */
2159
+ function seedGeoHierarchyFromValues(values, dataPath, widgetId, groupId) {
2160
+ if (!dataPath || typeof dataPath !== 'string') {
2161
+ return;
2162
+ }
2163
+ const storedLevels = readStoredHierarchyLevels(values, dataPath, widgetId);
2164
+ if (storedLevels.length === 0) {
2165
+ return;
2166
+ }
2167
+ if (builderMatchesStored(groupId, storedLevels)) {
2168
+ return;
2169
+ }
2170
+ geoHierarchyBuilder.clear(groupId);
2171
+ storedLevels.forEach((entry) => {
2172
+ geoHierarchyBuilder.addLevel(entry.level, String(entry.level_value_id), entry.level_value_mnemonic || String(entry.level_value_id), groupId);
2173
+ });
2174
+ }
2175
+ /** Force-clear builder for a group, then seed from Redux/schema values (e.g. after Cancel). */
2176
+ function resetAndSeedGeoHierarchyFromValues(values, dataPath, widgetId, groupId) {
2177
+ geoHierarchyBuilder.clear(groupId);
2178
+ seedGeoHierarchyFromValues(values, dataPath, widgetId, groupId);
2179
+ }
2180
+
1935
2181
  // Define stable empty arrays to avoid selector reference issues
1936
2182
  const EMPTY_ERRORS = [];
1937
2183
  const EMPTY_DATA_SOURCE$1 = [];
@@ -2021,6 +2267,17 @@ const useBaseWidget = (options) => {
2021
2267
  if (isLayoutWidget) {
2022
2268
  return undefined; // Layout widgets don't have values
2023
2269
  }
2270
+ const geoConfig = config['widget-geo-config'];
2271
+ if (geoConfig) {
2272
+ const value = resolveGeoWidgetLevelValue(values, widgetId, config['widget-data-path'], geoConfig);
2273
+ if (userHasSetValueRef.current) {
2274
+ return value;
2275
+ }
2276
+ if (value === null) {
2277
+ return null;
2278
+ }
2279
+ return value !== undefined ? value : config['widget-data-default'];
2280
+ }
2024
2281
  // Try to get value from widgetId first (this should have the actual selected value)
2025
2282
  // For geo widgets with dataPath, widgetId stores the actual ID, while dataPath stores the hierarchy object
2026
2283
  let value = values[widgetId];
@@ -2230,6 +2487,14 @@ const useBaseWidget = (options) => {
2230
2487
  // Track readonly state explicitly to detect changes
2231
2488
  // Use JSON.stringify to create a stable reference for the dependency array
2232
2489
  const isReadonly = config['widget-readonly'] ?? false;
2490
+ // Leaving edit mode (Cancel): allow mirror/rehydration on next Edit
2491
+ React.useEffect(() => {
2492
+ if (config['widget-readonly']) {
2493
+ userHasSetValueRef.current = false;
2494
+ lastMirroredValueRef.current = null;
2495
+ lastDispatchedValueRef.current = null;
2496
+ }
2497
+ }, [config['widget-readonly']]);
2233
2498
  const dataSource = config['widget-data-source'];
2234
2499
  const geoConfig = config['widget-geo-config'];
2235
2500
  // Use ref to store handler to avoid stale closures
@@ -2434,113 +2699,6 @@ const useWidgetCascade = (options) => {
2434
2699
  }, [cascadeConfig, eventBus, dataSource, widgetId, dispatch]);
2435
2700
  };
2436
2701
 
2437
- /**
2438
- * Geo Hierarchy Builder
2439
- * Manages geo hierarchy state and builds hierarchy JSON structure
2440
- */
2441
- class GeoHierarchyBuilder {
2442
- constructor() {
2443
- this.hierarchies = new Map();
2444
- }
2445
- /**
2446
- * Get or create hierarchy state for a group
2447
- */
2448
- getHierarchy(groupId = 'default') {
2449
- if (!this.hierarchies.has(groupId)) {
2450
- this.hierarchies.set(groupId, {
2451
- levels: new Map(),
2452
- order: [],
2453
- });
2454
- }
2455
- return this.hierarchies.get(groupId);
2456
- }
2457
- /**
2458
- * Add a level to the hierarchy
2459
- */
2460
- addLevel(level, level_value_id, level_value_mnemonic, groupId = 'default') {
2461
- const hierarchy = this.getHierarchy(groupId);
2462
- // If level already exists, remove it and everything after it
2463
- const existingIndex = hierarchy.order.indexOf(level);
2464
- if (existingIndex >= 0) {
2465
- // Remove this level and all subsequent levels
2466
- const levelsToRemove = hierarchy.order.slice(existingIndex);
2467
- levelsToRemove.forEach((l) => {
2468
- hierarchy.levels.delete(l);
2469
- hierarchy.order = hierarchy.order.filter((o) => o !== l);
2470
- });
2471
- }
2472
- // Add new level
2473
- hierarchy.levels.set(level, {
2474
- level,
2475
- level_value_id,
2476
- level_value_mnemonic,
2477
- });
2478
- hierarchy.order.push(level);
2479
- }
2480
- /**
2481
- * Remove a level and all levels below it
2482
- */
2483
- removeLevelAndBelow(level, groupId = 'default') {
2484
- const hierarchy = this.getHierarchy(groupId);
2485
- const index = hierarchy.order.indexOf(level);
2486
- if (index >= 0) {
2487
- // Remove this level and all subsequent levels
2488
- const levelsToRemove = hierarchy.order.slice(index);
2489
- levelsToRemove.forEach((l) => {
2490
- hierarchy.levels.delete(l);
2491
- hierarchy.order = hierarchy.order.filter((o) => o !== l);
2492
- });
2493
- }
2494
- }
2495
- /**
2496
- * Build hierarchy JSON structure
2497
- */
2498
- buildHierarchyJson(groupId = 'default') {
2499
- const hierarchy = this.getHierarchy(groupId);
2500
- if (hierarchy.order.length === 0) {
2501
- return null;
2502
- }
2503
- const hierarchyArray = hierarchy.order.map((level) => {
2504
- const data = hierarchy.levels.get(level);
2505
- return {
2506
- level: data.level,
2507
- level_value_id: data.level_value_id,
2508
- level_value_mnemonic: data.level_value_mnemonic,
2509
- };
2510
- });
2511
- const lowestLevel = hierarchy.order[hierarchy.order.length - 1];
2512
- const lowestLevelData = hierarchy.levels.get(lowestLevel);
2513
- return {
2514
- geo_lowest_level_value_id: lowestLevelData.level_value_id,
2515
- geo_code_hierarchy_json: {
2516
- hierarchy: hierarchyArray,
2517
- lowest_level_value_id: lowestLevelData.level_value_id,
2518
- },
2519
- };
2520
- }
2521
- /**
2522
- * Clear hierarchy for a group
2523
- */
2524
- clear(groupId = 'default') {
2525
- this.hierarchies.delete(groupId);
2526
- }
2527
- /**
2528
- * Clear all hierarchies
2529
- */
2530
- clearAll() {
2531
- this.hierarchies.clear();
2532
- }
2533
- /**
2534
- * Get current levels for a group
2535
- */
2536
- getLevels(groupId = 'default') {
2537
- const hierarchy = this.getHierarchy(groupId);
2538
- return hierarchy.order.map((level) => hierarchy.levels.get(level));
2539
- }
2540
- }
2541
- // Singleton instance
2542
- const geoHierarchyBuilder = new GeoHierarchyBuilder();
2543
-
2544
2702
  // Define stable empty array to avoid selector reference issues
2545
2703
  const EMPTY_DATA_SOURCE = [];
2546
2704
  /**
@@ -2560,138 +2718,108 @@ const useGeoWidgetCascade = (options) => {
2560
2718
  : 'default';
2561
2719
  const valuesRef = React.useRef(values);
2562
2720
  const handlerRef = React.useRef(dataSourceRequestHandler);
2721
+ const lastCascadePublishRef = React.useRef(undefined);
2563
2722
  // Keep refs updated
2564
2723
  React.useEffect(() => {
2565
2724
  valuesRef.current = values;
2566
2725
  handlerRef.current = dataSourceRequestHandler;
2567
2726
  }, [values, dataSourceRequestHandler]);
2568
2727
  // Get current value and data source options
2569
- const currentValue = reactRedux.useSelector((state) => {
2570
- // Try to get value from widgetId first (most recent selection)
2571
- let value = state.widget.values[widgetId];
2572
- // If not found in widgetId, try dataPath
2573
- if (value === undefined && dataPath) {
2574
- value = getWidgetValue(state.widget.values, dataPath, widgetId);
2575
- }
2576
- // Extract value if it's a geo hierarchy object
2577
- if (value && typeof value === 'object' && !Array.isArray(value) && geoConfig) {
2578
- const hierarchy = value.hierarchy || value.geo_code_hierarchy_json?.hierarchy;
2579
- if (Array.isArray(hierarchy)) {
2580
- const levelData = hierarchy.find((l) => l.level === geoConfig.level);
2581
- if (levelData) {
2582
- return levelData.level_value_id;
2583
- }
2584
- }
2585
- // Extended fallbacks (matching useBaseWidget)
2586
- if ('geo_lowest_level_value_id' in value) {
2587
- return value.geo_lowest_level_value_id;
2588
- }
2589
- if ('lowest_level_value_id' in value) {
2590
- return value.lowest_level_value_id;
2591
- }
2592
- if (value.geo_code_hierarchy_json?.lowest_level_value_id) {
2593
- return value.geo_code_hierarchy_json.lowest_level_value_id;
2594
- }
2595
- if (value.geo_code_hierarchy_json?.geo_lowest_level_value_id) {
2596
- return value.geo_code_hierarchy_json.geo_lowest_level_value_id;
2597
- }
2598
- }
2599
- return value;
2600
- });
2728
+ const currentValue = reactRedux.useSelector((state) => geoConfig
2729
+ ? resolveGeoWidgetLevelValue(state.widget.values, widgetId, dataPath, geoConfig)
2730
+ : state.widget.values[widgetId]);
2601
2731
  // Memoize selector to avoid returning new array reference
2602
2732
  const dataSourceOptions = reactRedux.useSelector((state) => state.widget.dataSources[widgetId] ?? EMPTY_DATA_SOURCE);
2733
+ // Register parent chain for upstream-ancestor detection (grandparent → grandchild reset)
2734
+ React.useEffect(() => {
2735
+ if (!geoConfig) {
2736
+ return;
2737
+ }
2738
+ registerGeoWidgetParent(widgetId, geoConfig.parentWidgetId);
2739
+ return () => unregisterGeoWidgetParent(widgetId);
2740
+ }, [widgetId, geoConfig]);
2741
+ // Keep in-memory builder in sync with persisted hierarchy (reload, cancel→re-edit, etc.)
2742
+ React.useEffect(() => {
2743
+ if (!geoConfig || typeof dataPath !== 'string') {
2744
+ return;
2745
+ }
2746
+ seedGeoHierarchyFromValues(values, dataPath, widgetId, groupId);
2747
+ }, [geoConfig, dataPath, widgetId, groupId, values]);
2603
2748
  React.useEffect(() => {
2604
2749
  if (!geoConfig || !eventBus || !dataSource || dataSource.type !== 'api') {
2605
2750
  return;
2606
2751
  }
2607
- const { level, isLastLevel, parentWidgetId } = geoConfig;
2608
- // Listen to parent widget changes
2609
- if (parentWidgetId) {
2610
- const handleParentChange = async (event) => {
2611
- if (event.widgetId !== parentWidgetId) {
2612
- return;
2613
- }
2614
- // CRITICAL: Use a small delay to ensure Redux state has been updated
2615
- // This prevents reading stale values from valuesRef
2616
- await new Promise(resolve => setTimeout(resolve, 0));
2617
- const currentValues = valuesRef.current;
2618
- const currentHandler = handlerRef.current;
2619
- // CRITICAL: Try to get parent value from event first, then from Redux
2620
- let parentValue = event.value;
2621
- if (parentValue === undefined || parentValue === null) {
2622
- parentValue = currentValues[parentWidgetId];
2623
- // If not found in top-level values, try to find it via dataPath or dependsOn
2624
- if (parentValue === undefined && dataSource.dependsOn) {
2625
- parentValue = getWidgetValue(currentValues, dataSource.dependsOn, '');
2626
- }
2627
- }
2628
- // Remove this level and all below from hierarchy
2629
- geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
2630
- // Clear this widget's value
2631
- // CRITICAL: Only dispatch setValue for THIS widget, not for parent or other widgets
2632
- // setWidgetValue returns the entire updated state, but we only want to update this widget
2633
- if (dataPath) {
2634
- const updatedValues = setWidgetValue(currentValues, dataPath, widgetId, undefined);
2635
- // Only dispatch setValue for this widget's widgetId, not for parent or other widgets
2636
- // This prevents accidentally overwriting the parent widget's value
2637
- // The setWidgetValue function updates the nested structure, but we only want to
2638
- // update the top-level widgetId key, not other keys that might be in updatedValues
2639
- const newWidgetValue = updatedValues[widgetId];
2640
- if (newWidgetValue !== undefined) {
2641
- dispatch(setValue({ widgetId, value: newWidgetValue }));
2642
- }
2643
- else {
2644
- // If widgetId is not in updatedValues, the value was set in a nested path
2645
- // In this case, we need to use setValues to update the entire structure
2646
- // But we need to be careful not to overwrite the parent widget's value
2647
- // Only update keys that are related to this widget's dataPath
2648
- const dataPathStr = typeof dataPath === 'string' ? dataPath : '';
2649
- if (dataPathStr && !dataPathStr.startsWith(parentWidgetId + '.')) {
2650
- // Only update if dataPath doesn't start with parentWidgetId
2651
- // This ensures we don't accidentally overwrite the parent widget's value
2652
- dispatch(setValue({ widgetId, value: undefined }));
2653
- }
2654
- }
2655
- }
2656
- else {
2657
- dispatch(setValue({ widgetId, value: undefined }));
2752
+ const { level, isLastLevel, parentWidgetId: rawParentWidgetId } = geoConfig;
2753
+ const parentWidgetId = rawParentWidgetId || null;
2754
+ if (!parentWidgetId) {
2755
+ return;
2756
+ }
2757
+ const clearThisLevel = (baseValues) => {
2758
+ geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
2759
+ if (dataPath) {
2760
+ dispatch(setValues(applySharedGeoHierarchyToValues(baseValues, groupId, dataPath, widgetId)));
2761
+ }
2762
+ dispatch(setValue({ widgetId, value: GEO_LEVEL_CLEARED }));
2763
+ if (!isLastLevel) {
2764
+ eventBus.publish({
2765
+ type: 'widget:change',
2766
+ widgetId,
2767
+ value: GEO_LEVEL_CLEARED,
2768
+ timestamp: Date.now(),
2769
+ });
2770
+ }
2771
+ dispatch(setDataSource({ widgetId, data: [] }));
2772
+ };
2773
+ const handleParentChange = async (event) => {
2774
+ const isDirectParent = event.widgetId === parentWidgetId;
2775
+ const isAncestor = isUpstreamGeoAncestor(event.widgetId, widgetId, parentWidgetId);
2776
+ if (!isDirectParent && !isAncestor) {
2777
+ return;
2778
+ }
2779
+ await new Promise(resolve => setTimeout(resolve, 0));
2780
+ const currentValues = valuesRef.current;
2781
+ const currentHandler = handlerRef.current;
2782
+ // Grandparent (or higher) changed: clear this level; only immediate parent drives reload
2783
+ if (isAncestor && !isDirectParent) {
2784
+ clearThisLevel(currentValues);
2785
+ return;
2786
+ }
2787
+ const parentCleared = event.value === undefined ||
2788
+ event.value === null ||
2789
+ event.value === '' ||
2790
+ event.value === GEO_LEVEL_CLEARED;
2791
+ let parentValue = event.value;
2792
+ if (!parentCleared && (parentValue === undefined || parentValue === null)) {
2793
+ parentValue = currentValues[parentWidgetId];
2794
+ if (parentValue === undefined && dataSource.dependsOn) {
2795
+ parentValue = getWidgetValue(currentValues, dataSource.dependsOn, '');
2658
2796
  }
2659
- // Reload data source with new parent value
2660
- // CRITICAL: Use parentValue from Redux, not event.value
2661
- if (currentHandler && parentValue !== null && parentValue !== undefined) {
2662
- try {
2663
- // Merge the new parent value into current values for the API call
2664
- // This ensures getApiDataSource can find the dependency value
2665
- const updatedValues = {
2666
- ...currentValues,
2667
- [parentWidgetId]: parentValue, // Use Redux value, not event.value
2668
- };
2669
- // Extract level_id from widget-geo-config.level
2670
- const levelId = geoConfig.level;
2671
- const data = await getApiDataSource(dataSource, updatedValues, currentHandler, levelId);
2672
- // Transform to { value, label } format
2673
- const valueKey = dataSource.valueKey || 'level_value_id';
2674
- const labelKey = dataSource.labelKey || 'level_value_mnemonic';
2675
- const transformed = transformDataSourceOptions(data, valueKey, labelKey);
2676
- dispatch(setDataSource({ widgetId, data: transformed }));
2677
- }
2678
- catch (error) {
2679
- console.error('Error reloading geo data source:', error);
2680
- dispatch(setDataSource({ widgetId, data: [] }));
2681
- }
2797
+ }
2798
+ clearThisLevel(currentValues);
2799
+ if (currentHandler && parentValue !== null && parentValue !== undefined && parentValue !== '') {
2800
+ try {
2801
+ const updatedValues = {
2802
+ ...currentValues,
2803
+ [parentWidgetId]: parentValue,
2804
+ };
2805
+ const levelId = geoConfig.level;
2806
+ const data = await getApiDataSource(dataSource, updatedValues, currentHandler, levelId);
2807
+ const valueKey = dataSource.valueKey || 'level_value_id';
2808
+ const labelKey = dataSource.labelKey || 'level_value_mnemonic';
2809
+ const transformed = transformDataSourceOptions(data, valueKey, labelKey);
2810
+ dispatch(setDataSource({ widgetId, data: transformed }));
2682
2811
  }
2683
- else {
2684
- // If parent value is cleared, clear the data source and hierarchy
2685
- geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
2812
+ catch (error) {
2813
+ console.error('Error reloading geo data source:', error);
2686
2814
  dispatch(setDataSource({ widgetId, data: [] }));
2687
2815
  }
2688
- };
2689
- const unsubscribe = eventBus.subscribe('widget:change', handleParentChange);
2690
- return () => {
2691
- unsubscribe();
2692
- };
2693
- }
2694
- }, [geoConfig, eventBus, dataSource, widgetId, dataPath, dispatch]);
2816
+ }
2817
+ };
2818
+ const unsubscribe = eventBus.subscribe('widget:change', handleParentChange);
2819
+ return () => {
2820
+ unsubscribe();
2821
+ };
2822
+ }, [geoConfig, eventBus, dataSource, widgetId, dataPath, dispatch, groupId]);
2695
2823
  // Handle value changes to build hierarchy
2696
2824
  React.useEffect(() => {
2697
2825
  if (!geoConfig) {
@@ -2700,22 +2828,20 @@ const useGeoWidgetCascade = (options) => {
2700
2828
  // Skip if value is undefined (it might still be loading or rehydrating)
2701
2829
  // ONLY clear hierarchy if the value is explicitly null or empty string (user action)
2702
2830
  if (currentValue === null || currentValue === '') {
2703
- const { level } = geoConfig;
2831
+ const { level, isLastLevel } = geoConfig;
2704
2832
  geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
2705
2833
  // If we have a dataPath, we need to update Redux with the cleared hierarchy
2706
2834
  if (dataPath) {
2707
- const hierarchyJson = geoHierarchyBuilder.buildHierarchyJson(groupId);
2708
- let finalUpdatedValues = valuesRef.current;
2709
- // Use logic similar to the build section below to update the dataPath
2710
- if (typeof dataPath === 'string' && dataPath.endsWith('.geo_code_hierarchy_json')) {
2711
- const prefix = dataPath.substring(0, dataPath.lastIndexOf('.'));
2712
- finalUpdatedValues = setWidgetValue(finalUpdatedValues, dataPath, widgetId, hierarchyJson?.geo_code_hierarchy_json);
2713
- finalUpdatedValues = setWidgetValue(finalUpdatedValues, `${prefix}.geo_lowest_level_value_id`, widgetId, hierarchyJson?.geo_lowest_level_value_id);
2714
- }
2715
- else {
2716
- finalUpdatedValues = setWidgetValue(finalUpdatedValues, dataPath, widgetId, hierarchyJson?.geo_code_hierarchy_json);
2717
- }
2718
- dispatch(setValues(finalUpdatedValues));
2835
+ dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
2836
+ }
2837
+ if (!isLastLevel && eventBus && lastCascadePublishRef.current !== GEO_LEVEL_CLEARED) {
2838
+ lastCascadePublishRef.current = GEO_LEVEL_CLEARED;
2839
+ eventBus.publish({
2840
+ type: 'widget:change',
2841
+ widgetId,
2842
+ value: GEO_LEVEL_CLEARED,
2843
+ timestamp: Date.now(),
2844
+ });
2719
2845
  }
2720
2846
  return;
2721
2847
  }
@@ -2766,30 +2892,20 @@ const useGeoWidgetCascade = (options) => {
2766
2892
  // Add level to hierarchy
2767
2893
  geoHierarchyBuilder.addLevel(level, level_value_id, level_value_mnemonic, groupId);
2768
2894
  // Build and store hierarchy JSON on every change
2769
- if (dataPath) {
2770
- const hierarchyJson = geoHierarchyBuilder.buildHierarchyJson(groupId);
2771
- if (hierarchyJson) {
2772
- // Fix: Avoid double nesting of geo_code_hierarchy_json
2773
- // If dataPath ends with .geo_code_hierarchy_json, we want to save the content directly to it
2774
- // and save the lowest level ID as a sibling
2775
- let finalUpdatedValues = valuesRef.current;
2776
- if (typeof dataPath === 'string' && dataPath.endsWith('.geo_code_hierarchy_json')) {
2777
- const prefix = dataPath.substring(0, dataPath.lastIndexOf('.'));
2778
- // Save hierarchy JSON content directly to dataPath (avoiding double nesting)
2779
- finalUpdatedValues = setWidgetValue(finalUpdatedValues, dataPath, widgetId, hierarchyJson.geo_code_hierarchy_json);
2780
- // Save lowest level ID as sibling
2781
- finalUpdatedValues = setWidgetValue(finalUpdatedValues, `${prefix}.geo_lowest_level_value_id`, widgetId, hierarchyJson.geo_lowest_level_value_id);
2782
- }
2783
- else {
2784
- // Fallback if path doesn't follow the naming convention
2785
- finalUpdatedValues = setWidgetValue(finalUpdatedValues, dataPath, widgetId, hierarchyJson.geo_code_hierarchy_json);
2786
- }
2787
- // CRITICAL: Use setValues for deep merge instead of replacing root keys with setValue
2788
- // setWidgetValue returns the complete updated state object with all keys preserved
2789
- dispatch(setValues(finalUpdatedValues));
2790
- }
2895
+ if (dataPath && geoHierarchyBuilder.buildHierarchyJson(groupId)) {
2896
+ dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
2897
+ }
2898
+ // Notify descendants when this level changes via hierarchy/rehydration (handleChange may not run).
2899
+ if (!isLastLevel && eventBus && lastCascadePublishRef.current !== level_value_id) {
2900
+ lastCascadePublishRef.current = level_value_id;
2901
+ eventBus.publish({
2902
+ type: 'widget:change',
2903
+ widgetId,
2904
+ value: level_value_id,
2905
+ timestamp: Date.now(),
2906
+ });
2791
2907
  }
2792
- }, [geoConfig, currentValue, widgetId, dataPath, dispatch, dataSourceOptions]);
2908
+ }, [geoConfig, currentValue, widgetId, dataPath, dispatch, dataSourceOptions, eventBus, groupId]);
2793
2909
  };
2794
2910
 
2795
2911
  class WidgetRegistry {
@@ -8788,16 +8904,65 @@ const DisplayWidget = ({ config }) => {
8788
8904
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DisplayFieldWidget flex flex-col sm:flex-row sm:items-start", children: [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 min-w-0", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
8789
8905
  };
8790
8906
 
8907
+ const getDateColumnConstraintError = (column, cellValue, rowValues, translateConfig) => {
8908
+ const displayValue = cellValue && typeof cellValue === 'string' ? cellValue.split('T')[0] : '';
8909
+ if (!displayValue) {
8910
+ return null;
8911
+ }
8912
+ const optionsConfig = column['widget-data-options'];
8913
+ const formatConfig = column['widget-data-format'];
8914
+ const dateConstraint = formatConfig?.dateConstraint || 'any';
8915
+ const minDate = optionsConfig?.minDate;
8916
+ const maxDate = optionsConfig?.maxDate;
8917
+ const minDateField = optionsConfig?.minDateField;
8918
+ const maxDateField = optionsConfig?.maxDateField;
8919
+ const minDateMessage = optionsConfig?.minDateMessage
8920
+ ? translateConfig(optionsConfig.minDateMessage)
8921
+ : undefined;
8922
+ const maxDateMessage = optionsConfig?.maxDateMessage
8923
+ ? translateConfig(optionsConfig.maxDateMessage)
8924
+ : undefined;
8925
+ const resolveSiblingDate = (fieldRef) => {
8926
+ if (!fieldRef) {
8927
+ return undefined;
8928
+ }
8929
+ const raw = getValueByPath(rowValues, fieldRef) ?? rowValues[fieldRef];
8930
+ return resolveDateBoundFromFieldValue(raw);
8931
+ };
8932
+ const effectiveMinDate = mergeMinDateBounds(getMinDate(dateConstraint, minDate), resolveSiblingDate(minDateField));
8933
+ const effectiveMaxDate = mergeMaxDateBounds(getMaxDate(dateConstraint, maxDate), resolveSiblingDate(maxDateField));
8934
+ return validateDateConstraints(displayValue, effectiveMinDate, effectiveMaxDate, dateConstraint, { minDateMessage, maxDateMessage });
8935
+ };
8936
+ const isTableRowDataValid = (rowData, columns, tableReadonly, translateConfig) => {
8937
+ for (const col of columns) {
8938
+ if (tableReadonly || col['widget-readonly'] === true) {
8939
+ continue;
8940
+ }
8941
+ const columnKey = col['column-key'];
8942
+ const cellValue = rowData[columnKey];
8943
+ const widgetErrors = validateWidget(cellValue, col['widget-data-validation'], col['widget-required']);
8944
+ if (widgetErrors.length > 0) {
8945
+ return false;
8946
+ }
8947
+ if ((col.widget || 'text') === 'date') {
8948
+ const dateError = getDateColumnConstraintError(col, cellValue, rowData, translateConfig);
8949
+ if (dateError) {
8950
+ return false;
8951
+ }
8952
+ }
8953
+ }
8954
+ return true;
8955
+ };
8791
8956
  const TableCellSelect = ({ config, value, onValueChange }) => {
8792
8957
  const { translate } = useWidgetTranslation();
8793
8958
  // Use useBaseWidget to get data source options (it handles loading)
8794
8959
  const { dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
8795
8960
  const isReadonly = config['widget-readonly'] || false;
8796
- return (jsxRuntimeExports.jsxs("select", { value: value || '', onChange: (e) => onValueChange(e.target.value), disabled: isReadonly || loading, className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly || loading ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
8797
- borderRadius: '10px',
8798
- borderColor: 'var(--owt-widget-input-border, #C4C4C4)',
8799
- backgroundColor: isReadonly || loading ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
8800
- }, children: [jsxRuntimeExports.jsx("option", { value: "", children: translate('common.select') || 'Select' }), dataSourceOptions.map((option) => (jsxRuntimeExports.jsx("option", { value: option.value, children: option.label }, option.value)))] }));
8961
+ return (jsxRuntimeExports.jsxs("div", { className: "table-cell-field w-full", children: [jsxRuntimeExports.jsxs("select", { value: value || '', onChange: (e) => onValueChange(e.target.value), disabled: isReadonly || loading, className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly || loading ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
8962
+ borderRadius: '10px',
8963
+ borderColor: 'var(--owt-widget-input-border, #C4C4C4)',
8964
+ backgroundColor: isReadonly || loading ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
8965
+ }, children: [jsxRuntimeExports.jsx("option", { value: "", children: translate('common.select') || 'Select' }), dataSourceOptions.map((option) => (jsxRuntimeExports.jsx("option", { value: option.value, children: option.label }, option.value)))] }), jsxRuntimeExports.jsx("p", { className: "table-cell-field-error", "aria-hidden": "true", children: '\u00a0' })] }));
8801
8966
  };
8802
8967
  const SelectDisplayValue$1 = ({ config, value }) => {
8803
8968
  const { dataSourceOptions, loading } = useBaseWidget({ config });
@@ -8816,11 +8981,11 @@ const TableCellText = ({ config, value, onValueChange }) => {
8816
8981
  config['widget-data-format'];
8817
8982
  const maxLength = config['widget-data-validation']?.maxLength;
8818
8983
  const displayValue = value !== null && value !== undefined ? String(value) : '';
8819
- return (jsxRuntimeExports.jsx("input", { type: "text", value: displayValue, onChange: (e) => onValueChange(e.target.value), disabled: isReadonly, placeholder: placeholder, maxLength: maxLength, className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
8820
- borderRadius: '10px',
8821
- borderColor: 'var(--owt-widget-input-border, #C4C4C4)',
8822
- backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
8823
- } }));
8984
+ return (jsxRuntimeExports.jsxs("div", { className: "table-cell-field w-full", children: [jsxRuntimeExports.jsx("input", { type: "text", value: displayValue, onChange: (e) => onValueChange(e.target.value), disabled: isReadonly, placeholder: placeholder, maxLength: maxLength, className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
8985
+ borderRadius: '10px',
8986
+ borderColor: 'var(--owt-widget-input-border, #C4C4C4)',
8987
+ backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
8988
+ } }), jsxRuntimeExports.jsx("p", { className: "table-cell-field-error", "aria-hidden": "true", children: '\u00a0' })] }));
8824
8989
  };
8825
8990
  const TableCellNumber = ({ config, value, onValueChange }) => {
8826
8991
  const isReadonly = config['widget-readonly'] || false;
@@ -8843,11 +9008,11 @@ const TableCellNumber = ({ config, value, onValueChange }) => {
8843
9008
  onValueChange(inputValue);
8844
9009
  }
8845
9010
  };
8846
- return (jsxRuntimeExports.jsx("input", { type: "number", value: displayValue, onChange: handleChange, disabled: isReadonly, placeholder: placeholder, min: validationConfig?.min, max: validationConfig?.max, step: formatConfig?.decimalPlaces ? Math.pow(0.1, formatConfig.decimalPlaces) : undefined, className: `w-full h-[28px] px-2 text-sm border focus:outline-none text-right ${isReadonly ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
8847
- borderRadius: '10px',
8848
- borderColor: 'var(--owt-widget-input-border, #C4C4C4)',
8849
- backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
8850
- } }));
9011
+ return (jsxRuntimeExports.jsxs("div", { className: "table-cell-field w-full", children: [jsxRuntimeExports.jsx("input", { type: "number", value: displayValue, onChange: handleChange, disabled: isReadonly, placeholder: placeholder, min: validationConfig?.min, max: validationConfig?.max, step: formatConfig?.decimalPlaces ? Math.pow(0.1, formatConfig.decimalPlaces) : undefined, className: `w-full h-[28px] px-2 text-sm border focus:outline-none text-right ${isReadonly ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
9012
+ borderRadius: '10px',
9013
+ borderColor: 'var(--owt-widget-input-border, #C4C4C4)',
9014
+ backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
9015
+ } }), jsxRuntimeExports.jsx("p", { className: "table-cell-field-error", "aria-hidden": "true", children: '\u00a0' })] }));
8851
9016
  };
8852
9017
  const TableCellDate = ({ config, value, rowValues, onValueChange }) => {
8853
9018
  const { translateConfig } = useWidgetTranslation();
@@ -8906,13 +9071,15 @@ const TableCellDate = ({ config, value, rowValues, onValueChange }) => {
8906
9071
  setConstraintError(error);
8907
9072
  };
8908
9073
  const hasError = Boolean(constraintError);
8909
- return (jsxRuntimeExports.jsxs("div", { className: "w-full", children: [jsxRuntimeExports.jsx("input", { type: "date", value: displayValue, onChange: handleChange, disabled: isReadonly, placeholder: placeholder, min: effectiveMinDate, max: effectiveMaxDate, title: constraintError || translateConfig(config['widget-data-tooltip']), className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly ? 'cursor-not-allowed' : ''} ${hasError ? 'border-red-500' : ''} table-cell-input`, style: {
9074
+ return (jsxRuntimeExports.jsxs("div", { className: "table-cell-field w-full", children: [jsxRuntimeExports.jsx("input", { type: "date", value: displayValue, onChange: handleChange, disabled: isReadonly, placeholder: placeholder, min: effectiveMinDate, max: effectiveMaxDate, title: constraintError || translateConfig(config['widget-data-tooltip']), className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
8910
9075
  borderRadius: '10px',
8911
9076
  borderColor: hasError
8912
9077
  ? 'var(--owt-color-error, #B91C1C)'
8913
9078
  : 'var(--owt-widget-input-border, #C4C4C4)',
8914
9079
  backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
8915
- } }), hasError && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-xs mt-0.5 leading-tight", children: constraintError }))] }));
9080
+ } }), jsxRuntimeExports.jsx("p", { className: "table-cell-field-error text-xs mt-0.5 leading-tight", style: {
9081
+ color: hasError ? 'var(--owt-color-error, #B91C1C)' : 'transparent',
9082
+ }, "aria-live": "polite", children: constraintError ?? '\u00a0' })] }));
8916
9083
  };
8917
9084
  const TableWidget = ({ config }) => {
8918
9085
  const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
@@ -8938,6 +9105,18 @@ const TableWidget = ({ config }) => {
8938
9105
  const isSectionEditMode = !isReadonly && operations.edit;
8939
9106
  // Check if any row is being edited (either manually or via section edit mode)
8940
9107
  const isAnyRowEditing = editingState !== null || isAdding;
9108
+ const canSaveEditingRow = React.useMemo(() => {
9109
+ if (!editingState) {
9110
+ return false;
9111
+ }
9112
+ return isTableRowDataValid(editingState.currentValue, columns, isReadonly, translateConfig);
9113
+ }, [editingState, columns, isReadonly, translateConfig]);
9114
+ const canSaveNewRow = React.useMemo(() => {
9115
+ if (!isAdding || !newRowData) {
9116
+ return false;
9117
+ }
9118
+ return isTableRowDataValid(newRowData, columns, isReadonly, translateConfig);
9119
+ }, [isAdding, newRowData, columns, isReadonly, translateConfig]);
8941
9120
  // Show confirmation dialog
8942
9121
  const showConfirmation = React.useCallback((message, onConfirm, onCancel) => {
8943
9122
  setConfirmationState({
@@ -9027,6 +9206,8 @@ const TableWidget = ({ config }) => {
9027
9206
  const saveEdit = React.useCallback(async () => {
9028
9207
  if (!editingState)
9029
9208
  return;
9209
+ if (!canSaveEditingRow)
9210
+ return;
9030
9211
  const rowData = editingState.currentValue;
9031
9212
  const rowIndex = editingState.rowIndex;
9032
9213
  setLoadingRowIndex(rowIndex);
@@ -9098,7 +9279,7 @@ const TableWidget = ({ config }) => {
9098
9279
  finally {
9099
9280
  setLoadingRowIndex(null);
9100
9281
  }
9101
- }, [editingState, rows, onChange, dataSourceRequestHandler, apiConfig, translate, isSectionEditMode, originalRows, columns, widgetConfig, dispatch]);
9282
+ }, [editingState, canSaveEditingRow, rows, onChange, dataSourceRequestHandler, apiConfig, translate, isSectionEditMode, originalRows, columns, widgetConfig, dispatch]);
9102
9283
  // Add new row
9103
9284
  const startAdd = React.useCallback(() => {
9104
9285
  // If there's an unsaved edit, cancel it first (no confirmation needed)
@@ -9116,6 +9297,8 @@ const TableWidget = ({ config }) => {
9116
9297
  const saveAdd = React.useCallback(async () => {
9117
9298
  if (!isAdding || !newRowData)
9118
9299
  return;
9300
+ if (!canSaveNewRow)
9301
+ return;
9119
9302
  setLoadingRowIndex(-1); // Use -1 to indicate new row
9120
9303
  try {
9121
9304
  let savedRow = { ...newRowData };
@@ -9146,7 +9329,7 @@ const TableWidget = ({ config }) => {
9146
9329
  finally {
9147
9330
  setLoadingRowIndex(null);
9148
9331
  }
9149
- }, [isAdding, newRowData, rows, onChange, dataSourceRequestHandler, apiConfig, translate, isSectionEditMode]);
9332
+ }, [isAdding, newRowData, canSaveNewRow, rows, onChange, dataSourceRequestHandler, apiConfig, translate, isSectionEditMode]);
9150
9333
  // Delete row
9151
9334
  const deleteRow = React.useCallback(async (rowIndex) => {
9152
9335
  if (isAnyRowEditing) {
@@ -9424,6 +9607,22 @@ const TableWidget = ({ config }) => {
9424
9607
  box-shadow: 0 0 0 1px var(--owt-widget-input-focus-border, #F07B1A);
9425
9608
  border-color: var(--owt-widget-input-focus-border, #F07B1A);
9426
9609
  }
9610
+
9611
+ /* Keep inputs and action buttons top-aligned when a cell shows validation text */
9612
+ .${tableWidgetId} tr.table-row-editing td {
9613
+ vertical-align: top;
9614
+ }
9615
+
9616
+ .${tableWidgetId} .table-cell-field-error {
9617
+ min-height: 1.125rem;
9618
+ }
9619
+
9620
+ .${tableWidgetId} .table-cell-actions {
9621
+ display: flex;
9622
+ flex-direction: row;
9623
+ gap: 0.5rem;
9624
+ align-items: flex-start;
9625
+ }
9427
9626
  ` }), jsxRuntimeExports.jsxs("div", { className: `table-widget-container ${tableWidgetId}`, children: [confirmationState?.show && (jsxRuntimeExports.jsx("div", { className: "fixed inset-0 flex items-center justify-center z-50", style: { backgroundColor: 'rgba(0,0,0,0.5)' }, children: jsxRuntimeExports.jsxs("div", { className: "rounded-lg p-6 max-w-md w-full mx-4", style: { backgroundColor: 'var(--owt-color-bg, #FFFFFF)' }, children: [jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold mb-4", style: { color: 'var(--owt-color-text, #011627)' }, children: translate('table.confirm') || 'Confirm Action' }), jsxRuntimeExports.jsx("p", { className: "mb-6", style: { color: 'var(--owt-color-text, #011627)' }, children: confirmationState.message }), jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-3", children: [jsxRuntimeExports.jsx("button", { onClick: confirmationState.onCancel, className: "px-4 py-2 text-sm font-medium", style: {
9428
9627
  borderRadius: 'var(--owt-btn-border-radius, 10px)',
9429
9628
  border: '1px solid var(--owt-btn-secondary-border, #C4C4C4)',
@@ -9442,7 +9641,7 @@ const TableWidget = ({ config }) => {
9442
9641
  }, children: translate('table.addRecord') || 'Add New Record' }) })), jsxRuntimeExports.jsx("div", { className: "overflow-x-auto border", style: { borderRadius: 'var(--owt-widget-table-border-radius, 15px)', borderColor: 'var(--owt-widget-table-border-color, #C4C4C4)' }, children: jsxRuntimeExports.jsxs("table", { className: "min-w-full", style: { borderCollapse: 'separate', borderSpacing: 0 }, children: [jsxRuntimeExports.jsx("thead", { style: { backgroundColor: 'var(--owt-widget-table-header-bg, #F6F6F6)' }, children: jsxRuntimeExports.jsxs("tr", { style: { borderBottom: '1px solid var(--owt-widget-table-row-divider, #E4E4E4)' }, children: [columns.map((col) => (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: translateConfig(col['widget-label']) }, col['column-key']))), ((operations.edit || operations.remove) && !isReadonly) || isAnyRowEditing || isSectionEditMode ? (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: translate('common.actions') || 'Actions' })) : null] }) }), jsxRuntimeExports.jsxs("tbody", { style: { backgroundColor: 'var(--owt-widget-table-body-bg, #FFFFFF)' }, children: [rows.length === 0 && !isAdding && (jsxRuntimeExports.jsx("tr", { children: jsxRuntimeExports.jsxs("td", { colSpan: columns.length + (((operations.edit || operations.remove) && !isReadonly) || isSectionEditMode ? 1 : 0), className: "px-4 py-6 text-center text-sm", style: { color: 'var(--owt-widget-table-empty-color, #727474)' }, children: [translate('table.noData') || 'No records available.', operations.add && !isReadonly && ` ${translate('table.clickToAdd') || 'Click "Add New Record" to add one.'}`] }) })), rows.map((row, rowIndex) => {
9443
9642
  const isEditing = isRowEditing(rowIndex);
9444
9643
  const isLoading = loadingRowIndex === rowIndex;
9445
- return (jsxRuntimeExports.jsxs("tr", { className: isLoading ? 'opacity-50' : '', style: {
9644
+ return (jsxRuntimeExports.jsxs("tr", { className: `${isLoading ? 'opacity-50' : ''}${isEditing ? ' table-row-editing' : ''}`, style: {
9446
9645
  borderBottom: '1px solid var(--owt-widget-table-row-divider, #E4E4E4)',
9447
9646
  backgroundColor: isEditing
9448
9647
  ? 'var(--owt-widget-table-editing-row-bg, #FBE6AA)'
@@ -9453,7 +9652,7 @@ const TableWidget = ({ config }) => {
9453
9652
  return (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: renderCell(rowIndex, col, row) }, col['column-key']));
9454
9653
  }), ((operations.edit || operations.remove) && !isReadonly) || isEditing || isSectionEditMode ? (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", style: { minWidth: '120px' }, children: isEditing ? (
9455
9654
  // Show OK (Save)/Cancel buttons when row is being edited (works in both section edit mode and normal mode)
9456
- jsxRuntimeExports.jsxs("div", { className: "flex flex-row gap-2 items-center", style: { width: '100%' }, children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: saveEdit, disabled: isLoading, className: "px-3 py-1 text-xs font-medium disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap flex-shrink-0", style: {
9655
+ jsxRuntimeExports.jsxs("div", { className: "table-cell-actions", style: { width: '100%' }, children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: saveEdit, disabled: isLoading || !canSaveEditingRow, className: "px-3 py-1 text-xs font-medium disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap flex-shrink-0", style: {
9457
9656
  display: 'inline-block',
9458
9657
  minWidth: '60px',
9459
9658
  backgroundColor: 'var(--owt-color-success, #16A34A)',
@@ -9480,7 +9679,7 @@ const TableWidget = ({ config }) => {
9480
9679
  backgroundColor: 'transparent',
9481
9680
  border: 'none',
9482
9681
  }, children: translate('common.remove') || 'Delete' }))] })) })) : null] }, rowIndex));
9483
- }), isAdding && newRowData && (jsxRuntimeExports.jsxs("tr", { style: { backgroundColor: 'var(--owt-widget-table-editing-row-bg, #FBE6AA)' }, children: [columns.map((col) => (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: renderCell(rows.length, col, { ...newRowData, edit_action: 'ADD' }) }, col['column-key']))), jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: saveAdd, disabled: loadingRowIndex === -1, className: "px-3 py-1 text-xs disabled:opacity-50", style: {
9682
+ }), isAdding && newRowData && (jsxRuntimeExports.jsxs("tr", { className: "table-row-editing", style: { backgroundColor: 'var(--owt-widget-table-editing-row-bg, #FBE6AA)' }, children: [columns.map((col) => (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: renderCell(rows.length, col, { ...newRowData, edit_action: 'ADD' }) }, col['column-key']))), jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: jsxRuntimeExports.jsxs("div", { className: "table-cell-actions", children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: saveAdd, disabled: loadingRowIndex === -1 || !canSaveNewRow, className: "px-3 py-1 text-xs disabled:opacity-50 disabled:cursor-not-allowed", style: {
9484
9683
  borderRadius: 'var(--owt-btn-border-radius, 10px)',
9485
9684
  backgroundColor: 'var(--owt-color-success, #16A34A)',
9486
9685
  color: 'var(--owt-color-bg, #FFFFFF)',
@@ -11778,6 +11977,7 @@ exports.DateTimeInputWidget = DateTimeInputWidget;
11778
11977
  exports.DialogTableWidget = DialogTableWidget;
11779
11978
  exports.DisplayWidget = DisplayWidget;
11780
11979
  exports.FileInputWidget = FileInputWidget;
11980
+ exports.GEO_LEVEL_CLEARED = GEO_LEVEL_CLEARED;
11781
11981
  exports.HeaderSectionWidget = HeaderSectionWidget;
11782
11982
  exports.IdAuthenticationWidget = IdAuthenticationWidget;
11783
11983
  exports.IterableAccordionWidget = IterableAccordionWidget;
@@ -11805,6 +12005,7 @@ exports.WidgetRenderer = WidgetRenderer;
11805
12005
  exports.applyCaseControl = applyCaseControl;
11806
12006
  exports.applyDecimalPrecision = applyDecimalPrecision;
11807
12007
  exports.applyMask = applyMask;
12008
+ exports.applySharedGeoHierarchyToValues = applySharedGeoHierarchyToValues;
11808
12009
  exports.createWidgetStore = createWidgetStore;
11809
12010
  exports.createZodSchema = createZodSchema;
11810
12011
  exports.defaultTheme = defaultTheme;
@@ -11824,14 +12025,20 @@ exports.getValueByPath = getValueByPath;
11824
12025
  exports.getWidgetValue = getWidgetValue;
11825
12026
  exports.initI18n = initI18n;
11826
12027
  exports.isAllowedKey = isAllowedKey;
12028
+ exports.isUpstreamGeoAncestor = isUpstreamGeoAncestor;
11827
12029
  exports.normalizeNumericDefault = normalizeNumericDefault;
11828
12030
  exports.parseDataPath = parseDataPath;
11829
12031
  exports.parseNumber = parseNumber;
11830
12032
  exports.registerDefaultWidgets = registerDefaultWidgets;
12033
+ exports.registerGeoWidgetParent = registerGeoWidgetParent;
11831
12034
  exports.removeMask = removeMask;
11832
12035
  exports.resetAll = resetAll;
12036
+ exports.resetAndSeedGeoHierarchyFromValues = resetAndSeedGeoHierarchyFromValues;
11833
12037
  exports.resetWidget = resetWidget;
12038
+ exports.resolveGeoWidgetLevelValue = resolveGeoWidgetLevelValue;
11834
12039
  exports.resolveTheme = resolveTheme;
12040
+ exports.resolveWidgetIdValue = resolveWidgetIdValue;
12041
+ exports.seedGeoHierarchyFromValues = seedGeoHierarchyFromValues;
11835
12042
  exports.setDataSource = setDataSource;
11836
12043
  exports.setError = setError;
11837
12044
  exports.setLoading = setLoading;
@@ -11846,6 +12053,7 @@ exports.transformDataSourceOptions = transformDataSourceOptions;
11846
12053
  exports.translatePanelConfig = translatePanelConfig;
11847
12054
  exports.translateUISchema = translateUISchema;
11848
12055
  exports.translateWidgetConfig = translateWidgetConfig;
12056
+ exports.unregisterGeoWidgetParent = unregisterGeoWidgetParent;
11849
12057
  exports.useBaseWidget = useBaseWidget;
11850
12058
  exports.useGeoWidgetCascade = useGeoWidgetCascade;
11851
12059
  exports.useWidgetCascade = useWidgetCascade;