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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -2701,9 +2697,9 @@ const useBaseWidget = (options) => {
2701
2697
  if (!dataSource) {
2702
2698
  return;
2703
2699
  }
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) {
2700
+ const loadApiInReadonly = !!geoConfig ||
2701
+ ['select', 'radio', 'checkbox', 'multi-select'].includes(config.widget);
2702
+ if (dataSource.type === 'api' && isReadonly && !loadApiInReadonly) {
2707
2703
  return;
2708
2704
  }
2709
2705
  // For widgets with dependencies, check if dependency value exists
@@ -2745,6 +2741,30 @@ const useBaseWidget = (options) => {
2745
2741
  // React will call this effect again when the handler is ready
2746
2742
  return;
2747
2743
  }
2744
+ const resolveOptionKeys = () => {
2745
+ if (dataSource.type === 'static') {
2746
+ return { valueKey: undefined, labelKey: undefined };
2747
+ }
2748
+ if (geoConfig) {
2749
+ return {
2750
+ valueKey: dataSource.valueKey || 'level_value_id',
2751
+ labelKey: dataSource.labelKey || 'level_value_mnemonic',
2752
+ };
2753
+ }
2754
+ return { valueKey: dataSource.valueKey, labelKey: dataSource.labelKey };
2755
+ };
2756
+ if (dataSource.type === 'api') {
2757
+ const levelId = geoConfig?.level;
2758
+ const cached = getCachedApiDataSource(dataSource, valuesRef.current, levelId);
2759
+ if (cached) {
2760
+ const { valueKey, labelKey } = resolveOptionKeys();
2761
+ dispatch(setDataSource({
2762
+ widgetId,
2763
+ data: transformDataSourceOptions(cached, valueKey, labelKey),
2764
+ }));
2765
+ return;
2766
+ }
2767
+ }
2748
2768
  dispatch(setLoading({ widgetId, loading: true }));
2749
2769
  let data = [];
2750
2770
  if (dataSource.type === 'static') {
@@ -2757,31 +2777,13 @@ const useBaseWidget = (options) => {
2757
2777
  dispatch(setDataSource({ widgetId, data: [] }));
2758
2778
  return;
2759
2779
  }
2760
- // Extract level_id from widget-geo-config.level if available
2761
2780
  const levelId = geoConfig?.level;
2762
2781
  data = await getApiDataSource(dataSource, valuesRef.current, currentHandler, levelId);
2763
2782
  }
2764
2783
  else if (dataSource.type === 'schema') {
2765
2784
  data = getSchemaDataSource(dataSource, schemaData || {});
2766
2785
  }
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
- }
2786
+ const { valueKey, labelKey } = resolveOptionKeys();
2785
2787
  const transformed = transformDataSourceOptions(data, valueKey, labelKey);
2786
2788
  dispatch(setDataSource({ widgetId, data: transformed }));
2787
2789
  }
@@ -8757,10 +8759,12 @@ const SelectWidget = ({ config }) => {
8757
8759
  if (widgetConfig['widget-readonly']) {
8758
8760
  const label = translateConfig(widgetConfig['widget-label']);
8759
8761
  // Find the selected option's label
8760
- const selectedOption = dataSourceOptions.find((option) => option.value === value);
8762
+ const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
8761
8763
  const displayValue = selectedOption
8762
8764
  ? translateConfig(selectedOption.label)
8763
- : (geoDisplayLabel || (value != null && value !== '' ? translateConfig(String(value)) : '-'));
8765
+ : loading
8766
+ ? (geoDisplayLabel || '-')
8767
+ : (geoDisplayLabel || (value != null && value !== '' ? translateConfig(String(value)) : '-'));
8764
8768
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] SelectDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
8765
8769
  }
8766
8770
  return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("select", { value: value || '', onChange: (e) => onChange(e.target.value === '' ? undefined : e.target.value), onBlur: onBlur, disabled: !isEnabled || loading || widgetConfig['widget-readonly'], className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (!value || value === ''))
@@ -9259,7 +9263,7 @@ const SelectDisplayValue$1 = ({ config, value }) => {
9259
9263
  if (value === null || value === undefined || value === '') {
9260
9264
  return jsxRuntimeExports.jsx("span", { children: "-" });
9261
9265
  }
9262
- const selectedOption = dataSourceOptions.find((option) => option.value === value);
9266
+ const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
9263
9267
  return jsxRuntimeExports.jsx("span", { children: selectedOption ? selectedOption.label : String(value) });
9264
9268
  };
9265
9269
  const TableCellText = ({ config, value, onValueChange }) => {
@@ -9990,7 +9994,7 @@ const SelectDisplayValue = ({ config, value }) => {
9990
9994
  return jsxRuntimeExports.jsx("span", { children: "-" });
9991
9995
  if (value === null || value === undefined || value === '')
9992
9996
  return jsxRuntimeExports.jsx("span", { children: "-" });
9993
- const selectedOption = dataSourceOptions.find((option) => option.value === value);
9997
+ const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
9994
9998
  return jsxRuntimeExports.jsx("span", { children: selectedOption ? selectedOption.label : String(value) });
9995
9999
  };
9996
10000
  /**
@@ -12997,6 +13001,7 @@ exports.formatPhone = formatPhone;
12997
13001
  exports.formatValue = formatValue;
12998
13002
  exports.geoHierarchyBuilder = geoHierarchyBuilder;
12999
13003
  exports.getApiDataSource = getApiDataSource;
13004
+ exports.getCachedApiDataSource = getCachedApiDataSource;
13000
13005
  exports.getFormattedNumberLength = getFormattedNumberLength;
13001
13006
  exports.getGeoDescendantWidgetIds = getGeoDescendantWidgetIds;
13002
13007
  exports.getGeoGroupId = getGeoGroupId;