@acorex/cdk 20.2.0-next.13 → 20.2.0-next.15

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/common/index.d.ts CHANGED
@@ -996,8 +996,8 @@ declare abstract class MXSelectionValueComponent<T = unknown> extends MXValueCom
996
996
  */
997
997
  private createCacheKey;
998
998
  /**
999
- * Normalizes currently selected items and updates the data service
1000
- */
999
+ * Normalizes currently selected items and updates the data service
1000
+ */
1001
1001
  private _normalizeSelectedItems;
1002
1002
  /**
1003
1003
  * Unselects the specified items from the selection
@@ -1026,6 +1026,12 @@ declare abstract class MXSelectionValueComponent<T = unknown> extends MXValueCom
1026
1026
  * @returns True if the item is disabled
1027
1027
  */
1028
1028
  isItemDisabled(item: T): boolean;
1029
+ /**
1030
+ * Checks if an item is currently loading
1031
+ * @param item Item to check
1032
+ * @returns True if the item is loading
1033
+ */
1034
+ isItemLoading(item: T): boolean;
1029
1035
  /**
1030
1036
  * Gets the display text for an item using template or text field
1031
1037
  * @param item Item to get display text for
@@ -1046,6 +1052,25 @@ declare abstract class MXSelectionValueComponent<T = unknown> extends MXValueCom
1046
1052
  * Clears only the cache while preserving selected items
1047
1053
  */
1048
1054
  protected softClearSelectionCache(): void;
1055
+ /**
1056
+ * Clears the debouncing cache for specific items to ensure fresh data is displayed
1057
+ */
1058
+ protected clearItemDebouncingCache(item: T): void;
1059
+ /**
1060
+ * Forces a refresh of the display when items are loaded asynchronously
1061
+ * This should be called when the data source is updated
1062
+ */
1063
+ protected refreshDisplay(): void;
1064
+ /**
1065
+ * Refreshes the display for all selected items to ensure consistency
1066
+ * This is useful when the data source has been updated
1067
+ */
1068
+ protected refreshSelectedItemsDisplay(): void;
1069
+ /**
1070
+ * Forces a refresh of all selected items by clearing their debouncing cache
1071
+ * This ensures fresh data is displayed when the data source is updated
1072
+ */
1073
+ protected forceRefreshSelectedItems(): void;
1049
1074
  /**
1050
1075
  * Asynchronously loads an item by its key and caches the result
1051
1076
  * @param key Key to load item by
@@ -1806,9 +1806,15 @@ class MXSelectionValueComponent extends MXValueComponent {
1806
1806
  if (value == null || (isArray && value.length === 0)) {
1807
1807
  return this.multiple ? [] : null;
1808
1808
  }
1809
- // Normalize items and find by key if needed
1809
+ // Check if items already have text properties to avoid unnecessary findByKey calls
1810
1810
  const itemsToNormalize = isArray ? value : [value];
1811
- const normalizedItems = this.normalizeItemsList(itemsToNormalize, true);
1811
+ const needsFindByKey = itemsToNormalize.some(item => {
1812
+ if (typeof item === 'object' && item !== null) {
1813
+ return get(item, this.textField) == null;
1814
+ }
1815
+ return true; // Primitive values need findByKey
1816
+ });
1817
+ const normalizedItems = this.normalizeItemsList(itemsToNormalize, needsFindByKey);
1812
1818
  if (normalizedItems.length === 0) {
1813
1819
  return this.multiple ? [] : null;
1814
1820
  }
@@ -1858,13 +1864,14 @@ class MXSelectionValueComponent extends MXValueComponent {
1858
1864
  }
1859
1865
  const hasTextProperty = !isComplexObject || get(itemRecord, this.textField) != null;
1860
1866
  const normalizedObj = {};
1861
- if (isComplexObject && hasTextProperty) {
1862
- // Item already has all required properties
1863
- Object.assign(normalizedObj, item);
1867
+ // If findByKey is true, we need to resolve the item to get its text
1868
+ // This is needed for string values like 'ir', 'us' that don't have text
1869
+ if (findByKey) {
1870
+ this.handleItemNormalization(normalizedObj, item, key, true, cacheKey);
1864
1871
  }
1865
1872
  else {
1866
- // Need to find or create item properties
1867
- this.handleItemNormalization(normalizedObj, item, key, findByKey, cacheKey);
1873
+ // Item already has all required properties, just copy them
1874
+ Object.assign(normalizedObj, item);
1868
1875
  }
1869
1876
  this.dataService.cacheList[cacheKey] = normalizedObj;
1870
1877
  return normalizedObj;
@@ -1891,7 +1898,7 @@ class MXSelectionValueComponent extends MXValueComponent {
1891
1898
  // Set loading state
1892
1899
  obj[this.valueField] = key;
1893
1900
  obj['isLoading'] = true;
1894
- obj[this.textField] = 'Loading';
1901
+ obj[this.textField] = 'Loading...';
1895
1902
  promise
1896
1903
  .then(result => {
1897
1904
  if (typeof result === 'object' && result) {
@@ -1901,11 +1908,23 @@ class MXSelectionValueComponent extends MXValueComponent {
1901
1908
  obj[this.valueField] = result || key;
1902
1909
  obj[this.textField] = result;
1903
1910
  }
1911
+ })
1912
+ .catch(error => {
1913
+ console.warn('Failed to load item by key:', key, error);
1914
+ // On error, keep the original value but mark as failed
1915
+ obj[this.textField] = this.defaultText;
1904
1916
  })
1905
1917
  .finally(() => {
1906
1918
  delete obj['isLoading'];
1919
+ delete obj['_displayTextLoading'];
1920
+ delete obj['_loadingTriggered'];
1907
1921
  this.dataService.cacheList[cacheKey] = obj;
1908
- this.cdr.markForCheck();
1922
+ // Use setTimeout to defer change detection and prevent immediate re-rendering
1923
+ setTimeout(() => {
1924
+ if (this.cdr) {
1925
+ this.cdr.markForCheck();
1926
+ }
1927
+ }, 0);
1909
1928
  });
1910
1929
  }
1911
1930
  /**
@@ -1934,13 +1953,19 @@ class MXSelectionValueComponent extends MXValueComponent {
1934
1953
  return `k-${key}`;
1935
1954
  }
1936
1955
  /**
1937
- * Normalizes currently selected items and updates the data service
1938
- */
1956
+ * Normalizes currently selected items and updates the data service
1957
+ */
1939
1958
  _normalizeSelectedItems() {
1940
1959
  const values = Array.isArray(this.value)
1941
1960
  ? this.value
1942
1961
  : this.value != null ? [this.value] : [];
1943
- this.dataService.selectedItems = values.map(value => this.normalizeItem(value));
1962
+ // Only call normalizeItem with findByKey=true if the item doesn't have text
1963
+ // This prevents unnecessary API calls for items that already have text
1964
+ this.dataService.selectedItems = values.map(value => {
1965
+ const hasText = typeof value === 'object' && value !== null &&
1966
+ get(value, this.textField) != null;
1967
+ return this.normalizeItem(value, !hasText);
1968
+ });
1944
1969
  }
