najm-kit 2.2.1 → 2.2.3

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/dist/index.d.ts CHANGED
@@ -2871,8 +2871,12 @@ 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;
2878
+ /** Placeholder shaped like `CardComponent`; see `renderCardSkeleton`. */
2879
+ CardSkeletonComponent: ComponentType<any> | null;
2876
2880
  className: string;
2877
2881
  classNames: NTableClassNames;
2878
2882
  bordered?: boolean;
@@ -2909,6 +2913,8 @@ interface TableState {
2909
2913
  calculatedPageSize: number;
2910
2914
  /** Whole card rows that fit the measured body, multiplied by the column count. */
2911
2915
  calculatedCardPageSize: number;
2916
+ /** True once a real layout measurement has replaced the seeded defaults. */
2917
+ hasMeasuredLayout: boolean;
2912
2918
  skeletonRowCount: number;
2913
2919
  maxHeight: number | null;
2914
2920
  bodyWidth: number;
@@ -2970,7 +2976,16 @@ interface TableState {
2970
2976
  hasSyncedExpandedFromProps: boolean;
2971
2977
  }
2972
2978
  type TableStore = ReturnType<typeof createTableStore>;
2973
- declare const createTableStore: () => {
2979
+ /**
2980
+ * `seed` becomes part of the store's *initial* state rather than a `set()` call
2981
+ * made after creation. Zustand serves `getInitialState()` as the snapshot for
2982
+ * server rendering and for the hydration render, so anything applied after
2983
+ * creation is invisible on the first paint: `isLoading` would read false while
2984
+ * the caller passed true (rendering the empty state instead of the skeleton),
2985
+ * and `manualPagination` would read false long enough for layout effects to
2986
+ * push a default page size back to a consumer that owns pagination.
2987
+ */
2988
+ declare const createTableStore: (seed?: Partial<TableState>) => {
2974
2989
  (): TableState;
2975
2990
  <U>(selector: (state: TableState) => U): U;
2976
2991
  } & StoreApi<TableState> & {
@@ -2998,8 +3013,10 @@ declare const createTableStore: () => {
2998
3013
  hasData: () => boolean;
2999
3014
  hasControls: () => boolean;
3000
3015
  hasNoData: () => boolean;
3016
+ isRefreshing: () => boolean;
3001
3017
  dynamicHeight: () => boolean;
3002
3018
  CardComponent: () => ComponentType<any>;
3019
+ CardSkeletonComponent: () => ComponentType<any>;
3003
3020
  className: () => string;
3004
3021
  classNames: () => NTableClassNames;
3005
3022
  bordered?: () => boolean;
@@ -3035,6 +3052,7 @@ declare const createTableStore: () => {
3035
3052
  pageSizeOptions: () => number[];
3036
3053
  calculatedPageSize: () => number;
3037
3054
  calculatedCardPageSize: () => number;
3055
+ hasMeasuredLayout: () => boolean;
3038
3056
  skeletonRowCount: () => number;
3039
3057
  maxHeight: () => number;
3040
3058
  bodyWidth: () => number;
@@ -3184,6 +3202,32 @@ interface NTableProps<T = any, M extends ViewMode = ViewMode> {
3184
3202
  'data-row'?: string;
3185
3203
  'data-row-id'?: string;
3186
3204
  }>;
3205
+ /**
3206
+ * A placeholder shaped like `renderCard`.
3207
+ *
3208
+ * The card page size is measured from whatever is on screen, and during a
3209
+ * first load that is the skeleton. The built-in placeholder is an avatar row,
3210
+ * so a consumer whose cards are a different height — a media card, say — gets
3211
+ * a page size measured against a card it does not use, and the grid re-lays
3212
+ * out once real cards arrive. Supplying a placeholder with the real card's
3213
+ * geometry makes the first measurement the correct one.
3214
+ */
3215
+ renderCardSkeleton?: ComponentType<Record<string, never>>;
3216
+ /**
3217
+ * Emit both skeleton shapes and let a media query choose between them.
3218
+ *
3219
+ * A view mode derived from the viewport is unknowable on the server, so the
3220
+ * server renders the card shape and the client corrects it after hydration —
3221
+ * on a desktop table page that is a visible card skeleton followed by a table
3222
+ * skeleton. No client-side detection can fix it, because the first paint is
3223
+ * whatever the server sent. Rendering both and hiding one in CSS puts the
3224
+ * right shape in that first paint. The skeleton is `aria-hidden` decoration,
3225
+ * so the duplicate costs nothing to assistive technology.
3226
+ *
3227
+ * The breakpoint is `lg` (1024px), matching the point at which a table
3228
+ * becomes usable.
3229
+ */
3230
+ responsiveSkeleton?: boolean;
3187
3231
  renderToolbar?: (state: NTableState) => React__default.ReactNode;
3188
3232
  renderEmpty?: () => React__default.ReactNode;
3189
3233
  renderError?: (error: any) => React__default.ReactNode;
@@ -3417,8 +3461,10 @@ declare const TableStoreContext: React$1.Context<{
3417
3461
  hasData: () => boolean;
3418
3462
  hasControls: () => boolean;
3419
3463
  hasNoData: () => boolean;
3464
+ isRefreshing: () => boolean;
3420
3465
  dynamicHeight: () => boolean;
3421
3466
  CardComponent: () => React$1.ComponentType<any>;
3467
+ CardSkeletonComponent: () => React$1.ComponentType<any>;
3422
3468
  className: () => string;
3423
3469
  classNames: () => NTableClassNames;
3424
3470
  bordered?: () => boolean;
@@ -3454,6 +3500,7 @@ declare const TableStoreContext: React$1.Context<{
3454
3500
  pageSizeOptions: () => number[];
3455
3501
  calculatedPageSize: () => number;
3456
3502
  calculatedCardPageSize: () => number;
3503
+ hasMeasuredLayout: () => boolean;
3457
3504
  skeletonRowCount: () => number;
3458
3505
  maxHeight: () => number;
3459
3506
  bodyWidth: () => number;
@@ -3547,8 +3594,10 @@ declare function useStoreSync(props: any): {
3547
3594
  hasData: () => boolean;
3548
3595
  hasControls: () => boolean;
3549
3596
  hasNoData: () => boolean;
3597
+ isRefreshing: () => boolean;
3550
3598
  dynamicHeight: () => boolean;
3551
3599
  CardComponent: () => React__default.ComponentType<any>;
3600
+ CardSkeletonComponent: () => React__default.ComponentType<any>;
3552
3601
  className: () => string;
3553
3602
  classNames: () => NTableClassNames;
3554
3603
  bordered?: () => boolean;
@@ -3584,6 +3633,7 @@ declare function useStoreSync(props: any): {
3584
3633
  pageSizeOptions: () => number[];
3585
3634
  calculatedPageSize: () => number;
3586
3635
  calculatedCardPageSize: () => number;
3636
+ hasMeasuredLayout: () => boolean;
3587
3637
  skeletonRowCount: () => number;
3588
3638
  maxHeight: () => number;
3589
3639
  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,193 @@ 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
+ CardSkeletonComponent: null,
12187
+ className: "",
12188
+ classNames: {},
12189
+ bordered: void 0,
12190
+ headerClassName: "bg-card",
12191
+ headerColor: void 0,
12192
+ headerTextColor: void 0,
12193
+ borderColor: void 0,
12194
+ showCheckbox: true,
12195
+ selectedRowId: null,
12196
+ headerSlot: null,
12197
+ noResultsText: "No results.",
12198
+ filterPlaceholder: "",
12199
+ loadingText: "Loading...",
12200
+ noDataText: "No data available",
12201
+ addButtonText: "",
12202
+ pageSizeOptions: [10, 20, 30, 40, 50],
12203
+ calculatedPageSize: 10,
12204
+ calculatedCardPageSize: 0,
12205
+ hasMeasuredLayout: false,
12206
+ skeletonRowCount: 6,
12207
+ maxHeight: null,
12208
+ bodyWidth: 0,
12209
+ bodyHeight: 0,
12210
+ tableHeaderHeight: 48,
12211
+ cardColumnCount: 1,
12212
+ cardRowHeight: 0,
12213
+ cardGap: 12,
12214
+ // JSON mode
12215
+ jsonValue: void 0,
12216
+ jsonColors: null,
12217
+ renderJson: null,
12218
+ // Custom mode
12219
+ renderCustomMode: null,
12220
+ // Controlled mode
12221
+ isModeControlled: false,
12222
+ onModeChange: null,
12223
+ // availableModes
12224
+ availableModes: ["table", "cards", "json"],
12225
+ // User-intent setter
12226
+ setViewMode: (mode) => {
12227
+ const { isModeControlled, onModeChange } = get();
12228
+ onModeChange?.(mode);
12229
+ if (!isModeControlled) {
12230
+ const updates = { viewMode: mode };
12231
+ const currentState = get();
12232
+ const mergedState = { ...currentState, ...updates };
12233
+ const flags = computeFlags(mergedState);
12234
+ set({ ...updates, ...flags });
12235
+ }
12236
+ },
12237
+ // Track if we've ever synced viewMode from props
12238
+ hasSyncedFromProps: false,
12239
+ // Server-side pagination
12240
+ manualPagination: false,
12241
+ pageCount: void 0,
12242
+ rowCount: void 0,
12243
+ pagination: { pageIndex: 0, pageSize: 10 },
12244
+ isPaginationControlled: false,
12245
+ onPaginationChange: null,
12246
+ // User-intent setter for pagination
12247
+ setPagination: (pagination) => {
12248
+ const { isPaginationControlled, onPaginationChange } = get();
12249
+ onPaginationChange?.(pagination);
12250
+ if (!isPaginationControlled) {
12251
+ set({ pagination });
12252
+ }
12253
+ },
12254
+ // Track if we've ever synced pagination from props
12255
+ hasSyncedPaginationFromProps: false,
12256
+ // Row selection
12257
+ rowSelection: {},
12258
+ isRowSelectionControlled: false,
12259
+ onRowSelectionChange: null,
12260
+ setRowSelection: (state) => {
12261
+ const { isRowSelectionControlled, onRowSelectionChange } = get();
12262
+ onRowSelectionChange?.(state);
12263
+ if (!isRowSelectionControlled) {
12264
+ set({ rowSelection: state });
12265
+ }
12266
+ },
12267
+ hasSyncedRowSelectionFromProps: false,
12268
+ // Sorting
12269
+ sorting: [],
12270
+ isSortingControlled: false,
12271
+ onSortingChange: null,
12272
+ setSorting: (state) => {
12273
+ const { isSortingControlled, onSortingChange } = get();
12274
+ onSortingChange?.(state);
12275
+ if (!isSortingControlled) {
12276
+ set({ sorting: state });
12277
+ }
12278
+ },
12279
+ hasSyncedSortingFromProps: false,
12280
+ // Responsive cards
12281
+ responsiveCards: true,
12282
+ isMobile: false,
12283
+ effectiveViewMode: "table",
12284
+ cardPagination: { mode: "paged" },
12285
+ // Empty states
12286
+ isEmpty: void 0,
12287
+ isFilteredEmpty: false,
12288
+ renderFilteredEmpty: null,
12289
+ // Row expansion
12290
+ expanded: {},
12291
+ isExpandedControlled: false,
12292
+ onExpandedChange: null,
12293
+ getRowCanExpand: null,
12294
+ renderSubRow: null,
12295
+ setExpanded: (next) => {
12296
+ const { isExpandedControlled, onExpandedChange } = get();
12297
+ onExpandedChange?.(next);
12298
+ if (!isExpandedControlled) {
12299
+ set({ expanded: next });
12300
+ }
12301
+ },
12302
+ hasSyncedExpandedFromProps: false,
12303
+ syncWithProps: (updates) => {
12226
12304
  const currentState = get();
12227
- const mergedState = { ...currentState, ...updates };
12305
+ const hasSyncedFromProps = currentState.hasSyncedFromProps || "viewMode" in updates;
12306
+ const hasSyncedPaginationFromProps = currentState.hasSyncedPaginationFromProps || "pagination" in updates;
12307
+ const hasSyncedRowSelectionFromProps = currentState.hasSyncedRowSelectionFromProps || "rowSelection" in updates;
12308
+ const hasSyncedExpandedFromProps = currentState.hasSyncedExpandedFromProps || "expanded" in updates;
12309
+ const hasSyncedSortingFromProps = currentState.hasSyncedSortingFromProps || "sorting" in updates;
12310
+ const mergedState = { ...currentState, ...updates};
12228
12311
  const flags = computeFlags(mergedState);
12229
- set({ ...updates, ...flags });
12230
- }
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 });
12312
+ set({ ...updates, ...flags, hasSyncedFromProps, hasSyncedPaginationFromProps, hasSyncedRowSelectionFromProps, hasSyncedExpandedFromProps, hasSyncedSortingFromProps });
12272
12313
  }
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
- }));
12314
+ };
12315
+ if (!seed) return defaults;
12316
+ const merged = {
12317
+ ...defaults,
12318
+ ...seed,
12319
+ hasSyncedFromProps: "viewMode" in seed,
12320
+ hasSyncedPaginationFromProps: "pagination" in seed,
12321
+ hasSyncedRowSelectionFromProps: "rowSelection" in seed,
12322
+ hasSyncedExpandedFromProps: "expanded" in seed,
12323
+ hasSyncedSortingFromProps: "sorting" in seed
12324
+ };
12325
+ return { ...merged, ...computeFlags(merged) };
12326
+ });
12310
12327
  return createSelectors(store);
