@controleonline/ui-default 1.0.280 → 1.0.282

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 = {},
@@ -78,15 +76,18 @@ const DefaultTable = ({
78
76
  showSearch = null,
79
77
  showRowActions = true,
80
78
  showToolbar = true,
79
+ showCompactCaption = true,
80
+ showAdministrativeTools = true,
81
81
  showToolbarActions = null,
82
82
  showToolbarControls = null,
83
83
  showTotalItemsInCompactToolbar = false,
84
84
  showTotalItemsInFooter = true,
85
85
  sort = null,
86
86
  storeName = '',
87
- summary = undefined,
87
+ summary = undefined, totalItems = undefined,
88
88
  summaryLabels = {},
89
89
  toolbarActions = [],
90
+ viewMode = null,
90
91
  visibleColumnsPreferenceKey = '',
91
92
  }) => {
92
93
  const { width } = useWindowDimensions();
@@ -128,13 +129,13 @@ const DefaultTable = ({
128
129
  const columnsForTable = storeColumns.length > 0 ? storeColumns : columns;
129
130
  const storeFilters = isObject(store?.getters?.filters) ? store.getters.filters : {};
130
131
  const requestParamsSeed = isObject(requestParams) ? requestParams : {};
131
- const resolvedTotalItems = store?.getters?.totalItems;
132
+ const resolvedTotalItems = totalItems ?? (!autoMode && paginationMode === 'pages' ? data.length : store?.getters?.totalItems);
133
+ useEffect(() => {if ((!autoMode && paginationMode === 'pages') || totalItems !== undefined) publishStoreValue(store, 'setTotalItems', resolvedTotalItems, 'totalItems');}, [autoMode, paginationMode, totalItems, resolvedTotalItems, storeName, store?.actions?.setTotalItems]);
132
134
  if (storeDeclaredConfigsRef.current === null) {
133
135
  storeDeclaredConfigsRef.current = isObject(store?.getters?.configs)
134
136
  ? store.getters.configs
135
137
  : {};
136
138
  }
137
-
138
139
  const storeDeclaredConfigs = storeDeclaredConfigsRef.current || {};
139
140
  const currentConfigs = {
140
141
  ...storeDeclaredConfigs,
@@ -148,11 +149,9 @@ const DefaultTable = ({
148
149
  normalizeText(addLabel) ||
149
150
  normalizeText(global.t?.t(storeName, 'button', 'add')) ||
150
151
  'Adicionar';
151
-
152
152
  if (storeColumns.length === 0 && Array.isArray(columns) && columns.length > 0) {
153
153
  assignGetterValue(store, 'columns', columns);
154
154
  }
155
-
156
155
  if (data !== undefined && Array.isArray(data)) {
157
156
  assignGetterValue(store, 'items', data);
158
157
  }
@@ -167,7 +166,7 @@ const DefaultTable = ({
167
166
  const {
168
167
  buildRequestQuery,
169
168
  currentPage,
170
- pageSizeNumber,
169
+ pageSizeNumber, paginateData,
171
170
  goToPage,
172
171
  resolvedIsLoading,
173
172
  handleEndReached,
@@ -194,12 +193,13 @@ const DefaultTable = ({
194
193
  store,
195
194
  storeName,
196
195
  });
197
- const sortedData = useDefaultTableSortedData({
196
+ const sortedItems = useDefaultTableSortedData({
198
197
  resolvedData,
199
198
  resolvedSort,
200
199
  storeName,
201
200
  tableColumns: columnsForTable,
202
201
  });
202
+ const sortedData = useMemo(() => paginateData(sortedItems), [paginateData, sortedItems]);
203
203
  const {isCreateFormOpen, closeCreateForm, resolvedOnAdd, handleDefaultCreateSaved} =
204
204
  useDefaultTableCreateForm({onAdd, onSaved, resolvedActions, handleRefresh});
205
205
  const hasAddAction =
@@ -225,11 +225,11 @@ const DefaultTable = ({
225
225
  const previousCompactRef = useRef(isCompactView);
226
226
  const enteringCompact = isCompactView && !previousCompactRef.current;
227
227
  useEffect(() => {previousCompactRef.current = isCompactView;}, [isCompactView]);
228
- const effectiveViewMode =
228
+ const effectiveViewMode = viewMode || (
229
229
  enteringCompact && forceCardsOnCompact !== false ? 'cards' :
230
230
  currentConfigs.viewMode ||
231
231
  storedViewMode ||
232
- (isCompactView && forceCardsOnCompact !== false ? 'cards' : initialViewMode);
232
+ (isCompactView && forceCardsOnCompact !== false ? 'cards' : initialViewMode));
233
233
  const tableFiltersVisible = Boolean(currentConfigs.tableFiltersVisible);
234
234
  const defaultTableConfigs = useMemo(
235
235
  () => ({
@@ -247,7 +247,7 @@ const DefaultTable = ({
247
247
  compactBreakpoint,
248
248
  debugFallbackParameters,
249
249
  defaultColor,
250
- effectiveViewMode,
250
+ effectiveViewMode, viewMode,
251
251
  filters,
252
252
  footerComponent,
253
253
  forceCardsOnCompact,
@@ -274,6 +274,7 @@ const DefaultTable = ({
274
274
  requestParams: requestParamsSeed,
275
275
  requestSort,
276
276
  resolvedSort,
277
+ presentationColumns,
277
278
  rowActionsComponent,
278
279
  rowActionsWidth,
279
280
  rowStyle,
@@ -282,8 +283,10 @@ const DefaultTable = ({
282
283
  selectable: storeSelectable,
283
284
  showColumnFiltersButton,
284
285
  showSearch,
285
- showRowActions: storeDeclaredConfigs.showRowActions === false ? false : showRowActions,
286
+ showRowActions: presentationColumns !== null ? showRowActions : storeDeclaredConfigs.showRowActions === false ? false : showRowActions,
287
+ showAdministrativeTools,
286
288
  showToolbar,
289
+ showCompactCaption,
287
290
  showToolbarActions:
288
291
  showToolbarActions === null
289
292
  ? storeDeclaredConfigs.showToolbarActions !== false
@@ -303,7 +306,8 @@ const DefaultTable = ({
303
306
  refreshing: resolvedIsRefreshing,
304
307
  tablePreferenceScope,
305
308
  toolbarActions,
306
- viewMode: effectiveViewMode,
309
+ // A controlled presentation must not replace the saved list mode.
310
+ viewMode: viewMode ? storeDeclaredConfigs.viewMode : effectiveViewMode,
307
311
  }),
308
312
  [
309
313
  appearance, paginationMode, currentPage, pageSizeNumber, goToPage, resolvedIsLoading,
@@ -314,7 +318,7 @@ const DefaultTable = ({
314
318
  compactBreakpoint,
315
319
  debugFallbackParameters,
316
320
  defaultColor,
317
- effectiveViewMode,
321
+ effectiveViewMode, viewMode,
318
322
  filters,
319
323
  footerComponent,
320
324
  forceCardsOnCompact,
@@ -344,6 +348,7 @@ const DefaultTable = ({
344
348
  requestSort,
345
349
  resolvedIsRefreshing,
346
350
  resolvedSort,
351
+ presentationColumns,
347
352
  rowActionsComponent,
348
353
  rowActionsWidth,
349
354
  rowStyle,
@@ -352,7 +357,9 @@ const DefaultTable = ({
352
357
  showColumnFiltersButton,
353
358
  showSearch,
354
359
  showRowActions,
360
+ showAdministrativeTools,
355
361
  showToolbar,
362
+ showCompactCaption,
356
363
  showToolbarActions,
357
364
  showToolbarControls,
358
365
  showTotalItemsInCompactToolbar,
@@ -384,6 +391,7 @@ const DefaultTable = ({
384
391
  hasImportAction: Boolean(storeDeclaredConfigs.import || importAction || onImport),
385
392
  initialViewMode,
386
393
  pinRowActions,
394
+ presentationColumns,
387
395
  rowActionsComponentType: rowActionsComponent ? typeof rowActionsComponent : '',
388
396
  rowActionsWidth,
389
397
  resolvedIsRefreshing,
@@ -394,12 +402,14 @@ const DefaultTable = ({
394
402
  showColumnFiltersButton,
395
403
  showSearch,
396
404
  showRowActions,
405
+ showAdministrativeTools,
397
406
  showToolbar,
407
+ showCompactCaption,
398
408
  showToolbarActions,
399
409
  showToolbarControls,
400
410
  showTotalItemsInCompactToolbar,
401
411
  showTotalItemsInFooter,
402
- storedViewMode,
412
+ storedViewMode, controlledViewMode: viewMode,
403
413
  tableFiltersVisible,
404
414
  sortedData: stableSerialize(sortedData),
405
415
  toolbarActionsLength: Array.isArray(toolbarActions) ? toolbarActions.length : 0,
@@ -413,7 +423,7 @@ const DefaultTable = ({
413
423
  compactBreakpoint,
414
424
  defaultColor,
415
425
  debugFallbackParameters,
416
- effectiveViewMode,
426
+ effectiveViewMode, viewMode,
417
427
  filters,
418
428
  forceCardsOnCompact,
419
429
  storeDeclaredConfigs,
@@ -424,6 +434,7 @@ const DefaultTable = ({
424
434
  onExport,
425
435
  onImport,
426
436
  pinRowActions,
437
+ presentationColumns,
427
438
  rowActionsComponent,
428
439
  rowActionsWidth,
429
440
  resolvedIsRefreshing,
@@ -433,7 +444,9 @@ const DefaultTable = ({
433
444
  showColumnFiltersButton,
434
445
  showSearch,
435
446
  showRowActions,
447
+ showAdministrativeTools,
436
448
  showToolbar,
449
+ showCompactCaption,
437
450
  showToolbarActions,
438
451
  showToolbarControls,
439
452
  showTotalItemsInCompactToolbar,
@@ -444,7 +457,6 @@ const DefaultTable = ({
444
457
  toolbarActions,
445
458
  ],
446
459
  );
447
-
448
460
  // Do not mutate store.configs during render. A new configs object is built
449
461
  // every pass; writing it here retriggers subscribers and React #185 when
450
462
  // opening Adicionar on DefaultTable (categories). Sync only in the hook
@@ -490,5 +502,4 @@ const DefaultTable = ({
490
502
  requestParamsSeed
491
503
  }} />;
492
504
  };
493
-
494
505
  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}
@@ -83,12 +83,12 @@ export default function DefaultTableCompactToolbar({storeName, actions = [], sho
83
83
  <Text numberOfLines={1} style={[s.small, {color: c.chipText || text, flexShrink: 1}]}>{label}</Text><Icon name="x" size={12} color={text} />
84
84
  </TouchableOpacity>;
85
85
  })}<TouchableOpacity accessibilityRole="button" onPress={() => apply({})}><Text style={[s.small, {color: muted}]}>Limpar filtros</Text></TouchableOpacity></View> : null}
86
- <View style={s.secondary}>
86
+ {configs.showCompactCaption !== false && <View style={s.secondary}>
87
87
  <Text style={[s.small, {color: muted}]}>{configs.showTotalItemsInFooter !== false ? `${store.getters?.totalItems ?? 0} ${Number(store.getters?.totalItems) === 1 ? 'resultado' : 'resultados'}` : 'Pedidos'}</Text>
88
88
  {showControls ? <TouchableOpacity accessibilityRole="button" accessibilityLabel="Escolher ordenação" onPress={() => setModal('sort')} style={s.row}>
89
89
  <Icon name="arrow-down" size={14} color={muted} /><Text style={[s.small, {color: text}]}>{sortColumn ? labelFor(sortColumn) : 'Ordenar'} · {configs.resolvedSort?.direction === 'asc' ? '↑' : '↓'}</Text>
90
90
  </TouchableOpacity> : null}
91
- </View>
91
+ </View>}
92
92
  <DefaultFiltersModal storeName={storeName} visible={modal === 'filters'} deferred onClose={() => setModal('')} />
93
93
  <DefaultColumnMenu storeName={storeName} visible={modal === 'columns'} onClose={() => setModal('')} />
94
94
  <DefaultCompactDialog visible={modal === 'more' || modal === 'sort'} title={modal === 'sort' ? 'Ordenação' : 'Mais opções'}
@@ -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
  }