1945
1970
  // #endregion
1946
1971
  // #region Public Selection Methods
@@ -2017,6 +2042,15 @@ class MXSelectionValueComponent extends MXValueComponent {
2017
2042
  coerceBooleanProperty(get(itemRecord, this.disabledField)) === true ||
2018
2043
  (this.disabledCallback?.({ item, index: -1 }) ?? false));
2019
2044
  }
2045
+ /**
2046
+ * Checks if an item is currently loading
2047
+ * @param item Item to check
2048
+ * @returns True if the item is loading
2049
+ */
2050
+ isItemLoading(item) {
2051
+ const normalizedItem = this.normalizeItem(item);
2052
+ return normalizedItem['isLoading'] === true;
2053
+ }
2020
2054
  // #endregion
2021
2055
  // #region Protected Utility Methods
2022
2056
  /**
@@ -2025,26 +2059,108 @@ class MXSelectionValueComponent extends MXValueComponent {
2025
2059
  * @returns Formatted display text
2026
2060
  */
2027
2061
  getDisplayText(item) {
2028
- const normalizedItem = this.normalizeItem(item);
2062
+ // Prevent recursive calls that could cause infinite loops
2063
+ if (item && typeof item === 'object' && item['_displayTextLoading']) {
2064
+ return 'Loading...';
2065
+ }
2066
+ // Add timestamp-based debouncing to prevent excessive calls for ALL item types
2067
+ const now = Date.now();
2068
+ const itemKey = typeof item === 'string' ? item : get(item, this.valueField);
2069
+ const cacheKey = this.createCacheKey(String(itemKey));
2070
+ // Check if we have a recent result in the cache to prevent duplicate processing
2071
+ const cachedResult = this.dataService.cacheList[cacheKey];
2072
+ if (cachedResult && cachedResult['_lastDisplayTextCall']) {
2073
+ const timeSinceLastCall = now - cachedResult['_lastDisplayTextCall'];
2074
+ if (timeSinceLastCall < 50) { // 50ms debounce
2075
+ return cachedResult['_lastDisplayTextResult'] || String(itemKey || '');
2076
+ }
2077
+ }
2078
+ // For string values, we need to normalize with findByKey=true to trigger async loading
2079
+ const isStringValue = typeof item === 'string';
2080
+ const normalizedItem = this.normalizeItem(item, isStringValue);
2081
+ // Check if this item is already in the cache with proper text
2082
+ const itemValue = get(normalizedItem, this.valueField);
2083
+ if (itemValue != null && itemValue !== '') {
2084
+ const cacheKey = this.createCacheKey(String(itemValue));
2085
+ const cachedItem = this.dataService.cacheList[cacheKey];
2086
+ if (cachedItem && get(cachedItem, this.textField) != null) {
2087
+ // Use cached text if available
2088
+ const cachedText = get(cachedItem, this.textField);
2089
+ if (cachedText && cachedText !== 'Loading...') {
2090
+ const result = String(cachedText);
2091
+ // Cache the timestamp and result for debouncing in the cache (works for all item types)
2092
+ if (cachedItem) {
2093
+ cachedItem['_lastDisplayTextCall'] = now;
2094
+ cachedItem['_lastDisplayTextResult'] = result;
2095
+ }
2096
+ return result;
2097
+ }
2098
+ }
2099
+ }
2029
2100
  // Try template formatting first
2030
2101
  if (this.textTemplate) {
2031
2102
  const formattedTemplate = this.formatService.format(this.textTemplate, 'string', normalizedItem);
2032
2103
  if (formattedTemplate !== this.textTemplate) {
2033
- return formattedTemplate;
2104
+ const result = formattedTemplate;
2105
+ // Cache the timestamp and result for debouncing in the cache
2106
+ if (normalizedItem) {
2107
+ normalizedItem['_lastDisplayTextCall'] = now;
2108
+ normalizedItem['_lastDisplayTextResult'] = result;
2109
+ }
2110
+ return result;
2034
2111
  }
2035
2112
  }
2036
2113
  // Use text field or fallback to value field
2037
2114
  const textValue = get(normalizedItem, this.textField);
2038
2115
  if (textValue != null && textValue !== '') {
2039
- return String(textValue);
2116
+ const result = String(textValue);
2117
+ // Cache the timestamp and result for debouncing in the cache
2118
+ if (normalizedItem) {
2119
+ normalizedItem['_lastDisplayTextCall'] = now;
2120
+ normalizedItem['_lastDisplayTextResult'] = result;
2121
+ }
2122
+ return result;
2123
+ }
2124
+ // If text is missing and item is loading, show loading state
2125
+ if (normalizedItem['isLoading']) {
2126
+ const result = 'Loading...';
2127
+ // Cache the timestamp and result for debouncing in the cache
2128
+ if (normalizedItem) {
2129
+ normalizedItem['_lastDisplayTextCall'] = now;
2130
+ normalizedItem['_lastDisplayTextResult'] = result;
2131
+ }
2132
+ return result;
2040
2133
  }
2041
2134
  // Attempt to load item by key if text is missing
2042
2135
  const value = get(normalizedItem, this.valueField);
2043
2136
  if (value == null || value === '') {
2044
- return this.defaultText;
2137
+ const result = this.defaultText;
2138
+ // Cache the timestamp and result for debouncing in the cache
2139
+ if (normalizedItem) {
2140
+ normalizedItem['_lastDisplayTextCall'] = now;
2141
+ normalizedItem['_lastDisplayTextResult'] = result;
2142
+ }
2143
+ return result;
2045
2144
  }
2046
- this.loadAndCacheItemByKey(value);
2047
- return String(value);
2145
+ // If we have a value but no text, trigger async loading only once
2146
+ // Use a flag to prevent multiple calls
2147
+ if (!normalizedItem['_loadingTriggered'] && !normalizedItem[this.textField]) {
2148
+ normalizedItem['_loadingTriggered'] = true;
2149
+ normalizedItem['_displayTextLoading'] = true;
2150
+ // Use setTimeout to defer the async loading and prevent immediate change detection
2151
+ setTimeout(() => {
2152
+ this.loadAndCacheItemByKey(value);
2153
+ }, 0);
2154
+ }
2155
+ // Return the value itself as a fallback
2156
+ // This will be updated once the async loading completes
2157
+ const result = String(value);
2158
+ // Cache the timestamp and result for debouncing in the cache
2159
+ if (normalizedItem) {
2160
+ normalizedItem['_lastDisplayTextCall'] = now;
2161
+ normalizedItem['_lastDisplayTextResult'] = result;
2162
+ }
2163
+ return result;
2048
2164
  }
