@openg2p/registry-widgets 1.1.2-dev.7 → 1.1.2-dev.9

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.esm.js CHANGED
@@ -1082,6 +1082,67 @@ const formatValue = (value, format, widgetType) => {
1082
1082
  return value?.toString() || '';
1083
1083
  };
1084
1084
 
1085
+ const apiDataSourceCache = new Map();
1086
+ const apiDataSourceInflight = new Map();
1087
+ function buildApiRequestContext(dataSource, allValues, levelId) {
1088
+ let depValue = null;
1089
+ if (dataSource.dependsOn) {
1090
+ if (dataSource.dependsOn.includes('.')) {
1091
+ depValue = getValueByPath(allValues, dataSource.dependsOn);
1092
+ }
1093
+ else {
1094
+ depValue = resolveWidgetIdValue(allValues, dataSource.dependsOn);
1095
+ }
1096
+ if (depValue === null || depValue === undefined || depValue === '') {
1097
+ return null;
1098
+ }
1099
+ }
1100
+ const method = dataSource.method || 'GET';
1101
+ const staticParams = { ...dataSource.params };
1102
+ const standardFields = ['type', 'service', 'endpoint', 'url', 'method', 'dependsOn', 'valueKey', 'labelKey', 'headers', 'body', 'params', 'level_id'];
1103
+ for (const [key, value] of Object.entries(dataSource)) {
1104
+ if (!standardFields.includes(key) && value !== undefined && value !== null) {
1105
+ staticParams[key] = value;
1106
+ }
1107
+ }
1108
+ if (levelId) {
1109
+ staticParams.level_id = levelId;
1110
+ }
1111
+ const requestParams = { ...staticParams };
1112
+ if (dataSource.dependsOn && depValue !== null && depValue !== undefined) {
1113
+ const parentValueId = typeof depValue === 'object' && depValue !== null
1114
+ ? (depValue.level_value_id || depValue.id || depValue.value || depValue)
1115
+ : depValue;
1116
+ if (staticParams.level_id) {
1117
+ requestParams.parent_level_value_id = parentValueId;
1118
+ }
1119
+ else {
1120
+ const paramKey = dataSource.dependsOn.split('.').pop() || 'filter';
1121
+ requestParams[paramKey] = parentValueId;
1122
+ }
1123
+ }
1124
+ else if (staticParams.level_id) {
1125
+ requestParams.parent_level_value_id = '';
1126
+ }
1127
+ const service = dataSource.service;
1128
+ const endpoint = dataSource.endpoint;
1129
+ if (!service || !endpoint) {
1130
+ return null;
1131
+ }
1132
+ return { service, endpoint, method, requestParams };
1133
+ }
1134
+ function buildApiDataSourceCacheKey(service, endpoint, method, requestParams) {
1135
+ return `${service}|${endpoint}|${method}|${JSON.stringify(requestParams)}`;
1136
+ }
1137
+ /** Return cached API options when already fetched (e.g. duplicate table cells). */
1138
+ function getCachedApiDataSource(dataSource, allValues, levelId) {
1139
+ const context = buildApiRequestContext(dataSource, allValues, levelId);
1140
+ if (!context) {
1141
+ return undefined;
1142
+ }
1143
+ const cacheKey = buildApiDataSourceCacheKey(context.service, context.endpoint, context.method, context.requestParams);
1144
+ return apiDataSourceCache.get(cacheKey);
1145
+ }
1085
1146
  /**
1086
1147
  * Get static data source options
1087
1148
  */
@@ -1099,98 +1160,33 @@ const getApiDataSource = async (dataSource, allValues, dataSourceRequestHandler,
1099
1160
  return [];
1100
1161
  }
