@pk-design/react-tailwind 1.1.5 → 1.1.7

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.
@@ -1,8 +1,9 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { twMerge } from 'tailwind-merge';
3
3
  import * as React from 'react';
4
- import React__default, { forwardRef, useMemo, useState, useId, useEffect, useRef, Fragment as Fragment$1, useCallback, useImperativeHandle, createContext, useContext, memo, useLayoutEffect, createElement } from 'react';
4
+ import React__default, { forwardRef, useMemo, useId, useRef, useState, useEffect, Fragment as Fragment$1, useCallback, useImperativeHandle, createContext, useContext } from 'react';
5
5
  import { Dialog, DialogBackdrop, DialogPanel, DialogTitle, Menu, MenuButton, MenuItems, MenuItem, RadioGroup as RadioGroup$1, Radio as Radio$1, Combobox, ComboboxInput, ComboboxButton, ComboboxOptions, ComboboxOption, TabGroup, TabList, Tab, TabPanels, TabPanel, Transition } from '@headlessui/react';
6
+ import { FixedSizeList } from 'react-window';
6
7
 
7
8
  const buildClassName = (...classes) => twMerge(classes.filter(Boolean));
8
9
 
@@ -223,7 +224,7 @@ const TypeClassMap = {
223
224
  const Alert = (props) => {
224
225
  const { type = 'info', className = '', message, children, removable = false, onRemove = () => { }, ...restProps } = props;
225
226
  const Icon = AlertTypeIconMap[type] || SvgInfo;
226
- return (jsxs("div", { className: buildClassName(className, 'flex items-center justify-between rounded-md p-3 text-sm ring-1 ring-inset gap-2', TypeClassMap[type]), ...restProps, children: [jsxs("span", { className: "inline-flex items-center gap-2", children: [jsx(Icon, { className: "size-5", "data-testid": `alert-${type}-icon` }), jsx("span", { className: "pr-2 inline-flex items-center", children: message || children })] }), removable && (jsx(Button, { iconOnly: true, size: "xs", variant: "plain", theme: "secondary", className: "hover:bg-black/5 text-gray-600 dark:hover:bg-black/5 dark:text-gray-600", onClick: () => onRemove(), "data-testid": "alert-remove-btn", children: jsx(SvgX, { className: "size-4.5", "data-testid": "alert-remove-icon" }) }))] }));
227
+ return (jsxs("div", { className: buildClassName(className, 'flex items-center justify-between rounded-md p-3 text-sm ring-1 ring-inset gap-2', TypeClassMap[type]), ...restProps, children: [jsxs("span", { className: "inline-flex items-center gap-2", children: [jsx(Icon, { className: "size-5", "data-testid": `alert-${type}-icon` }), jsx("span", { className: "pr-2 inline-flex items-center", children: message || children })] }), removable && (jsx(Button, { iconOnly: true, "aria-label": "Dismiss alert", size: "xs", variant: "plain", theme: "secondary", className: "hover:bg-black/5 text-gray-600 dark:hover:bg-black/5 dark:text-gray-600", onClick: () => onRemove(), "data-testid": "alert-remove-btn", children: jsx(SvgX, { className: "size-4.5", "data-testid": "alert-remove-icon" }) }))] }));
227
228
  };
228
229
  Alert.displayName = 'Alert';
229
230
 
@@ -236,7 +237,12 @@ const ThemeClassMap = {
236
237
  };
237
238
  const Badge = forwardRef((props, ref) => {
238
239
  const { theme = 'info', className = '', children, removable = false, rounded = false, onRemove = () => { }, ...restProps } = props;
239
- return (jsxs(TextContent, { ref: ref, className: buildClassName(className, 'inline-flex items-center pl-3 py-1 text-xs font-medium ring-1 ring-inset', ThemeClassMap[theme], rounded ? 'rounded-full' : 'rounded-md'), "data-testid": `badge-${theme}`, ...restProps, children: [jsx(TextContent, { className: "pr-3 inline-flex items-center", children: children }), removable ? (jsx(SvgX, { className: "size-5 px-0.5 mr-1 hover:bg-slate-200 dark:hover:bg-white/10 rounded-sm cursor-pointer text-gray-600 dark:text-gray-300", role: "button", onClick: () => onRemove() })) : null] }));
240
+ return (jsxs(TextContent, { ref: ref, className: buildClassName(className, 'inline-flex items-center pl-3 py-1 text-xs font-medium ring-1 ring-inset', ThemeClassMap[theme], rounded ? 'rounded-full' : 'rounded-md'), "data-testid": `badge-${theme}`, ...restProps, children: [jsx(TextContent, { className: "pr-3 inline-flex items-center", children: children }), removable ? (jsx(SvgX, { className: "size-5 px-0.5 mr-1 hover:bg-slate-200 dark:hover:bg-white/10 rounded-sm cursor-pointer text-gray-600 dark:text-gray-300", role: "button", tabIndex: 0, "aria-label": "Remove", onClick: () => onRemove(), onKeyDown: (e) => {
241
+ if (e.key === 'Enter' || e.key === ' ') {
242
+ e.preventDefault();
243
+ onRemove();
244
+ }
245
+ } })) : null] }));
240
246
  });
241
247
  Badge.displayName = 'Badge';
242
248
 
@@ -340,7 +346,7 @@ var SvgSlash = function SvgSlash(props) {
340
346
  const Breadcrumb = ({ items, render }) => {
341
347
  const count = items.length;
342
348
  const isLast = (index) => index === count - 1;
343
- return (jsx("div", { className: "flex flex-wrap items-center gap-2", "data-testid": "breadcrumb-container", children: items.map((item, index) => (jsxs("span", { className: "inline-flex gap-2 items-center dark:text-white", "data-testid": `breadcrumb-item-${item.key}`, children: [jsx("span", { className: "font-medium", children: isLast(index) ? item.text : render(item) }), !isLast(index) && (jsx(SvgSlash, { "data-testid": `breadcrumb-divider-${index}`, className: "size-4" }))] }, item.key))) }));
349
+ return (jsx("nav", { "aria-label": "Breadcrumb", className: "flex flex-wrap items-center gap-2", "data-testid": "breadcrumb-container", children: items.map((item, index) => (jsxs("span", { className: "inline-flex gap-2 items-center dark:text-white", "data-testid": `breadcrumb-item-${item.key}`, children: [jsx("span", { className: "font-medium", ...(isLast(index) ? { 'aria-current': 'page' } : {}), children: isLast(index) ? item.text : render(item) }), !isLast(index) && (jsx(SvgSlash, { "data-testid": `breadcrumb-divider-${index}`, className: "size-4" }))] }, item.key))) }));
344
350
  };
345
351
  Breadcrumb.displayName = 'Breadcrumb';
346
352
 
@@ -367,7 +373,8 @@ const Checkbox = forwardRef((props, ref) => {
367
373
  const { id, name, label, labelClass = '', wrapperClass = '', containerClass = '', error, disabled = false, className = '',
368
374
  // Checkbox-only props removed from rest
369
375
  ...rest } = props;
370
- const checkboxId = (_a = id !== null && id !== void 0 ? id : name) !== null && _a !== void 0 ? _a : 'checkbox-' + Math.random();
376
+ const generatedId = useId();
377
+ const checkboxId = (_a = id !== null && id !== void 0 ? id : name) !== null && _a !== void 0 ? _a : generatedId;
371
378
  return (jsxs("div", { className: buildClassName(wrapperClass), children: [jsxs("div", { className: buildClassName('flex items-center', containerClass), children: [jsxs("label", { htmlFor: checkboxId, className: buildClassName('inline-flex items-center relative select-none', disabled ? 'cursor-not-allowed opacity-70' : 'cursor-pointer'), children: [jsx("input", { ref: ref, ...rest, id: checkboxId, name: name, type: "checkbox", disabled: disabled, "aria-checked": rest.checked, className: buildClassName('peer size-4.5 cursor-pointer transition-all appearance-none rounded', 'border focus:ring-2 focus:ring-offset-2', 'checked:bg-blue-600 checked:border-blue-600', 'focus:ring-blue-500 dark:focus:ring-offset-gray-900', disabled
372
379
  ? 'border-gray-200 dark:border-gray-700 bg-gray-100 dark:bg-gray-800 pointer-events-none'
373
380
  : 'bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500 hover:shadow-md', className) }), jsx("span", { className: "absolute text-white opacity-0 peer-checked:opacity-100 \n top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2", children: jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-3.25 w-3.25", viewBox: "0 0 20 20", fill: "currentColor", stroke: "currentColor", strokeWidth: "1", children: jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) })] }), label && (jsx(Label, { htmlFor: checkboxId, className: buildClassName(disabled
@@ -378,15 +385,8 @@ Checkbox.displayName = 'Checkbox';
378
385
 
379
386
  const CheckboxGroup = (props) => {
380
387
  const { name, options, label, labelClass = '', labelWrapperClass = '', labelHint, containerClass = '', value = [], onChange, inline = false, } = props;
381
- const [items, setItems] = useState([]);
382
- const groupId = useId(); // unique per group
383
- /**
384
- * Normalize options (strings → {label, value})
385
- */
386
- useEffect(() => {
387
- const normalized = options.map((opt) => typeof opt === 'string' ? { label: opt, value: opt } : opt);
388
- setItems(normalized);
389
- }, [options]);
388
+ const groupId = useId();
389
+ const items = useMemo(() => options.map((opt) => typeof opt === 'string' ? { label: opt, value: opt } : opt), [options]);
390
390
  /**
391
391
  * Group-level change handler. Converts raw checkbox change events
392
392
  * into a typed updated values array.
@@ -537,14 +537,14 @@ const Pagination = ({ page: currentPage, limit, total, onChange, className, elli
537
537
  onChange({ page: nextVal });
538
538
  };
539
539
  if (ellipsis) {
540
- return (jsxs("div", { "data-testid": "pagination-container", className: buildClassName('flex items-center justify-between bg-white dark:bg-gray-800 px-4 py-3 sm:px-6', className), children: [jsxs("div", { className: "flex flex-1 justify-between sm:hidden", children: [jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": "ellipsis-mobile-prev-btn", children: [jsx("span", { children: "Previous" }), jsx(SvgChevronLeft, { className: "size-4" })] }), jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "ellipsis-mobile-next-btn", children: [jsx("span", { children: "Next" }), jsx(SvgChevronRight, { className: "size-4" })] })] }), jsxs("div", { className: "hidden sm:flex sm:flex-1 sm:items-center sm:justify-between", children: [jsxs("p", { "data-testid": "pagination-summary", className: "text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1", children: ["Showing", jsx("span", { className: "font-medium", children: total === 0 ? 0 : Math.max(currentPage * limit - limit + 1, 1) }), "to", jsx("span", { className: "font-medium", children: Math.min(currentPage * limit, total) }), "of", jsx("span", { className: "font-medium", children: total }), "results"] }), jsxs("ul", { className: "flex items-center space-x-2 text-sm", children: [jsx("li", { children: jsx(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": "ellipsis-next-btn", children: "Prev" }) }), range.map((page, idx) => {
540
+ return (jsxs("div", { "data-testid": "pagination-container", className: buildClassName('flex items-center justify-between bg-white dark:bg-gray-800 px-4 py-3 sm:px-6', className), children: [jsxs("div", { className: "flex flex-1 justify-between sm:hidden", children: [jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": "ellipsis-mobile-prev-btn", children: [jsx("span", { children: "Previous" }), jsx(SvgChevronLeft, { className: "size-4" })] }), jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "ellipsis-mobile-next-btn", children: [jsx("span", { children: "Next" }), jsx(SvgChevronRight, { className: "size-4" })] })] }), jsxs("div", { className: "hidden sm:flex sm:flex-1 sm:items-center sm:justify-between", children: [jsxs("p", { "data-testid": "pagination-summary", className: "text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1", children: ["Showing", jsx("span", { className: "font-medium", children: total === 0 ? 0 : Math.max(currentPage * limit - limit + 1, 1) }), "to", jsx("span", { className: "font-medium", children: Math.min(currentPage * limit, total) }), "of", jsx("span", { className: "font-medium", children: total }), "results"] }), jsxs("ul", { className: "flex items-center space-x-2 text-sm", children: [jsx("li", { children: jsx(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": "ellipsis-prev-btn", children: "Prev" }) }), range.map((page, idx) => {
541
541
  if (page === DOTS) {
542
542
  return (jsx("li", { "data-testid": `page-${page}`, className: "px-2 py-1 text-gray-400", children: "\u2026" }, `${page}-${idx}`));
543
543
  }
544
544
  return (jsx("li", { children: jsx(Button, { size: "xs", theme: page === currentPage ? 'primary' : 'secondary', variant: page === currentPage ? 'default' : 'plain', "aria-current": page === currentPage ? 'page' : undefined, onClick: () => handlePageChange(page), children: page }) }, `${page}-${idx}`));
545
- }), jsx("li", { children: jsx(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "ellipsis-prev-btn", children: "Next" }) })] })] })] }));
545
+ }), jsx("li", { children: jsx(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "ellipsis-next-btn", children: "Next" }) })] })] })] }));
546
546
  }
547
- return (jsxs("div", { "data-testid": "pagination-container", className: buildClassName('flex items-center justify-between bg-white dark:bg-gray-800 px-4 py-3 sm:px-6', className), children: [jsxs("div", { className: "flex flex-1 justify-between sm:hidden", children: [jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": "mobile-prev-btn", children: [jsx("span", { children: "Previous" }), jsx(SvgChevronLeft, { className: "size-4" })] }), jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "mobile-next-btn", children: [jsx("span", { children: "Next" }), jsx(SvgChevronRight, { className: "size-4" })] })] }), jsxs("div", { className: "hidden sm:flex sm:flex-1 sm:items-center sm:justify-between", children: [jsxs("p", { "data-testid": "pagination-summary", className: "text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1", children: ["Showing", jsx("span", { className: "font-medium", children: total === 0 ? 0 : Math.max(currentPage * limit - limit + 1, 1) }), "to", jsx("span", { className: "font-medium", children: Math.min(currentPage * limit, total) }), "of", jsx("span", { className: "font-medium", children: total }), "results"] }), jsxs("nav", { className: "isolate inline-flex items-center rounded-md", "aria-label": "Pagination", children: [jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": "next-btn", children: [jsx("span", { className: "sr-only", children: "Previous" }), jsx(SvgChevronLeft, { className: "size-4" })] }), pages.map((page) => (jsx(Button, { size: "xs", theme: page === currentPage ? 'primary' : 'secondary', variant: page === currentPage ? 'default' : 'plain', "aria-current": page === currentPage ? 'page' : undefined, onClick: () => handlePageChange(page), children: jsx("span", { className: "size-4 block", children: page }) }, page))), jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "prev-btn", children: [jsx("span", { className: "sr-only", children: "Next" }), jsx(SvgChevronRight, { className: "size-4" })] })] })] })] }));
547
+ return (jsxs("div", { "data-testid": "pagination-container", className: buildClassName('flex items-center justify-between bg-white dark:bg-gray-800 px-4 py-3 sm:px-6', className), children: [jsxs("div", { className: "flex flex-1 justify-between sm:hidden", children: [jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": "mobile-prev-btn", children: [jsx("span", { children: "Previous" }), jsx(SvgChevronLeft, { className: "size-4" })] }), jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "mobile-next-btn", children: [jsx("span", { children: "Next" }), jsx(SvgChevronRight, { className: "size-4" })] })] }), jsxs("div", { className: "hidden sm:flex sm:flex-1 sm:items-center sm:justify-between", children: [jsxs("p", { "data-testid": "pagination-summary", className: "text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1", children: ["Showing", jsx("span", { className: "font-medium", children: total === 0 ? 0 : Math.max(currentPage * limit - limit + 1, 1) }), "to", jsx("span", { className: "font-medium", children: Math.min(currentPage * limit, total) }), "of", jsx("span", { className: "font-medium", children: total }), "results"] }), jsxs("nav", { className: "isolate inline-flex items-center rounded-md", "aria-label": "Pagination", children: [jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": "prev-btn", children: [jsx("span", { className: "sr-only", children: "Previous" }), jsx(SvgChevronLeft, { className: "size-4" })] }), pages.map((page) => (jsx(Button, { size: "xs", theme: page === currentPage ? 'primary' : 'secondary', variant: page === currentPage ? 'default' : 'plain', "aria-current": page === currentPage ? 'page' : undefined, onClick: () => handlePageChange(page), children: jsx("span", { className: "size-4 block", children: page }) }, page))), jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "next-btn", children: [jsx("span", { className: "sr-only", children: "Next" }), jsx(SvgChevronRight, { className: "size-4" })] })] })] })] }));
548
548
  };
549
549
 
550
550
  function get(obj, path, defaultValue) {
@@ -733,9 +733,13 @@ const autoSortItems = (items, column, sort) => {
733
733
  };
734
734
  const DataTable = ({ items, columns, sorting = {}, onSort, loading, pagination = null, setPagination = () => { }, emptyMessage = 'No data found', containerClass = '', wrapperClass = '', layout = 'auto', }) => {
735
735
  const internalSorting = useRef({});
736
+ const [sortedItems, setSortedItems] = useState(items);
737
+ useEffect(() => {
738
+ setSortedItems(items);
739
+ }, [items]);
736
740
  const doAutoSort = (column) => {
737
741
  internalSorting.current = updateSortQuery(internalSorting.current, column.name, column.type);
738
- autoSortItems(items, column, internalSorting.current[column.name]);
742
+ setSortedItems(autoSortItems(items, column, internalSorting.current[column.name]));
739
743
  };
740
744
  const handleSort = (column) => {
741
745
  if (!column.sortable)
@@ -744,7 +748,7 @@ const DataTable = ({ items, columns, sorting = {}, onSort, loading, pagination =
744
748
  };
745
749
  return (jsxs("div", { className: buildClassName('w-full rounded-lg relative', containerClass), children: [jsx("div", { className: buildClassName('overflow-x-auto overflow-y-hidden rounded-lg border border-gray-200 dark:border-gray-800', wrapperClass, pagination && 'rounded-b-none border-b-0'), children: jsxs(Table, { className: "relative w-full max-w-full h-fit", layout: layout, children: [jsx(TableHead, { children: columns.map((column) => (jsx(TableHeaderCell, { align: column.headerAlign, style: { width: column.width || 'auto' }, className: buildClassName('bg-gray-50 dark:bg-gray-800', column.sticky === 'left' &&
746
750
  'sticky left-0 z-10 shadow-[2px_0_6px_rgba(0,0,0,0.06)]', column.sticky === 'right' &&
747
- 'sticky right-0 z-10 shadow-[-2px_0_6px_rgba(0,0,0,0.06)]'), children: jsxs(TextContent, { role: column.sortable ? 'button' : undefined, className: buildClassName('group', column.sortable && 'inline-flex items-center gap-2 cursor-pointer select-none'), onClick: () => handleSort(column), children: [jsx(TextContent, { className: "shrink-0", children: column.label }), column.sortable ? getSortIcon(sorting[column.name]) : null] }) }, column.name))) }), jsx(TableBody, { loading: loading, colSize: columns.length, rowSize: (pagination === null || pagination === void 0 ? void 0 : pagination.limit) || 5, children: !loading && items.length === 0 ? (jsx(EmptyTableRow, { colSpan: columns.length, children: emptyMessage })) : (items.map((item) => (jsx(TableRow, { className: "group", children: columns.map((column) => {
751
+ 'sticky right-0 z-10 shadow-[-2px_0_6px_rgba(0,0,0,0.06)]'), children: jsxs(TextContent, { role: column.sortable ? 'button' : undefined, className: buildClassName('group', column.sortable && 'inline-flex items-center gap-2 cursor-pointer select-none'), onClick: () => handleSort(column), children: [jsx(TextContent, { className: "shrink-0", children: column.label }), column.sortable ? getSortIcon(sorting[column.name]) : null] }) }, column.name))) }), jsx(TableBody, { loading: loading, colSize: columns.length, rowSize: (pagination === null || pagination === void 0 ? void 0 : pagination.limit) || 5, children: !loading && sortedItems.length === 0 ? (jsx(EmptyTableRow, { colSpan: columns.length, children: emptyMessage })) : (sortedItems.map((item) => (jsx(TableRow, { className: "group", children: columns.map((column) => {
748
752
  var _a;
749
753
  return (jsx(TableCell, { align: column.align, className: buildClassName(column.sticky === 'left' &&
750
754
  'sticky left-0 z-10 shadow-[2px_0_6px_rgba(0,0,0,0.06)]', column.sticky === 'right' &&
@@ -858,7 +862,7 @@ Input.displayName = 'Input';
858
862
  const PASSWORD_TYPE = 'password';
859
863
  const PASSWORD_PLACEHOLDER = '••••••••••';
860
864
  const PasswordInput = (props) => {
861
- const { type, placeholder, ...inputProps } = props;
865
+ const { ...inputProps } = props;
862
866
  const [showPassword, setShowPassword] = useState(false);
863
867
  return (jsx(Input, { ...inputProps, type: showPassword ? 'text' : PASSWORD_TYPE, placeholder: PASSWORD_PLACEHOLDER, rightGroup: jsx(Button, { theme: "secondary", variant: "plain", size: "xs", className: "px-1 mr-1", type: "button", onClick: () => setShowPassword(() => !showPassword), tabIndex: -1, children: showPassword ? (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", className: "size-4", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88" }) })) : (jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", className: "size-4", children: [jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" }), jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" })] })) }) }));
864
868
  };
@@ -868,8 +872,9 @@ const Radio = forwardRef((props, ref) => {
868
872
  const { id, name, label, labelClass = '', wrapperClass = '', containerClass = '', error, disabled = false, className = '',
869
873
  // RadioButton-only props removed from rest
870
874
  ...rest } = props;
871
- const radioId = (_a = id !== null && id !== void 0 ? id : name) !== null && _a !== void 0 ? _a : 'checkbox-' + Math.random();
872
- return (jsxs("div", { className: buildClassName(wrapperClass), children: [jsxs("div", { className: buildClassName('flex items-center gap-2', containerClass), children: [jsx("div", { className: "grid place-items-center", children: jsx("input", { ref: ref, ...rest, id: radioId, name: name, type: "radio", disabled: disabled, "aria-selected": rest.checked, className: buildClassName('peer transition-all col-start-1 row-start-1 appearance-none shrink-0 size-4.5 rounded-full', 'border focus:ring-2 focus:ring-offset-2', 'checked:border-5 checked:bg-white', 'focus:ring-blue-500 checked:focus:ring-blue-600 dark:focus:ring-offset-gray-800', disabled
875
+ const generatedId = useId();
876
+ const radioId = (_a = id !== null && id !== void 0 ? id : name) !== null && _a !== void 0 ? _a : generatedId;
877
+ return (jsxs("div", { className: buildClassName(wrapperClass), children: [jsxs("div", { className: buildClassName('flex items-center gap-2', containerClass), children: [jsx("div", { className: "grid place-items-center", children: jsx("input", { ref: ref, ...rest, id: radioId, name: name, type: "radio", disabled: disabled, "aria-checked": rest.checked, className: buildClassName('peer transition-all col-start-1 row-start-1 appearance-none shrink-0 size-4.5 rounded-full', 'border focus:ring-2 focus:ring-offset-2', 'checked:border-5 checked:bg-white', 'focus:ring-blue-500 checked:focus:ring-blue-600 dark:focus:ring-offset-gray-800', disabled
873
878
  ? 'border-gray-200 dark:border-gray-700 bg-gray-100 dark:bg-gray-800 pointer-events-none'
874
879
  : buildClassName('bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500', 'checked:border-blue-600 checked:hover:border-blue-600 dark:checked:border-blue-600 dark:checked:hover:border-blue-600'), className) }) }), label && (jsx(Label, { htmlFor: radioId, className: buildClassName(disabled
875
880
  ? 'cursor-not-allowed text-gray-400 dark:text-gray-500'
@@ -878,16 +883,9 @@ const Radio = forwardRef((props, ref) => {
878
883
 
879
884
  const RadioGroup = (props) => {
880
885
  const { name, label, labelClass, labelWrapperClass, labelHint, containerClass, options, value, row, error, showErrorMessage, onChange, } = props;
881
- const [items, setItems] = useState([]);
882
- useEffect(() => {
883
- setItems(() => {
884
- return options === null || options === void 0 ? void 0 : options.map((option) => {
885
- if (typeof option === 'string') {
886
- return { label: option, value: option };
887
- }
888
- return option;
889
- });
890
- });
886
+ const items = useMemo(() => {
887
+ var _a;
888
+ return (_a = options === null || options === void 0 ? void 0 : options.map((option) => typeof option === 'string' ? { label: option, value: option } : option)) !== null && _a !== void 0 ? _a : [];
891
889
  }, [options]);
892
890
  return (jsxs("div", { className: buildClassName(containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between mb-4', labelWrapperClass), children: [jsx(Label, { className: labelClass, children: label }), labelHint] })), jsx("div", { className: buildClassName('flex gap-4', row ? 'flex-row items-center' : 'flex-col items-start'), children: items.map((item) => (jsx(Radio, { name: name, id: `${name}-${item.value}`, label: item.label, value: item.value, checked: value === item.value, onChange: onChange }, item.value))) }), showErrorMessage && error && (jsx("div", { className: "mt-1", children: jsx(TextContent, { error: true, small: true, children: error }) }))] }));
893
891
  };
@@ -981,35 +979,53 @@ const RangeSlider = ({ label, labelHint, labelClass = '', containerClass = '', c
981
979
  const activeThumbRef = useRef(null);
982
980
  // State for UI styling
983
981
  const [activeThumbState, setActiveThumbState] = useState(null);
982
+ // Keep prop values in refs so drag handlers always use latest values (no stale closure)
983
+ const valueMinRef = useRef(valueMin);
984
+ const valueMaxRef = useRef(valueMax);
985
+ const onChangeRef = useRef(onChange);
986
+ valueMinRef.current = valueMin;
987
+ valueMaxRef.current = valueMax;
988
+ onChangeRef.current = onChange;
984
989
  const hasError = !isEmpty(error);
985
990
  const percent = (v) => ((v - min) / (max - min)) * 100;
986
- // ---------------------------------------------
987
- // Pointer Move (drag)
988
- // ---------------------------------------------
989
- const pointerMoveHandler = (e) => {
991
+ // Stable handler references registered on window — these never change identity
992
+ // so removeEventListener always finds the right listener to remove.
993
+ const stablePointerMoveRef = useRef((e) => {
990
994
  if (!trackRef.current || !activeThumbRef.current)
991
995
  return;
992
996
  const rect = trackRef.current.getBoundingClientRect();
993
997
  const x = Math.min(Math.max(e.clientX - rect.left, 0), rect.width);
994
998
  const rawVal = min + (x / rect.width) * (max - min);
995
999
  if (activeThumbRef.current === 'min') {
996
- onChange(Math.min(rawVal, valueMax - 1), valueMax);
1000
+ onChangeRef.current(Math.min(rawVal, valueMaxRef.current - 1), valueMaxRef.current);
997
1001
  }
998
1002
  else {
999
- onChange(valueMin, Math.max(rawVal, valueMin + 1));
1003
+ onChangeRef.current(valueMinRef.current, Math.max(rawVal, valueMinRef.current + 1));
1000
1004
  }
1001
- };
1002
- const pointerUpHandler = () => {
1005
+ });
1006
+ const stablePointerUpRef = useRef(() => {
1003
1007
  activeThumbRef.current = null;
1004
1008
  setActiveThumbState(null);
1005
- window.removeEventListener('pointermove', pointerMoveHandler);
1006
- window.removeEventListener('pointerup', pointerUpHandler);
1007
- };
1008
- const startDrag = (thumb) => (e) => {
1009
+ window.removeEventListener('pointermove', stablePointerMoveRef.current);
1010
+ window.removeEventListener('pointerup', stablePointerUpRef.current);
1011
+ });
1012
+ // Remove listeners on unmount in case a drag is in progress
1013
+ useEffect(() => {
1014
+ const move = stablePointerMoveRef.current;
1015
+ const up = stablePointerUpRef.current;
1016
+ return () => {
1017
+ window.removeEventListener('pointermove', move);
1018
+ window.removeEventListener('pointerup', up);
1019
+ };
1020
+ }, []);
1021
+ // ---------------------------------------------
1022
+ // Pointer Move (drag)
1023
+ // ---------------------------------------------
1024
+ const startDrag = (thumb) => (_e) => {
1009
1025
  activeThumbRef.current = thumb;
1010
1026
  setActiveThumbState(thumb);
1011
- window.addEventListener('pointermove', pointerMoveHandler);
1012
- window.addEventListener('pointerup', pointerUpHandler);
1027
+ window.addEventListener('pointermove', stablePointerMoveRef.current);
1028
+ window.addEventListener('pointerup', stablePointerUpRef.current);
1013
1029
  };
1014
1030
  // ---------------------------------------------
1015
1031
  // Click Track to move closest thumb
@@ -1071,6 +1087,9 @@ const useDebounce = (value, delay) => {
1071
1087
  };
1072
1088
  const useDebounceFn = (fn, ms) => {
1073
1089
  const timeout = useRef(null);
1090
+ useEffect(() => {
1091
+ return () => clearTimeout(timeout.current);
1092
+ }, []);
1074
1093
  return useCallback((...args) => {
1075
1094
  clearTimeout(timeout.current);
1076
1095
  timeout.current = setTimeout(() => fn(...args), ms);
@@ -1235,7 +1254,7 @@ const SelectBox = forwardRef((props, forwardedRef) => {
1235
1254
  console.error('onAdd handler error', err);
1236
1255
  }
1237
1256
  }, [onAdd, handleOnChangeInternal]);
1238
- const handleSearch = async (searchString) => {
1257
+ const handleSearch = useCallback(async (searchString) => {
1239
1258
  var _a;
1240
1259
  try {
1241
1260
  setOptionsLoading(true);
@@ -1247,11 +1266,11 @@ const SelectBox = forwardRef((props, forwardedRef) => {
1247
1266
  finally {
1248
1267
  setOptionsLoading(false);
1249
1268
  }
1250
- };
1269
+ }, [props.onSearch]);
1251
1270
  useEffect(() => {
1252
1271
  if (async && debouncedQuery)
1253
1272
  handleSearch(debouncedQuery);
1254
- }, [debouncedQuery, async]);
1273
+ }, [debouncedQuery, async, handleSearch]);
1255
1274
  return (jsxs("div", { className: buildClassName('group space-y-2', hasError && 'has-error', containerClass), ref: containerRef, children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between', labelWrapperClass), children: [jsx(Label, { className: labelClass, htmlFor: id, "aria-required": required, children: label }), labelHint] })), jsxs(Combobox, { multiple: multiple, immediate: immediate, name: name, value: selected, onChange: comboboxOnChangeWrapper, onClose: () => setQuery(''), disabled: disabled, children: [multiple ? (jsxs("div", { className: buildClassName(inputClass, 'flex flex-wrap gap-2 peer relative'), ref: forwardedRef, children: [isArray(selected) &&
1256
1275
  (selected === null || selected === void 0 ? void 0 : selected.length) > 0 &&
1257
1276
  selected.map((option, idx) => (jsx(Badge, { theme: "secondary", removable: !disabled, onRemove: () => removeSelection(idx), children: getDisplayValue(option) }, `${getValue(option)}-${idx}`))), jsx(ComboboxInput, { className: "outline-none flex-1 dark:placeholder-gray-400 placeholder-gray-400", "aria-label": name, value: query, autoComplete: "off", placeholder: (selected === null || selected === void 0 ? void 0 : selected.length) ? '' : placeholder, onChange: (e) => setQuery(e.target.value), id: id, ref: inputRef, onKeyDown: (e) => {
@@ -1364,7 +1383,11 @@ const ToastProvider = ({ children, placement = 'top-center' }) => {
1364
1383
  timeoutRefs.current.delete(id);
1365
1384
  }
1366
1385
  };
1367
- const scheduleClose = (toast) => {
1386
+ const closeToast = useCallback((id) => {
1387
+ clearTimer(id);
1388
+ setToasts((prev) => prev.filter((t) => t.id !== id));
1389
+ }, []);
1390
+ const scheduleClose = useCallback((toast) => {
1368
1391
  if (!toast.autoClose)
1369
1392
  return;
1370
1393
  clearTimer(toast.id);
@@ -1372,7 +1395,11 @@ const ToastProvider = ({ children, placement = 'top-center' }) => {
1372
1395
  closeToast(toast.id);
1373
1396
  }, toast.duration);
1374
1397
  timeoutRefs.current.set(toast.id, timeout);
1375
- };
1398
+ }, [closeToast]);
1399
+ useEffect(() => {
1400
+ const refs = timeoutRefs.current;
1401
+ return () => refs.forEach((id) => clearTimeout(id));
1402
+ }, []);
1376
1403
  const showToast = useCallback((message, options) => {
1377
1404
  var _a, _b, _c, _d;
1378
1405
  const id = ++toastCounter;
@@ -1391,612 +1418,12 @@ const ToastProvider = ({ children, placement = 'top-center' }) => {
1391
1418
  return updated;
1392
1419
  });
1393
1420
  scheduleClose(newToast);
1394
- }, []);
1395
- const closeToast = useCallback((id) => {
1396
- clearTimer(id);
1397
- setToasts((prev) => prev.filter((t) => t.id !== id));
1398
- }, []);
1421
+ }, [scheduleClose]);
1399
1422
  return (jsxs(ToastContext.Provider, { value: { showToast, closeToast }, children: [children, jsx("div", { className: buildClassName('fixed z-50 flex flex-col gap-3 pointer-events-none', placementClass[placement]), children: toasts.map((toast, index) => (jsx("div", { className: "pointer-events-auto", onMouseEnter: () => toast.pauseOnHover && clearTimer(toast.id), onMouseLeave: () => toast.pauseOnHover && scheduleClose(toast), children: jsx(Toast, { ...toast, isTop: placement.startsWith('top'), onClose: () => closeToast(toast.id) }) }, toast.id))) })] }));
1400
1423
  };
1401
1424
 
1402
- const q = typeof window < "u" ? useLayoutEffect : useEffect;
1403
- function oe(e) {
1404
- if (e !== void 0)
1405
- switch (typeof e) {
1406
- case "number":
1407
- return e;
1408
- case "string": {
1409
- if (e.endsWith("px"))
1410
- return parseFloat(e);
1411
- break;
1412
- }
1413
- }
1414
- }
1415
- function Ie({
1416
- box: e,
1417
- defaultHeight: t,
1418
- defaultWidth: s,
1419
- disabled: r,
1420
- element: n,
1421
- mode: o,
1422
- style: i
1423
- }) {
1424
- const { styleHeight: f, styleWidth: l } = useMemo(
1425
- () => ({
1426
- styleHeight: oe(i?.height),
1427
- styleWidth: oe(i?.width)
1428
- }),
1429
- [i?.height, i?.width]
1430
- ), [c, d] = useState({
1431
- height: t,
1432
- width: s
1433
- }), a = r || f !== void 0 || o === "only-width" || f !== void 0 && l !== void 0;
1434
- return q(() => {
1435
- if (n === null || a)
1436
- return;
1437
- const u = new ResizeObserver((I) => {
1438
- for (const m of I) {
1439
- const { contentRect: b, target: g } = m;
1440
- n === g && d((w) => w.height === b.height && w.width === b.width ? w : {
1441
- height: b.height,
1442
- width: b.width
1443
- });
1444
- }
1445
- });
1446
- return u.observe(n, { box: e }), () => {
1447
- u?.unobserve(n);
1448
- };
1449
- }, [e, a, n, f, l]), useMemo(
1450
- () => ({
1451
- height: f ?? c.height,
1452
- width: l ?? c.width
1453
- }),
1454
- [c, f, l]
1455
- );
1456
- }
1457
- function ae(e) {
1458
- const t = useRef(() => {
1459
- throw new Error("Cannot call during render.");
1460
- });
1461
- return q(() => {
1462
- t.current = e;
1463
- }, [e]), useCallback((s) => t.current?.(s), [t]);
1464
- }
1465
- function Z({
1466
- containerElement: e,
1467
- direction: t,
1468
- isRtl: s,
1469
- scrollOffset: r
1470
- }) {
1471
- return r;
1472
- }
1473
- function $(e, t = "Assertion error") {
1474
- if (!e)
1475
- throw console.error(t), Error(t);
1476
- }
1477
- function Y(e, t) {
1478
- if (e === t)
1479
- return true;
1480
- if (!!e != !!t || ($(e !== void 0), $(t !== void 0), Object.keys(e).length !== Object.keys(t).length))
1481
- return false;
1482
- for (const s in e)
1483
- if (!Object.is(t[s], e[s]))
1484
- return false;
1485
- return true;
1486
- }
1487
- function fe({
1488
- cachedBounds: e,
1489
- itemCount: t,
1490
- itemSize: s
1491
- }) {
1492
- if (t === 0)
1493
- return 0;
1494
- if (typeof s == "number")
1495
- return t * s;
1496
- {
1497
- const r = e.get(
1498
- e.size === 0 ? 0 : e.size - 1
1499
- );
1500
- $(r !== void 0, "Unexpected bounds cache miss");
1501
- const n = (r.scrollOffset + r.size) / e.size;
1502
- return t * n;
1503
- }
1504
- }
1505
- function we({
1506
- align: e,
1507
- cachedBounds: t,
1508
- index: s,
1509
- itemCount: r,
1510
- itemSize: n,
1511
- containerScrollOffset: o,
1512
- containerSize: i
1513
- }) {
1514
- if (s < 0 || s >= r)
1515
- throw RangeError(`Invalid index specified: ${s}`, {
1516
- cause: `Index ${s} is not within the range of 0 - ${r - 1}`
1517
- });
1518
- const f = fe({
1519
- cachedBounds: t,
1520
- itemCount: r,
1521
- itemSize: n
1522
- }), l = t.get(s), c = Math.max(
1523
- 0,
1524
- Math.min(f - i, l.scrollOffset)
1525
- ), d = Math.max(
1526
- 0,
1527
- l.scrollOffset - i + l.size
1528
- );
1529
- switch (e === "smart" && (o >= d && o <= c ? e = "auto" : e = "center"), e) {
1530
- case "start":
1531
- return c;
1532
- case "end":
1533
- return d;
1534
- case "center":
1535
- return l.scrollOffset <= i / 2 ? 0 : l.scrollOffset + l.size / 2 >= f - i / 2 ? f - i : l.scrollOffset + l.size / 2 - i / 2;
1536
- case "auto":
1537
- default:
1538
- return o >= d && o <= c ? o : o < d ? d : c;
1539
- }
1540
- }
1541
- function ie({
1542
- cachedBounds: e,
1543
- containerScrollOffset: t,
1544
- containerSize: s,
1545
- itemCount: r,
1546
- overscanCount: n
1547
- }) {
1548
- const o = r - 1;
1549
- let i = 0, f = -1, l = 0, c = -1, d = 0;
1550
- for (; d < o; ) {
1551
- const a = e.get(d);
1552
- if (a.scrollOffset + a.size > t)
1553
- break;
1554
- d++;
1555
- }
1556
- for (i = d, l = Math.max(0, i - n); d < o; ) {
1557
- const a = e.get(d);
1558
- if (a.scrollOffset + a.size >= t + s)
1559
- break;
1560
- d++;
1561
- }
1562
- return f = Math.min(o, d), c = Math.min(r - 1, f + n), i < 0 && (i = 0, f = -1, l = 0, c = -1), {
1563
- startIndexVisible: i,
1564
- stopIndexVisible: f,
1565
- startIndexOverscan: l,
1566
- stopIndexOverscan: c
1567
- };
1568
- }
1569
- function me({
1570
- itemCount: e,
1571
- itemProps: t,
1572
- itemSize: s
1573
- }) {
1574
- const r = /* @__PURE__ */ new Map();
1575
- return {
1576
- get(n) {
1577
- for ($(n < e, `Invalid index ${n}`); r.size - 1 < n; ) {
1578
- const i = r.size;
1579
- let f;
1580
- switch (typeof s) {
1581
- case "function": {
1582
- f = s(i, t);
1583
- break;
1584
- }
1585
- case "number": {
1586
- f = s;
1587
- break;
1588
- }
1589
- }
1590
- if (i === 0)
1591
- r.set(i, {
1592
- size: f,
1593
- scrollOffset: 0
1594
- });
1595
- else {
1596
- const l = r.get(i - 1);
1597
- $(
1598
- l !== void 0,
1599
- `Unexpected bounds cache miss for index ${n}`
1600
- ), r.set(i, {
1601
- scrollOffset: l.scrollOffset + l.size,
1602
- size: f
1603
- });
1604
- }
1605
- }
1606
- const o = r.get(n);
1607
- return $(
1608
- o !== void 0,
1609
- `Unexpected bounds cache miss for index ${n}`
1610
- ), o;
1611
- },
1612
- set(n, o) {
1613
- r.set(n, o);
1614
- },
1615
- get size() {
1616
- return r.size;
1617
- }
1618
- };
1619
- }
1620
- function Oe({
1621
- itemCount: e,
1622
- itemProps: t,
1623
- itemSize: s
1624
- }) {
1625
- return useMemo(
1626
- () => me({
1627
- itemCount: e,
1628
- itemProps: t,
1629
- itemSize: s
1630
- }),
1631
- [e, t, s]
1632
- );
1633
- }
1634
- function ye({
1635
- containerSize: e,
1636
- itemSize: t
1637
- }) {
1638
- let s;
1639
- switch (typeof t) {
1640
- case "string": {
1641
- $(
1642
- t.endsWith("%"),
1643
- `Invalid item size: "${t}"; string values must be percentages (e.g. "100%")`
1644
- ), $(
1645
- e !== void 0,
1646
- "Container size must be defined if a percentage item size is specified"
1647
- ), s = e * parseInt(t) / 100;
1648
- break;
1649
- }
1650
- default: {
1651
- s = t;
1652
- break;
1653
- }
1654
- }
1655
- return s;
1656
- }
1657
- function ee({
1658
- containerElement: e,
1659
- containerStyle: t,
1660
- defaultContainerSize: s = 0,
1661
- direction: r,
1662
- isRtl: n = false,
1663
- itemCount: o,
1664
- itemProps: i,
1665
- itemSize: f,
1666
- onResize: l,
1667
- overscanCount: c
1668
- }) {
1669
- const [d, a] = useState({
1670
- startIndexVisible: 0,
1671
- startIndexOverscan: 0,
1672
- stopIndexVisible: -1,
1673
- stopIndexOverscan: -1
1674
- }), {
1675
- startIndexVisible: u,
1676
- startIndexOverscan: I,
1677
- stopIndexVisible: m,
1678
- stopIndexOverscan: b
1679
- } = {
1680
- startIndexVisible: Math.min(o - 1, d.startIndexVisible),
1681
- startIndexOverscan: Math.min(o - 1, d.startIndexOverscan),
1682
- stopIndexVisible: Math.min(o - 1, d.stopIndexVisible),
1683
- stopIndexOverscan: Math.min(o - 1, d.stopIndexOverscan)
1684
- }, { height: g = s, width: w = s } = Ie({
1685
- defaultHeight: s ,
1686
- defaultWidth: void 0,
1687
- element: e,
1688
- mode: "only-height" ,
1689
- style: t
1690
- }), y = useRef({
1691
- height: 0,
1692
- width: 0
1693
- }), V = g , h = ye({ containerSize: V, itemSize: f });
1694
- useLayoutEffect(() => {
1695
- if (typeof l == "function") {
1696
- const p = y.current;
1697
- (p.height !== g || p.width !== w) && (l({ height: g, width: w }, { ...p }), p.height = g, p.width = w);
1698
- }
1699
- }, [g, l, w]);
1700
- const z = Oe({
1701
- itemCount: o,
1702
- itemProps: i,
1703
- itemSize: h
1704
- }), k = useCallback(
1705
- (p) => z.get(p),
1706
- [z]
1707
- ), S = useCallback(
1708
- () => fe({
1709
- cachedBounds: z,
1710
- itemCount: o,
1711
- itemSize: h
1712
- }),
1713
- [z, o, h]
1714
- ), W = useCallback(
1715
- (p) => {
1716
- const T = Z({
1717
- containerElement: e,
1718
- direction: r,
1719
- isRtl: n,
1720
- scrollOffset: p
1721
- });
1722
- return ie({
1723
- cachedBounds: z,
1724
- containerScrollOffset: T,
1725
- containerSize: V,
1726
- itemCount: o,
1727
- overscanCount: c
1728
- });
1729
- },
1730
- [
1731
- z,
1732
- e,
1733
- V,
1734
- r,
1735
- n,
1736
- o,
1737
- c
1738
- ]
1739
- );
1740
- q(() => {
1741
- const p = (e?.scrollTop ) ?? 0;
1742
- a(W(p));
1743
- }, [e, r, W]), q(() => {
1744
- if (!e)
1745
- return;
1746
- const p = () => {
1747
- a((T) => {
1748
- const { scrollLeft: R, scrollTop: v } = e, x = Z({
1749
- containerElement: e,
1750
- direction: r,
1751
- isRtl: n,
1752
- scrollOffset: v
1753
- }), A = ie({
1754
- cachedBounds: z,
1755
- containerScrollOffset: x,
1756
- containerSize: V,
1757
- itemCount: o,
1758
- overscanCount: c
1759
- });
1760
- return Y(A, T) ? T : A;
1761
- });
1762
- };
1763
- return e.addEventListener("scroll", p), () => {
1764
- e.removeEventListener("scroll", p);
1765
- };
1766
- }, [
1767
- z,
1768
- e,
1769
- V,
1770
- r,
1771
- o,
1772
- c
1773
- ]);
1774
- const O = ae(
1775
- ({
1776
- align: p = "auto",
1777
- containerScrollOffset: T,
1778
- index: R
1779
- }) => {
1780
- let v = we({
1781
- align: p,
1782
- cachedBounds: z,
1783
- containerScrollOffset: T,
1784
- containerSize: V,
1785
- index: R,
1786
- itemCount: o,
1787
- itemSize: h
1788
- });
1789
- if (e) {
1790
- if (v = Z({
1791
- containerElement: e,
1792
- direction: r,
1793
- isRtl: n,
1794
- scrollOffset: v
1795
- }), typeof e.scrollTo != "function") {
1796
- const x = W(v);
1797
- Y(d, x) || a(x);
1798
- }
1799
- return v;
1800
- }
1801
- }
1802
- );
1803
- return {
1804
- getCellBounds: k,
1805
- getEstimatedSize: S,
1806
- scrollToIndex: O,
1807
- startIndexOverscan: I,
1808
- startIndexVisible: u,
1809
- stopIndexOverscan: b,
1810
- stopIndexVisible: m
1811
- };
1812
- }
1813
- function de(e) {
1814
- return useMemo(() => e, Object.values(e));
1815
- }
1816
- function ue(e, t) {
1817
- const {
1818
- ariaAttributes: s,
1819
- style: r,
1820
- ...n
1821
- } = e, {
1822
- ariaAttributes: o,
1823
- style: i,
1824
- ...f
1825
- } = t;
1826
- return Y(s, o) && Y(r, i) && Y(n, f);
1827
- }
1828
- function ze(e) {
1829
- return e != null && typeof e == "object" && "getAverageRowHeight" in e && typeof e.getAverageRowHeight == "function";
1830
- }
1831
- const te = "data-react-window-index";
1832
- function Ae({
1833
- children: e,
1834
- className: t,
1835
- defaultHeight: s = 0,
1836
- listRef: r,
1837
- onResize: n,
1838
- onRowsRendered: o,
1839
- overscanCount: i = 3,
1840
- rowComponent: f,
1841
- rowCount: l,
1842
- rowHeight: c,
1843
- rowProps: d,
1844
- tagName: a = "div",
1845
- style: u,
1846
- ...I
1847
- }) {
1848
- const m = de(d), b = useMemo(
1849
- () => memo(f, ue),
1850
- [f]
1851
- ), [g, w] = useState(null), y = ze(c), V = useMemo(() => y ? (v) => c.getRowHeight(v) ?? c.getAverageRowHeight() : c, [y, c]), {
1852
- getCellBounds: h,
1853
- getEstimatedSize: z,
1854
- scrollToIndex: k,
1855
- startIndexOverscan: S,
1856
- startIndexVisible: W,
1857
- stopIndexOverscan: O,
1858
- stopIndexVisible: p
1859
- } = ee({
1860
- containerElement: g,
1861
- containerStyle: u,
1862
- defaultContainerSize: s,
1863
- direction: "vertical",
1864
- itemCount: l,
1865
- itemProps: m,
1866
- itemSize: V,
1867
- onResize: n,
1868
- overscanCount: i
1869
- });
1870
- useImperativeHandle(
1871
- r,
1872
- () => ({
1873
- get element() {
1874
- return g;
1875
- },
1876
- scrollToRow({
1877
- align: v = "auto",
1878
- behavior: x = "auto",
1879
- index: A
1880
- }) {
1881
- const M = k({
1882
- align: v,
1883
- containerScrollOffset: g?.scrollTop ?? 0,
1884
- index: A
1885
- });
1886
- typeof g?.scrollTo == "function" && g.scrollTo({
1887
- behavior: x,
1888
- top: M
1889
- });
1890
- }
1891
- }),
1892
- [g, k]
1893
- ), q(() => {
1894
- if (!g)
1895
- return;
1896
- const v = Array.from(g.children).filter((x, A) => {
1897
- if (x.hasAttribute("aria-hidden"))
1898
- return false;
1899
- const M = `${S + A}`;
1900
- return x.setAttribute(te, M), true;
1901
- });
1902
- if (y)
1903
- return c.observeRowElements(v);
1904
- }, [
1905
- g,
1906
- y,
1907
- c,
1908
- S,
1909
- O
1910
- ]), useEffect(() => {
1911
- S >= 0 && O >= 0 && o && o(
1912
- {
1913
- startIndex: W,
1914
- stopIndex: p
1915
- },
1916
- {
1917
- startIndex: S,
1918
- stopIndex: O
1919
- }
1920
- );
1921
- }, [
1922
- o,
1923
- S,
1924
- W,
1925
- O,
1926
- p
1927
- ]);
1928
- const T = useMemo(() => {
1929
- const v = [];
1930
- if (l > 0)
1931
- for (let x = S; x <= O; x++) {
1932
- const A = h(x);
1933
- v.push(
1934
- /* @__PURE__ */ createElement(
1935
- b,
1936
- {
1937
- ...m,
1938
- ariaAttributes: {
1939
- "aria-posinset": x + 1,
1940
- "aria-setsize": l,
1941
- role: "listitem"
1942
- },
1943
- key: x,
1944
- index: x,
1945
- style: {
1946
- position: "absolute",
1947
- left: 0,
1948
- transform: `translateY(${A.scrollOffset}px)`,
1949
- // In case of dynamic row heights, don't specify a height style
1950
- // otherwise a default/estimated height would mask the actual height
1951
- height: y ? void 0 : A.size,
1952
- width: "100%"
1953
- }
1954
- }
1955
- )
1956
- );
1957
- }
1958
- return v;
1959
- }, [
1960
- b,
1961
- h,
1962
- y,
1963
- l,
1964
- m,
1965
- S,
1966
- O
1967
- ]), R = /* @__PURE__ */ jsx(
1968
- "div",
1969
- {
1970
- "aria-hidden": true,
1971
- style: {
1972
- height: z(),
1973
- width: "100%",
1974
- zIndex: -1
1975
- }
1976
- }
1977
- );
1978
- return createElement(
1979
- a,
1980
- {
1981
- role: "list",
1982
- ...I,
1983
- className: t,
1984
- ref: w,
1985
- style: {
1986
- position: "relative",
1987
- maxHeight: "100%",
1988
- flexGrow: 1,
1989
- overflowY: "auto",
1990
- ...u
1991
- }
1992
- },
1993
- T,
1994
- e,
1995
- R
1996
- );
1997
- }
1998
-
1999
- const VirtualizedRow = ({ index, style, items, columns, gridTemplate, }) => {
1425
+ const VirtualizedRow = ({ index, style, data }) => {
1426
+ const { items, columns, gridTemplate } = data;
2000
1427
  const item = items[index];
2001
1428
  return (jsx("div", { role: "row", className: "grid border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 group", style: {
2002
1429
  ...style,
@@ -2018,14 +1445,21 @@ const VirtualizedRow = ({ index, style, items, columns, gridTemplate, }) => {
2018
1445
  const useGridTemplate = (columns) => useMemo(() => columns.map((col) => (col.width ? `${col.width}px` : '1fr')).join(' '), [columns]);
2019
1446
  const VirtualizedDataTable = ({ items, columns, sorting = {}, onSort, loading = false, emptyMessage = 'No data available', containerClass = '', wrapperClass = '', rowHeight = 48, maxHeight = 400, }) => {
2020
1447
  const scrollRef = useRef(null);
1448
+ const outerRef = useRef(null);
2021
1449
  const headerRef = useRef(null);
2022
1450
  const gridTemplate = useGridTemplate(columns);
2023
- /** Sync body → header scroll */
2024
- const handleScrollSync = (scrollOffset) => {
2025
- if (headerRef.current) {
2026
- headerRef.current.scrollLeft = scrollOffset;
2027
- }
2028
- };
1451
+ /** Sync horizontal scroll of the virtualized body → header */
1452
+ useEffect(() => {
1453
+ const el = outerRef.current;
1454
+ if (!el)
1455
+ return;
1456
+ const handler = () => {
1457
+ if (headerRef.current)
1458
+ headerRef.current.scrollLeft = el.scrollLeft;
1459
+ };
1460
+ el.addEventListener('scroll', handler);
1461
+ return () => el.removeEventListener('scroll', handler);
1462
+ }, []);
2029
1463
  const handleSort = useCallback((column) => {
2030
1464
  if (column.sortable)
2031
1465
  onSort === null || onSort === void 0 ? void 0 : onSort(column);
@@ -2057,11 +1491,7 @@ const VirtualizedDataTable = ({ items, columns, sorting = {}, onSort, loading =
2057
1491
  /* -------------------------------------------------------
2058
1492
  * MAIN (virtualized body)
2059
1493
  * ------------------------------------------------------- */
2060
- return (jsx("div", { className: buildClassName('w-full rounded-lg relative', containerClass), children: jsxs("div", { className: buildClassName('rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden', wrapperClass), children: [Header, jsx("div", { style: {
2061
- height: maxHeight,
2062
- overflowY: 'auto',
2063
- overflowX: 'auto',
2064
- }, children: jsx(Ae, { listRef: scrollRef, rowComponent: VirtualizedRow, rowCount: items.length, rowHeight: rowHeight, rowProps: { items, columns, gridTemplate }, onScroll: (e) => handleScrollSync(e.currentTarget.scrollLeft) }) })] }) }));
1494
+ return (jsx("div", { className: buildClassName('w-full rounded-lg relative', containerClass), children: jsxs("div", { className: buildClassName('rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden', wrapperClass), children: [Header, jsx(FixedSizeList, { ref: scrollRef, outerRef: outerRef, height: maxHeight, width: "100%", itemCount: items.length, itemSize: rowHeight, itemData: { items, columns, gridTemplate }, children: VirtualizedRow })] }) }));
2065
1495
  };
2066
1496
 
2067
1497
  const useIsMobile = () => {