@acorex/cdk 20.2.0-next.14 → 20.2.0-next.16
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
|
@@ -653,7 +653,7 @@ declare function convertArrayToDataSource<T = unknown>(items: T[], options?: {
|
|
|
653
653
|
key: string;
|
|
654
654
|
pageSize: number;
|
|
655
655
|
}): AXDataSource<T>;
|
|
656
|
-
declare function applyDataSourceQuery<T extends Record<string,
|
|
656
|
+
declare function applyDataSourceQuery<T extends Record<string, unknown>>(items: T[], filter: AXDataSourceFilterOption): T[];
|
|
657
657
|
|
|
658
658
|
interface AXDataListQuery {
|
|
659
659
|
take?: number;
|
|
@@ -996,8 +996,8 @@ declare abstract class MXSelectionValueComponent<T = unknown> extends MXValueCom
|
|
|
996
996
|
*/
|
|
997
997
|
private createCacheKey;
|
|
998
998
|
/**
|
|
999
|
-
|
|
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
|
|
@@ -1173,18 +1173,34 @@ class AXDataSource {
|
|
|
1173
1173
|
}
|
|
1174
1174
|
}
|
|
1175
1175
|
function convertArrayToDataSource(items, options = { key: 'id', pageSize: 100 }) {
|
|
1176
|
+
// Normalize primitives to objects so consumers (e.g., select/list) can rely on
|
|
1177
|
+
// value and text fields without triggering extra byKey lookups.
|
|
1178
|
+
const normalizedItems = items.map((candidate) => {
|
|
1179
|
+
const isObjectItem = candidate != null && typeof candidate === 'object';
|
|
1180
|
+
if (isObjectItem) {
|
|
1181
|
+
return candidate;
|
|
1182
|
+
}
|
|
1183
|
+
// For primitive values, create an object with both key and text
|
|
1184
|
+
return { [options.key]: candidate, text: String(candidate ?? '') };
|
|
1185
|
+
});
|
|
1176
1186
|
const config = {
|
|
1177
1187
|
key: options.key,
|
|
1178
1188
|
pageSize: options.pageSize,
|
|
1179
1189
|
load: async (e) => {
|
|
1180
|
-
const
|
|
1190
|
+
const itemsForFilter = normalizedItems;
|
|
1191
|
+
const resultNormalized = e.filter
|
|
1192
|
+
? applyDataSourceQuery(itemsForFilter, e.filter)
|
|
1193
|
+
: itemsForFilter;
|
|
1194
|
+
const result = resultNormalized;
|
|
1181
1195
|
return {
|
|
1182
1196
|
items: result.slice(e.skip, e.skip + e.take),
|
|
1183
|
-
total:
|
|
1197
|
+
total: resultNormalized.length,
|
|
1184
1198
|
};
|
|
1185
1199
|
},
|
|
1186
|
-
byKey: async (v) =>
|
|
1187
|
-
|
|
1200
|
+
byKey: async (v) =>
|
|
1201
|
+
// Search in the normalized list (supports primitive and object arrays)
|
|
1202
|
+
normalizedItems.find((record) => {
|
|
1203
|
+
return record[options.key] == v;
|
|
1188
1204
|
}),
|
|
1189
1205
|
};
|
|
1190
1206
|
return new AXDataSource(config);
|
|
@@ -1806,9 +1822,15 @@ class MXSelectionValueComponent extends MXValueComponent {
|
|
|
1806
1822
|
if (value == null || (isArray && value.length === 0)) {
|
|
1807
1823
|
return this.multiple ? [] : null;
|
|
1808
1824
|
}
|
|
1809
|
-
//
|
|
1825
|
+
// Check if items already have text properties to avoid unnecessary findByKey calls
|
|
1810
1826
|
const itemsToNormalize = isArray ? value : [value];
|
|
1811
|
-
const
|
|
1827
|
+
const needsFindByKey = itemsToNormalize.some(item => {
|
|
1828
|
+
if (typeof item === 'object' && item !== null) {
|
|
1829
|
+
return get(item, this.textField) == null;
|
|
1830
|
+
}
|
|
1831
|
+
return true; // Primitive values need findByKey
|
|
1832
|
+
});
|
|
1833
|
+
const normalizedItems = this.normalizeItemsList(itemsToNormalize, needsFindByKey);
|
|
1812
1834
|
if (normalizedItems.length === 0) {
|
|
1813
1835
|
return this.multiple ? [] : null;
|
|
1814
1836
|
}
|
|
@@ -1858,13 +1880,14 @@ class MXSelectionValueComponent extends MXValueComponent {
|
|
|
1858
1880
|
}
|
|
1859
1881
|
const hasTextProperty = !isComplexObject || get(itemRecord, this.textField) != null;
|
|
1860
1882
|
const normalizedObj = {};
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1883
|
+
// If findByKey is true, we need to resolve the item to get its text
|
|
1884
|
+
// This is needed for string values like 'ir', 'us' that don't have text
|
|
1885
|
+
if (findByKey) {
|
|
1886
|
+
this.handleItemNormalization(normalizedObj, item, key, true, cacheKey);
|
|
1864
1887
|
}
|
|
1865
1888
|
else {
|
|
1866
|
-
//
|
|
1867
|
-
|
|
1889
|
+
// Item already has all required properties, just copy them
|
|
1890
|
+
Object.assign(normalizedObj, item);
|
|
1868
1891
|
}
|
|
1869
1892
|
this.dataService.cacheList[cacheKey] = normalizedObj;
|
|
1870
1893
|
return normalizedObj;
|
|
@@ -1891,7 +1914,7 @@ class MXSelectionValueComponent extends MXValueComponent {
|
|
|
1891
1914
|
// Set loading state
|
|
1892
1915
|
obj[this.valueField] = key;
|
|
1893
1916
|
obj['isLoading'] = true;
|
|
1894
|
-
obj[this.textField] = 'Loading';
|
|
1917
|
+
obj[this.textField] = 'Loading...';
|
|
1895
1918
|
promise
|
|
1896
1919
|
.then(result => {
|
|
1897
1920
|
if (typeof result === 'object' && result) {
|
|
@@ -1901,11 +1924,23 @@ class MXSelectionValueComponent extends MXValueComponent {
|
|
|
1901
1924
|
obj[this.valueField] = result || key;
|
|
1902
1925
|
obj[this.textField] = result;
|
|
1903
1926
|
}
|
|
1927
|
+
})
|
|
1928
|
+
.catch(error => {
|
|
1929
|
+
console.warn('Failed to load item by key:', key, error);
|
|
1930
|
+
// On error, keep the original value but mark as failed
|
|
1931
|
+
obj[this.textField] = this.defaultText;
|
|
1904
1932
|
})
|
|
1905
1933
|
.finally(() => {
|
|
1906
1934
|
delete obj['isLoading'];
|
|
1935
|
+
delete obj['_displayTextLoading'];
|
|
1936
|
+
delete obj['_loadingTriggered'];
|
|
1907
1937
|
this.dataService.cacheList[cacheKey] = obj;
|
|
1908
|
-
|
|
1938
|
+
// Use setTimeout to defer change detection and prevent immediate re-rendering
|
|
1939
|
+
setTimeout(() => {
|
|
1940
|
+
if (this.cdr) {
|
|
1941
|
+
this.cdr.markForCheck();
|
|
1942
|
+
}
|
|
1943
|
+
}, 0);
|
|
1909
1944
|
});
|
|
1910
1945
|
}
|
|
1911
1946
|
/**
|
|
@@ -1934,13 +1969,19 @@ class MXSelectionValueComponent extends MXValueComponent {
|
|
|
1934
1969
|
return `k-${key}`;
|
|
1935
1970
|
}
|
|
1936
1971
|
/**
|
|
1937
|
-
|
|
1938
|
-
|
|
1972
|
+
* Normalizes currently selected items and updates the data service
|
|
1973
|
+
*/
|
|
1939
1974
|
_normalizeSelectedItems() {
|
|
1940
1975
|
const values = Array.isArray(this.value)
|
|
1941
1976
|
? this.value
|
|
1942
1977
|
: this.value != null ? [this.value] : [];
|
|
1943
|
-
|
|
1978
|
+
// Only call normalizeItem with findByKey=true if the item doesn't have text
|
|
1979
|
+
// This prevents unnecessary API calls for items that already have text
|
|
1980
|
+
this.dataService.selectedItems = values.map(value => {
|
|
1981
|
+
const hasText = typeof value === 'object' && value !== null &&
|
|
1982
|
+
get(value, this.textField) != null;
|
|
1983
|
+
return this.normalizeItem(value, !hasText);
|
|
1984
|
+
});
|
|
1944
1985
|
}
|
|
1945
1986
|
// #endregion
|
|
1946
1987
|
// #region Public Selection Methods
|
|
@@ -2017,6 +2058,15 @@ class MXSelectionValueComponent extends MXValueComponent {
|
|
|
2017
2058
|
coerceBooleanProperty(get(itemRecord, this.disabledField)) === true ||
|
|
2018
2059
|
(this.disabledCallback?.({ item, index: -1 }) ?? false));
|
|
2019
2060
|
}
|
|
2061
|
+
/**
|
|
2062
|
+
* Checks if an item is currently loading
|
|
2063
|
+
* @param item Item to check
|
|
2064
|
+
* @returns True if the item is loading
|
|
2065
|
+
*/
|
|
2066
|
+
isItemLoading(item) {
|
|
2067
|
+
const normalizedItem = this.normalizeItem(item);
|
|
2068
|
+
return normalizedItem['isLoading'] === true;
|
|
2069
|
+
}
|
|
2020
2070
|
// #endregion
|
|
2021
2071
|
// #region Protected Utility Methods
|
|
2022
2072
|
/**
|
|
@@ -2025,26 +2075,108 @@ class MXSelectionValueComponent extends MXValueComponent {
|
|
|
2025
2075
|
* @returns Formatted display text
|
|
2026
2076
|
*/
|
|
2027
2077
|
getDisplayText(item) {
|
|
2028
|
-
|
|
2078
|
+
// Prevent recursive calls that could cause infinite loops
|
|
2079
|
+
if (item && typeof item === 'object' && item['_displayTextLoading']) {
|
|
2080
|
+
return 'Loading...';
|
|
2081
|
+
}
|
|
2082
|
+
// Add timestamp-based debouncing to prevent excessive calls for ALL item types
|
|
2083
|
+
const now = Date.now();
|
|
2084
|
+
const itemKey = typeof item === 'string' ? item : get(item, this.valueField);
|
|
2085
|
+
const cacheKey = this.createCacheKey(String(itemKey));
|
|
2086
|
+
// Check if we have a recent result in the cache to prevent duplicate processing
|
|
2087
|
+
const cachedResult = this.dataService.cacheList[cacheKey];
|
|
2088
|
+
if (cachedResult && cachedResult['_lastDisplayTextCall']) {
|
|
2089
|
+
const timeSinceLastCall = now - cachedResult['_lastDisplayTextCall'];
|
|
2090
|
+
if (timeSinceLastCall < 50) { // 50ms debounce
|
|
2091
|
+
return cachedResult['_lastDisplayTextResult'] || String(itemKey || '');
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
// For string values, we need to normalize with findByKey=true to trigger async loading
|
|
2095
|
+
const isStringValue = typeof item === 'string';
|
|
2096
|
+
const normalizedItem = this.normalizeItem(item, isStringValue);
|
|
2097
|
+
// Check if this item is already in the cache with proper text
|
|
2098
|
+
const itemValue = get(normalizedItem, this.valueField);
|
|
2099
|
+
if (itemValue != null && itemValue !== '') {
|
|
2100
|
+
const cacheKey = this.createCacheKey(String(itemValue));
|
|
2101
|
+
const cachedItem = this.dataService.cacheList[cacheKey];
|
|
2102
|
+
if (cachedItem && get(cachedItem, this.textField) != null) {
|
|
2103
|
+
// Use cached text if available
|
|
2104
|
+
const cachedText = get(cachedItem, this.textField);
|
|
2105
|
+
if (cachedText && cachedText !== 'Loading...') {
|
|
2106
|
+
const result = String(cachedText);
|
|
2107
|
+
// Cache the timestamp and result for debouncing in the cache (works for all item types)
|
|
2108
|
+
if (cachedItem) {
|
|
2109
|
+
cachedItem['_lastDisplayTextCall'] = now;
|
|
2110
|
+
cachedItem['_lastDisplayTextResult'] = result;
|
|
2111
|
+
}
|
|
2112
|
+
return result;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2029
2116
|
// Try template formatting first
|
|
2030
2117
|
if (this.textTemplate) {
|
|
2031
2118
|
const formattedTemplate = this.formatService.format(this.textTemplate, 'string', normalizedItem);
|
|
2032
2119
|
if (formattedTemplate !== this.textTemplate) {
|
|
2033
|
-
|
|
2120
|
+
const result = formattedTemplate;
|
|
2121
|
+
// Cache the timestamp and result for debouncing in the cache
|
|
2122
|
+
if (normalizedItem) {
|
|
2123
|
+
normalizedItem['_lastDisplayTextCall'] = now;
|
|
2124
|
+
normalizedItem['_lastDisplayTextResult'] = result;
|
|
2125
|
+
}
|
|
2126
|
+
return result;
|
|
2034
2127
|
}
|
|
2035
2128
|
}
|
|
2036
2129
|
// Use text field or fallback to value field
|
|
2037
2130
|
const textValue = get(normalizedItem, this.textField);
|
|
2038
2131
|
if (textValue != null && textValue !== '') {
|
|
2039
|
-
|
|
2132
|
+
const result = String(textValue);
|
|
2133
|
+
// Cache the timestamp and result for debouncing in the cache
|
|
2134
|
+
if (normalizedItem) {
|
|
2135
|
+
normalizedItem['_lastDisplayTextCall'] = now;
|
|
2136
|
+
normalizedItem['_lastDisplayTextResult'] = result;
|
|
2137
|
+
}
|
|
2138
|
+
return result;
|
|
2139
|
+
}
|
|
2140
|
+
// If text is missing and item is loading, show loading state
|
|
2141
|
+
if (normalizedItem['isLoading']) {
|
|
2142
|
+
const result = 'Loading...';
|
|
2143
|
+
// Cache the timestamp and result for debouncing in the cache
|
|
2144
|
+
if (normalizedItem) {
|
|
2145
|
+
normalizedItem['_lastDisplayTextCall'] = now;
|
|
2146
|
+
normalizedItem['_lastDisplayTextResult'] = result;
|
|
2147
|
+
}
|
|
2148
|
+
return result;
|
|
2040
2149
|
}
|
|
2041
2150
|
// Attempt to load item by key if text is missing
|
|
2042
2151
|
const value = get(normalizedItem, this.valueField);
|
|
2043
2152
|
if (value == null || value === '') {
|
|
2044
|
-
|
|
2153
|
+
const result = this.defaultText;
|
|
2154
|
+
// Cache the timestamp and result for debouncing in the cache
|
|
2155
|
+
if (normalizedItem) {
|
|
2156
|
+
normalizedItem['_lastDisplayTextCall'] = now;
|
|
2157
|
+
normalizedItem['_lastDisplayTextResult'] = result;
|
|
2158
|
+
}
|
|
2159
|
+
return result;
|
|
2160
|
+
}
|
|
2161
|
+
// If we have a value but no text, trigger async loading only once
|
|
2162
|
+
// Use a flag to prevent multiple calls
|
|
2163
|
+
if (!normalizedItem['_loadingTriggered'] && !normalizedItem[this.textField]) {
|
|
2164
|
+
normalizedItem['_loadingTriggered'] = true;
|
|
2165
|
+
normalizedItem['_displayTextLoading'] = true;
|
|
2166
|
+
// Use setTimeout to defer the async loading and prevent immediate change detection
|
|
2167
|
+
setTimeout(() => {
|
|
2168
|
+
this.loadAndCacheItemByKey(value);
|
|
2169
|
+
}, 0);
|
|
2170
|
+
}
|
|
2171
|
+
// Return the value itself as a fallback
|
|
2172
|
+
// This will be updated once the async loading completes
|
|
2173
|
+
const result = String(value);
|
|
2174
|
+
// Cache the timestamp and result for debouncing in the cache
|
|
2175
|
+
if (normalizedItem) {
|
|
2176
|
+
normalizedItem['_lastDisplayTextCall'] = now;
|
|
2177
|
+
normalizedItem['_lastDisplayTextResult'] = result;
|
|
2045
2178
|
}
|
|
2046
|
-
|
|
2047
|
-
return String(value);
|
|
2179
|
+
return result;
|
|
2048
2180
|
}
|
|
2049
2181
|
/**
|
|
2050
2182
|
* Gets the value of an item
|
|
@@ -2070,6 +2202,52 @@ class MXSelectionValueComponent extends MXValueComponent {
|
|
|
2070
2202
|
this.dataService.cacheList = {};
|
|
2071
2203
|
this.cdr.markForCheck();
|
|
2072
2204
|
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Clears the debouncing cache for specific items to ensure fresh data is displayed
|
|
2207
|
+
*/
|
|
2208
|
+
clearItemDebouncingCache(item) {
|
|
2209
|
+
// Get the cache key for this item
|
|
2210
|
+
const itemKey = typeof item === 'string' ? item : get(item, this.valueField);
|
|
2211
|
+
if (itemKey != null) {
|
|
2212
|
+
const cacheKey = this.createCacheKey(String(itemKey));
|
|
2213
|
+
const cachedItem = this.dataService.cacheList[cacheKey];
|
|
2214
|
+
if (cachedItem) {
|
|
2215
|
+
delete cachedItem['_lastDisplayTextCall'];
|
|
2216
|
+
delete cachedItem['_lastDisplayTextResult'];
|
|
2217
|
+
delete cachedItem['_displayTextLoading'];
|
|
2218
|
+
delete cachedItem['_loadingTriggered'];
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
/**
|
|
2223
|
+
* Forces a refresh of the display when items are loaded asynchronously
|
|
2224
|
+
* This should be called when the data source is updated
|
|
2225
|
+
*/
|
|
2226
|
+
refreshDisplay() {
|
|
2227
|
+
this.cdr.markForCheck();
|
|
2228
|
+
}
|
|
2229
|
+
/**
|
|
2230
|
+
* Refreshes the display for all selected items to ensure consistency
|
|
2231
|
+
* This is useful when the data source has been updated
|
|
2232
|
+
*/
|
|
2233
|
+
refreshSelectedItemsDisplay() {
|
|
2234
|
+
// Force a refresh of the display without re-normalizing to avoid infinite loops
|
|
2235
|
+
if (this.selectedItems.length > 0) {
|
|
2236
|
+
this.cdr.markForCheck();
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Forces a refresh of all selected items by clearing their debouncing cache
|
|
2241
|
+
* This ensures fresh data is displayed when the data source is updated
|
|
2242
|
+
*/
|
|
2243
|
+
forceRefreshSelectedItems() {
|
|
2244
|
+
if (this.selectedItems.length > 0) {
|
|
2245
|
+
this.selectedItems.forEach(item => {
|
|
2246
|
+
this.clearItemDebouncingCache(item);
|
|
2247
|
+
});
|
|
2248
|
+
this.cdr.markForCheck();
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2073
2251
|
// #endregion
|
|
2074
2252
|
// #region Private Helper Methods
|
|
2075
2253
|
/**
|
|
@@ -2082,12 +2260,29 @@ class MXSelectionValueComponent extends MXValueComponent {
|
|
|
2082
2260
|
if (item) {
|
|
2083
2261
|
const normalizedItem = this.normalizeItem(item);
|
|
2084
2262
|
const cacheKey = this.createCacheKey(String(normalizedItem[this.valueField]));
|
|
2263
|
+
// Update the cache with the loaded item
|
|
2085
2264
|
this.dataService.cacheList[cacheKey] = normalizedItem;
|
|
2086
|
-
|
|
2265
|
+
// Clear loading flags
|
|
2266
|
+
if (this.dataService.cacheList[cacheKey]) {
|
|
2267
|
+
delete this.dataService.cacheList[cacheKey]['_displayTextLoading'];
|
|
2268
|
+
delete this.dataService.cacheList[cacheKey]['_loadingTriggered'];
|
|
2269
|
+
}
|
|
2270
|
+
// Use setTimeout to defer change detection and prevent immediate re-rendering
|
|
2271
|
+
setTimeout(() => {
|
|
2272
|
+
if (this.cdr) {
|
|
2273
|
+
this.cdr.markForCheck();
|
|
2274
|
+
}
|
|
2275
|
+
}, 0);
|
|
2087
2276
|
}
|
|
2088
2277
|
}
|
|
2089
2278
|
catch (error) {
|
|
2090
2279
|
console.warn('Failed to load item by key:', key, error);
|
|
2280
|
+
// Clear loading flags on error too
|
|
2281
|
+
const cacheKey = this.createCacheKey(String(key));
|
|
2282
|
+
if (this.dataService.cacheList[cacheKey]) {
|
|
2283
|
+
delete this.dataService.cacheList[cacheKey]['_displayTextLoading'];
|
|
2284
|
+
delete this.dataService.cacheList[cacheKey]['_loadingTriggered'];
|
|
2285
|
+
}
|
|
2091
2286
|
}
|
|
2092
2287
|
}
|
|
2093
2288
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: MXSelectionValueComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|