@controleonline/ui-default 1.0.279 → 1.0.281

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.
@@ -19,7 +19,7 @@ import DefaultCompactDialog from './DefaultCompactDialog';
19
19
 
20
20
  const DefaultColumnMenu = ({ storeName, visible = false, onClose }) => {
21
21
  const store = useStore(storeName);
22
- const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
22
+ const columns = Array.isArray(store?.getters?.configs?.presentationColumns) ? store.getters.configs.presentationColumns : Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
23
23
  const visibleColumns = store?.getters?.visibleColumns || {};
24
24
  const configs = store?.getters?.configs || {};
25
25
  const tablePreferenceScope =
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { Modal, Text, TouchableOpacity, View } from 'react-native';
2
+ import { KeyboardAvoidingView, Modal, Platform, Text, TouchableOpacity, View } from 'react-native';
3
3
  import Icon from 'react-native-vector-icons/Feather';
4
4
  import DefaultSearch from '../filters/DefaultSearch';
5
5
  import styles from './DefaultTable.styles';
@@ -11,6 +11,11 @@ const DefaultSearchModal = ({
11
11
  storeName,
12
12
  visible = false,
13
13
  onClose,
14
+ onSearch,
15
+ filters,
16
+ onChangeFilters,
17
+ value,
18
+ title,
14
19
  }) => {
15
20
  const { modalColors } = useDefaultTableTheme();
16
21
  if (!visible) return null;
@@ -25,11 +30,12 @@ const DefaultSearchModal = ({
25
30
 
26
31
  return (
27
32
  <Modal visible transparent animationType="fade" onRequestClose={onClose}>
28
- <View style={[styles.modalOverlay, { backgroundColor: overlayColor }]}>
33
+ <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : Platform.OS === 'android' ? 'height' : undefined}
34
+ style={[styles.modalOverlay, { backgroundColor: overlayColor }]}>
29
35
  <View style={[styles.modalCard, styles.searchModalCard, { borderColor, backgroundColor }]}>
30
36
  <View style={[styles.modalHeader, { borderBottomColor: borderColor }]}>
31
37
  <Text style={[styles.modalTitle, { color: headerTextColor }]} numberOfLines={1}>
32
- {global.t?.t(storeName, 'label', 'search')}
38
+ {title || global.t?.t(storeName, 'label', 'search')}
33
39
  </Text>
34
40
  <TouchableOpacity
35
41
  style={[styles.modalCloseButton, { borderColor, backgroundColor }]}
@@ -45,11 +51,14 @@ const DefaultSearchModal = ({
45
51
  placeholder={searchPlaceholder}
46
52
  searchKey={searchKey}
47
53
  storeName={storeName}
48
- onSearch={onClose}
54
+ filters={filters}
55
+ onChangeFilters={onChangeFilters}
56
+ value={value}
57
+ onSearch={query => {onSearch?.(query); onClose?.();}}
49
58
  />
50
59
  </View>
51
60
  </View>
52
- </View>
61
+ </KeyboardAvoidingView>
53
62
  </Modal>
54
63
  );
55
64
  };
@@ -17,18 +17,15 @@ import {
17
17
  import useDefaultTableTheme from './useDefaultTableTheme';
18
18
  import DefaultTableView from './DefaultTableView';
19
19
  import useDefaultTableCreateForm from './useDefaultTableCreateForm';
20
-
21
20
  import {
22
- assignGetterValue,
21
+ assignGetterValue, publishStoreValue,
23
22
  useDefaultTableStoreSync,
24
23
  } from './useDefaultTableStoreSync';
25
-
26
24
  export {
27
25
  mergeSortedDataWithLiveItems,
28
26
  resolveColumnListLoadParams,
29
27
  shouldTriggerEndReachedFromScroll,
30
28
  } from './DefaultTable.utils';
31
-
32
29
  const DefaultTable = ({
33
30
  accentColor = null,
34
31
  actions = {},
@@ -38,6 +35,7 @@ const DefaultTable = ({
38
35
  compactBreakpoint = DEFAULT_COMPACT_BREAKPOINT,
39
36
  cardListProps = {},
40
37
  columns = [],
38
+ presentationColumns = null,
41
39
  data = undefined,
42
40
  defaultColor = '',
43
41
  filters = {},
@@ -64,7 +62,7 @@ const DefaultTable = ({
64
62
  onSelectionChange = null,
65
63
  onSortChange = null,
66
64
  pageSize = null,
67
- appearance = 'default',
65
+ appearance = 'compact',
68
66
  paginationMode = 'infinite',
69
67
  pinRowActions = true,
70
68
  renderCard = null,
@@ -78,15 +76,17 @@ const DefaultTable = ({
78
76
  showSearch = null,
79
77
  showRowActions = true,
80
78
  showToolbar = true,
79
+ showAdministrativeTools = true,
81
80
  showToolbarActions = null,
82
81
  showToolbarControls = null,
83
82
  showTotalItemsInCompactToolbar = false,
84
83
  showTotalItemsInFooter = true,
85
84
  sort = null,
86
85
  storeName = '',
87
- summary = undefined,
86
+ summary = undefined, totalItems = undefined,
88
87
  summaryLabels = {},
89
88
  toolbarActions = [],
89
+ viewMode = null,
90
90
  visibleColumnsPreferenceKey = '',
91
91
  }) => {
92
92
  const { width } = useWindowDimensions();
@@ -128,13 +128,13 @@ const DefaultTable = ({
128
128
  const columnsForTable = storeColumns.length > 0 ? storeColumns : columns;
129
129
  const storeFilters = isObject(store?.getters?.filters) ? store.getters.filters : {};
130
130
  const requestParamsSeed = isObject(requestParams) ? requestParams : {};
131
- const resolvedTotalItems = store?.getters?.totalItems;
131
+ const resolvedTotalItems = totalItems ?? (!autoMode && paginationMode === 'pages' ? data.length : store?.getters?.totalItems);
132
+ useEffect(() => {if ((!autoMode && paginationMode === 'pages') || totalItems !== undefined) publishStoreValue(store, 'setTotalItems', resolvedTotalItems, 'totalItems');}, [autoMode, paginationMode, totalItems, resolvedTotalItems, storeName, store?.actions?.setTotalItems]);
132
133
  if (storeDeclaredConfigsRef.current === null) {
133
134
  storeDeclaredConfigsRef.current = isObject(store?.getters?.configs)
134
135
  ? store.getters.configs
135
136
  : {};
136
137
  }
137
-
138
138
  const storeDeclaredConfigs = storeDeclaredConfigsRef.current || {};
139
139
  const currentConfigs = {
140
140
  ...storeDeclaredConfigs,
@@ -148,11 +148,9 @@ const DefaultTable = ({
148
148
  normalizeText(addLabel) ||
149
149
  normalizeText(global.t?.t(storeName, 'button', 'add')) ||
150
150
  'Adicionar';
151
-
152
151
  if (storeColumns.length === 0 && Array.isArray(columns) && columns.length > 0) {
153
152
  assignGetterValue(store, 'columns', columns);
154
153
  }
155
-
156
154
  if (data !== undefined && Array.isArray(data)) {
157
155
  assignGetterValue(store, 'items', data);
158
156
  }
@@ -167,7 +165,7 @@ const DefaultTable = ({
167
165
  const {
168
166
  buildRequestQuery,
169
167
  currentPage,
170
- pageSizeNumber,
168
+ pageSizeNumber, paginateData,
171
169
  goToPage,
172
170
  resolvedIsLoading,
173
171
  handleEndReached,
@@ -194,12 +192,13 @@ const DefaultTable = ({
194
192
  store,
195
193
  storeName,
196
194
  });
197
- const sortedData = useDefaultTableSortedData({
195
+ const sortedItems = useDefaultTableSortedData({
198
196
  resolvedData,
199
197
  resolvedSort,
200
198
  storeName,
201
199
  tableColumns: columnsForTable,
202
200
  });
201
+ const sortedData = useMemo(() => paginateData(sortedItems), [paginateData, sortedItems]);
203
202
  const {isCreateFormOpen, closeCreateForm, resolvedOnAdd, handleDefaultCreateSaved} =
204
203
  useDefaultTableCreateForm({onAdd, onSaved, resolvedActions, handleRefresh});
205
204
  const hasAddAction =
@@ -225,11 +224,11 @@ const DefaultTable = ({
225
224
  const previousCompactRef = useRef(isCompactView);
226
225
  const enteringCompact = isCompactView && !previousCompactRef.current;
227
226
  useEffect(() => {previousCompactRef.current = isCompactView;}, [isCompactView]);
228
- const effectiveViewMode =
227
+ const effectiveViewMode = viewMode || (
229
228
  enteringCompact && forceCardsOnCompact !== false ? 'cards' :
230
229
  currentConfigs.viewMode ||
231
230
  storedViewMode ||
232
- (isCompactView && forceCardsOnCompact !== false ? 'cards' : initialViewMode);
231
+ (isCompactView && forceCardsOnCompact !== false ? 'cards' : initialViewMode));
233
232
  const tableFiltersVisible = Boolean(currentConfigs.tableFiltersVisible);
234
233
  const defaultTableConfigs = useMemo(
235
234
  () => ({
@@ -247,7 +246,7 @@ const DefaultTable = ({
247
246
  compactBreakpoint,
248
247
  debugFallbackParameters,
249
248
  defaultColor,
250
- effectiveViewMode,
249
+ effectiveViewMode, viewMode,
251
250
  filters,
252
251
  footerComponent,
253
252
  forceCardsOnCompact,
@@ -274,6 +273,7 @@ const DefaultTable = ({
274
273
  requestParams: requestParamsSeed,
275
274
  requestSort,
276
275
  resolvedSort,
276
+ presentationColumns,
277
277
  rowActionsComponent,
278
278
  rowActionsWidth,
279
279
  rowStyle,
@@ -282,7 +282,8 @@ const DefaultTable = ({
282
282
  selectable: storeSelectable,
283
283
  showColumnFiltersButton,
284
284
  showSearch,
285
- showRowActions: storeDeclaredConfigs.showRowActions === false ? false : showRowActions,
285
+ showRowActions: presentationColumns !== null ? showRowActions : storeDeclaredConfigs.showRowActions === false ? false : showRowActions,
286
+ showAdministrativeTools,
286
287
  showToolbar,
287
288
  showToolbarActions:
288
289
  showToolbarActions === null
@@ -303,7 +304,8 @@ const DefaultTable = ({
303
304
  refreshing: resolvedIsRefreshing,
304
305
  tablePreferenceScope,
305
306
  toolbarActions,
306
- viewMode: effectiveViewMode,
307
+ // A controlled presentation must not replace the saved list mode.
308
+ viewMode: viewMode ? storeDeclaredConfigs.viewMode : effectiveViewMode,
307
309
  }),
308
310
  [
309
311
  appearance, paginationMode, currentPage, pageSizeNumber, goToPage, resolvedIsLoading,
@@ -314,7 +316,7 @@ const DefaultTable = ({
314
316
  compactBreakpoint,
315
317
  debugFallbackParameters,
316
318
  defaultColor,
317
- effectiveViewMode,
319
+ effectiveViewMode, viewMode,
318
320
  filters,
319
321
  footerComponent,
320
322
  forceCardsOnCompact,
@@ -344,6 +346,7 @@ const DefaultTable = ({
344
346
  requestSort,
345
347
  resolvedIsRefreshing,
346
348
  resolvedSort,
349
+ presentationColumns,
347
350
  rowActionsComponent,
348
351
  rowActionsWidth,
349
352
  rowStyle,
@@ -352,6 +355,7 @@ const DefaultTable = ({
352
355
  showColumnFiltersButton,
353
356
  showSearch,
354
357
  showRowActions,
358
+ showAdministrativeTools,
355
359
  showToolbar,
356
360
  showToolbarActions,
357
361
  showToolbarControls,
@@ -384,6 +388,7 @@ const DefaultTable = ({
384
388
  hasImportAction: Boolean(storeDeclaredConfigs.import || importAction || onImport),
385
389
  initialViewMode,
386
390
  pinRowActions,
391
+ presentationColumns,
387
392
  rowActionsComponentType: rowActionsComponent ? typeof rowActionsComponent : '',
388
393
  rowActionsWidth,
389
394
  resolvedIsRefreshing,
@@ -394,12 +399,13 @@ const DefaultTable = ({
394
399
  showColumnFiltersButton,
395
400
  showSearch,
396
401
  showRowActions,
402
+ showAdministrativeTools,
397
403
  showToolbar,
398
404
  showToolbarActions,
399
405
  showToolbarControls,
400
406
  showTotalItemsInCompactToolbar,
401
407
  showTotalItemsInFooter,
402
- storedViewMode,
408
+ storedViewMode, controlledViewMode: viewMode,
403
409
  tableFiltersVisible,
404
410
  sortedData: stableSerialize(sortedData),
405
411
  toolbarActionsLength: Array.isArray(toolbarActions) ? toolbarActions.length : 0,
@@ -413,7 +419,7 @@ const DefaultTable = ({
413
419
  compactBreakpoint,
414
420
  defaultColor,
415
421
  debugFallbackParameters,
416
- effectiveViewMode,
422
+ effectiveViewMode, viewMode,
417
423
  filters,
418
424
  forceCardsOnCompact,
419
425
  storeDeclaredConfigs,
@@ -424,6 +430,7 @@ const DefaultTable = ({
424
430
  onExport,
425
431
  onImport,
426
432
  pinRowActions,
433
+ presentationColumns,
427
434
  rowActionsComponent,
428
435
  rowActionsWidth,
429
436
  resolvedIsRefreshing,
@@ -433,6 +440,7 @@ const DefaultTable = ({
433
440
  showColumnFiltersButton,
434
441
  showSearch,
435
442
  showRowActions,
443
+ showAdministrativeTools,
436
444
  showToolbar,
437
445
  showToolbarActions,
438
446
  showToolbarControls,
@@ -444,7 +452,6 @@ const DefaultTable = ({
444
452
  toolbarActions,
445
453
  ],
446
454
  );
447
-
448
455
  // Do not mutate store.configs during render. A new configs object is built
449
456
  // every pass; writing it here retriggers subscribers and React #185 when
450
457
  // opening Adicionar on DefaultTable (categories). Sync only in the hook
@@ -490,5 +497,4 @@ const DefaultTable = ({
490
497
  requestParamsSeed
491
498
  }} />;
492
499
  };
493
-
494
500
  export default DefaultTable;
@@ -214,6 +214,7 @@ export const getColumnStyle = column => {
214
214
  if (Number.isFinite(columnWidth) && columnWidth > 0) {
215
215
  customSize.width = columnWidth;
216
216
  customSize.flexBasis = columnWidth;
217
+ if(column?.cellComponent){customSize.flexGrow=0;customSize.maxWidth=columnWidth;}
217
218
  }
218
219
 
219
220
  if (Number.isFinite(columnMinWidth) && columnMinWidth > 0) {
@@ -66,6 +66,8 @@ const DefaultTableCards = ({ storeName }) => {
66
66
  key: cardListKey,
67
67
  listKey,
68
68
  numColumns: _ignoredNumColumns,
69
+ transformData,
70
+ unframedCards,
69
71
  ...flatListProps
70
72
  } = cardListProps;
71
73
  const isLoading = Boolean(store?.getters?.isLoadingList || store?.getters?.isLoading);
@@ -137,6 +139,7 @@ const DefaultTableCards = ({ storeName }) => {
137
139
  hasRowActions ? styles.cardItemWithActions : null,
138
140
  rowStyleValue,
139
141
  compact ? [compactStyles.cardItem, {flex: 0, flexGrow: 0, flexShrink: 0, flexBasis: 'auto', width: compactCardWidth, maxWidth: compactCardWidth, backgroundColor: palette.cardBackground || palette.panelBackground, borderColor: palette.cardBorder || palette.inputBorder}] : null,
142
+ unframedCards ? {borderWidth:0,backgroundColor:'transparent',shadowOpacity:0,elevation:0} : null,
140
143
  ]}
141
144
  >
142
145
  <View style={[styles.cardContent, hasRowActions ? null : intrinsicHeight, compact ? {flex: 0, flexBasis: 'auto', flexGrow: 0} : null]}>
@@ -206,11 +209,11 @@ const DefaultTableCards = ({ storeName }) => {
206
209
  return (
207
210
  <FlatList
208
211
  {...flatListProps}
209
- key={resolvedListKey}
212
+ key={`${resolvedListKey}:columns-${resolvedNumColumns}`}
210
213
  numColumns={resolvedNumColumns}
211
214
  onLayout={event => {flatListProps.onLayout?.(event); setListWidth(event.nativeEvent.layout.width);}}
212
215
  columnWrapperStyle={compact && gridColumns > 1 ? {gap: 12} : flatListProps.columnWrapperStyle}
213
- data={sortedData}
216
+ data={typeof transformData === 'function' ? transformData(sortedData) : sortedData}
214
217
  keyExtractor={getRowKey}
215
218
  renderItem={({ item, index }) => renderCardItem(item, index)}
216
219
  style={styles.cardsScroll}
@@ -43,7 +43,7 @@ const DefaultTableControls = ({ storeName }) => {
43
43
  const addConfig = store?.getters?.add;
44
44
  const addButtonPlacement = normalizeText(configs.addButtonPlacement) || 'toolbar';
45
45
  const shouldRenderAddButton =
46
- addButtonPlacement === 'toolbar' &&
46
+ configs.add !== false && addButtonPlacement === 'toolbar' &&
47
47
  (addConfig === true || configs.add === true) &&
48
48
  (typeof configs.onAdd === 'function' || typeof store?.actions?.save === 'function');
49
49
  const { tableButtonColors } = useDefaultTableTheme();
@@ -78,7 +78,7 @@ const DefaultTableControls = ({ storeName }) => {
78
78
 
79
79
  return (
80
80
  <>
81
- <DefaultDebug storeName={storeName} />
81
+ {configs.showAdministrativeTools !== false ? <DefaultDebug storeName={storeName} /> : null}
82
82
  {/* app-community#811: LISTA (table) e CARD abrem o mesmo DefaultFiltersModal */}
83
83
  {hasFilterableColumns ? (
84
84
  <DefaultModalButton
@@ -83,7 +83,7 @@ const DefaultTableInput = ({
83
83
  const compactDisplay = compact && storeName === 'orders' ? (resolvedFieldName === 'price' ? Formatter.formatMoney(rawValue || 0) : dateField && rawValue ? `${Formatter.formatDateYmdTodmY(rawValue, false)}${time && (time !== '00:00' || resolvedFieldName === 'alterDate') ? '\n' + time : ''}` : compactType) : undefined;
84
84
  const compactTextStyle = compact ? {fontSize: 13, fontWeight: column.isIdentity ? '600' : '400', ...(resolvedFieldName === 'status' ? {color: statusColor} : {color: themeColors.textPrimary})} : null;
85
85
  const statusStyle = compact && resolvedFieldName === 'status' ? {width: 'auto', alignSelf: 'flex-start', borderWidth: 1, borderColor: statusColor, borderRadius: 18, paddingHorizontal: 8, paddingVertical: 3, backgroundColor: /^#[0-9a-f]{6}$/i.test(statusColor) ? statusColor + '10' : themeColors.panelBackground} : null;
86
- const input = (
86
+ const input = column.cellComponent ? React.createElement(column.cellComponent,{row,column}) : (
87
87
  <DefaultInput
88
88
  accentColor={options.accentColor || resolvedAccentColor}
89
89
  column={column}
@@ -37,7 +37,7 @@ const DefaultTableRows = ({ storeName }) => {
37
37
  const store = useStore(storeName);
38
38
  const configs = store?.getters?.configs || {};
39
39
  const { palette, resolvedAccentColor, tableBorderColors, themeTokens } = useDefaultTableTheme();
40
- const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
40
+ const columns = Array.isArray(configs.presentationColumns) ? configs.presentationColumns : Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
41
41
  const visibleColumns = store?.getters?.visibleColumns || {};
42
42
  const tableColumns = columns.filter(
43
43
  column => shouldIncludeColumn(column) && visibleColumns[getColumnKey(column)] !== false,
@@ -76,7 +76,7 @@ const DefaultTableToolbar = ({
76
76
 
77
77
  return null;
78
78
  };
79
- const importConfig = configs.import || null;
79
+ const importConfig = configs.showAdministrativeTools === false ? null : configs.import || null;
80
80
  const importActionOverrides =
81
81
  configs.importAction && typeof configs.importAction === 'object'
82
82
  ? configs.importAction
@@ -75,6 +75,7 @@ const useAutoPageLoader = ({
75
75
  resolvedActions,
76
76
  showError,
77
77
  storeName,
78
+ store,
78
79
  }) => {
79
80
  const [currentPage, setCurrentPage] = useState(1);
80
81
  const [autoHasLoaded, setAutoHasLoaded] = useState(false);
@@ -84,6 +85,7 @@ const useAutoPageLoader = ({
84
85
  const [autoLastPageCount, setAutoLastPageCount] = useState(0);
85
86
  const autoRequestIdRef = useRef(0);
86
87
  const autoLoadedQueryKeyRef = useRef('');
88
+ const autoLoadedStoreKeyRef = useRef(null);
87
89
  const autoErroredQueryKeyRef = useRef('');
88
90
  const autoPageRef = useRef(0);
89
91
  const isFetchingRef = useRef(false);
@@ -116,13 +118,22 @@ const useAutoPageLoader = ({
116
118
  const query = buildRequestQuery(page, append);
117
119
  if (refresh && resolvedActions.catalogCache) query.__storeMeta = {catalogRefresh: true};
118
120
 
119
- return Promise.resolve(resolvedActions.getItems(query))
121
+ const request = resolvedActions.getItems(query);
122
+ const owningRequestKey = store?.getters?.activeRequestKey;
123
+ return Promise.resolve(request)
120
124
  .then(response => {
121
125
  if (autoRequestIdRef.current !== requestId) {
122
126
  return response;
123
127
  }
124
128
 
125
129
  const pageItems = normalizeCollectionItems(response);
130
+ // A retained screen must not present another screen's shared-store result.
131
+ if (owningRequestKey && store?.getters?.lastCompletedRequest?.requestKey !== owningRequestKey) {
132
+ autoLoadedQueryKeyRef.current = '';
133
+ setAutoHasLoaded(false);
134
+ return pageItems;
135
+ }
136
+ autoLoadedStoreKeyRef.current = owningRequestKey || store?.getters?.lastCompletedRequest?.requestKey || null;
126
137
  autoPageRef.current = Number(response?.catalogPage) || page;
127
138
  setCurrentPage(autoPageRef.current);
128
139
  autoLoadedQueryKeyRef.current = autoQuerySignature;
@@ -159,6 +170,7 @@ const useAutoPageLoader = ({
159
170
  resolvedActions,
160
171
  showError,
161
172
  storeName,
173
+ store,
162
174
  ],
163
175
  );
164
176
 
@@ -168,6 +180,7 @@ const useAutoPageLoader = ({
168
180
  autoHasLoaded,
169
181
  autoLastPageCount,
170
182
  autoLoadedQueryKeyRef,
183
+ autoLoadedStoreKeyRef,
171
184
  autoLoading,
172
185
  autoLoadingMore,
173
186
  autoRefreshing,
@@ -188,21 +201,30 @@ const useAutoPageEffect = ({
188
201
  isFocused,
189
202
  loadAutoPage,
190
203
  resolvedActions,
204
+ ownsStoreResult,
205
+ autoPageRef,
191
206
  }) => {
207
+ const wasFocused = useRef(false);
192
208
  useEffect(() => {
209
+ const entering = isFocused && !wasFocused.current;
210
+ wasFocused.current = isFocused;
193
211
  if (!autoMode || !isFocused) return;
194
212
  if (typeof resolvedActions.getItems !== 'function') return;
213
+ // A failed visit may retry on the next focus, but never spin while still focused.
214
+ if (entering) autoErroredQueryKeyRef.current = '';
195
215
  if (
196
216
  autoLoading ||
197
217
  autoLoadingMore ||
198
- autoLoadedQueryKeyRef.current === autoQuerySignature ||
218
+ (autoLoadedQueryKeyRef.current === autoQuerySignature && ownsStoreResult && !(entering && resolvedActions.catalogCache)) ||
199
219
  autoErroredQueryKeyRef.current === autoQuerySignature
200
220
  ) {
201
221
  return;
202
222
  }
203
223
 
204
224
  endReachedLockRef.current = false;
205
- loadAutoPage(1, { append: false });
225
+ const retainedPage = autoLoadedQueryKeyRef.current === autoQuerySignature && (!ownsStoreResult || resolvedActions.catalogCache)
226
+ ? Math.max(1, autoPageRef.current) : 1;
227
+ loadAutoPage(retainedPage, { append: false });
206
228
  }, [
207
229
  autoErroredQueryKeyRef,
208
230
  autoLoadedQueryKeyRef,
@@ -214,6 +236,8 @@ const useAutoPageEffect = ({
214
236
  isFocused,
215
237
  loadAutoPage,
216
238
  resolvedActions,
239
+ ownsStoreResult,
240
+ autoPageRef,
217
241
  ]);
218
242
  };
219
243
 
@@ -245,6 +269,10 @@ export const useDefaultTablePagination = ({
245
269
  sortField: resolvedSort?.field,
246
270
  });
247
271
  const pageSizeNumber = Number(requestParams.itemsPerPage || pageSize || 50) || 50;
272
+ const [localPage, setLocalPage] = useState({key: '', page: 1});
273
+ const localQueryKey = stableSerialize([storeName, pageSizeNumber, filters, resolvedSort,
274
+ Array.isArray(data) ? data.map(item => item?.['@id'] ?? item?.id) : []]);
275
+ const localPages = Math.max(1, Math.ceil((Array.isArray(data) ? data.length : 0) / pageSizeNumber));
248
276
  const resourceEndpoint = store?.getters?.resourceEndpoint || '';
249
277
  const storeReload = store?.getters?.reload;
250
278
  const buildRequestQuery = useCallback(
@@ -279,11 +307,12 @@ export const useDefaultTablePagination = ({
279
307
  [columnsForTable, filters, pageSizeNumber, requestParams, resolvedSort, resourceEndpoint, storeName, storeReload],
280
308
  );
281
309
  const {
282
- currentPage,
310
+ currentPage: autoCurrentPage,
283
311
  autoErroredQueryKeyRef,
284
312
  autoHasLoaded,
285
313
  autoLastPageCount,
286
314
  autoLoadedQueryKeyRef,
315
+ autoLoadedStoreKeyRef,
287
316
  autoLoading,
288
317
  autoLoadingMore,
289
318
  autoRefreshing,
@@ -298,8 +327,17 @@ export const useDefaultTablePagination = ({
298
327
  resolvedActions,
299
328
  showError,
300
329
  storeName,
330
+ store,
301
331
  });
302
332
 
333
+ const currentPage = autoMode ? autoCurrentPage : Math.min(localPages, localPage.key === localQueryKey ? localPage.page : 1);
334
+ const paginateData = useCallback(items => !autoMode && paginationMode === 'pages'
335
+ ? items.slice((currentPage - 1) * pageSizeNumber, currentPage * pageSizeNumber) : items,
336
+ [autoMode, paginationMode, currentPage, pageSizeNumber]);
337
+
338
+ const ownsStoreResult = !autoLoadedStoreKeyRef.current ||
339
+ autoLoadedStoreKeyRef.current === store?.getters?.lastCompletedRequest?.requestKey;
340
+
303
341
  useAutoPageEffect({
304
342
  autoErroredQueryKeyRef,
305
343
  autoLoadedQueryKeyRef,
@@ -311,6 +349,8 @@ export const useDefaultTablePagination = ({
311
349
  isFocused,
312
350
  loadAutoPage,
313
351
  resolvedActions,
352
+ ownsStoreResult,
353
+ autoPageRef,
314
354
  });
315
355
 
316
356
  useEffect(() => {
@@ -326,7 +366,7 @@ export const useDefaultTablePagination = ({
326
366
  ? autoRefreshing
327
367
  : false;
328
368
  const resolvedData = autoMode
329
- ? (autoHasLoaded && Array.isArray(store?.getters?.items) ? store.getters.items : [])
369
+ ? (autoHasLoaded && ownsStoreResult && Array.isArray(store?.getters?.items) ? store.getters.items : [])
330
370
  : (Array.isArray(data) ? data : []);
331
371
  const resolvedHasMore = useMemo(
332
372
  () => {
@@ -416,8 +456,10 @@ export const useDefaultTablePagination = ({
416
456
  return {
417
457
  buildRequestQuery,
418
458
  currentPage,
459
+ paginateData,
419
460
  goToPage: page => {
420
- const totalPages = Math.max(1, Math.ceil(Number(resolvedTotalItems || 0) / pageSizeNumber));
461
+ const totalPages = autoMode ? Math.max(1, Math.ceil(Number(resolvedTotalItems || 0) / pageSizeNumber)) : localPages;
462
+ if (!autoMode && paginationMode === 'pages' && Number.isInteger(page) && page >= 1 && page <= totalPages && !resolvedIsLoading) setLocalPage({key: localQueryKey, page});
421
463
  if (autoMode && paginationMode === 'pages' && Number.isInteger(page) && page >= 1 && page <= totalPages && !resolvedIsLoading) {
422
464
  return loadAutoPage(page, {append: false});
423
465
  }
@@ -1,4 +1,4 @@
1
- import { useEffect, useRef } from 'react';
1
+ import { useEffect, useMemo, useRef } from 'react';
2
2
  import {
3
3
  persistTableFiltersPreference,
4
4
  resolveStoredTableFiltersPreference,
@@ -65,7 +65,15 @@ export function useDefaultTableStoreSync({
65
65
  const storeRef = useRef(store);
66
66
  storeRef.current = store;
67
67
  const lastPublishedItemsRef = useRef(undefined);
68
- const configsSignatureRef = useRef('');
68
+ // Keep custom presentation closures current (for example after a theme switch)
69
+ // without publishing configs for every new callback identity and reintroducing #185.
70
+ const cardOwner = useMemo(() => {
71
+ const owner = {current: null, signature: ''};
72
+ owner.render = (...args) => owner.current?.(...args);
73
+ return owner;
74
+ }, [storeName, store?.actions?.setConfigs]);
75
+ cardOwner.current = defaultTableConfigs.renderCard;
76
+ const hasRenderCard = typeof defaultTableConfigs.renderCard === 'function';
69
77
  const lastHydratedFiltersKeyRef = useRef('');
70
78
  const storeFiltersRef = useRef(storeFilters);
71
79
  storeFiltersRef.current = storeFilters;
@@ -163,11 +171,15 @@ export function useDefaultTableStoreSync({
163
171
  }, [filters]);
164
172
 
165
173
  useEffect(() => {
166
- if (configsSignatureRef.current === defaultTableConfigsSignature) {
174
+ const signature = `${storeName}:${defaultTableConfigsSignature}:render-card:${hasRenderCard}`;
175
+ if (cardOwner.signature === signature) {
167
176
  return;
168
177
  }
169
178
 
170
- configsSignatureRef.current = defaultTableConfigsSignature;
171
- publishStoreValue(storeRef.current, 'setConfigs', defaultTableConfigs, 'configs');
172
- }, [defaultTableConfigs, defaultTableConfigsSignature, storeName]);
179
+ cardOwner.signature = signature;
180
+ publishStoreValue(storeRef.current, 'setConfigs', {
181
+ ...defaultTableConfigs,
182
+ renderCard: hasRenderCard ? cardOwner.render : defaultTableConfigs.renderCard,
183
+ }, 'configs');
184
+ }, [defaultTableConfigs, defaultTableConfigsSignature, storeName, hasRenderCard, cardOwner]);
173
185
  }