najm-kit 2.2.0 → 2.2.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.2.1
4
+
5
+ - Fixed a regression in 2.2.0: the dynamic page size reported under `manualPagination` could oscillate. Card row height is measured from rendered cards, so it grows as images decode; feeding that back into the page size refetched, re-rendered, re-measured, and refetched again. A list visibly settled from one page size to another with the loading skeleton flashing twice. The report is now allowed once per container geometry, which does not depend on the rows inside it, so it terminates. A resize still re-arms it, and the debounce still waits for the measurement to settle before reporting.
6
+
3
7
  ## 2.2.0
4
8
 
5
9
  - Added `cardPagination` mode `infinite`: card lists continue on scroll instead of behind a button. No control and no end-of-list element render while the list is healthy. The continuation button appears only after an append failure, as the retry target.
package/dist/index.d.ts CHANGED
@@ -2871,6 +2871,8 @@ interface TableState {
2871
2871
  hasData: boolean;
2872
2872
  hasControls: boolean;
2873
2873
  hasNoData: boolean;
2874
+ /** Loading while rows are already on screen: a refresh, not a first load. */
2875
+ isRefreshing: boolean;
2874
2876
  dynamicHeight: boolean;
2875
2877
  CardComponent: ComponentType<any> | null;
2876
2878
  className: string;
@@ -2909,6 +2911,8 @@ interface TableState {
2909
2911
  calculatedPageSize: number;
2910
2912
  /** Whole card rows that fit the measured body, multiplied by the column count. */
2911
2913
  calculatedCardPageSize: number;
2914
+ /** True once a real layout measurement has replaced the seeded defaults. */
2915
+ hasMeasuredLayout: boolean;
2912
2916
  skeletonRowCount: number;
2913
2917
  maxHeight: number | null;
2914
2918
  bodyWidth: number;
@@ -2970,7 +2974,16 @@ interface TableState {
2970
2974
  hasSyncedExpandedFromProps: boolean;
2971
2975
  }
2972
2976
  type TableStore = ReturnType<typeof createTableStore>;
2973
- declare const createTableStore: () => {
2977
+ /**
2978
+ * `seed` becomes part of the store's *initial* state rather than a `set()` call
2979
+ * made after creation. Zustand serves `getInitialState()` as the snapshot for
2980
+ * server rendering and for the hydration render, so anything applied after
2981
+ * creation is invisible on the first paint: `isLoading` would read false while
2982
+ * the caller passed true (rendering the empty state instead of the skeleton),
2983
+ * and `manualPagination` would read false long enough for layout effects to
2984
+ * push a default page size back to a consumer that owns pagination.
2985
+ */
2986
+ declare const createTableStore: (seed?: Partial<TableState>) => {
2974
2987
  (): TableState;
2975
2988
  <U>(selector: (state: TableState) => U): U;
2976
2989
  } & StoreApi<TableState> & {
@@ -2998,6 +3011,7 @@ declare const createTableStore: () => {
2998
3011
  hasData: () => boolean;
2999
3012
  hasControls: () => boolean;
3000
3013
  hasNoData: () => boolean;
3014
+ isRefreshing: () => boolean;
3001
3015
  dynamicHeight: () => boolean;
3002
3016
  CardComponent: () => ComponentType<any>;
3003
3017
  className: () => string;
@@ -3035,6 +3049,7 @@ declare const createTableStore: () => {
3035
3049
  pageSizeOptions: () => number[];
3036
3050
  calculatedPageSize: () => number;
3037
3051
  calculatedCardPageSize: () => number;
3052
+ hasMeasuredLayout: () => boolean;
3038
3053
  skeletonRowCount: () => number;
3039
3054
  maxHeight: () => number;
3040
3055
  bodyWidth: () => number;
@@ -3417,6 +3432,7 @@ declare const TableStoreContext: React$1.Context<{
3417
3432
  hasData: () => boolean;
3418
3433
  hasControls: () => boolean;
3419
3434
  hasNoData: () => boolean;
3435
+ isRefreshing: () => boolean;
3420
3436
  dynamicHeight: () => boolean;
3421
3437
  CardComponent: () => React$1.ComponentType<any>;
3422
3438
  className: () => string;
@@ -3454,6 +3470,7 @@ declare const TableStoreContext: React$1.Context<{
3454
3470
  pageSizeOptions: () => number[];
3455
3471
  calculatedPageSize: () => number;
3456
3472
  calculatedCardPageSize: () => number;
3473
+ hasMeasuredLayout: () => boolean;
3457
3474
  skeletonRowCount: () => number;
3458
3475
  maxHeight: () => number;
3459
3476
  bodyWidth: () => number;
@@ -3547,6 +3564,7 @@ declare function useStoreSync(props: any): {
3547
3564
  hasData: () => boolean;
3548
3565
  hasControls: () => boolean;
3549
3566
  hasNoData: () => boolean;
3567
+ isRefreshing: () => boolean;
3550
3568
  dynamicHeight: () => boolean;
3551
3569
  CardComponent: () => React__default.ComponentType<any>;
3552
3570
  className: () => string;
@@ -3584,6 +3602,7 @@ declare function useStoreSync(props: any): {
3584
3602
  pageSizeOptions: () => number[];
3585
3603
  calculatedPageSize: () => number;
3586
3604
  calculatedCardPageSize: () => number;
3605
+ hasMeasuredLayout: () => boolean;
3587
3606
  skeletonRowCount: () => number;
3588
3607
  maxHeight: () => number;
3589
3608
  bodyWidth: () => number;
package/dist/index.mjs CHANGED
@@ -12125,8 +12125,9 @@ var computeFlags = (state) => {
12125
12125
  state.menuButton && state.openRowMenu || state.onView || state.onEdit || state.onDelete
12126
12126
  );
12127
12127
  const hasControls = Boolean(state.showColumnVisibility || state.showAddButton || state.showViewToggle);
12128
- const showContent = !state.isLoading && !state.error && !hasNoData && !isFilteredEmpty;
12129
- return { hasData, hasNoData, isTableView, isCardView, isJsonView, isFilesView, isCustomMode, hasActions, hasControls, showContent, showPagination: state.showPagination && showContent };
12128
+ const isRefreshing = Boolean(state.isLoading) && hasData;
12129
+ const showContent = !state.error && !hasNoData && !isFilteredEmpty && (!state.isLoading || hasData);
12130
+ return { hasData, hasNoData, isTableView, isCardView, isJsonView, isFilesView, isCustomMode, hasActions, hasControls, isRefreshing, showContent, showPagination: state.showPagination && showContent };
12130
12131
  };
12131
12132
  var createSelectors = (_store) => {
12132
12133
  const store = _store;
@@ -12136,177 +12137,192 @@ var createSelectors = (_store) => {
12136
12137
  }
12137
12138
  return store;
12138
12139
  };
12139
- var createTableStore = () => {
12140
- const store = create((set, get) => ({
12141
- table: null,
12142
- data: [],
12143
- columns: [],
12144
- filters: [],
12145
- isLoading: false,
12146
- error: null,
12147
- viewMode: "table",
12148
- showSorting: true,
12149
- showPagination: true,
12150
- showColumnVisibility: false,
12151
- showAddButton: true,
12152
- showViewToggle: true,
12153
- toolbarLabels: true,
12154
- dynamicHeight: true,
12155
- showContent: false,
12156
- isTableView: true,
12157
- isCardView: false,
12158
- isJsonView: false,
12159
- isFilesView: false,
12160
- isCustomMode: false,
12161
- hasActions: false,
12162
- hasData: false,
12163
- hasControls: true,
12164
- hasNoData: true,
12165
- onView: null,
12166
- onEdit: null,
12167
- onDelete: null,
12168
- onAddClick: null,
12169
- onRowClick: null,
12170
- onRowContextMenu: null,
12171
- onBackgroundContextMenu: null,
12172
- openRowMenu: null,
12173
- getRowClassName: null,
12174
- menuButton: false,
12175
- onCellClick: null,
12176
- onBulkDelete: null,
12177
- onRetry: null,
12178
- onCellEdit: null,
12179
- onStateChange: null,
12180
- getRowId: null,
12181
- renderToolbar: null,
12182
- CardComponent: null,
12183
- className: "",
12184
- classNames: {},
12185
- bordered: void 0,
12186
- headerClassName: "bg-card",
12187
- headerColor: void 0,
12188
- headerTextColor: void 0,
12189
- borderColor: void 0,
12190
- showCheckbox: true,
12191
- selectedRowId: null,
12192
- headerSlot: null,
12193
- noResultsText: "No results.",
12194
- filterPlaceholder: "",
12195
- loadingText: "Loading...",
12196
- noDataText: "No data available",
12197
- addButtonText: "",
12198
- pageSizeOptions: [10, 20, 30, 40, 50],
12199
- calculatedPageSize: 10,
12200
- calculatedCardPageSize: 0,
12201
- skeletonRowCount: 6,
12202
- maxHeight: null,
12203
- bodyWidth: 0,
12204
- bodyHeight: 0,
12205
- tableHeaderHeight: 48,
12206
- cardColumnCount: 1,
12207
- cardRowHeight: 0,
12208
- cardGap: 12,
12209
- // JSON mode
12210
- jsonValue: void 0,
12211
- jsonColors: null,
12212
- renderJson: null,
12213
- // Custom mode
12214
- renderCustomMode: null,
12215
- // Controlled mode
12216
- isModeControlled: false,
12217
- onModeChange: null,
12218
- // availableModes
12219
- availableModes: ["table", "cards", "json"],
12220
- // User-intent setter
12221
- setViewMode: (mode) => {
12222
- const { isModeControlled, onModeChange } = get();
12223
- onModeChange?.(mode);
12224
- if (!isModeControlled) {
12225
- const updates = { viewMode: mode };
12140
+ var createTableStore = (seed) => {
12141
+ const store = create((set, get) => {
12142
+ const defaults = {
12143
+ table: null,
12144
+ data: [],
12145
+ columns: [],
12146
+ filters: [],
12147
+ isLoading: false,
12148
+ error: null,
12149
+ viewMode: "table",
12150
+ showSorting: true,
12151
+ showPagination: true,
12152
+ showColumnVisibility: false,
12153
+ showAddButton: true,
12154
+ showViewToggle: true,
12155
+ toolbarLabels: true,
12156
+ dynamicHeight: true,
12157
+ showContent: false,
12158
+ isTableView: true,
12159
+ isCardView: false,
12160
+ isJsonView: false,
12161
+ isFilesView: false,
12162
+ isCustomMode: false,
12163
+ hasActions: false,
12164
+ hasData: false,
12165
+ hasControls: true,
12166
+ hasNoData: true,
12167
+ isRefreshing: false,
12168
+ onView: null,
12169
+ onEdit: null,
12170
+ onDelete: null,
12171
+ onAddClick: null,
12172
+ onRowClick: null,
12173
+ onRowContextMenu: null,
12174
+ onBackgroundContextMenu: null,
12175
+ openRowMenu: null,
12176
+ getRowClassName: null,
12177
+ menuButton: false,
12178
+ onCellClick: null,
12179
+ onBulkDelete: null,
12180
+ onRetry: null,
12181
+ onCellEdit: null,
12182
+ onStateChange: null,
12183
+ getRowId: null,
12184
+ renderToolbar: null,
12185
+ CardComponent: null,
12186
+ className: "",
12187
+ classNames: {},
12188
+ bordered: void 0,
12189
+ headerClassName: "bg-card",
12190
+ headerColor: void 0,
12191
+ headerTextColor: void 0,
12192
+ borderColor: void 0,
12193
+ showCheckbox: true,
12194
+ selectedRowId: null,
12195
+ headerSlot: null,
12196
+ noResultsText: "No results.",
12197
+ filterPlaceholder: "",
12198
+ loadingText: "Loading...",
12199
+ noDataText: "No data available",
12200
+ addButtonText: "",
12201
+ pageSizeOptions: [10, 20, 30, 40, 50],
12202
+ calculatedPageSize: 10,
12203
+ calculatedCardPageSize: 0,
12204
+ hasMeasuredLayout: false,
12205
+ skeletonRowCount: 6,
12206
+ maxHeight: null,
12207
+ bodyWidth: 0,
12208
+ bodyHeight: 0,
12209
+ tableHeaderHeight: 48,
12210
+ cardColumnCount: 1,
12211
+ cardRowHeight: 0,
12212
+ cardGap: 12,
12213
+ // JSON mode
12214
+ jsonValue: void 0,
12215
+ jsonColors: null,
12216
+ renderJson: null,
12217
+ // Custom mode
12218
+ renderCustomMode: null,
12219
+ // Controlled mode
12220
+ isModeControlled: false,
12221
+ onModeChange: null,
12222
+ // availableModes
12223
+ availableModes: ["table", "cards", "json"],
12224
+ // User-intent setter
12225
+ setViewMode: (mode) => {
12226
+ const { isModeControlled, onModeChange } = get();
12227
+ onModeChange?.(mode);
12228
+ if (!isModeControlled) {
12229
+ const updates = { viewMode: mode };
12230
+ const currentState = get();
12231
+ const mergedState = { ...currentState, ...updates };
12232
+ const flags = computeFlags(mergedState);
12233
+ set({ ...updates, ...flags });
12234
+ }
12235
+ },
12236
+ // Track if we've ever synced viewMode from props
12237
+ hasSyncedFromProps: false,
12238
+ // Server-side pagination
12239
+ manualPagination: false,
12240
+ pageCount: void 0,
12241
+ rowCount: void 0,
12242
+ pagination: { pageIndex: 0, pageSize: 10 },
12243
+ isPaginationControlled: false,
12244
+ onPaginationChange: null,
12245
+ // User-intent setter for pagination
12246
+ setPagination: (pagination) => {
12247
+ const { isPaginationControlled, onPaginationChange } = get();
12248
+ onPaginationChange?.(pagination);
12249
+ if (!isPaginationControlled) {
12250
+ set({ pagination });
12251
+ }
12252
+ },
12253
+ // Track if we've ever synced pagination from props
12254
+ hasSyncedPaginationFromProps: false,
12255
+ // Row selection
12256
+ rowSelection: {},
12257
+ isRowSelectionControlled: false,
12258
+ onRowSelectionChange: null,
12259
+ setRowSelection: (state) => {
12260
+ const { isRowSelectionControlled, onRowSelectionChange } = get();
12261
+ onRowSelectionChange?.(state);
12262
+ if (!isRowSelectionControlled) {
12263
+ set({ rowSelection: state });
12264
+ }
12265
+ },
12266
+ hasSyncedRowSelectionFromProps: false,
12267
+ // Sorting
12268
+ sorting: [],
12269
+ isSortingControlled: false,
12270
+ onSortingChange: null,
12271
+ setSorting: (state) => {
12272
+ const { isSortingControlled, onSortingChange } = get();
12273
+ onSortingChange?.(state);
12274
+ if (!isSortingControlled) {
12275
+ set({ sorting: state });
12276
+ }
12277
+ },
12278
+ hasSyncedSortingFromProps: false,
12279
+ // Responsive cards
12280
+ responsiveCards: true,
12281
+ isMobile: false,
12282
+ effectiveViewMode: "table",
12283
+ cardPagination: { mode: "paged" },
12284
+ // Empty states
12285
+ isEmpty: void 0,
12286
+ isFilteredEmpty: false,
12287
+ renderFilteredEmpty: null,
12288
+ // Row expansion
12289
+ expanded: {},
12290
+ isExpandedControlled: false,
12291
+ onExpandedChange: null,
12292
+ getRowCanExpand: null,
12293
+ renderSubRow: null,
12294
+ setExpanded: (next) => {
12295
+ const { isExpandedControlled, onExpandedChange } = get();
12296
+ onExpandedChange?.(next);
12297
+ if (!isExpandedControlled) {
12298
+ set({ expanded: next });
12299
+ }
12300
+ },
12301
+ hasSyncedExpandedFromProps: false,
12302
+ syncWithProps: (updates) => {
12226
12303
  const currentState = get();
12227
- const mergedState = { ...currentState, ...updates };
12304
+ const hasSyncedFromProps = currentState.hasSyncedFromProps || "viewMode" in updates;
12305
+ const hasSyncedPaginationFromProps = currentState.hasSyncedPaginationFromProps || "pagination" in updates;
12306
+ const hasSyncedRowSelectionFromProps = currentState.hasSyncedRowSelectionFromProps || "rowSelection" in updates;
12307
+ const hasSyncedExpandedFromProps = currentState.hasSyncedExpandedFromProps || "expanded" in updates;
12308
+ const hasSyncedSortingFromProps = currentState.hasSyncedSortingFromProps || "sorting" in updates;
12309
+ const mergedState = { ...currentState, ...updates};
12228
12310
  const flags = computeFlags(mergedState);
12229
- set({ ...updates, ...flags });
12311
+ set({ ...updates, ...flags, hasSyncedFromProps, hasSyncedPaginationFromProps, hasSyncedRowSelectionFromProps, hasSyncedExpandedFromProps, hasSyncedSortingFromProps });
12230
12312
  }
12231
- },
12232
- // Track if we've ever synced viewMode from props
12233
- hasSyncedFromProps: false,
12234
- // Server-side pagination
12235
- manualPagination: false,
12236
- pageCount: void 0,
12237
- rowCount: void 0,
12238
- pagination: { pageIndex: 0, pageSize: 10 },
12239
- isPaginationControlled: false,
12240
- onPaginationChange: null,
12241
- // User-intent setter for pagination
12242
- setPagination: (pagination) => {
12243
- const { isPaginationControlled, onPaginationChange } = get();
12244
- onPaginationChange?.(pagination);
12245
- if (!isPaginationControlled) {
12246
- set({ pagination });
12247
- }
12248
- },
12249
- // Track if we've ever synced pagination from props
12250
- hasSyncedPaginationFromProps: false,
12251
- // Row selection
12252
- rowSelection: {},
12253
- isRowSelectionControlled: false,
12254
- onRowSelectionChange: null,
12255
- setRowSelection: (state) => {
12256
- const { isRowSelectionControlled, onRowSelectionChange } = get();
12257
- onRowSelectionChange?.(state);
12258
- if (!isRowSelectionControlled) {
12259
- set({ rowSelection: state });
12260
- }
12261
- },
12262
- hasSyncedRowSelectionFromProps: false,
12263
- // Sorting
12264
- sorting: [],
12265
- isSortingControlled: false,
12266
- onSortingChange: null,
12267
- setSorting: (state) => {
12268
- const { isSortingControlled, onSortingChange } = get();
12269
- onSortingChange?.(state);
12270
- if (!isSortingControlled) {
12271
- set({ sorting: state });
12272
- }
12273
- },
12274
- hasSyncedSortingFromProps: false,
12275
- // Responsive cards
12276
- responsiveCards: true,
12277
- isMobile: false,
12278
- effectiveViewMode: "table",
12279
- cardPagination: { mode: "paged" },
12280
- // Empty states
12281
- isEmpty: void 0,
12282
- isFilteredEmpty: false,
12283
- renderFilteredEmpty: null,
12284
- // Row expansion
12285
- expanded: {},
12286
- isExpandedControlled: false,
12287
- onExpandedChange: null,
12288
- getRowCanExpand: null,
12289
- renderSubRow: null,
12290
- setExpanded: (next) => {
12291
- const { isExpandedControlled, onExpandedChange } = get();
12292
- onExpandedChange?.(next);
12293
- if (!isExpandedControlled) {
12294
- set({ expanded: next });
12295
- }
12296
- },
12297
- hasSyncedExpandedFromProps: false,
12298
- syncWithProps: (updates) => {
12299
- const currentState = get();
12300
- const hasSyncedFromProps = currentState.hasSyncedFromProps || "viewMode" in updates;
12301
- const hasSyncedPaginationFromProps = currentState.hasSyncedPaginationFromProps || "pagination" in updates;
12302
- const hasSyncedRowSelectionFromProps = currentState.hasSyncedRowSelectionFromProps || "rowSelection" in updates;
12303
- const hasSyncedExpandedFromProps = currentState.hasSyncedExpandedFromProps || "expanded" in updates;
12304
- const hasSyncedSortingFromProps = currentState.hasSyncedSortingFromProps || "sorting" in updates;
12305
- const mergedState = { ...currentState, ...updates};
12306
- const flags = computeFlags(mergedState);
12307
- set({ ...updates, ...flags, hasSyncedFromProps, hasSyncedPaginationFromProps, hasSyncedRowSelectionFromProps, hasSyncedExpandedFromProps, hasSyncedSortingFromProps });
12308
- }
12309
- }));
12313
+ };
12314
+ if (!seed) return defaults;
12315
+ const merged = {
12316
+ ...defaults,
12317
+ ...seed,
12318
+ hasSyncedFromProps: "viewMode" in seed,
12319
+ hasSyncedPaginationFromProps: "pagination" in seed,
12320
+ hasSyncedRowSelectionFromProps: "rowSelection" in seed,
12321
+ hasSyncedExpandedFromProps: "expanded" in seed,
12322
+ hasSyncedSortingFromProps: "sorting" in seed
12323
+ };
12324
+ return { ...merged, ...computeFlags(merged) };
12325
+ });
12310
12326
  return createSelectors(store);
12311
12327
  };
12312
12328
 
@@ -12355,7 +12371,9 @@ var DEFAULT_TABLE_HEADER_HEIGHT = 48;
12355
12371
  var DEFAULT_CARD_HEIGHT = 176;
12356
12372
  var DEFAULT_CARD_GAP = 12;
12357
12373
  var ROOT_SECTION_GAP_COUNT = 2;
12358
- var DYNAMIC_PAGE_SIZE_DEBOUNCE_MS = 200;
12374
+ var DYNAMIC_PAGE_SIZE_DEBOUNCE_MS = 800;
12375
+ var MAX_PAGE_SIZE_REPORTS_PER_GEOMETRY = 2;
12376
+ var GEOMETRY_BUCKET_PX = 32;
12359
12377
  function useStoreSync(props) {
12360
12378
  const storeRef = useRef(null);
12361
12379
  const isControlled = props.mode !== void 0;
@@ -12364,7 +12382,6 @@ function useStoreSync(props) {
12364
12382
  const isExpandedControlled = props.expanded !== void 0;
12365
12383
  const isSortingControlled = props.sorting !== void 0;
12366
12384
  if (!storeRef.current) {
12367
- storeRef.current = createTableStore();
12368
12385
  const syncSnapshot = { ...props, isModeControlled: isControlled, isPaginationControlled, isRowSelectionControlled, isExpandedControlled, isSortingControlled };
12369
12386
  delete syncSnapshot.pagination;
12370
12387
  delete syncSnapshot.defaultPagination;
@@ -12382,7 +12399,7 @@ function useStoreSync(props) {
12382
12399
  delete syncSnapshot.defaultSorting;
12383
12400
  if (props.sorting !== void 0) syncSnapshot.sorting = props.sorting;
12384
12401
  else if (props.defaultSorting !== void 0) syncSnapshot.sorting = props.defaultSorting;
12385
- storeRef.current.getState().syncWithProps(syncSnapshot);
12402
+ storeRef.current = createTableStore(syncSnapshot);
12386
12403
  }
12387
12404
  useLayoutEffect(() => {
12388
12405
  const syncData = { ...props };
@@ -12515,6 +12532,9 @@ function useDynamicPageSize(containerRef, effectiveViewMode) {
12515
12532
  // mutating the table directly, so the consumer still owns fetching.
12516
12533
  calculatedPageSize: newPageSize,
12517
12534
  calculatedCardPageSize: cardPageSize,
12535
+ // Distinguishes a real measurement from the seeded defaults, so a page
12536
+ // size is never reported before the container has been measured.
12537
+ hasMeasuredLayout: bodyHeight > 0,
12518
12538
  ...!manualPagination ? { maxHeight: calculatedMaxHeight } : {},
12519
12539
  skeletonRowCount: newPageSize,
12520
12540
  bodyWidth,
@@ -12566,6 +12586,8 @@ function useTable(effectiveViewModeOverride) {
12566
12586
  const calculatedPageSize = useTableStore.use.calculatedPageSize();
12567
12587
  const calculatedCardPageSize = useTableStore.use.calculatedCardPageSize();
12568
12588
  const measuredBodyHeight = useTableStore.use.bodyHeight();
12589
+ const measuredBodyWidth = useTableStore.use.bodyWidth();
12590
+ const hasMeasuredLayout = useTableStore.use.hasMeasuredLayout();
12569
12591
  const syncWithProps = useTableStore.use.syncWithProps();
12570
12592
  const onStateChange = useTableStore.use.onStateChange();
12571
12593
  const getRowId = useTableStore.use.getRowId();
@@ -12573,6 +12595,7 @@ function useTable(effectiveViewModeOverride) {
12573
12595
  const pageCount = useTableStore.use.pageCount();
12574
12596
  const rowCount = useTableStore.use.rowCount();
12575
12597
  const storePagination = useTableStore.use.pagination();
12598
+ const isPaginationControlled = useTableStore.use.isPaginationControlled();
12576
12599
  const setPagination = useTableStore.use.setPagination();
12577
12600
  const storeRowSelection = useTableStore.use.rowSelection();
12578
12601
  const setRowSelection = useTableStore.use.setRowSelection();
@@ -12686,23 +12709,26 @@ function useTable(effectiveViewModeOverride) {
12686
12709
  syncWithProps({ table });
12687
12710
  }, [table]);
12688
12711
  useLayoutEffect(() => {
12689
- if (manualPagination) return;
12712
+ if (manualPagination || isPaginationControlled) return;
12690
12713
  if (dynamicHeight && renderedMode === "table") table.setPageSize(calculatedPageSize);
12691
12714
  if (viewMode === "cards" && cardPagination.mode === "paged") {
12692
12715
  table.setPageSize(data.length || 9999);
12693
12716
  }
12694
- }, [calculatedPageSize, dynamicHeight, renderedMode, viewMode, cardPagination.mode, table, data.length, manualPagination]);
12717
+ }, [calculatedPageSize, dynamicHeight, renderedMode, viewMode, cardPagination.mode, table, data.length, manualPagination, isPaginationControlled]);
12695
12718
  const dynamicPageSizeTarget = renderedMode === "cards" ? calculatedCardPageSize : calculatedPageSize;
12696
- const lastRequestedDynamicSizeRef = useRef(null);
12719
+ const geometryKey = `${Math.round(measuredBodyWidth / GEOMETRY_BUCKET_PX)}x${Math.round(measuredBodyHeight / GEOMETRY_BUCKET_PX)}`;
12720
+ const reportedGeometryRef = useRef(null);
12697
12721
  useEffect(() => {
12698
12722
  if (!manualPagination || !dynamicHeight) return;
12699
12723
  if (cardPagination.mode !== "paged") return;
12700
- if (measuredBodyHeight <= 0) return;
12724
+ if (!hasMeasuredLayout || measuredBodyHeight <= 0) return;
12701
12725
  if (!dynamicPageSizeTarget || dynamicPageSizeTarget < 1) return;
12702
12726
  if (dynamicPageSizeTarget === storePagination.pageSize) return;
12703
- if (lastRequestedDynamicSizeRef.current === dynamicPageSizeTarget) return;
12727
+ const reported = reportedGeometryRef.current;
12728
+ if (reported?.key === geometryKey && reported.count >= MAX_PAGE_SIZE_REPORTS_PER_GEOMETRY) return;
12704
12729
  const timer = setTimeout(() => {
12705
- lastRequestedDynamicSizeRef.current = dynamicPageSizeTarget;
12730
+ const current = reportedGeometryRef.current;
12731
+ reportedGeometryRef.current = current?.key === geometryKey ? { key: geometryKey, count: current.count + 1 } : { key: geometryKey, count: 1 };
12706
12732
  setPagination({ pageIndex: storePagination.pageIndex, pageSize: dynamicPageSizeTarget });
12707
12733
  }, DYNAMIC_PAGE_SIZE_DEBOUNCE_MS);
12708
12734
  return () => clearTimeout(timer);
@@ -12710,7 +12736,9 @@ function useTable(effectiveViewModeOverride) {
12710
12736
  manualPagination,
12711
12737
  dynamicHeight,
12712
12738
  cardPagination.mode,
12739
+ hasMeasuredLayout,
12713
12740
  measuredBodyHeight,
12741
+ geometryKey,
12714
12742
  dynamicPageSizeTarget,
12715
12743
  storePagination.pageIndex,
12716
12744
  storePagination.pageSize,
@@ -12905,7 +12933,7 @@ function NTableContent({ effectiveMode }) {
12905
12933
  const onBackgroundContextMenu = useTableStore.use.onBackgroundContextMenu();
12906
12934
  const getRowClassName = useTableStore.use.getRowClassName();
12907
12935
  const onCellEdit = useTableStore.use.onCellEdit();
12908
- const isLoading = useTableStore.use.isLoading();
12936
+ useTableStore.use.isLoading();
12909
12937
  const error = useTableStore.use.error();
12910
12938
  const hasNoData = useTableStore.use.hasNoData();
12911
12939
  const showContent = useTableStore.use.showContent();
@@ -12925,7 +12953,7 @@ function NTableContent({ effectiveMode }) {
12925
12953
  onBackgroundContextMenu(e);
12926
12954
  }
12927
12955
  }, [onBackgroundContextMenu]);
12928
- if (isLoading || error || hasNoData || !showContent || !table) return null;
12956
+ if (error || hasNoData || !showContent || !table) return null;
12929
12957
  const isTableView = effectiveMode ? effectiveMode === "table" : storeIsTableView;
12930
12958
  if (!isTableView) return null;
12931
12959
  const getSortIcon = (column) => {
@@ -13179,7 +13207,7 @@ function NDataCardShell({ row, onClick, onContextMenu, actions, children, classN
13179
13207
  );
13180
13208
  }
13181
13209
  var DEFAULT_ROWS2 = 6;
13182
- var DEFAULT_CARD_COUNT = 16;
13210
+ var DEFAULT_CARD_COUNT = 48;
13183
13211
  function NTableCardSkeleton({ surface }) {
13184
13212
  return /* @__PURE__ */ jsx(
13185
13213
  "div",
@@ -13482,13 +13510,13 @@ function useCardContinuation({
13482
13510
  });
13483
13511
  React__default.useEffect(() => {
13484
13512
  const previous = previousRowCountRef.current;
13485
- if (rowCount > previous) {
13486
- const appended = rowCount - previous;
13487
- setAnnouncement(
13488
- configRef.current?.itemsLoadedLabel?.(appended) ?? `${appended} more ${appended === 1 ? "item" : "items"} loaded.`
13489
- );
13490
- }
13491
13513
  previousRowCountRef.current = rowCount;
13514
+ if (!configRef.current) return;
13515
+ if (rowCount <= previous) return;
13516
+ const appended = rowCount - previous;
13517
+ setAnnouncement(
13518
+ configRef.current.itemsLoadedLabel?.(appended) ?? `${appended} more ${appended === 1 ? "item" : "items"} loaded.`
13519
+ );
13492
13520
  }, [rowCount]);
13493
13521
  React__default.useEffect(() => {
13494
13522
  if (!pending) doneLoading();
@@ -13534,7 +13562,7 @@ function NTableCards({ effectiveMode }) {
13534
13562
  const selectedRowId = useTableStore.use.selectedRowId();
13535
13563
  const CardComponent = useTableStore.use.CardComponent();
13536
13564
  const storeIsCardView = useTableStore.use.isCardView();
13537
- const isLoading = useTableStore.use.isLoading();
13565
+ useTableStore.use.isLoading();
13538
13566
  const error = useTableStore.use.error();
13539
13567
  const hasNoData = useTableStore.use.hasNoData();
13540
13568
  const showContent = useTableStore.use.showContent();
@@ -13580,7 +13608,7 @@ function NTableCards({ effectiveMode }) {
13580
13608
  onBackgroundContextMenu(e);
13581
13609
  }
13582
13610
  }, [table, onRowContextMenu, onBackgroundContextMenu]);
13583
- if (isLoading || error || hasNoData || !showContent || !table) return null;
13611
+ if (error || hasNoData || !showContent || !table) return null;
13584
13612
  const isCardView = effectiveMode ? effectiveMode === "cards" : storeIsCardView;
13585
13613
  if (!isCardView) return null;
13586
13614
  const rows = table.getRowModel().rows;
@@ -13699,7 +13727,16 @@ function NTableCards({ effectiveMode }) {
13699
13727
  }
13700
13728
  ) : null,
13701
13729
  continuation.active ? /* @__PURE__ */ jsx("div", { ref: continuation.sentinelRef, "data-ntable-cards-sentinel": true, "aria-hidden": "true" }) : null,
13702
- /* @__PURE__ */ jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: continuation.announcement })
13730
+ continuationConfig ? /* @__PURE__ */ jsx(
13731
+ "span",
13732
+ {
13733
+ role: "status",
13734
+ "aria-live": "polite",
13735
+ "aria-atomic": "true",
13736
+ className: "sr-only absolute -m-px h-px w-px overflow-hidden border-0 p-0 whitespace-nowrap [clip:rect(0,0,0,0)]",
13737
+ children: continuation.announcement
13738
+ }
13739
+ ) : null
13703
13740
  ] });
13704
13741
  }
13705
13742
  function CardLoadMorePagination({
@@ -14137,6 +14174,7 @@ function TableToolbarSlot() {
14137
14174
  function NTableHeader() {
14138
14175
  const hasControls = useTableStore.use.hasControls();
14139
14176
  const isLoading = useTableStore.use.isLoading();
14177
+ const isRefreshing = useTableStore.use.isRefreshing();
14140
14178
  const error = useTableStore.use.error();
14141
14179
  const hasNoData = useTableStore.use.hasNoData();
14142
14180
  const isFilteredEmpty = useTableStore.use.isFilteredEmpty();
@@ -14147,7 +14185,7 @@ function NTableHeader() {
14147
14185
  const isCustomMode = useTableStore.use.isCustomMode();
14148
14186
  const showViewToggle = useTableStore.use.showViewToggle();
14149
14187
  const showColumnVisibility = useTableStore.use.showColumnVisibility();
14150
- const hideDataChrome = isLoading || error || hasNoData && !isFilteredEmpty;
14188
+ const hideDataChrome = isLoading && !isRefreshing || error || hasNoData && !isFilteredEmpty;
14151
14189
  if (isCustomMode) {
14152
14190
  if (!showViewToggle && !showColumnVisibility && !headerSlot && !hasControls) return null;
14153
14191
  if (hideDataChrome) return null;
@@ -14229,6 +14267,7 @@ function TableLayout(props) {
14229
14267
  const className = useTableStore.use.className();
14230
14268
  useTableStore.use.dynamicHeight();
14231
14269
  const isLoading = useTableStore.use.isLoading();
14270
+ const isRefreshing = useTableStore.use.isRefreshing();
14232
14271
  const error = useTableStore.use.error();
14233
14272
  const hasNoData = useTableStore.use.hasNoData();
14234
14273
  const noDataText = useTableStore.use.noDataText();
@@ -14269,15 +14308,24 @@ function TableLayout(props) {
14269
14308
  const customRenderer = isCustomMode ? renderCustomMode?.[viewMode] : void 0;
14270
14309
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, "data-ntable-root": true, className: cn("flex h-full min-h-0 flex-1 w-full flex-col gap-2 overflow-hidden", classNames?.root, className), children: [
14271
14310
  /* @__PURE__ */ jsx(NTableHeader, {}),
14272
- /* @__PURE__ */ jsx("div", { "data-ntable-body": true, className: "flex min-h-0 flex-1 flex-col gap-2 overflow-hidden", children: isCustomMode ? customRenderer ? customRenderer() : null : /* @__PURE__ */ jsxs(Fragment, { children: [
14273
- isLoading && (props.renderLoading ? /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderLoading() }) : effectiveMode === "table" ? /* @__PURE__ */ jsx(NTableLoadingSkeleton, {}) : effectiveMode === "cards" ? /* @__PURE__ */ jsx(NTableCardsLoadingSkeleton, {}) : /* @__PURE__ */ jsx(NTableLoadingSkeleton, {})),
14274
- error && !isLoading && /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderError ? props.renderError(error) : /* @__PURE__ */ jsx(NErrorState, { message: typeof error === "string" ? error : "An error occurred" }) }),
14275
- showFilteredEmpty && /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderFilteredEmpty ? props.renderFilteredEmpty() : renderFilteredEmpty ? renderFilteredEmpty() : props.renderEmpty ? props.renderEmpty() : /* @__PURE__ */ jsx(DefaultTableFilteredEmptyState, {}) }),
14276
- showEmpty && /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderEmpty ? props.renderEmpty() : /* @__PURE__ */ jsx(DefaultTableEmptyState, { title: noDataText }) }),
14277
- /* @__PURE__ */ jsx(NTableContent, { effectiveMode }),
14278
- /* @__PURE__ */ jsx(NTableCards, { effectiveMode }),
14279
- /* @__PURE__ */ jsx(NTableJson, {})
14280
- ] }) }),
14311
+ /* @__PURE__ */ jsx(
14312
+ "div",
14313
+ {
14314
+ "data-ntable-body": true,
14315
+ "data-ntable-refreshing": isRefreshing ? "true" : void 0,
14316
+ "aria-busy": isRefreshing ? "true" : void 0,
14317
+ className: "flex min-h-0 flex-1 flex-col gap-2 overflow-hidden",
14318
+ children: isCustomMode ? customRenderer ? customRenderer() : null : /* @__PURE__ */ jsxs(Fragment, { children: [
14319
+ isLoading && !isRefreshing && (props.renderLoading ? /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderLoading() }) : effectiveMode === "table" ? /* @__PURE__ */ jsx(NTableLoadingSkeleton, {}) : effectiveMode === "cards" ? /* @__PURE__ */ jsx(NTableCardsLoadingSkeleton, {}) : /* @__PURE__ */ jsx(NTableLoadingSkeleton, {})),
14320
+ error && !isLoading && /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderError ? props.renderError(error) : /* @__PURE__ */ jsx(NErrorState, { message: typeof error === "string" ? error : "An error occurred" }) }),
14321
+ showFilteredEmpty && /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderFilteredEmpty ? props.renderFilteredEmpty() : renderFilteredEmpty ? renderFilteredEmpty() : props.renderEmpty ? props.renderEmpty() : /* @__PURE__ */ jsx(DefaultTableFilteredEmptyState, {}) }),
14322
+ showEmpty && /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderEmpty ? props.renderEmpty() : /* @__PURE__ */ jsx(DefaultTableEmptyState, { title: noDataText }) }),
14323
+ /* @__PURE__ */ jsx(NTableContent, { effectiveMode }),
14324
+ /* @__PURE__ */ jsx(NTableCards, { effectiveMode }),
14325
+ /* @__PURE__ */ jsx(NTableJson, {})
14326
+ ] })
14327
+ }
14328
+ ),
14281
14329
  /* @__PURE__ */ jsx("div", { "data-ntable-pagination": true, className: "min-w-0 shrink-0 bg-background text-foreground", children: /* @__PURE__ */ jsx(NTablePagination, {}) })
14282
14330
  ] });
14283
14331
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-kit",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Reusable React UI component package for Najm framework",