najm-kit 2.11.2 → 2.11.4

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,19 +1,33 @@
1
- # Changelog
2
-
3
- ## 2.11.2 - 2026-08-11
4
-
5
- - Fixed `PasswordInput` forwarding native input attributes such as generated
6
- form IDs and ARIA metadata to the actual `<input>`, restoring programmatic
7
- label association for assistive technology and browser automation.
8
-
9
- ## 2.11.1 - 2026-08-11
10
-
11
- - Fixed `NajmThemeProvider` precedence so an explicit runtime `mode` overrides
12
- a preset stored in the design config. An explicit `preset` prop still wins,
13
- allowing deliberately fixed-preset subtrees without producing mixed light
14
- and dark application surfaces.
15
-
16
- ## 2.11.0 - 2026-08-10
1
+ # Changelog
2
+
3
+ ## 2.11.4 - 2026-08-12
4
+
5
+ - Fixed server-paginated card grids briefly rendering at a placeholder-derived
6
+ page size before correcting to the real card height. Card mode now waits for
7
+ real rows and publishes the measured fit before their first paint; fixed-row
8
+ table sizing remains immediate during loading.
9
+
10
+ ## 2.11.3 - 2026-08-12
11
+
12
+ - Fixed `NTable` replacing or clipping an explicit Rows/page choice when
13
+ `dynamicHeight` was enabled. Automatic fit sizing still owns the initial page
14
+ size; after the reader chooses a value, the table preserves it and scrolls
15
+ its bounded body when the requested rows exceed the measured fit.
16
+
17
+ ## 2.11.2 - 2026-08-11
18
+
19
+ - Fixed `PasswordInput` forwarding native input attributes such as generated
20
+ form IDs and ARIA metadata to the actual `<input>`, restoring programmatic
21
+ label association for assistive technology and browser automation.
22
+
23
+ ## 2.11.1 - 2026-08-11
24
+
25
+ - Fixed `NajmThemeProvider` precedence so an explicit runtime `mode` overrides
26
+ a preset stored in the design config. An explicit `preset` prop still wins,
27
+ allowing deliberately fixed-preset subtrees without producing mixed light
28
+ and dark application surfaces.
29
+
30
+ ## 2.11.0 - 2026-08-10
17
31
 
18
32
  - Gave every keyboard-focusable primitive a visible focus ring, from one shared
19
33
  source: `focusRingClasses` and `focusRingWithinClasses`, exported from the
package/README.md CHANGED
@@ -730,6 +730,9 @@ available body. Table rows use the same header/row geometry as dynamic page
730
730
  sizing, while cards measure the active grid columns, card height, and gap. The
731
731
  loading surface also follows the loaded `bordered`, design recipe, radius,
732
732
  border color, shadow, and `classNames.content`/`classNames.cards` contract.
733
+ The measured fit owns the initial page size. Once a reader explicitly chooses
734
+ Rows/page, `NTable` preserves that choice and scrolls the bounded table body
735
+ when the requested rows exceed the available height.
733
736
 
734
737
  Use `cardPagination` to choose pagination presentation whenever the effective
735
738
  rendered mode is cards:
package/dist/index.d.ts CHANGED
@@ -2969,10 +2969,13 @@ interface TableState {
2969
2969
  pageIndex: number;
2970
2970
  pageSize: number;
2971
2971
  }) => void) | null;
2972
+ /** True after the reader explicitly chooses a Rows/page value. */
2973
+ isPageSizeUserSelected: boolean;
2972
2974
  setPagination: (pagination: {
2973
2975
  pageIndex: number;
2974
2976
  pageSize: number;
2975
2977
  }) => void;
2978
+ setPageSizeFromUser: (pageSize: number) => void;
2976
2979
  hasSyncedPaginationFromProps: boolean;
2977
2980
  rowSelection: Record<string, boolean>;
2978
2981
  isRowSelectionControlled: boolean;
@@ -3110,10 +3113,12 @@ declare const createTableStore: (seed?: Partial<TableState>) => {
3110
3113
  pageIndex: number;
3111
3114
  pageSize: number;
3112
3115
  }) => void;