2049
2165
  /**
2050
2166
  * Gets the value of an item
@@ -2070,6 +2186,52 @@ class MXSelectionValueComponent extends MXValueComponent {
2070
2186
  this.dataService.cacheList = {};
2071
2187
  this.cdr.markForCheck();
2072
2188
  }
2189
+ /**
2190
+ * Clears the debouncing cache for specific items to ensure fresh data is displayed
2191
+ */
2192
+ clearItemDebouncingCache(item) {
2193
+ // Get the cache key for this item
2194
+ const itemKey = typeof item === 'string' ? item : get(item, this.valueField);
2195
+ if (itemKey != null) {
2196
+ const cacheKey = this.createCacheKey(String(itemKey));
2197
+ const cachedItem = this.dataService.cacheList[cacheKey];
2198
+ if (cachedItem) {
2199
+ delete cachedItem['_lastDisplayTextCall'];
2200
+ delete cachedItem['_lastDisplayTextResult'];
2201
+ delete cachedItem['_displayTextLoading'];
2202
+ delete cachedItem['_loadingTriggered'];
2203
+ }
2204
+ }
2205
+ }
2206
+ /**
2207
+ * Forces a refresh of the display when items are loaded asynchronously
2208
+ * This should be called when the data source is updated
2209
+ */
2210
+ refreshDisplay() {
2211
+ this.cdr.markForCheck();
2212
+ }
2213
+ /**
2214
+ * Refreshes the display for all selected items to ensure consistency
2215
+ * This is useful when the data source has been updated
2216
+ */
2217
+ refreshSelectedItemsDisplay() {
2218
+ // Force a refresh of the display without re-normalizing to avoid infinite loops
2219
+ if (this.selectedItems.length > 0) {
2220
+ this.cdr.markForCheck();
2221
+ }
2222
+ }
2223
+ /**
2224
+ * Forces a refresh of all selected items by clearing their debouncing cache
2225
+ * This ensures fresh data is displayed when the data source is updated
2226
+ */
2227
+ forceRefreshSelectedItems() {
2228
+ if (this.selectedItems.length > 0) {
2229
+ this.selectedItems.forEach(item => {
2230
+ this.clearItemDebouncingCache(item);
2231
+ });
2232
+ this.cdr.markForCheck();
2233
+ }
2234
+ }
2073
2235
  // #endregion
