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