najm-kit 2.11.1 → 2.11.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/CHANGELOG.md CHANGED
@@ -1,13 +1,30 @@
1
- # Changelog
2
-
3
- ## 2.11.1 - 2026-08-11
4
-
5
- - Fixed `NajmThemeProvider` precedence so an explicit runtime `mode` overrides
6
- a preset stored in the design config. An explicit `preset` prop still wins,
7
- allowing deliberately fixed-preset subtrees without producing mixed light
8
- and dark application surfaces.
9
-
10
- ## 2.11.0 - 2026-08-10
1
+ # Changelog
2
+
3
+ ## 2.11.3 - 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
+ - Fixed `NTable` replacing or clipping an explicit Rows/page choice when
10
+ `dynamicHeight` was enabled. Automatic fit sizing still owns the initial page
11
+ size; after the reader chooses a value, the table preserves it and scrolls
12
+ its bounded body when the requested rows exceed the measured fit.
13
+
14
+ ## 2.11.2 - 2026-08-11
15
+
16
+ - Fixed `PasswordInput` forwarding native input attributes such as generated
17
+ form IDs and ARIA metadata to the actual `<input>`, restoring programmatic
18
+ label association for assistive technology and browser automation.
19
+
20
+ ## 2.11.1 - 2026-08-11
21
+
22
+ - Fixed `NajmThemeProvider` precedence so an explicit runtime `mode` overrides
23
+ a preset stored in the design config. An explicit `preset` prop still wins,
24
+ allowing deliberately fixed-preset subtrees without producing mixed light
25
+ and dark application surfaces.
26
+
27
+ ## 2.11.0 - 2026-08-10
11
28
 
12
29
  - Gave every keyboard-focusable primitive a visible focus ring, from one shared
13
30
  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
@@ -2203,7 +2203,7 @@ interface NumberInputProps extends BaseProps {
2203
2203
  icon?: InputIcon;
2204
2204
  showIcon?: boolean;
2205
2205
  }
2206
- interface PasswordInputProps extends BaseProps {
2206
+ interface PasswordInputProps extends BaseProps, Omit<InputHTMLAttributes<HTMLInputElement>, "className" | "onChange" | "placeholder" | "type" | "value"> {
2207
2207
  value: string;
2208
2208
  onChange: (value: string) => void;
2209
2209
  placeholder?: string;
@@ -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
@@ -4349,13 +4349,13 @@ var NumberInput = ({ value, onChange, placeholder = "", icon, showIcon = true, i
4349
4349
  /* @__PURE__ */ jsx(Input, { type: "number", placeholder, value: value ?? "", onChange: (ev) => onChange(Number(ev.target.value)), className: "p-0 border-0 shadow-none bg-transparent dark:bg-transparent focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 text-foreground placeholder:text-muted-foreground" })
4350
4350
  ] });
4351
4351
  };