12311
12328
  };
12312
12329
 
@@ -12355,7 +12372,9 @@ var DEFAULT_TABLE_HEADER_HEIGHT = 48;
12355
12372
  var DEFAULT_CARD_HEIGHT = 176;
12356
12373
  var DEFAULT_CARD_GAP = 12;
12357
12374
  var ROOT_SECTION_GAP_COUNT = 2;
12358
- var DYNAMIC_PAGE_SIZE_DEBOUNCE_MS = 200;
12375
+ var DYNAMIC_PAGE_SIZE_DEBOUNCE_MS = 800;
12376
+ var MAX_PAGE_SIZE_REPORTS_PER_GEOMETRY = 2;
12377
+ var GEOMETRY_BUCKET_PX = 32;
12359
12378
  function useStoreSync(props) {
12360
12379
  const storeRef = useRef(null);
12361
12380
  const isControlled = props.mode !== void 0;
@@ -12364,7 +12383,6 @@ function useStoreSync(props) {
12364
12383
  const isExpandedControlled = props.expanded !== void 0;
12365
12384
  const isSortingControlled = props.sorting !== void 0;
12366
12385
  if (!storeRef.current) {
12367
- storeRef.current = createTableStore();
12368
12386
  const syncSnapshot = { ...props, isModeControlled: isControlled, isPaginationControlled, isRowSelectionControlled, isExpandedControlled, isSortingControlled };
12369
12387
  delete syncSnapshot.pagination;
12370
12388
  delete syncSnapshot.defaultPagination;
@@ -12382,7 +12400,7 @@ function useStoreSync(props) {
12382
12400
  delete syncSnapshot.defaultSorting;
12383
12401
  if (props.sorting !== void 0) syncSnapshot.sorting = props.sorting;
12384
12402
  else if (props.defaultSorting !== void 0) syncSnapshot.sorting = props.defaultSorting;
12385
- storeRef.current.getState().syncWithProps(syncSnapshot);
12403
+ storeRef.current = createTableStore(syncSnapshot);
12386
12404
  }
12387
12405
  useLayoutEffect(() => {
12388
12406
  const syncData = { ...props };
@@ -12475,9 +12493,11 @@ function useDynamicPageSize(containerRef, effectiveViewMode) {
12475
12493
  const bodyEl = container2.querySelector("[data-ntable-body]");
12476
12494
  const tableHeaderEl = container2.querySelector("[data-ntable-table-header]");
12477
12495
  const loadingHeaderEl = container2.querySelector("[data-ntable-loading-header]");
12478
- const cardsGridEl = container2.querySelector(
12479
- "[data-ntable-loading-cards-grid], [data-ntable-cards-grid]"
12480
- );
12496
+ const cardsGridEl = Array.from(
12497
+ container2.querySelectorAll(
12498
+ "[data-ntable-loading-cards-grid], [data-ntable-cards-grid]"
12499
+ )
12500
+ ).find((el) => el.clientHeight > 0 || el.clientWidth > 0) ?? null;
12481
12501
  let bodyHeight = bodyEl?.clientHeight ?? 0;
12482
12502
  const bodyWidth = bodyEl?.clientWidth ?? container2.clientWidth ?? 0;
12483
12503
  if (!bodyHeight) {
@@ -12515,6 +12535,9 @@ function useDynamicPageSize(containerRef, effectiveViewMode) {
12515
12535
  // mutating the table directly, so the consumer still owns fetching.
12516
12536
  calculatedPageSize: newPageSize,
12517
12537
  calculatedCardPageSize: cardPageSize,
12538
+ // Distinguishes a real measurement from the seeded defaults, so a page
12539
+ // size is never reported before the container has been measured.
12540
+ hasMeasuredLayout: bodyHeight > 0,
12518
12541
  ...!manualPagination ? { maxHeight: calculatedMaxHeight } : {},
12519
12542
  skeletonRowCount: newPageSize,
12520
12543
  bodyWidth,
@@ -12567,6 +12590,7 @@ function useTable(effectiveViewModeOverride) {
12567
12590
  const calculatedCardPageSize = useTableStore.use.calculatedCardPageSize();
12568
12591
  const measuredBodyHeight = useTableStore.use.bodyHeight();
12569
12592
  const measuredBodyWidth = useTableStore.use.bodyWidth();
12593
+ const hasMeasuredLayout = useTableStore.use.hasMeasuredLayout();
12570
12594
  const syncWithProps = useTableStore.use.syncWithProps();
12571
12595
  const onStateChange = useTableStore.use.onStateChange();
12572
12596
  const getRowId = useTableStore.use.getRowId();
@@ -12574,6 +12598,7 @@ function useTable(effectiveViewModeOverride) {
12574
12598
  const pageCount = useTableStore.use.pageCount();
12575
12599
  const rowCount = useTableStore.use.rowCount();
12576
12600
  const storePagination = useTableStore.use.pagination();
12601
+ const isPaginationControlled = useTableStore.use.isPaginationControlled();
12577
12602
  const setPagination = useTableStore.use.setPagination();
12578
12603
  const storeRowSelection = useTableStore.use.rowSelection();
12579
12604
  const setRowSelection = useTableStore.use.setRowSelection();
@@ -12687,24 +12712,26 @@ function useTable(effectiveViewModeOverride) {
12687
12712
  syncWithProps({ table });
12688
12713
  }, [table]);
12689
12714
  useLayoutEffect(() => {
12690
- if (manualPagination) return;
12715
+ if (manualPagination || isPaginationControlled) return;
12691
12716
  if (dynamicHeight && renderedMode === "table") table.setPageSize(calculatedPageSize);
12692
12717
  if (viewMode === "cards" && cardPagination.mode === "paged") {
12693
12718
  table.setPageSize(data.length || 9999);
12694
12719
  }
12695
- }, [calculatedPageSize, dynamicHeight, renderedMode, viewMode, cardPagination.mode, table, data.length, manualPagination]);
12720
+ }, [calculatedPageSize, dynamicHeight, renderedMode, viewMode, cardPagination.mode, table, data.length, manualPagination, isPaginationControlled]);
12696
12721
  const dynamicPageSizeTarget = renderedMode === "cards" ? calculatedCardPageSize : calculatedPageSize;
12697
- const geometryKey = `${renderedMode}:${measuredBodyWidth}x${measuredBodyHeight}`;
12722
+ const geometryKey = `${Math.round(measuredBodyWidth / GEOMETRY_BUCKET_PX)}x${Math.round(measuredBodyHeight / GEOMETRY_BUCKET_PX)}`;
12698
12723
  const reportedGeometryRef = useRef(null);
12699
12724
  useEffect(() => {
12700
12725
  if (!manualPagination || !dynamicHeight) return;
12701
12726
  if (cardPagination.mode !== "paged") return;
12702
- if (measuredBodyHeight <= 0) return;
12727
+ if (!hasMeasuredLayout || measuredBodyHeight <= 0) return;
12703
12728
  if (!dynamicPageSizeTarget || dynamicPageSizeTarget < 1) return;
12704
12729
  if (dynamicPageSizeTarget === storePagination.pageSize) return;
12705
- if (reportedGeometryRef.current === geometryKey) return;
12730
+ const reported = reportedGeometryRef.current;
12731
+ if (reported?.key === geometryKey && reported.count >= MAX_PAGE_SIZE_REPORTS_PER_GEOMETRY) return;
12706
12732
  const timer = setTimeout(() => {
12707
- reportedGeometryRef.current = geometryKey;
12733
+ const current = reportedGeometryRef.current;
12734
+ reportedGeometryRef.current = current?.key === geometryKey ? { key: geometryKey, count: current.count + 1 } : { key: geometryKey, count: 1 };
12708
12735
  setPagination({ pageIndex: storePagination.pageIndex, pageSize: dynamicPageSizeTarget });
12709
12736
  }, DYNAMIC_PAGE_SIZE_DEBOUNCE_MS);
12710
12737
  return () => clearTimeout(timer);
@@ -12712,6 +12739,7 @@ function useTable(effectiveViewModeOverride) {
12712
12739
  manualPagination,
12713
12740
  dynamicHeight,
12714
12741
  cardPagination.mode,
12742
+ hasMeasuredLayout,
12715
12743
  measuredBodyHeight,
12716
12744
  geometryKey,
12717
12745
  dynamicPageSizeTarget,
@@ -12908,7 +12936,7 @@ function NTableContent({ effectiveMode }) {
12908
12936
  const onBackgroundContextMenu = useTableStore.use.onBackgroundContextMenu();
12909
12937
  const getRowClassName = useTableStore.use.getRowClassName();
12910
12938
  const onCellEdit = useTableStore.use.onCellEdit();
12911
- const isLoading = useTableStore.use.isLoading();
12939
+ useTableStore.use.isLoading();
12912
12940
  const error = useTableStore.use.error();
12913
12941
  const hasNoData = useTableStore.use.hasNoData();
12914
12942
  const showContent = useTableStore.use.showContent();
@@ -12928,7 +12956,7 @@ function NTableContent({ effectiveMode }) {
12928
12956
  onBackgroundContextMenu(e);
12929
12957
  }
12930
12958
  }, [onBackgroundContextMenu]);
12931
- if (isLoading || error || hasNoData || !showContent || !table) return null;
12959
+ if (error || hasNoData || !showContent || !table) return null;
12932
12960
  const isTableView = effectiveMode ? effectiveMode === "table" : storeIsTableView;
12933
12961
  if (!isTableView) return null;
12934
12962
  const getSortIcon = (column) => {
@@ -13182,7 +13210,7 @@ function NDataCardShell({ row, onClick, onContextMenu, actions, children, classN
13182
13210
  );
13183
13211
  }
13184
13212
  var DEFAULT_ROWS2 = 6;
13185
- var DEFAULT_CARD_COUNT = 16;
13213
+ var DEFAULT_CARD_COUNT = 48;
13186
13214
  function NTableCardSkeleton({ surface }) {
13187
13215
  return /* @__PURE__ */ jsx(
13188
13216
  "div",
@@ -13398,6 +13426,7 @@ function NTableCardsLoadingSkeleton({ rows }) {
13398
13426
  const cardRowHeight = useTableStore.use.cardRowHeight();
13399
13427
  const cardGap = useTableStore.use.cardGap();
13400
13428
  const loadingText = useTableStore.use.loadingText();
13429
+ const CardSkeletonComponent = useTableStore.use.CardSkeletonComponent();
13401
13430
  const cardCount = rows ?? (dynamicHeight && bodyHeight > 0 ? calculateCardSkeletonCount({
13402
13431
  bodyHeight,
13403
13432
  columnCount: cardColumnCount,
@@ -13426,7 +13455,7 @@ function NTableCardsLoadingSkeleton({ rows }) {
13426
13455
  "data-ntable-loading-card-count": cardCount,
13427
13456
  "aria-hidden": "true",
13428
13457
  className: cn(containerClass),
13429
- children: Array.from({ length: cardCount }).map((_, index) => /* @__PURE__ */ jsx(NTableCardSkeleton, { surface }, index))
13458
+ children: Array.from({ length: cardCount }).map((_, index) => CardSkeletonComponent ? /* @__PURE__ */ jsx("div", { "data-ntable-loading-card": true, children: /* @__PURE__ */ jsx(CardSkeletonComponent, {}) }, index) : /* @__PURE__ */ jsx(NTableCardSkeleton, { surface }, index))
13430
13459
  }
13431
13460
  )
13432
13461
  ]
@@ -13485,13 +13514,13 @@ function useCardContinuation({
13485
13514
  });
13486
13515
  React__default.useEffect(() => {
13487
13516
  const previous = previousRowCountRef.current;
13488
- if (rowCount > previous) {
13489
- const appended = rowCount - previous;
13490
- setAnnouncement(
13491
- configRef.current?.itemsLoadedLabel?.(appended) ?? `${appended} more ${appended === 1 ? "item" : "items"} loaded.`
13492
- );
13493
- }
13494
13517
  previousRowCountRef.current = rowCount;
13518
+ if (!configRef.current) return;
13519
+ if (rowCount <= previous) return;
13520
+ const appended = rowCount - previous;
13521
+ setAnnouncement(
13522
+ configRef.current.itemsLoadedLabel?.(appended) ?? `${appended} more ${appended === 1 ? "item" : "items"} loaded.`
13523
+ );
13495
13524
  }, [rowCount]);
13496
13525
  React__default.useEffect(() => {
13497
13526
  if (!pending) doneLoading();
@@ -13536,8 +13565,9 @@ function NTableCards({ effectiveMode }) {
13536
13565
  const showCheckbox = useTableStore.use.showCheckbox();
13537
13566
  const selectedRowId = useTableStore.use.selectedRowId();
13538
13567
  const CardComponent = useTableStore.use.CardComponent();
13568
+ const CardSkeletonComponent = useTableStore.use.CardSkeletonComponent();
13539
13569
  const storeIsCardView = useTableStore.use.isCardView();
13540
- const isLoading = useTableStore.use.isLoading();
13570
+ useTableStore.use.isLoading();
13541
13571
  const error = useTableStore.use.error();
13542
13572
  const hasNoData = useTableStore.use.hasNoData();
13543
13573
  const showContent = useTableStore.use.showContent();
@@ -13583,7 +13613,7 @@ function NTableCards({ effectiveMode }) {
13583
13613
  onBackgroundContextMenu(e);
13584
13614
  }
13585
13615
  }, [table, onRowContextMenu, onBackgroundContextMenu]);
13586
- if (isLoading || error || hasNoData || !showContent || !table) return null;
13616
+ if (error || hasNoData || !showContent || !table) return null;
13587
13617
  const isCardView = effectiveMode ? effectiveMode === "cards" : storeIsCardView;
13588
13618
  if (!isCardView) return null;
13589
13619
  const rows = table.getRowModel().rows;
@@ -13677,7 +13707,7 @@ function NTableCards({ effectiveMode }) {
13677
13707
  row.id
13678
13708
  );
13679
13709
  }),
13680
- continuation.pending ? Array.from({ length: Math.max(1, cardColumnCount || 1) }).map((_, index) => /* @__PURE__ */ jsx(NTableCardSkeleton, { surface }, `ntable-continuation-skeleton-${index}`)) : null
13710
+ continuation.pending ? Array.from({ length: Math.max(1, cardColumnCount || 1) }).map((_, index) => CardSkeletonComponent ? /* @__PURE__ */ jsx(CardSkeletonComponent, {}, `ntable-continuation-skeleton-${index}`) : /* @__PURE__ */ jsx(NTableCardSkeleton, { surface }, `ntable-continuation-skeleton-${index}`)) : null
13681
13711
  ] }),
13682
13712
  continuation.error ? /* @__PURE__ */ jsxs(
13683
13713
  "div",
@@ -13702,7 +13732,16 @@ function NTableCards({ effectiveMode }) {
13702
13732
  }
13703
13733
  ) : null,
13704
13734
  continuation.active ? /* @__PURE__ */ jsx("div", { ref: continuation.sentinelRef, "data-ntable-cards-sentinel": true, "aria-hidden": "true" }) : null,
13705
- /* @__PURE__ */ jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: continuation.announcement })
13735
+ continuationConfig ? /* @__PURE__ */ jsx(
13736
+ "span",
13737
+ {
13738
+ role: "status",
13739
+ "aria-live": "polite",
13740
+ "aria-atomic": "true",
13741
+ className: "sr-only absolute -m-px h-px w-px overflow-hidden border-0 p-0 whitespace-nowrap [clip:rect(0,0,0,0)]",
13742
+ children: continuation.announcement
13743
+ }
13744
+ ) : null
13706
13745
  ] });
13707
13746
  }
13708
13747
  function CardLoadMorePagination({
@@ -14140,6 +14179,7 @@ function TableToolbarSlot() {
14140
14179
  function NTableHeader() {
14141
14180
  const hasControls = useTableStore.use.hasControls();
14142
14181
  const isLoading = useTableStore.use.isLoading();
14182
+ const isRefreshing = useTableStore.use.isRefreshing();
14143
14183
  const error = useTableStore.use.error();
14144
14184
  const hasNoData = useTableStore.use.hasNoData();
14145
14185
  const isFilteredEmpty = useTableStore.use.isFilteredEmpty();
@@ -14150,7 +14190,7 @@ function NTableHeader() {
14150
14190
  const isCustomMode = useTableStore.use.isCustomMode();
14151
14191
  const showViewToggle = useTableStore.use.showViewToggle();
14152
14192
  const showColumnVisibility = useTableStore.use.showColumnVisibility();
14153
- const hideDataChrome = isLoading || error || hasNoData && !isFilteredEmpty;
14193
+ const hideDataChrome = isLoading && !isRefreshing || error || hasNoData && !isFilteredEmpty;
14154
14194
  if (isCustomMode) {
14155
14195
  if (!showViewToggle && !showColumnVisibility && !headerSlot && !hasControls) return null;
14156
14196
  if (hideDataChrome) return null;
@@ -14232,6 +14272,7 @@ function TableLayout(props) {
14232
14272
  const className = useTableStore.use.className();
14233
14273
  useTableStore.use.dynamicHeight();
14234
14274
  const isLoading = useTableStore.use.isLoading();
14275
+ const isRefreshing = useTableStore.use.isRefreshing();
14235
14276
  const error = useTableStore.use.error();
14236
14277
  const hasNoData = useTableStore.use.hasNoData();
14237
14278
  const noDataText = useTableStore.use.noDataText();
@@ -14272,15 +14313,27 @@ function TableLayout(props) {
14272
14313
  const customRenderer = isCustomMode ? renderCustomMode?.[viewMode] : void 0;
14273
14314
  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: [
14274
14315
  /* @__PURE__ */ jsx(NTableHeader, {}),
14275
- /* @__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: [
14276
- isLoading && (props.renderLoading ? /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderLoading() }) : effectiveMode === "table" ? /* @__PURE__ */ jsx(NTableLoadingSkeleton, {}) : effectiveMode === "cards" ? /* @__PURE__ */ jsx(NTableCardsLoadingSkeleton, {}) : /* @__PURE__ */ jsx(NTableLoadingSkeleton, {})),
14277
- error && !isLoading && /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderError ? props.renderError(error) : /* @__PURE__ */ jsx(NErrorState, { message: typeof error === "string" ? error : "An error occurred" }) }),
14278
- showFilteredEmpty && /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderFilteredEmpty ? props.renderFilteredEmpty() : renderFilteredEmpty ? renderFilteredEmpty() : props.renderEmpty ? props.renderEmpty() : /* @__PURE__ */ jsx(DefaultTableFilteredEmptyState, {}) }),
14279
- showEmpty && /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderEmpty ? props.renderEmpty() : /* @__PURE__ */ jsx(DefaultTableEmptyState, { title: noDataText }) }),
14280
- /* @__PURE__ */ jsx(NTableContent, { effectiveMode }),
14281
- /* @__PURE__ */ jsx(NTableCards, { effectiveMode }),
14282
- /* @__PURE__ */ jsx(NTableJson, {})
14283
- ] }) }),
14316
+ /* @__PURE__ */ jsx(
14317
+ "div",
14318
+ {
14319
+ "data-ntable-body": true,
14320
+ "data-ntable-refreshing": isRefreshing ? "true" : void 0,
14321
+ "aria-busy": isRefreshing ? "true" : void 0,
14322
+ className: "flex min-h-0 flex-1 flex-col gap-2 overflow-hidden",
14323
+ children: isCustomMode ? customRenderer ? customRenderer() : null : /* @__PURE__ */ jsxs(Fragment, { children: [
14324
+ isLoading && !isRefreshing && (props.renderLoading ? /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderLoading() }) : props.responsiveSkeleton ? /* @__PURE__ */ jsxs(Fragment, { children: [
14325
+ /* @__PURE__ */ jsx("div", { "data-ntable-skeleton-variant": "table", className: "hidden min-h-0 flex-1 flex-col lg:flex", children: /* @__PURE__ */ jsx(NTableLoadingSkeleton, {}) }),
14326
+ /* @__PURE__ */ jsx("div", { "data-ntable-skeleton-variant": "cards", className: "flex min-h-0 flex-1 flex-col lg:hidden", children: /* @__PURE__ */ jsx(NTableCardsLoadingSkeleton, {}) })
14327
+ ] }) : effectiveMode === "table" ? /* @__PURE__ */ jsx(NTableLoadingSkeleton, {}) : effectiveMode === "cards" ? /* @__PURE__ */ jsx(NTableCardsLoadingSkeleton, {}) : /* @__PURE__ */ jsx(NTableLoadingSkeleton, {})),
14328
+ error && !isLoading && /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderError ? props.renderError(error) : /* @__PURE__ */ jsx(NErrorState, { message: typeof error === "string" ? error : "An error occurred" }) }),
14329
+ showFilteredEmpty && /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderFilteredEmpty ? props.renderFilteredEmpty() : renderFilteredEmpty ? renderFilteredEmpty() : props.renderEmpty ? props.renderEmpty() : /* @__PURE__ */ jsx(DefaultTableFilteredEmptyState, {}) }),
14330
+ showEmpty && /* @__PURE__ */ jsx(TableStateSlot, { children: props.renderEmpty ? props.renderEmpty() : /* @__PURE__ */ jsx(DefaultTableEmptyState, { title: noDataText }) }),
14331
+ /* @__PURE__ */ jsx(NTableContent, { effectiveMode }),
14332
+ /* @__PURE__ */ jsx(NTableCards, { effectiveMode }),
14333
+ /* @__PURE__ */ jsx(NTableJson, {})
14334
+ ] })
14335
+ }
14336
+ ),
14284
14337
  /* @__PURE__ */ jsx("div", { "data-ntable-pagination": true, className: "min-w-0 shrink-0 bg-background text-foreground", children: /* @__PURE__ */ jsx(NTablePagination, {}) })
14285
14338
  ] });
14286
14339
  }
@@ -14375,6 +14428,7 @@ function NTable(props) {
14375
14428
  mode,
14376
14429
  onModeChange: props.onModeChange,
14377
14430
  CardComponent: props.renderCard ?? null,
14431
+ CardSkeletonComponent: props.renderCardSkeleton ?? null,
14378
14432
  className: props.className ?? "",
14379
14433
  classNames: props.classNames ?? {},
14380
14434
  bordered: props.bordered ?? (recipeBordered ? true : void 0),
@@ -14456,6 +14510,7 @@ function NTable(props) {
14456
14510
  renderFilteredEmpty: props.renderFilteredEmpty,
14457
14511
  renderError: props.renderError,
14458
14512
  renderLoading: props.renderLoading,
14513
+ responsiveSkeleton: props.responsiveSkeleton,
14459
14514
  contextMenuClose: ctx.close,
14460
14515
  contextMenuOpen: ctx.isOpen
14461
14516
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-kit",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Reusable React UI component package for Najm framework",