3116
+ isPageSizeUserSelected: () => boolean;
3113
3117
  setPagination: () => (pagination: {
3114
3118
  pageIndex: number;
3115
3119
  pageSize: number;
3116
3120
  }) => void;
3121
+ setPageSizeFromUser: () => (pageSize: number) => void;
3117
3122
  hasSyncedPaginationFromProps: () => boolean;
3118
3123
  rowSelection: () => Record<string, boolean>;
3119
3124
  isRowSelectionControlled: () => boolean;
@@ -3606,10 +3611,12 @@ declare const TableStoreContext: React$1.Context<{
3606
3611
  pageIndex: number;
3607
3612
  pageSize: number;
3608
3613
  }) => void;
3614
+ isPageSizeUserSelected: () => boolean;
3609
3615
  setPagination: () => (pagination: {
3610
3616
  pageIndex: number;
3611
3617
  pageSize: number;
3612
3618
  }) => void;
3619
+ setPageSizeFromUser: () => (pageSize: number) => void;
3613
3620
  hasSyncedPaginationFromProps: () => boolean;
3614
3621
  rowSelection: () => Record<string, boolean>;
3615
3622
  isRowSelectionControlled: () => boolean;
@@ -3742,10 +3749,12 @@ declare function useStoreSync(props: any): {
3742
3749
  pageIndex: number;
3743
3750
  pageSize: number;
3744
3751
  }) => void;
3752
+ isPageSizeUserSelected: () => boolean;
3745
3753
  setPagination: () => (pagination: {
3746
3754
  pageIndex: number;
3747
3755
  pageSize: number;
3748
3756
  }) => void;
3757
+ setPageSizeFromUser: () => (pageSize: number) => void;
3749
3758
  hasSyncedPaginationFromProps: () => boolean;
3750
3759
  rowSelection: () => Record<string, boolean>;
3751
3760
  isRowSelectionControlled: () => boolean;