2074
2236
  // #region Private Helper Methods
2075
2237
  /**
@@ -2082,12 +2244,29 @@ class MXSelectionValueComponent extends MXValueComponent {
2082
2244
  if (item) {
2083
2245
  const normalizedItem = this.normalizeItem(item);
2084
2246
  const cacheKey = this.createCacheKey(String(normalizedItem[this.valueField]));
2247
+ // Update the cache with the loaded item
2085
2248
  this.dataService.cacheList[cacheKey] = normalizedItem;
2086
- this.cdr.markForCheck();
2249
+ // Clear loading flags
2250
+ if (this.dataService.cacheList[cacheKey]) {
2251
+ delete this.dataService.cacheList[cacheKey]['_displayTextLoading'];
2252
+ delete this.dataService.cacheList[cacheKey]['_loadingTriggered'];
2253
+ }
2254
+ // Use setTimeout to defer change detection and prevent immediate re-rendering
2255
+ setTimeout(() => {
2256
+ if (this.cdr) {
2257
+ this.cdr.markForCheck();
2258
+ }
2259
+ }, 0);
2087
2260
  }
2088
2261
  }
2089
2262
  catch (error) {
2090
2263
  console.warn('Failed to load item by key:', key, error);
2264
+ // Clear loading flags on error too
2265
+ const cacheKey = this.createCacheKey(String(key));
2266
+ if (this.dataService.cacheList[cacheKey]) {
2267
+ delete this.dataService.cacheList[cacheKey]['_displayTextLoading'];
2268
+ delete this.dataService.cacheList[cacheKey]['_loadingTriggered'];
2269
+ }
2091
2270
  }
2092
2271
  }
2093
2272
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: MXSelectionValueComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }