@openg2p/registry-widgets 1.1.2-dev.8 → 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
@@ -2244,12 +2244,145 @@ const geoHierarchyBuilder = new GeoHierarchyBuilder();
2244
2244
  /** Sentinel written to Redux when a geo level is cleared by cascade (distinct from "never set"). */
2245
2245
  const GEO_LEVEL_CLEARED = null;
2246
2246
  const geoWidgetParentRegistry = new Map();
2247
+ const geoWidgetConfigRegistry = new Map();
2247
2248
  function registerGeoWidgetParent(widgetId, parentWidgetId) {
2248
2249
  geoWidgetParentRegistry.set(widgetId, parentWidgetId || null);
2249
2250
  }
2250
2251
  function unregisterGeoWidgetParent(widgetId) {
2251
2252
  geoWidgetParentRegistry.delete(widgetId);
2252
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
+ }
2253
2386
  /** True when changedWidgetId is any upstream geo parent of widgetId (not only immediate parent). */
2254
2387
  function isUpstreamGeoAncestor(changedWidgetId, widgetId, immediateParentWidgetId) {
2255
2388
  if (changedWidgetId === widgetId) {
@@ -2344,7 +2477,7 @@ function resetAndSeedGeoHierarchyFromValues(values, dataPath, widgetId, groupId)
2344
2477
 
2345
2478
  // Define stable empty arrays to avoid selector reference issues
2346
2479
  const EMPTY_ERRORS = [];
2347
- const EMPTY_DATA_SOURCE$1 = [];
2480
+ const EMPTY_DATA_SOURCE = [];
2348
2481
  const useBaseWidget = (options) => {
2349
2482
  const { config, dataSourceRequestHandler: propHandler, schemaData, onValueChange } = options;
2350
2483
  const dispatch = reactRedux.useDispatch();
@@ -2359,7 +2492,7 @@ const useBaseWidget = (options) => {
2359
2492
  const errors = reactRedux.useSelector((state) => state.widget.errors[widgetId] ?? EMPTY_ERRORS);
2360
2493
  const touched = reactRedux.useSelector((state) => state.widget.touched[widgetId] || false);
2361
2494
  const loading = reactRedux.useSelector((state) => state.widget.loading[widgetId] || false);
2362
- 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);
2363
2496
  // Skip value handling for layout widgets (they don't store data values)
2364
2497
  // Infer layout from widget-type
2365
2498
  const isLayoutWidget = config['widget-type'] === 'layout';
@@ -2887,8 +3020,6 @@ const useWidgetCascade = (options) => {
2887
3020
  }, [cascadeConfig, eventBus, dataSource, widgetId, dispatch]);
2888
3021
  };
2889
3022
 
2890
- // Define stable empty array to avoid selector reference issues
2891
- const EMPTY_DATA_SOURCE = [];
2892
3023
  /**
2893
3024
  * Hook for geo widget cascade functionality
2894
3025
  * Handles geo hierarchy building and cascade behavior
@@ -2918,15 +3049,15 @@ const useGeoWidgetCascade = (options) => {
2918
3049
  ? resolveGeoWidgetLevelValue(state.widget.values, widgetId, dataPath, geoConfig)
2919
3050
  : state.widget.values[widgetId]);
2920
3051
  // Memoize selector to avoid returning new array reference
2921
- const dataSourceOptions = reactRedux.useSelector((state) => state.widget.dataSources[widgetId] ?? EMPTY_DATA_SOURCE);
3052
+ const allDataSources = reactRedux.useSelector((state) => state.widget.dataSources);
2922
3053
  // Register parent chain for upstream-ancestor detection (grandparent → grandchild reset)
2923
3054
  React.useEffect(() => {
2924
- if (!geoConfig) {
3055
+ if (!geoConfig || typeof dataPath !== 'string') {
2925
3056
  return;
2926
3057
  }
2927
- registerGeoWidgetParent(widgetId, geoConfig.parentWidgetId);
2928
- return () => unregisterGeoWidgetParent(widgetId);
2929
- }, [widgetId, geoConfig]);
3058
+ registerGeoWidget(widgetId, geoConfig, dataPath);
3059
+ return () => unregisterGeoWidget(widgetId);
3060
+ }, [widgetId, geoConfig, dataPath]);
2930
3061
  // Keep in-memory builder in sync with persisted hierarchy (reload, cancel→re-edit, etc.)
2931
3062
  React.useEffect(() => {
2932
3063
  if (!geoConfig || typeof dataPath !== 'string') {
@@ -3018,18 +3149,20 @@ const useGeoWidgetCascade = (options) => {
3018
3149
  }, [geoConfig, eventBus, dataSource, widgetId, dataPath, dispatch, groupId]);
3019
3150
  // Handle value changes to build hierarchy
3020
3151
  React.useEffect(() => {
3021
- if (!geoConfig) {
3152
+ if (!geoConfig || typeof dataPath !== 'string') {
3022
3153
  return;
3023
3154
  }
3024
- // 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
+ };
3025
3162
  // ONLY clear hierarchy if the value is explicitly null or empty string (user action)
3026
3163
  if (currentValue === null || currentValue === '') {
3027
- const { level, isLastLevel } = geoConfig;
3028
3164
  geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
3029
- // If we have a dataPath, we need to update Redux with the cleared hierarchy
3030
- if (dataPath) {
3031
- dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
3032
- }
3165
+ applyGroupRebuild();
3033
3166
  if (!isLastLevel && eventBus && lastCascadePublishRef.current !== GEO_LEVEL_CLEARED) {
3034
3167
  lastCascadePublishRef.current = GEO_LEVEL_CLEARED;
3035
3168
  eventBus.publish({
@@ -3042,56 +3175,13 @@ const useGeoWidgetCascade = (options) => {
3042
3175
  return;
3043
3176
  }
3044
3177
  if (currentValue === undefined) {
3045
- return; // Skip if undefined (still initializing)
3046
- }
3047
- const { level, isLastLevel } = geoConfig;
3048
- // Check if hierarchy is already built to prevent endless loops
3049
- if (dataPath) {
3050
- const currentHierarchy = getWidgetValue(valuesRef.current, dataPath, widgetId);
3051
- // If hierarchy JSON is already set and matches current value, skip rebuilding
3052
- if (currentHierarchy && typeof currentHierarchy === 'object') {
3053
- // Check if this specific level's value matches the hierarchy
3054
- const hierarchyArray = currentHierarchy.hierarchy || currentHierarchy.geo_code_hierarchy_json?.hierarchy;
3055
- if (Array.isArray(hierarchyArray)) {
3056
- const currentLevelValue = typeof currentValue === 'object'
3057
- ? (currentValue.level_value_id || currentValue.id || currentValue.value)
3058
- : currentValue;
3059
- const levelData = hierarchyArray.find((l) => l.level === geoConfig.level);
3060
- // If this level is already correctly represented in the hierarchy, skip rebuilding
3061
- // String conversion ensures comparison works for mixed types
3062
- if (levelData && String(levelData.level_value_id) === String(currentLevelValue)) {
3063
- return;
3064
- }
3065
- }
3178
+ const hasOwnValue = Object.prototype.hasOwnProperty.call(valuesRef.current, widgetId);
3179
+ if (!hasOwnValue) {
3180
+ return;
3066
3181
  }
3067
3182
  }
3068
- // Extract level_value_id and level_value_mnemonic from current value
3069
- // The value could be the ID itself or an object with id/name
3070
- let level_value_id;
3071
- let level_value_mnemonic;
3072
- if (typeof currentValue === 'string' || typeof currentValue === 'number') {
3073
- // Value is just the ID, need to find mnemonic from data source
3074
- level_value_id = String(currentValue);
3075
- // Try to get mnemonic from data source options
3076
- const option = dataSourceOptions.find((opt) => opt.value === currentValue);
3077
- level_value_mnemonic = option?.label || String(currentValue);
3078
- }
3079
- else if (currentValue && typeof currentValue === 'object') {
3080
- level_value_id = currentValue.level_value_id || currentValue.id || currentValue.value;
3081
- level_value_mnemonic = currentValue.level_value_mnemonic || currentValue.name || currentValue.label;
3082
- }
3083
- else {
3084
- return;
3085
- }
3086
- // When a widget's own value changes, remove this level and all below from hierarchy first
3087
- geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
3088
- // Add level to hierarchy
3089
- geoHierarchyBuilder.addLevel(level, level_value_id, level_value_mnemonic, groupId);
3090
- // Build and store hierarchy JSON on every change
3091
- if (dataPath && geoHierarchyBuilder.buildHierarchyJson(groupId)) {
3092
- dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
3093
- }
3094
- }, [geoConfig, currentValue, widgetId, dataPath, dispatch, dataSourceOptions, groupId]);
3183
+ applyGroupRebuild();
3184
+ }, [geoConfig, currentValue, widgetId, dataPath, dispatch, allDataSources, groupId, eventBus]);
3095
3185
  };
3096
3186
 
3097
3187
  class WidgetRegistry {
@@ -4993,7 +5083,12 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4993
5083
  // This ensures we use the original widget IDs and data paths
4994
5084
  const sectionWidgets = collectWidgets(originalSection.panels);
4995
5085
  const currentState = store.getState().widget;
4996
- 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
+ }
4997
5092
  const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch, true);
4998
5093
  if (!isSectionValid) {
4999
5094
  return;
@@ -5059,7 +5154,12 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
5059
5154
  if (isDraft !== false && store && onSectionSave) {
5060
5155
  const sectionWidgets = collectWidgets(originalSection.panels);
5061
5156
  const currentState = store.getState().widget;
5062
- 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
+ }
5063
5163
  const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch, true);
5064
5164
  if (!isSectionValid)
5065
5165
  return;
@@ -12988,6 +13088,8 @@ exports.applyCaseControl = applyCaseControl;
12988
13088
  exports.applyDecimalPrecision = applyDecimalPrecision;
12989
13089
  exports.applyMask = applyMask;
12990
13090
  exports.applySharedGeoHierarchyToValues = applySharedGeoHierarchyToValues;
13091
+ exports.collectGeoWidgetRegistrationsFromWidgets = collectGeoWidgetRegistrationsFromWidgets;
13092
+ exports.createGeoLevelMnemonicResolver = createGeoLevelMnemonicResolver;
12991
13093
  exports.createWidgetStore = createWidgetStore;
12992
13094
  exports.createZodSchema = createZodSchema;
12993
13095
  exports.defaultTheme = defaultTheme;
@@ -13005,6 +13107,7 @@ exports.getCachedApiDataSource = getCachedApiDataSource;
13005
13107
  exports.getFormattedNumberLength = getFormattedNumberLength;
13006
13108
  exports.getGeoDescendantWidgetIds = getGeoDescendantWidgetIds;
13007
13109
  exports.getGeoGroupId = getGeoGroupId;
13110
+ exports.getGeoWidgetRegistrationsInGroup = getGeoWidgetRegistrationsInGroup;
13008
13111
  exports.getSchemaDataSource = getSchemaDataSource;
13009
13112
  exports.getStaticDataSource = getStaticDataSource;
13010
13113
  exports.getValueByPath = getValueByPath;
@@ -13015,9 +13118,13 @@ exports.isAllowedKey = isAllowedKey;
13015
13118
  exports.isUpstreamGeoAncestor = isUpstreamGeoAncestor;
13016
13119
  exports.normalizeNumericDefault = normalizeNumericDefault;
13017
13120
  exports.normalizeOptionRules = normalizeOptionRules;
13121
+ exports.orderGeoWidgetRegistrations = orderGeoWidgetRegistrations;
13018
13122
  exports.parseDataPath = parseDataPath;
13019
13123
  exports.parseNumber = parseNumber;
13124
+ exports.rebuildGeoHierarchyFromRegistrations = rebuildGeoHierarchyFromRegistrations;
13125
+ exports.reconcileGeoHierarchiesInValues = reconcileGeoHierarchiesInValues;
13020
13126
  exports.registerDefaultWidgets = registerDefaultWidgets;
13127
+ exports.registerGeoWidget = registerGeoWidget;
13021
13128
  exports.registerGeoWidgetParent = registerGeoWidgetParent;
13022
13129
  exports.removeMask = removeMask;
13023
13130
  exports.resetAll = resetAll;
@@ -13043,6 +13150,7 @@ exports.transformDataSourceOptions = transformDataSourceOptions;
13043
13150
  exports.translatePanelConfig = translatePanelConfig;
13044
13151
  exports.translateUISchema = translateUISchema;
13045
13152
  exports.translateWidgetConfig = translateWidgetConfig;
13153
+ exports.unregisterGeoWidget = unregisterGeoWidget;
13046
13154
  exports.unregisterGeoWidgetParent = unregisterGeoWidgetParent;
13047
13155
  exports.useBaseWidget = useBaseWidget;
13048
13156
  exports.useGeoWidgetCascade = useGeoWidgetCascade;