1101
1162
  try {
1102
- // Get dependency value if exists
1103
- // dependsOn can be either a data path (e.g., "person.address") or a widget-id
1104
- let depValue = null;
1105
- if (dataSource.dependsOn) {
1106
- if (dataSource.dependsOn.includes('.')) {
1107
- depValue = getValueByPath(allValues, dataSource.dependsOn);
1108
- }
1109
- else {
1110
- depValue = resolveWidgetIdValue(allValues, dataSource.dependsOn);
1111
- }
1112
- if (depValue === null || depValue === undefined || depValue === '') {
1113
- // If dependency is empty, return empty array
1114
- return [];
1115
- }
1116
- }
1117
- // Build request parameters
1118
- const method = dataSource.method || 'GET';
1119
- // Extract static params from dataSource
1120
- // Include explicit params object and any additional fields (like level_id)
1121
- const staticParams = { ...dataSource.params };
1122
- // Extract additional fields that aren't part of the standard ApiDataSource interface
1123
- // These are fields like level_id that might be directly on the dataSource
1124
- // BUT: level_id should come from widget-geo-config.level, not from dataSource
1125
- const standardFields = ['type', 'service', 'endpoint', 'url', 'method', 'dependsOn', 'valueKey', 'labelKey', 'headers', 'body', 'params', 'level_id'];
1126
- for (const [key, value] of Object.entries(dataSource)) {
1127
- if (!standardFields.includes(key) && value !== undefined && value !== null) {
1128
- staticParams[key] = value;
1129
- }
1130
- }
1131
- // If levelId is provided (from widget-geo-config.level), use it instead of any level_id in dataSource
1132
- if (levelId) {
1133
- staticParams.level_id = levelId;
1134
- }
1135
- // Build request params object
1136
- const requestParams = { ...staticParams };
1137
- // Add dependency value to params
1138
- if (dataSource.dependsOn && depValue !== null && depValue !== undefined) {
1139
- // Extract the actual value ID if depValue is an object
1140
- const parentValueId = typeof depValue === 'object' && depValue !== null
1141
- ? (depValue.level_value_id || depValue.id || depValue.value || depValue)
1142
- : depValue;
1143
- // For geo APIs, use parent_level_value_id
1144
- if (staticParams.level_id) {
1145
- requestParams.parent_level_value_id = parentValueId;
1146
- }
1147
- else {
1148
- // For other APIs, use the dependency field name as param key
1149
- const paramKey = dataSource.dependsOn.split('.').pop() || 'filter';
1150
- requestParams[paramKey] = parentValueId;
1151
- }
1152
- }
1153
- else if (staticParams.level_id) {
1154
- // First level has no parent, send empty string as many OpenG2P APIs expect it
1155
- requestParams.parent_level_value_id = "";
1156
- }
1157
- // Get service mnemonic and endpoint (required)
1158
- const service = dataSource.service;
1159
- const endpoint = dataSource.endpoint;
1160
- if (!service) {
1161
- console.error('[getApiDataSource] API data source missing service mnemonic. Use "service" field instead of "url"');
1162
- return [];
1163
- }
1164
- if (!endpoint) {
1165
- console.error('[getApiDataSource] API data source missing endpoint. Use "endpoint" field to specify the operation (e.g., "get_g2p_geo_level_values")');
1163
+ const context = buildApiRequestContext(dataSource, allValues, levelId);
1164
+ if (!context) {
1166
1165
  return [];
1167
1166
  }
1168
- // Call handler let any throw propagate to the outer catch so it is logged once
1169
- // by useBaseWidget rather than double-logged here (which can cascade when
1170
- // intercept-console-error.js converts console.error calls into thrown errors).
1171
- const response = await dataSourceRequestHandler(service, endpoint, method, requestParams, {
1172
- headers: dataSource.headers,
1173
- });
1174
- // Handle OpenG2P response format (response_body.response_payload)
1175
- if (response && typeof response === 'object') {
1176
- if (response.response_body?.response_payload && Array.isArray(response.response_body.response_payload)) {
1177
- return response.response_body.response_payload;
1178
- }
1179
- }
1180
- // Handle array response
1181
- if (Array.isArray(response)) {
1182
- return response;
1167
+ const { service, endpoint, method, requestParams } = context;
1168
+ const cacheKey = buildApiDataSourceCacheKey(service, endpoint, method, requestParams);
1169
+ const cached = apiDataSourceCache.get(cacheKey);
1170
+ if (cached) {
1171
+ return cached;
1172
+ }
1173
+ const inflight = apiDataSourceInflight.get(cacheKey);
1174
+ if (inflight) {
1175
+ return inflight;
1176
+ }
1177
+ const fetchPromise = (async () => {
1178
+ const response = await dataSourceRequestHandler(service, endpoint, method, requestParams, { headers: dataSource.headers });
1179
+ const parsed = Array.isArray(response) ? response : [];
1180
+ apiDataSourceCache.set(cacheKey, parsed);
1181
+ return parsed;
1182
+ })();
1183
+ apiDataSourceInflight.set(cacheKey, fetchPromise);
1184
+ try {
1185
+ return await fetchPromise;
1183
1186
  }
1184
- // Handle object response (extract array from common keys)
1185
- if (response && typeof response === 'object') {
1186
- if (response.data && Array.isArray(response.data)) {
1187
- return response.data;
1188
- }
1189
- if (response.results && Array.isArray(response.results)) {
1190
- return response.results;
1191
- }
1187
+ finally {
1188
+ apiDataSourceInflight.delete(cacheKey);
1192
1189
  }
1193
- return [];
1194
1190
  }
1195
1191
  catch (error) {
1196
1192
  // Rethrow so useBaseWidget's catch can log it with full widget context
@@ -2247,12 +2243,145 @@ const geoHierarchyBuilder = new GeoHierarchyBuilder();
2247
2243
  /** Sentinel written to Redux when a geo level is cleared by cascade (distinct from "never set"). */
2248
2244
  const GEO_LEVEL_CLEARED = null;
2249
2245
  const geoWidgetParentRegistry = new Map();
2246
+ const geoWidgetConfigRegistry = new Map();
2250
2247
  function registerGeoWidgetParent(widgetId, parentWidgetId) {
2251
2248
  geoWidgetParentRegistry.set(widgetId, parentWidgetId || null);
2252
2249
  }
2253
2250
  function unregisterGeoWidgetParent(widgetId) {
2254
2251
  geoWidgetParentRegistry.delete(widgetId);
2255
2252
  }
2253
+ function registerGeoWidget(widgetId, geoConfig, dataPath) {
2254
+ if (typeof dataPath !== 'string') {
2255
+ return;
2256
+ }
2257
+ const parentWidgetId = geoConfig.parentWidgetId?.trim() ? geoConfig.parentWidgetId : null;
2258
+ registerGeoWidgetParent(widgetId, parentWidgetId);
2259
+ geoWidgetConfigRegistry.set(widgetId, {
2260
+ widgetId,
2261
+ parentWidgetId,
2262
+ level: geoConfig.level,
2263
+ geoConfig,
2264
+ dataPath,
2265
+ groupId: getGeoGroupId(dataPath),
2266
+ });
2267
+ }
2268
+ function unregisterGeoWidget(widgetId) {
2269
+ unregisterGeoWidgetParent(widgetId);
2270
+ geoWidgetConfigRegistry.delete(widgetId);
2271
+ }
2272
+ function orderGeoWidgetRegistrations(registrations) {
2273
+ if (registrations.length <= 1) {
2274
+ return registrations;
2275
+ }
2276
+ const roots = registrations.filter((entry) => !entry.parentWidgetId);
2277
+ if (roots.length === 0) {
2278
+ return registrations;
2279
+ }
2280
+ const ordered = [];
2281
+ let current = roots[0];
2282
+ const visited = new Set();
2283
+ while (current && !visited.has(current.widgetId)) {
2284
+ visited.add(current.widgetId);
2285
+ ordered.push(current);
2286
+ current = registrations.find((entry) => entry.parentWidgetId === current.widgetId);
2287
+ }
2288
+ return ordered.length > 0 ? ordered : registrations;
2289
+ }
2290
+ function resolveLevelValueId(rawValue) {
2291
+ if (rawValue === null || rawValue === undefined || rawValue === '') {
2292
+ return null;
2293
+ }
2294
+ if (typeof rawValue === 'string' || typeof rawValue === 'number') {
2295
+ return String(rawValue);
2296
+ }
2297
+ if (typeof rawValue === 'object') {
2298
+ const id = rawValue.level_value_id || rawValue.id || rawValue.value;
2299
+ return id != null && id !== '' ? String(id) : null;
2300
+ }
2301
+ return null;
2302
+ }
2303
+ function resolveStoredMnemonic(values, registration, valueId) {
2304
+ const stored = getWidgetValue(values, registration.dataPath, registration.widgetId);
2305
+ const hierarchy = stored?.hierarchy || stored?.geo_code_hierarchy_json?.hierarchy;
2306
+ if (!Array.isArray(hierarchy)) {
2307
+ return undefined;
2308
+ }
2309
+ const levelData = hierarchy.find((entry) => entry.level === registration.level);
2310
+ if (levelData && String(levelData.level_value_id) === String(valueId)) {
2311
+ return levelData.level_value_mnemonic ? String(levelData.level_value_mnemonic) : undefined;
2312
+ }
2313
+ return undefined;
2314
+ }
2315
+ /** Resolve display mnemonic from cached dropdown options, then stored hierarchy. */
2316
+ function createGeoLevelMnemonicResolver(values, dataSources) {
2317
+ return (registration, valueId) => {
2318
+ const options = dataSources[registration.widgetId];
2319
+ const option = options?.find((entry) => String(entry.value) === String(valueId));
2320
+ if (option?.label) {
2321
+ return option.label;
2322
+ }
2323
+ return resolveStoredMnemonic(values, registration, valueId);
2324
+ };
2325
+ }
2326
+ /** Rebuild group hierarchy from widget values in parent→child order; stop at first missing level. */
2327
+ function rebuildGeoHierarchyFromRegistrations(groupId, values, registrations, resolveMnemonic) {
2328
+ const ordered = orderGeoWidgetRegistrations(registrations.filter((entry) => entry.groupId === groupId));
2329
+ geoHierarchyBuilder.clear(groupId);
2330
+ for (const registration of ordered) {
2331
+ const rawValue = resolveGeoWidgetLevelValue(values, registration.widgetId, registration.dataPath, registration.geoConfig);
2332
+ const valueId = resolveLevelValueId(rawValue);
2333
+ if (!valueId) {
2334
+ break;
2335
+ }
2336
+ const mnemonic = resolveMnemonic?.(registration, valueId) ??
2337
+ resolveStoredMnemonic(values, registration, valueId) ??
2338
+ valueId;
2339
+ geoHierarchyBuilder.addLevel(registration.level, valueId, mnemonic, groupId);
2340
+ }
2341
+ return geoHierarchyBuilder.buildHierarchyJson(groupId) !== null;
2342
+ }
2343
+ function collectGeoWidgetRegistrationsFromWidgets(widgets, namespace) {
2344
+ return widgets
2345
+ .filter((widget) => widget['widget-geo-config'] && typeof widget['widget-data-path'] === 'string')
2346
+ .map((widget) => {
2347
+ const originalWidgetId = widget['widget-id'];
2348
+ const originalDataPath = widget['widget-data-path'];
2349
+ const geoConfig = widget['widget-geo-config'];
2350
+ const widgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
2351
+ const parentWidgetId = geoConfig.parentWidgetId?.trim()
2352
+ ? (namespace ? `${namespace}__${geoConfig.parentWidgetId}` : geoConfig.parentWidgetId)
2353
+ : null;
2354
+ const dataPath = namespace ? `${namespace}.${originalDataPath}` : originalDataPath;
2355
+ return {
2356
+ widgetId,
2357
+ parentWidgetId,
2358
+ level: geoConfig.level,
2359
+ geoConfig,
2360
+ dataPath,
2361
+ groupId: getGeoGroupId(dataPath),
2362
+ };
2363
+ });
2364
+ }
2365
+ /** Reconcile all geo groups in section values before save. */
2366
+ function reconcileGeoHierarchiesInValues(values, registrations, dataSources = {}) {
2367
+ const groupIds = [...new Set(registrations.map((entry) => entry.groupId))];
2368
+ let updatedValues = values;
2369
+ const resolveMnemonic = createGeoLevelMnemonicResolver(updatedValues, dataSources);
2370
+ for (const groupId of groupIds) {
2371
+ const groupRegistrations = registrations.filter((entry) => entry.groupId === groupId);
2372
+ const dataPath = groupRegistrations[0]?.dataPath;
2373
+ const widgetId = groupRegistrations[0]?.widgetId;
2374
+ if (!dataPath || !widgetId) {
2375
+ continue;
2376
+ }
2377
+ rebuildGeoHierarchyFromRegistrations(groupId, updatedValues, groupRegistrations, resolveMnemonic);
2378
+ updatedValues = applySharedGeoHierarchyToValues(updatedValues, groupId, dataPath, widgetId);
2379
+ }
2380
+ return updatedValues;
2381
+ }
2382
+ function getGeoWidgetRegistrationsInGroup(groupId) {
2383
+ return orderGeoWidgetRegistrations([...geoWidgetConfigRegistry.values()].filter((entry) => entry.groupId === groupId));
2384
+ }
2256
2385
  /** True when changedWidgetId is any upstream geo parent of widgetId (not only immediate parent). */
2257
2386
  function isUpstreamGeoAncestor(changedWidgetId, widgetId, immediateParentWidgetId) {
2258
2387
  if (changedWidgetId === widgetId) {
@@ -2347,7 +2476,7 @@ function resetAndSeedGeoHierarchyFromValues(values, dataPath, widgetId, groupId)
2347
2476
 
2348
2477
  // Define stable empty arrays to avoid selector reference issues
2349
2478
  const EMPTY_ERRORS = [];
2350
- const EMPTY_DATA_SOURCE$1 = [];
2479
+ const EMPTY_DATA_SOURCE = [];
2351
2480
  const useBaseWidget = (options) => {
2352
2481
  const { config, dataSourceRequestHandler: propHandler, schemaData, onValueChange } = options;
2353
2482
  const dispatch = useDispatch();
@@ -2362,7 +2491,7 @@ const useBaseWidget = (options) => {
2362
2491
  const errors = useSelector((state) => state.widget.errors[widgetId] ?? EMPTY_ERRORS);
2363
2492
  const touched = useSelector((state) => state.widget.touched[widgetId] || false);
2364
2493
  const loading = useSelector((state) => state.widget.loading[widgetId] || false);
2365
- const dataSourceOptions = useSelector((state) => state.widget.dataSources[widgetId] ?? EMPTY_DATA_SOURCE$1);
2494
+ const dataSourceOptions = useSelector((state) => state.widget.dataSources[widgetId] ?? EMPTY_DATA_SOURCE);
2366
2495
  // Skip value handling for layout widgets (they don't store data values)
2367
2496
  // Infer layout from widget-type
2368
2497
  const isLayoutWidget = config['widget-type'] === 'layout';
@@ -2700,9 +2829,9 @@ const useBaseWidget = (options) => {
2700
2829
  if (!dataSource) {
2701
2830
  return;
2702
2831
  }
2703
- // Non-geo readonly widgets skip API loads. Geo widgets still load in readonly so
2704
- // labels can be resolved and translated on initial page view (not only after Edit).
2705
- if (dataSource.type === 'api' && isReadonly && !geoConfig) {
2832
+ const loadApiInReadonly = !!geoConfig ||
2833
+ ['select', 'radio', 'checkbox', 'multi-select'].includes(config.widget);
2834
+ if (dataSource.type === 'api' && isReadonly && !loadApiInReadonly) {
2706
2835
  return;
2707
2836
  }
2708
2837
  // For widgets with dependencies, check if dependency value exists
@@ -2744,6 +2873,30 @@ const useBaseWidget = (options) => {
2744
2873
  // React will call this effect again when the handler is ready
2745
2874
  return;
2746
2875
  }
2876
+ const resolveOptionKeys = () => {
2877
+ if (dataSource.type === 'static') {
2878
+ return { valueKey: undefined, labelKey: undefined };
2879
+ }
2880
+ if (geoConfig) {
2881
+ return {
2882
+ valueKey: dataSource.valueKey || 'level_value_id',
2883
+ labelKey: dataSource.labelKey || 'level_value_mnemonic',
2884
+ };
2885
+ }
2886
+ return { valueKey: dataSource.valueKey, labelKey: dataSource.labelKey };
2887
+ };
2888
+ if (dataSource.type === 'api') {
2889
+ const levelId = geoConfig?.level;
2890
+ const cached = getCachedApiDataSource(dataSource, valuesRef.current, levelId);
2891
+ if (cached) {
2892
+ const { valueKey, labelKey } = resolveOptionKeys();
2893
+ dispatch(setDataSource({
2894
+ widgetId,
2895
+ data: transformDataSourceOptions(cached, valueKey, labelKey),
2896
+ }));
2897
+ return;
2898
+ }
2899
+ }
2747
2900
  dispatch(setLoading({ widgetId, loading: true }));
2748
2901
  let data = [];
2749
2902
  if (dataSource.type === 'static') {
@@ -2756,31 +2909,13 @@ const useBaseWidget = (options) => {
2756
2909
  dispatch(setDataSource({ widgetId, data: [] }));
2757
2910
  return;
2758
2911
  }
2759
- // Extract level_id from widget-geo-config.level if available
2760
2912
  const levelId = geoConfig?.level;
2761
2913
  data = await getApiDataSource(dataSource, valuesRef.current, currentHandler, levelId);
2762
2914
  }
2763
2915
  else if (dataSource.type === 'schema') {
2764
2916
  data = getSchemaDataSource(dataSource, schemaData || {});
2765
2917
  }
2766
- // Transform to { value, label } format
2767
- // For geo widgets, default to level_value_id and level_value_mnemonic
2768
- let valueKey;
2769
- let labelKey;
2770
- if (dataSource.type === 'static') {
2771
- valueKey = undefined;
2772
- labelKey = undefined;
2773
- }
2774
- else if (geoConfig) {
2775
- // Geo widgets: default to level_value_id and level_value_mnemonic
2776
- valueKey = dataSource.valueKey || 'level_value_id';
2777
- labelKey = dataSource.labelKey || 'level_value_mnemonic';
2778
- }
2779
- else {
2780
- // Non-geo widgets: use specified keys or undefined
2781
- valueKey = dataSource.valueKey;
2782
- labelKey = dataSource.labelKey;
2783
- }
2918
+ const { valueKey, labelKey } = resolveOptionKeys();
2784
2919
  const transformed = transformDataSourceOptions(data, valueKey, labelKey);
2785
2920
  dispatch(setDataSource({ widgetId, data: transformed }));
2786
2921
  }
@@ -2884,8 +3019,6 @@ const useWidgetCascade = (options) => {
2884
3019
  }, [cascadeConfig, eventBus, dataSource, widgetId, dispatch]);
2885
3020
  };
2886
3021
 
2887
- // Define stable empty array to avoid selector reference issues
2888
- const EMPTY_DATA_SOURCE = [];
2889
3022
  /**
2890
3023
  * Hook for geo widget cascade functionality
2891
3024
  * Handles geo hierarchy building and cascade behavior
@@ -2915,15 +3048,15 @@ const useGeoWidgetCascade = (options) => {
2915
3048
  ? resolveGeoWidgetLevelValue(state.widget.values, widgetId, dataPath, geoConfig)
2916
3049
  : state.widget.values[widgetId]);
2917
3050
  // Memoize selector to avoid returning new array reference
2918
- const dataSourceOptions = useSelector((state) => state.widget.dataSources[widgetId] ?? EMPTY_DATA_SOURCE);
3051
+ const allDataSources = useSelector((state) => state.widget.dataSources);
2919
3052
  // Register parent chain for upstream-ancestor detection (grandparent → grandchild reset)
2920
3053
  useEffect(() => {
2921
- if (!geoConfig) {
3054
+ if (!geoConfig || typeof dataPath !== 'string') {
2922
3055
  return;
2923
3056
  }
2924
- registerGeoWidgetParent(widgetId, geoConfig.parentWidgetId);
2925
- return () => unregisterGeoWidgetParent(widgetId);
2926
- }, [widgetId, geoConfig]);
3057
+ registerGeoWidget(widgetId, geoConfig, dataPath);
3058
+ return () => unregisterGeoWidget(widgetId);
3059
+ }, [widgetId, geoConfig, dataPath]);
2927
3060
  // Keep in-memory builder in sync with persisted hierarchy (reload, cancel→re-edit, etc.)
2928
3061
  useEffect(() => {
2929
3062
  if (!geoConfig || typeof dataPath !== 'string') {
@@ -3015,18 +3148,20 @@ const useGeoWidgetCascade = (options) => {
3015
3148
  }, [geoConfig, eventBus, dataSource, widgetId, dataPath, dispatch, groupId]);
3016
3149
  // Handle value changes to build hierarchy
3017
3150
  useEffect(() => {
3018
- if (!geoConfig) {
3151
+ if (!geoConfig || typeof dataPath !== 'string') {
3019
3152
  return;
3020
3153
  }
3021
- // Skip if value is undefined (it might still be loading or rehydrating)
3154
+ const { level, isLastLevel } = geoConfig;
3155
+ const groupRegistrations = getGeoWidgetRegistrationsInGroup(groupId);
3156
+ const applyGroupRebuild = () => {
3157
+ const resolveMnemonic = createGeoLevelMnemonicResolver(valuesRef.current, allDataSources);
3158
+ rebuildGeoHierarchyFromRegistrations(groupId, valuesRef.current, groupRegistrations, resolveMnemonic);
3159
+ dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
3160
+ };
3022
3161
  // ONLY clear hierarchy if the value is explicitly null or empty string (user action)
3023
3162
  if (currentValue === null || currentValue === '') {
3024
- const { level, isLastLevel } = geoConfig;
3025
3163
  geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
3026
- // If we have a dataPath, we need to update Redux with the cleared hierarchy
3027
- if (dataPath) {
3028
- dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
3029
- }
3164
+ applyGroupRebuild();
3030
3165
  if (!isLastLevel && eventBus && lastCascadePublishRef.current !== GEO_LEVEL_CLEARED) {
3031
3166
  lastCascadePublishRef.current = GEO_LEVEL_CLEARED;
3032
3167
  eventBus.publish({
@@ -3039,56 +3174,13 @@ const useGeoWidgetCascade = (options) => {
3039
3174
  return;
3040
3175
  }
3041
3176
  if (currentValue === undefined) {
3042
- return; // Skip if undefined (still initializing)
3043
- }
3044
- const { level, isLastLevel } = geoConfig;
3045
- // Check if hierarchy is already built to prevent endless loops
3046
- if (dataPath) {
3047
- const currentHierarchy = getWidgetValue(valuesRef.current, dataPath, widgetId);
3048
- // If hierarchy JSON is already set and matches current value, skip rebuilding
3049
- if (currentHierarchy && typeof currentHierarchy === 'object') {
3050
- // Check if this specific level's value matches the hierarchy
3051
- const hierarchyArray = currentHierarchy.hierarchy || currentHierarchy.geo_code_hierarchy_json?.hierarchy;
3052
- if (Array.isArray(hierarchyArray)) {
3053
- const currentLevelValue = typeof currentValue === 'object'
3054
- ? (currentValue.level_value_id || currentValue.id || currentValue.value)
3055
- : currentValue;
3056
- const levelData = hierarchyArray.find((l) => l.level === geoConfig.level);
3057
- // If this level is already correctly represented in the hierarchy, skip rebuilding
3058
- // String conversion ensures comparison works for mixed types
3059
- if (levelData && String(levelData.level_value_id) === String(currentLevelValue)) {
3060
- return;
3061
- }
3062
- }
3177
+ const hasOwnValue = Object.prototype.hasOwnProperty.call(valuesRef.current, widgetId);
3178
+ if (!hasOwnValue) {
3179
+ return;
3063
3180
  }
3064
3181
  }
3065
- // Extract level_value_id and level_value_mnemonic from current value
3066
- // The value could be the ID itself or an object with id/name
3067
- let level_value_id;
3068
- let level_value_mnemonic;
3069
- if (typeof currentValue === 'string' || typeof currentValue === 'number') {
3070
- // Value is just the ID, need to find mnemonic from data source
3071
- level_value_id = String(currentValue);
3072
- // Try to get mnemonic from data source options
3073
- const option = dataSourceOptions.find((opt) => opt.value === currentValue);
3074
- level_value_mnemonic = option?.label || String(currentValue);
3075
- }
3076
- else if (currentValue && typeof currentValue === 'object') {
3077
- level_value_id = currentValue.level_value_id || currentValue.id || currentValue.value;
3078
- level_value_mnemonic = currentValue.level_value_mnemonic || currentValue.name || currentValue.label;
3079
- }
3080
- else {
3081
- return;
3082
- }
3083
- // When a widget's own value changes, remove this level and all below from hierarchy first
3084
- geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
3085
- // Add level to hierarchy
3086
- geoHierarchyBuilder.addLevel(level, level_value_id, level_value_mnemonic, groupId);
3087
- // Build and store hierarchy JSON on every change
3088
- if (dataPath && geoHierarchyBuilder.buildHierarchyJson(groupId)) {
3089
- dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
3090
- }
3091
- }, [geoConfig, currentValue, widgetId, dataPath, dispatch, dataSourceOptions, groupId]);
3182
+ applyGroupRebuild();
3183
+ }, [geoConfig, currentValue, widgetId, dataPath, dispatch, allDataSources, groupId, eventBus]);
3092
3184
  };
3093
3185
 
3094
3186
  class WidgetRegistry {
@@ -4990,7 +5082,12 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4990
5082
  // This ensures we use the original widget IDs and data paths
4991
5083
  const sectionWidgets = collectWidgets(originalSection.panels);
4992
5084
  const currentState = store.getState().widget;
4993
- const currentSchemaData = currentState.values || {};
5085
+ let currentSchemaData = currentState.values || {};
5086
+ const geoRegistrations = collectGeoWidgetRegistrationsFromWidgets(sectionWidgets, namespace);
5087
+ if (geoRegistrations.length > 0) {
5088
+ currentSchemaData = reconcileGeoHierarchiesInValues(currentSchemaData, geoRegistrations, currentState.dataSources || {});
5089
+ dispatch(setValues(currentSchemaData));
5090
+ }
4994
5091
  const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch, true);
4995
5092
  if (!isSectionValid) {
4996
5093
  return;
@@ -5056,7 +5153,12 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
5056
5153
  if (isDraft !== false && store && onSectionSave) {
5057
5154
  const sectionWidgets = collectWidgets(originalSection.panels);
5058
5155
  const currentState = store.getState().widget;
5059
- const currentSchemaData = currentState.values || {};
5156
+ let currentSchemaData = currentState.values || {};
5157
+ const geoRegistrations = collectGeoWidgetRegistrationsFromWidgets(sectionWidgets, namespace);
5158
+ if (geoRegistrations.length > 0) {
5159
+ currentSchemaData = reconcileGeoHierarchiesInValues(currentSchemaData, geoRegistrations, currentState.dataSources || {});
5160
+ dispatch(setValues(currentSchemaData));
5161
+ }
5060
5162
  const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch, true);
5061
5163
  if (!isSectionValid)
5062
5164
  return;
@@ -8756,10 +8858,12 @@ const SelectWidget = ({ config }) => {
8756
8858
  if (widgetConfig['widget-readonly']) {
8757
8859
  const label = translateConfig(widgetConfig['widget-label']);
8758
8860
  // Find the selected option's label
8759
- const selectedOption = dataSourceOptions.find((option) => option.value === value);
8861
+ const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
8760
8862
  const displayValue = selectedOption
8761
8863
  ? translateConfig(selectedOption.label)
8762
- : (geoDisplayLabel || (value != null && value !== '' ? translateConfig(String(value)) : '-'));
8864
+ : loading
8865
+ ? (geoDisplayLabel || '-')
8866
+ : (geoDisplayLabel || (value != null && value !== '' ? translateConfig(String(value)) : '-'));
8763
8867
  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 }) })] }));
8764
8868
  }
8765
8869
  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 === ''))
@@ -9258,7 +9362,7 @@ const SelectDisplayValue$1 = ({ config, value }) => {
9258
9362
  if (value === null || value === undefined || value === '') {
9259
9363
  return jsxRuntimeExports.jsx("span", { children: "-" });
9260
9364
  }
9261
- const selectedOption = dataSourceOptions.find((option) => option.value === value);
9365
+ const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
9262
9366
  return jsxRuntimeExports.jsx("span", { children: selectedOption ? selectedOption.label : String(value) });
9263
9367
  };
9264
9368
  const TableCellText = ({ config, value, onValueChange }) => {
@@ -9989,7 +10093,7 @@ const SelectDisplayValue = ({ config, value }) => {
9989
10093
  return jsxRuntimeExports.jsx("span", { children: "-" });
9990
10094
  if (value === null || value === undefined || value === '')
9991
10095
  return jsxRuntimeExports.jsx("span", { children: "-" });
9992
- const selectedOption = dataSourceOptions.find((option) => option.value === value);
10096
+ const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
9993
10097
  return jsxRuntimeExports.jsx("span", { children: selectedOption ? selectedOption.label : String(value) });
9994
10098
  };
9995
10099
  /**
@@ -12942,5 +13046,5 @@ const translateUISchema = (schema, translate) => {
12942
13046
  };
12943
13047
  };
12944
13048
 
12945
- export { ArrayWidget, BooleanWidget, CheckboxWidget, CurrencyInputWidget, DateInputWidget, DateTimeInputWidget, DialogTableWidget, DisplayWidget, FileInputWidget, GEO_LEVEL_CLEARED, HeaderSectionWidget, IdAuthenticationWidget, IterableAccordionWidget, JSONEditorPanel, MultiSelectWidget, NumberInputWidget, PanelRenderer, PhoneInputWidget, ProfileWidget, PropertyEditor, RadioWidget, RegisterLookupWidget, ScoresDisplayWidget, SectionBuilder, SectionRenderer, SectionTree, SectionsContainer, SelectWidget, SimpleTableWidget, TableWidget, TextAreaWidget, TextInputWidget, VisualBuilderPanel, WidgetEventBus, WidgetProvider, WidgetRenderer, applyCaseControl, applyDecimalPrecision, applyMask, applySharedGeoHierarchyToValues, createWidgetStore, createZodSchema, defaultTheme, evaluateCondition, evaluateWidgetConditions, filterByCharacterType, formatCurrency, formatDate, formatNumber, formatPhone, formatValue, geoHierarchyBuilder, getApiDataSource, getFormattedNumberLength, getGeoDescendantWidgetIds, getGeoGroupId, getSchemaDataSource, getStaticDataSource, getValueByPath, getWidgetValue, hasVisibilityRules, initI18n, isAllowedKey, isUpstreamGeoAncestor, normalizeNumericDefault, normalizeOptionRules, parseDataPath, parseNumber, registerDefaultWidgets, registerGeoWidgetParent, removeMask, resetAll, resetAndSeedGeoHierarchyFromValues, resetWidget, resolveGeoWidgetLevelLabel, resolveGeoWidgetLevelValue, resolveTheme, resolveWidgetIdValue, seedGeoHierarchyFromValues, setDataSource, setError, setLoading, setTouched, setValue, setValueByPath, setValues, setWidgetValue, shouldEnableWidget, shouldRequireWidget, shouldShowWidget, transformDataSourceOptions, translatePanelConfig, translateUISchema, translateWidgetConfig, unregisterGeoWidgetParent, useBaseWidget, useGeoWidgetCascade, useWidgetCascade, useWidgetContext, useWidgetEventBus, useWidgetTheme, useWidgetTranslation, validateNumericValue, validateWidget, widgetRegistry };
13049
+ export { ArrayWidget, BooleanWidget, CheckboxWidget, CurrencyInputWidget, DateInputWidget, DateTimeInputWidget, DialogTableWidget, DisplayWidget, FileInputWidget, GEO_LEVEL_CLEARED, HeaderSectionWidget, IdAuthenticationWidget, IterableAccordionWidget, JSONEditorPanel, MultiSelectWidget, NumberInputWidget, PanelRenderer, PhoneInputWidget, ProfileWidget, PropertyEditor, RadioWidget, RegisterLookupWidget, ScoresDisplayWidget, SectionBuilder, SectionRenderer, SectionTree, SectionsContainer, SelectWidget, SimpleTableWidget, TableWidget, TextAreaWidget, TextInputWidget, VisualBuilderPanel, WidgetEventBus, WidgetProvider, WidgetRenderer, applyCaseControl, applyDecimalPrecision, applyMask, applySharedGeoHierarchyToValues, collectGeoWidgetRegistrationsFromWidgets, createGeoLevelMnemonicResolver, createWidgetStore, createZodSchema, defaultTheme, evaluateCondition, evaluateWidgetConditions, filterByCharacterType, formatCurrency, formatDate, formatNumber, formatPhone, formatValue, geoHierarchyBuilder, getApiDataSource, getCachedApiDataSource, getFormattedNumberLength, getGeoDescendantWidgetIds, getGeoGroupId, getGeoWidgetRegistrationsInGroup, getSchemaDataSource, getStaticDataSource, getValueByPath, getWidgetValue, hasVisibilityRules, initI18n, isAllowedKey, isUpstreamGeoAncestor, normalizeNumericDefault, normalizeOptionRules, orderGeoWidgetRegistrations, parseDataPath, parseNumber, rebuildGeoHierarchyFromRegistrations, reconcileGeoHierarchiesInValues, registerDefaultWidgets, registerGeoWidget, registerGeoWidgetParent, removeMask, resetAll, resetAndSeedGeoHierarchyFromValues, resetWidget, resolveGeoWidgetLevelLabel, resolveGeoWidgetLevelValue, resolveTheme, resolveWidgetIdValue, seedGeoHierarchyFromValues, setDataSource, setError, setLoading, setTouched, setValue, setValueByPath, setValues, setWidgetValue, shouldEnableWidget, shouldRequireWidget, shouldShowWidget, transformDataSourceOptions, translatePanelConfig, translateUISchema, translateWidgetConfig, unregisterGeoWidget, unregisterGeoWidgetParent, useBaseWidget, useGeoWidgetCascade, useWidgetCascade, useWidgetContext, useWidgetEventBus, useWidgetTheme, useWidgetTranslation, validateNumericValue, validateWidget, widgetRegistry };
12946
13050
  //# sourceMappingURL=index.esm.js.map