package/dist/index.mjs CHANGED
@@ -11645,7 +11645,8 @@ var createTableStore = (seed) => {
11645
11645
  pagination: { pageIndex: 0, pageSize: 10 },
11646
11646
  isPaginationControlled: false,
11647
11647
  onPaginationChange: null,
11648
- // User-intent setter for pagination
11648
+ isPageSizeUserSelected: false,
11649
+ // Pagination navigation and automatic sizing.
11649
11650
  setPagination: (pagination) => {
11650
11651
  const { isPaginationControlled, onPaginationChange } = get();
11651
11652
  onPaginationChange?.(pagination);
@@ -11653,6 +11654,18 @@ var createTableStore = (seed) => {
11653
11654
  set({ pagination });
11654
11655
  }
11655
11656
  },
11657
+ // Rows/page is an explicit request to render that many rows. Keep the
11658
+ // bounded NTable body as the scroll viewport instead of letting the
11659
+ // dynamic fit measurement replace the reader's choice.
11660
+ setPageSizeFromUser: (pageSize) => {
11661
+ const { isPaginationControlled, onPaginationChange } = get();
11662
+ const next = { pageIndex: 0, pageSize };
11663
+ set({
11664
+ isPageSizeUserSelected: true,
11665
+ ...!isPaginationControlled ? { pagination: next } : {}
11666
+ });
11667
+ onPaginationChange?.(next);
11668
+ },
11656
11669
  // Track if we've ever synced pagination from props
11657
11670
  hasSyncedPaginationFromProps: false,
11658
11671
  // Row selection
@@ -11871,6 +11884,12 @@ function calculateCardPageSize(input) {
11871
11884
  const rows = Math.max(1, Math.floor((input.bodyHeight + gap) / (cardHeight + gap)));
11872
11885
  return rows * columns;
11873
11886
  }
11887
+ function shouldDeferCardPageSizeUntilRows(renderedMode, hasDataRows) {
11888
+ return renderedMode === "cards" && !hasDataRows;
11889
+ }
11890
+ function shouldCommitDynamicPageSizeImmediately(reportedMode, renderedMode) {
11891
+ return reportedMode === null || reportedMode !== renderedMode;
11892
+ }
11874
11893
  function fallbackCardColumns(width) {
11875
11894
  if (width >= 1280) return 4;
11876
11895
  if (width >= 1024) return 3;
@@ -11884,6 +11903,7 @@ function useDynamicPageSize(containerRef, effectiveViewMode) {
11884
11903
  const isLoading = useTableStore.use.isLoading();
11885
11904
  const error = useTableStore.use.error();
11886
11905
  const hasNoData = useTableStore.use.hasNoData();
11906
+ const hasDataRows = useTableStore.use.hasData();
11887
11907
  const isFilteredEmpty = useTableStore.use.isFilteredEmpty();
11888
11908
  const syncWithProps = useTableStore.use.syncWithProps();
11889
11909
  const lastMeasurementRef = useRef("");
@@ -11920,7 +11940,8 @@ function useDynamicPageSize(containerRef, effectiveViewMode) {
11920
11940
  const cardGap = Number.parseFloat(gridStyles?.rowGap || gridStyles?.gap || "") || DEFAULT_CARD_GAP;
11921
11941
  const firstCard = cardsGridEl?.querySelector("[data-ntable-loading-card], [data-row]");
11922
11942
  const cardRowHeight = firstCard?.offsetHeight || firstCard?.getBoundingClientRect().height || DEFAULT_CARD_HEIGHT;
11923
- const cardPageSize = calculateCardPageSize({
11943
+ const measuredMode = effectiveViewMode ?? viewMode;
11944
+ const cardPageSize = shouldDeferCardPageSizeUntilRows(measuredMode, hasDataRows) ? 0 : calculateCardPageSize({
11924
11945
  bodyHeight,
11925
11946
  columnCount: cardColumnCount,
11926
11947
  cardHeight: cardRowHeight,
@@ -11962,7 +11983,7 @@ function useDynamicPageSize(containerRef, effectiveViewMode) {
11962
11983
  ).forEach((el) => resizeObserver.observe(el));
11963
11984
  if (container.parentElement) resizeObserver.observe(container.parentElement);
11964
11985
  return () => resizeObserver.disconnect();
11965
- }, [dynamicHeight, effectiveViewMode, viewMode, containerRef, syncWithProps, manualPagination, isLoading, error, hasNoData, isFilteredEmpty]);
11986
+ }, [dynamicHeight, effectiveViewMode, viewMode, containerRef, syncWithProps, manualPagination, isLoading, error, hasNoData, hasDataRows, isFilteredEmpty]);
11966
11987
  }
11967
11988
  function useTable(effectiveViewModeOverride) {
11968
11989
  const [sorting, setSorting] = useState([]);
@@ -11997,6 +12018,7 @@ function useTable(effectiveViewModeOverride) {
11997
12018
  const rowCount = useTableStore.use.rowCount();
11998
12019
  const storePagination = useTableStore.use.pagination();
11999
12020
  const isPaginationControlled = useTableStore.use.isPaginationControlled();
12021
+ const isPageSizeUserSelected = useTableStore.use.isPageSizeUserSelected();
12000
12022
  const setPagination = useTableStore.use.setPagination();
12001
12023
  const storeRowSelection = useTableStore.use.rowSelection();
12002
12024
  const setRowSelection = useTableStore.use.setRowSelection();
@@ -12115,34 +12137,31 @@ function useTable(effectiveViewModeOverride) {
12115
12137
  syncWithProps({ table });
12116
12138
  }, [table]);
12117
12139
  useLayoutEffect(() => {
12118
- if (manualPagination || isPaginationControlled) return;
12140
+ if (manualPagination || isPaginationControlled || isPageSizeUserSelected) return;
12119
12141
  if (dynamicHeight && renderedMode === "table") table.setPageSize(calculatedPageSize);
12120
12142
  if (viewMode === "cards" && cardPagination.mode === "paged") {
12121
12143
  table.setPageSize(data.length || 9999);
12122
12144
  }
12123
- }, [calculatedPageSize, dynamicHeight, renderedMode, viewMode, cardPagination.mode, table, data.length, manualPagination, isPaginationControlled]);
12145
+ }, [calculatedPageSize, dynamicHeight, renderedMode, viewMode, cardPagination.mode, table, data.length, manualPagination, isPaginationControlled, isPageSizeUserSelected]);
12124
12146
  const dynamicPageSizeTarget = renderedMode === "cards" ? calculatedCardPageSize : calculatedPageSize;
12125
12147
  const geometryKey = `${Math.round(measuredBodyWidth / GEOMETRY_BUCKET_PX)}x${Math.round(measuredBodyHeight / GEOMETRY_BUCKET_PX)}`;
12126
12148
  const reportedGeometryRef = useRef(null);
12127
12149
  useLayoutEffect(() => {
12128
- if (!manualPagination || !dynamicHeight) return;
12150
+ if (!manualPagination || !dynamicHeight || isPageSizeUserSelected) return;
12129
12151
  if (cardPagination.mode !== "paged") return;
12130
12152
  if (!hasMeasuredLayout || measuredBodyHeight <= 0) return;
12131
12153
  if (!dynamicPageSizeTarget || dynamicPageSizeTarget < 1) return;
12132
12154
  if (dynamicPageSizeTarget === storePagination.pageSize) return;
12155
+ if (shouldDeferCardPageSizeUntilRows(renderedMode, hasDataRows)) return;
12133
12156
  const reported = reportedGeometryRef.current;
12134
- if (reported?.key === geometryKey && reported.target === dynamicPageSizeTarget) return;
12135
- if (reported?.key === geometryKey && reported.count >= MAX_PAGE_SIZE_REPORTS_PER_GEOMETRY) return;
12157
+ if (reported?.key === geometryKey && reported.mode === renderedMode && reported.target === dynamicPageSizeTarget) return;
12158
+ if (reported?.key === geometryKey && reported.mode === renderedMode && reported.count >= MAX_PAGE_SIZE_REPORTS_PER_GEOMETRY) return;
12136
12159
  const commit = () => {
12137
12160
  const current = reportedGeometryRef.current;
12138
- reportedGeometryRef.current = current?.key === geometryKey ? { key: geometryKey, count: current.count + 1, target: dynamicPageSizeTarget } : { key: geometryKey, count: 1, target: dynamicPageSizeTarget };
12161
+ reportedGeometryRef.current = current?.key === geometryKey && current.mode === renderedMode ? { key: geometryKey, count: current.count + 1, target: dynamicPageSizeTarget, mode: renderedMode } : { key: geometryKey, count: 1, target: dynamicPageSizeTarget, mode: renderedMode };
12139
12162
  setPagination({ pageIndex: storePagination.pageIndex, pageSize: dynamicPageSizeTarget });
12140
12163
  };
12141
- if (!hasDataRows) {
12142
- commit();
12143
- return;
12144
- }
12145
- if (!reported) {
12164
+ if (shouldCommitDynamicPageSizeImmediately(reported?.mode ?? null, renderedMode)) {
12146
12165
  commit();
12147
12166
  return;
12148
12167
  }
@@ -12151,6 +12170,7 @@ function useTable(effectiveViewModeOverride) {
12151
12170
  }, [
12152
12171
  manualPagination,
12153
12172
  dynamicHeight,
12173
+ isPageSizeUserSelected,
12154
12174
  cardPagination.mode,
12155
12175
  hasMeasuredLayout,
12156
12176
  measuredBodyHeight,
@@ -12158,6 +12178,7 @@ function useTable(effectiveViewModeOverride) {
12158
12178
  dynamicPageSizeTarget,
12159
12179
  storePagination.pageIndex,
12160
12180
  storePagination.pageSize,
12181
+ renderedMode,
12161
12182
  hasDataRows,
12162
12183
  setPagination
12163
12184
  ]);
@@ -12357,6 +12378,8 @@ function NTableContent({ effectiveMode }) {
12357
12378
  const contentCardPagination = useTableStore.use.cardPagination();
12358
12379
  const contentCalculatedPageSize = useTableStore.use.calculatedPageSize();
12359
12380
  const contentHasMeasuredLayout = useTableStore.use.hasMeasuredLayout();
12381
+ const isPageSizeUserSelected = useTableStore.use.isPageSizeUserSelected();
12382
+ const contentPagination = useTableStore.use.pagination();
12360
12383
  const hasNoData = useTableStore.use.hasNoData();
12361
12384
  const showContent = useTableStore.use.showContent();
12362
12385
  const classNames = useTableStore.use.classNames();
@@ -12379,8 +12402,9 @@ function NTableContent({ effectiveMode }) {
12379
12402
  const isTableView = effectiveMode ? effectiveMode === "table" : storeIsTableView;
12380
12403
  if (!isTableView) return null;
12381
12404
  const allRows = table.getRowModel().rows ?? [];
12382
- const clampRowsToMeasuredPage = contentDynamicHeight && contentManualPagination && contentCardPagination.mode === "paged" && contentHasMeasuredLayout && contentCalculatedPageSize > 0;
12405
+ const clampRowsToMeasuredPage = contentDynamicHeight && contentManualPagination && !isPageSizeUserSelected && contentCardPagination.mode === "paged" && contentHasMeasuredLayout && contentCalculatedPageSize > 0;
12383
12406
  const visibleRows = clampRowsToMeasuredPage ? allRows.slice(0, contentCalculatedPageSize) : allRows;
12407
+ const userPageNeedsScroll = isPageSizeUserSelected && contentPagination.pageSize > contentCalculatedPageSize;
12384
12408
  const getSortIcon = (column) => {
12385
12409
  const dir = column.getIsSorted();
12386
12410
  if (dir === "asc") return /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4" });
@@ -12402,7 +12426,7 @@ function NTableContent({ effectiveMode }) {
12402
12426
  // leftover belongs to the page, not to the table. Without
12403
12427
  // `dynamicHeight` the row count is the caller's, so the container has
12404
12428
  // to keep filling its height to stay a scroll viewport.
12405
- contentDynamicHeight ? "max-h-full shrink" : "flex-1",
12429
+ contentDynamicHeight && !userPageNeedsScroll ? "max-h-full shrink" : "flex-1",
12406
12430
  surface.className,
12407
12431
  classNames?.content
12408
12432
  ),
@@ -13333,6 +13357,7 @@ function NTablePagination() {
13333
13357
  const rowCount = useTableStore.use.rowCount();
13334
13358
  const suppliedHasNextPage = useTableStore.use.hasNextPage();
13335
13359
  const setPagination = useTableStore.use.setPagination();
13360
+ const setPageSizeFromUser = useTableStore.use.setPageSizeFromUser();
13336
13361
  const isPaginationControlled = useTableStore.use.isPaginationControlled();
13337
13362
  const bordered = useTableStore.use.bordered();
13338
13363
  const paginationVariant = useTableStore.use.paginationVariant();
@@ -13380,7 +13405,7 @@ function NTablePagination() {
13380
13405
  };
13381
13406
  const handlePageSizeChange = (value) => {
13382
13407
  const newSize = Number(value);
13383
- setPagination({ pageIndex: 0, pageSize: newSize });
13408
+ setPageSizeFromUser(newSize);
13384
13409
  };
13385
13410
  const hasTrustworthyPageCount = manualPagination ? pageCount !== void 0 && pageCount > 0 || unbounded : effectivePageCount > 0;
13386
13411
  const showNumbers = paginationVariant === "numbered" && hasTrustworthyPageCount;
package/dist/theme.css CHANGED
@@ -307,10 +307,10 @@ input:autofill {
307
307
  overflow-x: auto;
308
308
  scrollbar-width: none;
309
309
  }
310
- .najm-overlay-scroll-x::-webkit-scrollbar {
311
- display: none;
312
- }
313
-
310
+ .najm-overlay-scroll-x::-webkit-scrollbar {
311
+ display: none;
312
+ }
313
+
314
314
  /* Responsive table actions stay discoverable on touch and tablet layouts.
315
315
  Fine-pointer desktops may keep the quieter hover/focus reveal treatment. */
316
316
  .ntable-card-action {
@@ -367,7 +367,7 @@ input:autofill {
367
367
  .nimage-input-compact-overlay:focus-visible {
368
368
  opacity: 1;
369
369
  }
370
- }
370
+ }
371
371
 
372
372
  /* OverlayScrollbars theme used by the <NajmScroll> component — a thin,
373
373
  translucent slate bar that floats over content with no reserved space.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-kit",
3
- "version": "2.11.2",
3
+ "version": "2.11.4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Reusable React UI component package for Najm framework",