4352
- var PasswordInput = ({ value, onChange, placeholder = "", icon, showIcon = true, iconColor, className = "", variant = "default", status = "default", bordered, borderColor }) => {
4352
+ var PasswordInput = ({ value, onChange, placeholder = "", icon, showIcon = true, iconColor, className = "", variant = "default", status = "default", bordered, borderColor, disabled = false, ...props }) => {
4353
4353
  const [showPassword, setShowPassword] = useState(false);
4354
4354
  const shouldDisplayIcon = Boolean(icon) && showIcon;
4355
4355
  const iconProps = getIconColorProps(iconColor, "h-4 w-4");
4356
- return /* @__PURE__ */ jsxs(BaseInput, { variant, status, bordered, borderColor, className: cn("gap-2", className), children: [
4356
+ return /* @__PURE__ */ jsxs(BaseInput, { variant, status, bordered, borderColor, className: cn("gap-2", className), disabled, children: [
4357
4357
  shouldDisplayIcon && /* @__PURE__ */ jsx("span", { className: iconProps.className, style: iconProps.style, children: resolveIcon(icon) }),
4358
- /* @__PURE__ */ jsx(Input, { type: showPassword ? "text" : "password", placeholder, value, onChange: (ev) => onChange(ev.target.value), className: "p-0 border-0 shadow-none bg-transparent dark:bg-transparent focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 text-foreground placeholder:text-muted-foreground" }),
4358
+ /* @__PURE__ */ jsx(Input, { type: showPassword ? "text" : "password", placeholder, value, onChange: (ev) => onChange(ev.target.value), className: "p-0 border-0 shadow-none bg-transparent dark:bg-transparent focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 text-foreground placeholder:text-muted-foreground", disabled, ...props }),
4359
4359
  showPassword ? /* @__PURE__ */ jsx(Eye, { className: "absolute right-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground cursor-pointer", onClick: () => setShowPassword(false) }) : /* @__PURE__ */ jsx(EyeOff, { className: "absolute right-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground cursor-pointer", onClick: () => setShowPassword(true) })
4360
4360
  ] });
4361
4361
  };
@@ -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,9 @@ 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
+ }
11874
11890
  function fallbackCardColumns(width) {
11875
11891
  if (width >= 1280) return 4;
11876
11892
  if (width >= 1024) return 3;
@@ -11997,6 +12013,7 @@ function useTable(effectiveViewModeOverride) {
11997
12013
  const rowCount = useTableStore.use.rowCount();
11998
12014
  const storePagination = useTableStore.use.pagination();
11999
12015
  const isPaginationControlled = useTableStore.use.isPaginationControlled();
12016
+ const isPageSizeUserSelected = useTableStore.use.isPageSizeUserSelected();
12000
12017
  const setPagination = useTableStore.use.setPagination();
12001
12018
  const storeRowSelection = useTableStore.use.rowSelection();
12002
12019
  const setRowSelection = useTableStore.use.setRowSelection();
@@ -12115,21 +12132,22 @@ function useTable(effectiveViewModeOverride) {
12115
12132
  syncWithProps({ table });
12116
12133
  }, [table]);
12117
12134
  useLayoutEffect(() => {
12118
- if (manualPagination || isPaginationControlled) return;
12135
+ if (manualPagination || isPaginationControlled || isPageSizeUserSelected) return;
12119
12136
  if (dynamicHeight && renderedMode === "table") table.setPageSize(calculatedPageSize);
12120
12137
  if (viewMode === "cards" && cardPagination.mode === "paged") {
12121
12138
  table.setPageSize(data.length || 9999);
12122
12139
  }
12123
- }, [calculatedPageSize, dynamicHeight, renderedMode, viewMode, cardPagination.mode, table, data.length, manualPagination, isPaginationControlled]);
12140
+ }, [calculatedPageSize, dynamicHeight, renderedMode, viewMode, cardPagination.mode, table, data.length, manualPagination, isPaginationControlled, isPageSizeUserSelected]);
12124
12141
  const dynamicPageSizeTarget = renderedMode === "cards" ? calculatedCardPageSize : calculatedPageSize;
12125
12142
  const geometryKey = `${Math.round(measuredBodyWidth / GEOMETRY_BUCKET_PX)}x${Math.round(measuredBodyHeight / GEOMETRY_BUCKET_PX)}`;
12126
12143
  const reportedGeometryRef = useRef(null);
12127
12144
  useLayoutEffect(() => {
12128
- if (!manualPagination || !dynamicHeight) return;
12145
+ if (!manualPagination || !dynamicHeight || isPageSizeUserSelected) return;
12129
12146
  if (cardPagination.mode !== "paged") return;
12130
12147
  if (!hasMeasuredLayout || measuredBodyHeight <= 0) return;
12131
12148
  if (!dynamicPageSizeTarget || dynamicPageSizeTarget < 1) return;
12132
12149
  if (dynamicPageSizeTarget === storePagination.pageSize) return;
12150
+ if (shouldDeferCardPageSizeUntilRows(renderedMode, hasDataRows)) return;
12133
12151
  const reported = reportedGeometryRef.current;
12134
12152
  if (reported?.key === geometryKey && reported.target === dynamicPageSizeTarget) return;
12135
12153
  if (reported?.key === geometryKey && reported.count >= MAX_PAGE_SIZE_REPORTS_PER_GEOMETRY) return;
@@ -12151,6 +12169,7 @@ function useTable(effectiveViewModeOverride) {
12151
12169
  }, [
12152
12170
  manualPagination,
12153
12171
  dynamicHeight,
12172
+ isPageSizeUserSelected,
12154
12173
  cardPagination.mode,
12155
12174
  hasMeasuredLayout,
12156
12175
  measuredBodyHeight,
@@ -12158,6 +12177,7 @@ function useTable(effectiveViewModeOverride) {
12158
12177
  dynamicPageSizeTarget,
12159
12178
  storePagination.pageIndex,
12160
12179
  storePagination.pageSize,
12180
+ renderedMode,
12161
12181
  hasDataRows,
12162
12182
  setPagination
12163
12183
  ]);
@@ -12357,6 +12377,8 @@ function NTableContent({ effectiveMode }) {
12357
12377
  const contentCardPagination = useTableStore.use.cardPagination();
12358
12378
  const contentCalculatedPageSize = useTableStore.use.calculatedPageSize();
12359
12379
  const contentHasMeasuredLayout = useTableStore.use.hasMeasuredLayout();
12380
+ const isPageSizeUserSelected = useTableStore.use.isPageSizeUserSelected();
12381
+ const contentPagination = useTableStore.use.pagination();
12360
12382
  const hasNoData = useTableStore.use.hasNoData();
12361
12383
  const showContent = useTableStore.use.showContent();
12362
12384
  const classNames = useTableStore.use.classNames();
@@ -12379,8 +12401,9 @@ function NTableContent({ effectiveMode }) {
12379
12401
  const isTableView = effectiveMode ? effectiveMode === "table" : storeIsTableView;
12380
12402
  if (!isTableView) return null;
12381
12403
  const allRows = table.getRowModel().rows ?? [];
12382
- const clampRowsToMeasuredPage = contentDynamicHeight && contentManualPagination && contentCardPagination.mode === "paged" && contentHasMeasuredLayout && contentCalculatedPageSize > 0;
12404
+ const clampRowsToMeasuredPage = contentDynamicHeight && contentManualPagination && !isPageSizeUserSelected && contentCardPagination.mode === "paged" && contentHasMeasuredLayout && contentCalculatedPageSize > 0;
12383
12405
  const visibleRows = clampRowsToMeasuredPage ? allRows.slice(0, contentCalculatedPageSize) : allRows;
12406
+ const userPageNeedsScroll = isPageSizeUserSelected && contentPagination.pageSize > contentCalculatedPageSize;
12384
12407
  const getSortIcon = (column) => {
12385
12408
  const dir = column.getIsSorted();
12386
12409
  if (dir === "asc") return /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4" });
@@ -12402,7 +12425,7 @@ function NTableContent({ effectiveMode }) {
12402
12425
  // leftover belongs to the page, not to the table. Without
12403
12426
  // `dynamicHeight` the row count is the caller's, so the container has
12404
12427
  // to keep filling its height to stay a scroll viewport.
12405
- contentDynamicHeight ? "max-h-full shrink" : "flex-1",
12428
+ contentDynamicHeight && !userPageNeedsScroll ? "max-h-full shrink" : "flex-1",
12406
12429
  surface.className,
12407
12430
  classNames?.content
12408
12431
  ),
@@ -13333,6 +13356,7 @@ function NTablePagination() {
13333
13356
  const rowCount = useTableStore.use.rowCount();
13334
13357
  const suppliedHasNextPage = useTableStore.use.hasNextPage();
13335
13358
  const setPagination = useTableStore.use.setPagination();
13359
+ const setPageSizeFromUser = useTableStore.use.setPageSizeFromUser();
13336
13360
  const isPaginationControlled = useTableStore.use.isPaginationControlled();
13337
13361
  const bordered = useTableStore.use.bordered();
13338
13362
  const paginationVariant = useTableStore.use.paginationVariant();
@@ -13380,7 +13404,7 @@ function NTablePagination() {
13380
13404
  };
13381
13405
  const handlePageSizeChange = (value) => {
13382
13406
  const newSize = Number(value);
13383
- setPagination({ pageIndex: 0, pageSize: newSize });
13407
+ setPageSizeFromUser(newSize);
13384
13408
  };
13385
13409
  const hasTrustworthyPageCount = manualPagination ? pageCount !== void 0 && pageCount > 0 || unbounded : effectivePageCount > 0;
13386
13410
  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.1",
3
+ "version": "2.11.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Reusable React UI component package for Najm framework",