@papernote/ui 2.0.3 → 2.0.5

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.esm.js CHANGED
@@ -2745,7 +2745,7 @@ function FormControl({ label, required = false, error, helperText, children, cla
2745
2745
  return (jsxs("div", { className: `${className}`, children: [label && (jsxs("label", { htmlFor: htmlFor, className: "block text-sm font-medium text-ink-700 mb-1", children: [label, required && jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsx("div", { children: children }), (error || helperText) && (jsx("p", { className: `mt-1 text-xs ${error ? 'text-error-600' : 'text-ink-500'}`, children: error || helperText }))] }));
2746
2746
  }
2747
2747
 
2748
- function FilterBar({ filters, values, onChange, className = '', onClear, showClearButton = false, }) {
2748
+ function FilterBar({ filters, values, onChange, className = "", onClear, showClearButton = false, }) {
2749
2749
  const handleFilterChange = (key, value) => {
2750
2750
  onChange({
2751
2751
  ...values,
@@ -2759,16 +2759,19 @@ function FilterBar({ filters, values, onChange, className = '', onClear, showCle
2759
2759
  else {
2760
2760
  // Default clear: set all values to null/empty
2761
2761
  const clearedValues = {};
2762
- filters.forEach(filter => {
2763
- if (filter.type === 'text' || filter.type === 'search') {
2764
- clearedValues[filter.key] = '';
2762
+ filters.forEach((filter) => {
2763
+ if (filter.type === "text" || filter.type === "search") {
2764
+ clearedValues[filter.key] = "";
2765
2765
  }
2766
- else if (filter.type === 'dateRange') {
2766
+ else if (filter.type === "dateRange") {
2767
2767
  clearedValues[filter.key] = { from: undefined, to: undefined };
2768
2768
  }
2769
- else if (filter.type === 'multiSelect') {
2769
+ else if (filter.type === "multiSelect") {
2770
2770
  clearedValues[filter.key] = [];
2771
2771
  }
2772
+ else if (filter.type === "switch") {
2773
+ clearedValues[filter.key] = false;
2774
+ }
2772
2775
  else {
2773
2776
  clearedValues[filter.key] = null;
2774
2777
  }
@@ -2779,51 +2782,70 @@ function FilterBar({ filters, values, onChange, className = '', onClear, showCle
2779
2782
  const renderFilter = (filter) => {
2780
2783
  const value = values[filter.key];
2781
2784
  switch (filter.type) {
2782
- case 'text':
2783
- return (jsx(Input, { type: "text", placeholder: filter.placeholder || `Filter by ${filter.label}`, value: value || '', onChange: (e) => handleFilterChange(filter.key, e.target.value) }));
2784
- case 'select': {
2785
+ case "text":
2786
+ return (jsx(Input, { type: "text", placeholder: filter.placeholder || `Filter by ${filter.label}`, value: value || "", onChange: (e) => handleFilterChange(filter.key, e.target.value) }));
2787
+ case "select": {
2785
2788
  const selectOptions = [
2786
- { value: '', label: `All ${filter.label}` },
2787
- ...(filter.options?.map(opt => ({
2789
+ { value: "", label: `All ${filter.label}` },
2790
+ ...(filter.options?.map((opt) => ({
2788
2791
  value: String(opt.value),
2789
2792
  label: opt.label,
2790
2793
  })) || []),
2791
2794
  ];
2792
- return (jsx(Select, { options: selectOptions, value: String(value || ''), onChange: (newValue) => handleFilterChange(filter.key, newValue || null) }));
2795
+ return (jsx(Select, { options: selectOptions, value: String(value || ""), onChange: (newValue) => handleFilterChange(filter.key, newValue || null) }));
2793
2796
  }
2794
- case 'date':
2795
- return (jsx("input", { type: "date", value: value || '', onChange: (e) => handleFilterChange(filter.key, e.target.value), className: "input" }));
2796
- case 'number':
2797
- return (jsx("input", { type: "number", placeholder: filter.placeholder || `Filter by ${filter.label}`, value: value !== null && value !== undefined ? String(value) : '', onChange: (e) => handleFilterChange(filter.key, e.target.value ? Number(e.target.value) : null), className: "input" }));
2798
- case 'boolean': {
2797
+ case "date":
2798
+ return (jsx("input", { type: "date", value: value || "", onChange: (e) => handleFilterChange(filter.key, e.target.value), className: "input" }));
2799
+ case "number":
2800
+ return (jsx("input", { type: "number", placeholder: filter.placeholder || `Filter by ${filter.label}`, value: value !== null && value !== undefined ? String(value) : "", onChange: (e) => handleFilterChange(filter.key, e.target.value ? Number(e.target.value) : null), className: "input" }));
2801
+ case "boolean": {
2799
2802
  const boolOptions = [
2800
- { value: '', label: 'All' },
2801
- { value: 'true', label: 'Yes' },
2802
- { value: 'false', label: 'No' },
2803
+ { value: "", label: "All" },
2804
+ { value: "true", label: "Yes" },
2805
+ { value: "false", label: "No" },
2803
2806
  ];
2804
- return (jsx(Select, { options: boolOptions, value: value === null || value === undefined ? '' : String(value), onChange: (newValue) => handleFilterChange(filter.key, newValue === '' ? null : newValue === 'true') }));
2807
+ return (jsx(Select, { options: boolOptions, value: value === null || value === undefined ? "" : String(value), onChange: (newValue) => handleFilterChange(filter.key, newValue === "" ? null : newValue === "true") }));
2805
2808
  }
2806
- case 'search':
2807
- return (jsxs("div", { className: "relative", children: [jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none", children: jsx(Search, { className: "h-4 w-4 text-ink-400" }) }), jsx("input", { type: "text", placeholder: filter.placeholder || `Search ${filter.label}...`, value: value || '', onChange: (e) => handleFilterChange(filter.key, e.target.value), className: "input pl-9" })] }));
2808
- case 'dateRange': {
2809
+ case "search":
2810
+ return (jsxs("div", { className: "relative", children: [jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none", children: jsx(Search, { className: "h-4 w-4 text-ink-400" }) }), jsx("input", { type: "text", placeholder: filter.placeholder || `Search ${filter.label}...`, value: value || "", onChange: (e) => handleFilterChange(filter.key, e.target.value), className: "input pl-9" })] }));
2811
+ case "dateRange": {
2809
2812
  const rangeValue = value || {};
2810
- return (jsxs("div", { className: "flex items-center gap-2", children: [jsx("input", { type: "date", value: rangeValue.from || '', onChange: (e) => handleFilterChange(filter.key, { ...rangeValue, from: e.target.value || undefined }), className: "input text-sm", "aria-label": `${filter.label} from` }), jsx("span", { className: "text-ink-400 text-xs", children: "to" }), jsx("input", { type: "date", value: rangeValue.to || '', onChange: (e) => handleFilterChange(filter.key, { ...rangeValue, to: e.target.value || undefined }), className: "input text-sm", "aria-label": `${filter.label} to` })] }));
2811
- }
2812
- case 'toggle': {
2813
+ return (jsxs("div", { className: "flex items-center gap-2", children: [jsx("input", { type: "date", value: rangeValue.from || "", onChange: (e) => handleFilterChange(filter.key, {
2814
+ ...rangeValue,
2815
+ from: e.target.value || undefined,
2816
+ }), className: "input text-sm", "aria-label": `${filter.label} from` }), jsx("span", { className: "text-ink-400 text-xs", children: "to" }), jsx("input", { type: "date", value: rangeValue.to || "", onChange: (e) => handleFilterChange(filter.key, {
2817
+ ...rangeValue,
2818
+ to: e.target.value || undefined,
2819
+ }), className: "input text-sm", "aria-label": `${filter.label} to` })] }));
2820
+ }
2821
+ case "toggle": {
2813
2822
  const toggleOptions = [
2814
- { value: '', label: 'All' },
2815
- { value: 'true', label: 'Yes' },
2816
- { value: 'false', label: 'No' },
2823
+ { value: "", label: "All" },
2824
+ { value: "true", label: "Yes" },
2825
+ { value: "false", label: "No" },
2817
2826
  ];
2818
- const currentVal = value === null || value === undefined ? '' : String(value);
2819
- return (jsx("div", { className: "flex rounded-lg border border-paper-300 overflow-hidden", role: "group", children: toggleOptions.map((opt) => (jsx("button", { type: "button", onClick: () => handleFilterChange(filter.key, opt.value === '' ? null : opt.value === 'true'), className: `px-3 py-1.5 text-xs font-medium transition-colors ${currentVal === opt.value
2820
- ? 'bg-accent-500 text-white'
2821
- : 'bg-white text-ink-600 hover:bg-paper-50'} ${opt.value !== '' ? 'border-l border-paper-300' : ''}`, children: opt.label }, opt.value))) }));
2822
- }
2823
- case 'multiSelect': {
2827
+ const currentVal = value === null || value === undefined ? "" : String(value);
2828
+ return (jsx("div", { className: "flex rounded-lg border border-paper-300 overflow-hidden", role: "group", children: toggleOptions.map((opt) => (jsx("button", { type: "button", onClick: () => handleFilterChange(filter.key, opt.value === "" ? null : opt.value === "true"), className: `px-3 py-1.5 text-xs font-medium transition-colors ${currentVal === opt.value
2829
+ ? "bg-accent-500 text-white"
2830
+ : "bg-white text-ink-600 hover:bg-paper-50"} ${opt.value !== "" ? "border-l border-paper-300" : ""}`, children: opt.label }, opt.value))) }));
2831
+ }
2832
+ case "switch": {
2833
+ // Single binary toggle — use when the filter is naturally on/off
2834
+ // (e.g. "Mine only", "Archived"), unlike `boolean` / `toggle` which
2835
+ // present an All/Yes/No tri-state. Stored value is a plain boolean.
2836
+ const checked = value === true;
2837
+ return (jsxs("button", { type: "button", role: "switch", "aria-checked": checked, onClick: () => handleFilterChange(filter.key, !checked), className: `relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-accent-400 focus:ring-offset-2 ${checked ? "bg-accent-500" : "bg-paper-300"}`, children: [jsx("span", { className: `inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${checked ? "translate-x-6" : "translate-x-1"}` }), jsx("span", { className: "sr-only", children: filter.label })] }));
2838
+ }
2839
+ case "multiSelect": {
2824
2840
  const selectedValues = Array.isArray(value) ? value : [];
2825
2841
  const msOptions = filter.options || [];
2826
- return (jsxs("div", { className: "relative", children: [jsx(Select, { options: [{ value: '', label: `All ${filter.label}` }, ...msOptions.map(o => ({ value: String(o.value), label: o.label }))], value: "", onChange: (newValue) => {
2842
+ return (jsxs("div", { className: "relative", children: [jsx(Select, { options: [
2843
+ { value: "", label: `All ${filter.label}` },
2844
+ ...msOptions.map((o) => ({
2845
+ value: String(o.value),
2846
+ label: o.label,
2847
+ })),
2848
+ ], value: "", onChange: (newValue) => {
2827
2849
  if (!newValue) {
2828
2850
  handleFilterChange(filter.key, []);
2829
2851
  }
@@ -2831,8 +2853,8 @@ function FilterBar({ filters, values, onChange, className = '', onClear, showCle
2831
2853
  handleFilterChange(filter.key, [...selectedValues, newValue]);
2832
2854
  }
2833
2855
  } }), selectedValues.length > 0 && (jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: selectedValues.map((sv) => {
2834
- const opt = msOptions.find(o => String(o.value) === sv);
2835
- return (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-accent-100 text-accent-700 rounded-full", children: [opt?.label || sv, jsx("button", { type: "button", onClick: () => handleFilterChange(filter.key, selectedValues.filter(v => v !== sv)), className: "hover:text-accent-900", children: jsx(X, { className: "h-3 w-3" }) })] }, sv));
2856
+ const opt = msOptions.find((o) => String(o.value) === sv);
2857
+ return (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-accent-100 text-accent-700 rounded-full", children: [opt?.label || sv, jsx("button", { type: "button", onClick: () => handleFilterChange(filter.key, selectedValues.filter((v) => v !== sv)), className: "hover:text-accent-900", children: jsx(X, { className: "h-3 w-3" }) })] }, sv));
2836
2858
  }) }))] }));
2837
2859
  }
2838
2860
  default:
@@ -14784,25 +14806,27 @@ function ActionMenu({ actions, item, }) {
14784
14806
  // Click outside handler
14785
14807
  useEffect(() => {
14786
14808
  const handleClickOutside = (event) => {
14787
- if (menuRef.current && !menuRef.current.contains(event.target) &&
14788
- buttonRef.current && !buttonRef.current.contains(event.target)) {
14809
+ if (menuRef.current &&
14810
+ !menuRef.current.contains(event.target) &&
14811
+ buttonRef.current &&
14812
+ !buttonRef.current.contains(event.target)) {
14789
14813
  setIsOpen(false);
14790
14814
  }
14791
14815
  };
14792
14816
  if (isOpen) {
14793
- document.addEventListener('mousedown', handleClickOutside);
14817
+ document.addEventListener("mousedown", handleClickOutside);
14794
14818
  }
14795
14819
  return () => {
14796
- document.removeEventListener('mousedown', handleClickOutside);
14820
+ document.removeEventListener("mousedown", handleClickOutside);
14797
14821
  };
14798
14822
  }, [isOpen]);
14799
- const visibleActions = actions.filter(action => !action.show || action.show(item));
14823
+ const visibleActions = actions.filter((action) => !action.show || action.show(item));
14800
14824
  if (visibleActions.length === 0)
14801
14825
  return null;
14802
14826
  const dropdownContent = isOpen && (jsx("div", { ref: menuRef, className: "fixed w-56 bg-white rounded-lg shadow-lg border border-paper-300 py-1", style: {
14803
14827
  zIndex: 999999,
14804
14828
  top: `${position.top}px`,
14805
- left: `${position.left}px`
14829
+ left: `${position.left}px`,
14806
14830
  }, children: visibleActions.map((action, idx) => {
14807
14831
  let iconElement = null;
14808
14832
  if (action.icon) {
@@ -14810,7 +14834,9 @@ function ActionMenu({ actions, item, }) {
14810
14834
  iconElement = action.icon;
14811
14835
  }
14812
14836
  else {
14813
- iconElement = React__default.createElement(action.icon, { className: 'h-4 w-4 flex-shrink-0' });
14837
+ iconElement = React__default.createElement(action.icon, {
14838
+ className: "h-4 w-4 flex-shrink-0",
14839
+ });
14814
14840
  }
14815
14841
  }
14816
14842
  return (jsxs("button", { type: "button", onClick: async (e) => {
@@ -14820,12 +14846,12 @@ function ActionMenu({ actions, item, }) {
14820
14846
  await action.onClick(item);
14821
14847
  }
14822
14848
  catch (error) {
14823
- console.error('DataTable action error:', error);
14849
+ console.error("DataTable action error:", error);
14824
14850
  }
14825
14851
  setIsOpen(false);
14826
- }, className: `w-full flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${action.variant === 'danger'
14827
- ? 'text-error-600 hover:bg-error-50 hover:text-error-700'
14828
- : 'text-ink-700 hover:bg-paper-50'}`, title: action.tooltip, children: [iconElement, jsx("span", { className: "flex-1 text-left", children: action.label })] }, idx));
14852
+ }, className: `w-full flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${action.variant === "danger"
14853
+ ? "text-error-600 hover:bg-error-50 hover:text-error-700"
14854
+ : "text-ink-700 hover:bg-paper-50"}`, title: action.tooltip, children: [iconElement, jsx("span", { className: "flex-1 text-left", children: action.label })] }, idx));
14829
14855
  }) }));
14830
14856
  return (jsxs(Fragment, { children: [jsx("button", { ref: buttonRef, onClick: (e) => {
14831
14857
  e.stopPropagation();
@@ -14842,13 +14868,20 @@ function getColumnStyle(column, dynamicWidth) {
14842
14868
  style.width = `${dynamicWidth}px`;
14843
14869
  }
14844
14870
  else if (column.width !== undefined) {
14845
- style.width = typeof column.width === 'number' ? `${column.width}px` : column.width;
14871
+ style.width =
14872
+ typeof column.width === "number" ? `${column.width}px` : column.width;
14846
14873
  }
14847
14874
  if (column.minWidth !== undefined) {
14848
- style.minWidth = typeof column.minWidth === 'number' ? `${column.minWidth}px` : column.minWidth;
14875
+ style.minWidth =
14876
+ typeof column.minWidth === "number"
14877
+ ? `${column.minWidth}px`
14878
+ : column.minWidth;
14849
14879
  }
14850
14880
  if (column.maxWidth !== undefined) {
14851
- style.maxWidth = typeof column.maxWidth === 'number' ? `${column.maxWidth}px` : column.maxWidth;
14881
+ style.maxWidth =
14882
+ typeof column.maxWidth === "number"
14883
+ ? `${column.maxWidth}px`
14884
+ : column.maxWidth;
14852
14885
  }
14853
14886
  if (column.flex !== undefined) {
14854
14887
  style.flexGrow = column.flex;
@@ -14907,13 +14940,13 @@ function getColumnStyle(column, dynamicWidth) {
14907
14940
  * />
14908
14941
  * ```
14909
14942
  */
14910
- function DataTable({ data, columns, loading = false, error = null, emptyMessage = 'No data available', loadingRows = 5, className = '', onSortChange, currentSort = null, onEdit, onDelete, actions = [], enableContextMenu = true, onRowClick, onRowDoubleClick, selectable = false, selectedRows: externalSelectedRows, onRowSelect, keyExtractor, expandable = false, expandedRows: externalExpandedRows, renderExpandedRow, expandedRowConfig, showExpandChevron = false,
14943
+ function DataTable({ data, columns, loading = false, error = null, emptyMessage = "No data available", loadingRows = 5, className = "", onSortChange, currentSort = null, onEdit, onDelete, actions = [], enableContextMenu = true, onRowClick, onRowDoubleClick, selectable = false, selectedRows: externalSelectedRows, onRowSelect, keyExtractor, expandable = false, expandedRows: externalExpandedRows, renderExpandedRow, expandedRowConfig, showExpandChevron = false,
14911
14944
  // Visual customization props
14912
- striped = false, stripedColor, density = 'normal', rowClassName, rowHighlight, highlightedRowId, highlightedRows = [], highlightDuration = 2000, bordered = false, borderColor = 'border-paper-200', disableHover = false, hiddenColumns = [], headerClassName = '', renderEmptyState: customRenderEmptyState, resizable = false, onColumnResize, reorderable = false, onColumnReorder, virtualized = false, virtualHeight = '600px', virtualRowHeight = 60,
14945
+ striped = false, stripedColor, density = "normal", rowClassName, rowHighlight, highlightedRowId, highlightedRows = [], highlightDuration = 2000, bordered = false, borderColor = "border-paper-200", disableHover = false, hiddenColumns = [], headerClassName = "", renderEmptyState: customRenderEmptyState, resizable = false, onColumnResize, reorderable = false, onColumnReorder, virtualized = false, virtualHeight = "600px", virtualRowHeight = 60,
14913
14946
  // Pagination props
14914
14947
  paginated = false, currentPage = 1, pageSize = 10, totalItems, onPageChange, pageSizeOptions = [10, 25, 50, 100], onPageSizeChange, showPageSizeSelector = true,
14915
14948
  // Mobile view props
14916
- mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
14949
+ mobileView = "auto", cardConfig, cardGap = "md", cardClassName, }) {
14917
14950
  // Mobile detection for auto mode
14918
14951
  const isMobileViewport = useIsMobile();
14919
14952
  // Column resizing state
@@ -14932,7 +14965,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
14932
14965
  const [hoveredRowKey, setHoveredRowKey] = useState(null);
14933
14966
  // Keyboard navigation state
14934
14967
  const [focusedCell, setFocusedCell] = useState(null);
14935
- const [announcement, setAnnouncement] = useState('');
14968
+ const [announcement, setAnnouncement] = useState("");
14936
14969
  const tableBodyRef = useRef(null);
14937
14970
  // Temporary row highlight state (for flash animation)
14938
14971
  const [flashingRows, setFlashingRows] = useState(new Set());
@@ -14944,18 +14977,18 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
14944
14977
  item: null,
14945
14978
  });
14946
14979
  // Filter columns based on hiddenColumns
14947
- const baseVisibleColumns = columns.filter(col => !hiddenColumns.includes(String(col.key)));
14980
+ const baseVisibleColumns = columns.filter((col) => !hiddenColumns.includes(String(col.key)));
14948
14981
  // Initialize column order on mount or when columns change
14949
14982
  useEffect(() => {
14950
14983
  if (columnOrder.length === 0) {
14951
- setColumnOrder(baseVisibleColumns.map(col => String(col.key)));
14984
+ setColumnOrder(baseVisibleColumns.map((col) => String(col.key)));
14952
14985
  }
14953
14986
  }, [baseVisibleColumns, columnOrder.length]);
14954
14987
  // Handle temporary row highlighting (flash animation)
14955
14988
  useEffect(() => {
14956
14989
  if (highlightedRows.length > 0) {
14957
14990
  // Add new highlighted rows to flashing set
14958
- const newFlashingRows = new Set(highlightedRows.map(id => String(id)));
14991
+ const newFlashingRows = new Set(highlightedRows.map((id) => String(id)));
14959
14992
  setFlashingRows(newFlashingRows);
14960
14993
  // Clear any existing timeout
14961
14994
  if (flashTimeoutRef.current) {
@@ -14975,25 +15008,25 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
14975
15008
  // Apply column order
14976
15009
  const visibleColumns = reorderable && columnOrder.length > 0
14977
15010
  ? columnOrder
14978
- .map(key => baseVisibleColumns.find(col => String(col.key) === key))
15011
+ .map((key) => baseVisibleColumns.find((col) => String(col.key) === key))
14979
15012
  .filter((col) => col !== undefined)
14980
15013
  : baseVisibleColumns;
14981
15014
  // Density classes
14982
15015
  const densityClasses = {
14983
15016
  compact: {
14984
- cell: 'px-3 py-1',
14985
- text: 'text-xs',
14986
- header: 'px-3 py-2',
15017
+ cell: "px-3 py-1",
15018
+ text: "text-xs",
15019
+ header: "px-3 py-2",
14987
15020
  },
14988
15021
  normal: {
14989
- cell: 'px-6 py-1.5',
14990
- text: 'text-sm',
14991
- header: 'px-6 py-3',
15022
+ cell: "px-6 py-1.5",
15023
+ text: "text-sm",
15024
+ header: "px-6 py-3",
14992
15025
  },
14993
15026
  comfortable: {
14994
- cell: 'px-6 py-3',
14995
- text: 'text-base',
14996
- header: 'px-6 py-4',
15027
+ cell: "px-6 py-3",
15028
+ text: "text-base",
15029
+ header: "px-6 py-4",
14997
15030
  },
14998
15031
  };
14999
15032
  const currentDensity = densityClasses[density];
@@ -15001,20 +15034,25 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15001
15034
  const getRowKey = keyExtractor || ((row) => String(row.id));
15002
15035
  // Calculate if there are any actions (for keyboard navigation column calculation)
15003
15036
  // This is computed early so it can be used in keyboard handlers
15004
- const hasAnyActions = !!(onEdit || onDelete || actions.length > 0 ||
15005
- expandedRowConfig?.edit || expandedRowConfig?.details ||
15006
- expandedRowConfig?.addRelated?.length || expandedRowConfig?.manageRelated?.length);
15037
+ const hasAnyActions = !!(onEdit ||
15038
+ onDelete ||
15039
+ actions.length > 0 ||
15040
+ expandedRowConfig?.edit ||
15041
+ expandedRowConfig?.details ||
15042
+ expandedRowConfig?.addRelated?.length ||
15043
+ expandedRowConfig?.manageRelated?.length);
15007
15044
  // Get row background class based on striping and highlighting
15008
15045
  const getRowBackgroundClass = (item, index) => {
15009
15046
  const classes = [];
15010
15047
  const rowKey = getRowKey(item);
15011
15048
  // Check for temporary flash highlight (takes priority)
15012
15049
  if (flashingRows.has(rowKey)) {
15013
- classes.push('animate-row-flash');
15050
+ classes.push("animate-row-flash");
15014
15051
  }
15015
15052
  // Check for highlighted row
15016
- else if (highlightedRowId !== undefined && rowKey === String(highlightedRowId)) {
15017
- classes.push('bg-accent-100');
15053
+ else if (highlightedRowId !== undefined &&
15054
+ rowKey === String(highlightedRowId)) {
15055
+ classes.push("bg-accent-100");
15018
15056
  }
15019
15057
  // Check for custom row highlight
15020
15058
  else if (rowHighlight) {
@@ -15026,24 +15064,27 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15026
15064
  // Check for striping
15027
15065
  else if (striped) {
15028
15066
  const isOdd = index % 2 === 0; // 0-indexed, so even index = odd row
15029
- const shouldStripe = striped === true ? isOdd :
15030
- striped === 'odd' ? isOdd :
15031
- striped === 'even' ? !isOdd :
15032
- false;
15067
+ const shouldStripe = striped === true
15068
+ ? isOdd
15069
+ : striped === "odd"
15070
+ ? isOdd
15071
+ : striped === "even"
15072
+ ? !isOdd
15073
+ : false;
15033
15074
  if (shouldStripe) {
15034
- classes.push(stripedColor || 'bg-paper-50');
15075
+ classes.push(stripedColor || "bg-paper-50");
15035
15076
  }
15036
15077
  }
15037
15078
  // Add custom row class
15038
15079
  if (rowClassName) {
15039
- if (typeof rowClassName === 'string') {
15080
+ if (typeof rowClassName === "string") {
15040
15081
  classes.push(rowClassName);
15041
15082
  }
15042
15083
  else {
15043
15084
  classes.push(rowClassName(item, index));
15044
15085
  }
15045
15086
  }
15046
- return classes.join(' ');
15087
+ return classes.join(" ");
15047
15088
  };
15048
15089
  // NEW: Expansion mode state management (for expandedRowConfig)
15049
15090
  const [expansionState, setExpansionState] = useState(null);
@@ -15058,12 +15099,12 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15058
15099
  // Column reorder handlers
15059
15100
  const handleDragStart = (e, columnKey) => {
15060
15101
  setDraggingColumn(columnKey);
15061
- e.dataTransfer.effectAllowed = 'move';
15062
- e.dataTransfer.setData('text/html', columnKey);
15102
+ e.dataTransfer.effectAllowed = "move";
15103
+ e.dataTransfer.setData("text/html", columnKey);
15063
15104
  };
15064
15105
  const handleDragOver = (e, columnKey) => {
15065
15106
  e.preventDefault();
15066
- e.dataTransfer.dropEffect = 'move';
15107
+ e.dataTransfer.dropEffect = "move";
15067
15108
  if (draggingColumn && draggingColumn !== columnKey) {
15068
15109
  setDragOverColumn(columnKey);
15069
15110
  }
@@ -15094,7 +15135,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15094
15135
  const handleMouseMove = (e) => {
15095
15136
  const delta = e.clientX - resizeStartX;
15096
15137
  const newWidth = Math.max(50, resizeStartWidth + delta); // Min width 50px
15097
- setColumnWidths(prev => ({
15138
+ setColumnWidths((prev) => ({
15098
15139
  ...prev,
15099
15140
  [resizingColumn]: newWidth,
15100
15141
  }));
@@ -15105,56 +15146,62 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15105
15146
  }
15106
15147
  setResizingColumn(null);
15107
15148
  };
15108
- document.addEventListener('mousemove', handleMouseMove);
15109
- document.addEventListener('mouseup', handleMouseUp);
15149
+ document.addEventListener("mousemove", handleMouseMove);
15150
+ document.addEventListener("mouseup", handleMouseUp);
15110
15151
  return () => {
15111
- document.removeEventListener('mousemove', handleMouseMove);
15112
- document.removeEventListener('mouseup', handleMouseUp);
15152
+ document.removeEventListener("mousemove", handleMouseMove);
15153
+ document.removeEventListener("mouseup", handleMouseUp);
15113
15154
  };
15114
- }, [resizingColumn, resizeStartX, resizeStartWidth, columnWidths, onColumnResize]);
15155
+ }, [
15156
+ resizingColumn,
15157
+ resizeStartX,
15158
+ resizeStartWidth,
15159
+ columnWidths,
15160
+ onColumnResize,
15161
+ ]);
15115
15162
  // Build combined actions: built-in edit/delete + custom actions + expansion mode actions
15116
15163
  const builtInActions = [];
15117
15164
  // Legacy onEdit (still supported)
15118
15165
  if (onEdit) {
15119
15166
  builtInActions.push({
15120
- label: 'Edit',
15167
+ label: "Edit",
15121
15168
  icon: Edit,
15122
15169
  onClick: onEdit,
15123
- variant: 'secondary',
15124
- tooltip: 'Edit item'
15170
+ variant: "secondary",
15171
+ tooltip: "Edit item",
15125
15172
  });
15126
15173
  }
15127
15174
  // NEW: Edit mode from expandedRowConfig
15128
15175
  if (expandedRowConfig?.edit && !onEdit) {
15129
15176
  const editConfig = expandedRowConfig.edit;
15130
15177
  builtInActions.push({
15131
- label: editConfig.menuLabel || 'Edit',
15178
+ label: editConfig.menuLabel || "Edit",
15132
15179
  icon: editConfig.menuIcon || Edit,
15133
15180
  onClick: (item) => {
15134
15181
  const rowKey = getRowKey(item);
15135
- handleExpansionWithMode(rowKey, 'edit');
15182
+ handleExpansionWithMode(rowKey, "edit");
15136
15183
  },
15137
- variant: 'secondary',
15138
- tooltip: 'Edit inline'
15184
+ variant: "secondary",
15185
+ tooltip: "Edit inline",
15139
15186
  });
15140
15187
  }
15141
15188
  // NEW: View details mode from expandedRowConfig
15142
15189
  if (expandedRowConfig?.details) {
15143
15190
  const detailsConfig = expandedRowConfig.details;
15144
15191
  builtInActions.push({
15145
- label: detailsConfig.menuLabel || 'View Details',
15192
+ label: detailsConfig.menuLabel || "View Details",
15146
15193
  icon: detailsConfig.menuIcon,
15147
15194
  onClick: (item) => {
15148
15195
  const rowKey = getRowKey(item);
15149
- handleExpansionWithMode(rowKey, 'details');
15196
+ handleExpansionWithMode(rowKey, "details");
15150
15197
  },
15151
- variant: 'ghost',
15152
- tooltip: 'View details'
15198
+ variant: "ghost",
15199
+ tooltip: "View details",
15153
15200
  });
15154
15201
  }
15155
15202
  // NEW: Add related modes from expandedRowConfig
15156
15203
  if (expandedRowConfig?.addRelated) {
15157
- expandedRowConfig.addRelated.forEach(config => {
15204
+ expandedRowConfig.addRelated.forEach((config) => {
15158
15205
  if (config.showInMenu !== false) {
15159
15206
  builtInActions.push({
15160
15207
  label: config.label,
@@ -15163,15 +15210,15 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15163
15210
  const rowKey = getRowKey(item);
15164
15211
  handleExpansionWithMode(rowKey, `addRelated-${config.key}`);
15165
15212
  },
15166
- variant: 'secondary',
15167
- tooltip: config.label
15213
+ variant: "secondary",
15214
+ tooltip: config.label,
15168
15215
  });
15169
15216
  }
15170
15217
  });
15171
15218
  }
15172
15219
  // NEW: Manage related modes from expandedRowConfig
15173
15220
  if (expandedRowConfig?.manageRelated) {
15174
- expandedRowConfig.manageRelated.forEach(config => {
15221
+ expandedRowConfig.manageRelated.forEach((config) => {
15175
15222
  if (config.showInMenu !== false) {
15176
15223
  builtInActions.push({
15177
15224
  label: config.label,
@@ -15180,8 +15227,8 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15180
15227
  const rowKey = getRowKey(item);
15181
15228
  handleExpansionWithMode(rowKey, `manageRelated-${config.key}`);
15182
15229
  },
15183
- variant: 'ghost',
15184
- tooltip: config.label
15230
+ variant: "ghost",
15231
+ tooltip: config.label,
15185
15232
  });
15186
15233
  }
15187
15234
  });
@@ -15191,11 +15238,11 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15191
15238
  let deleteAction = null;
15192
15239
  if (onDelete) {
15193
15240
  deleteAction = {
15194
- label: 'Delete',
15241
+ label: "Delete",
15195
15242
  icon: Trash,
15196
15243
  onClick: onDelete,
15197
- variant: 'danger',
15198
- tooltip: 'Delete item'
15244
+ variant: "danger",
15245
+ tooltip: "Delete item",
15199
15246
  };
15200
15247
  }
15201
15248
  // Build final actions array with consistent ordering:
@@ -15208,11 +15255,11 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15208
15255
  const allActions = [
15209
15256
  ...builtInActions,
15210
15257
  ...actions,
15211
- ...(deleteAction ? [deleteAction] : [])
15258
+ ...(deleteAction ? [deleteAction] : []),
15212
15259
  ];
15213
15260
  // Convert actions to menu items for context menu
15214
15261
  const convertActionsToMenuItems = (item) => {
15215
- const visibleActions = allActions.filter(action => !action.show || action.show(item));
15262
+ const visibleActions = allActions.filter((action) => !action.show || action.show(item));
15216
15263
  return visibleActions.map((action, idx) => {
15217
15264
  let iconElement = null;
15218
15265
  if (action.icon) {
@@ -15220,7 +15267,9 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15220
15267
  iconElement = action.icon;
15221
15268
  }
15222
15269
  else {
15223
- iconElement = React__default.createElement(action.icon, { className: 'h-4 w-4' });
15270
+ iconElement = React__default.createElement(action.icon, {
15271
+ className: "h-4 w-4",
15272
+ });
15224
15273
  }
15225
15274
  }
15226
15275
  return {
@@ -15228,7 +15277,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15228
15277
  label: action.label,
15229
15278
  icon: iconElement,
15230
15279
  onClick: () => action.onClick(item),
15231
- danger: action.variant === 'danger',
15280
+ danger: action.variant === "danger",
15232
15281
  };
15233
15282
  });
15234
15283
  };
@@ -15237,7 +15286,9 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15237
15286
  // Expansion state management
15238
15287
  const [internalExpandedRows, setInternalExpandedRows] = useState(new Set());
15239
15288
  // Use external selection if provided, otherwise internal
15240
- const selectedRowsSet = externalSelectedRows !== undefined ? externalSelectedRows : internalSelectedRows;
15289
+ const selectedRowsSet = externalSelectedRows !== undefined
15290
+ ? externalSelectedRows
15291
+ : internalSelectedRows;
15241
15292
  const setSelectedRows = (newSet) => {
15242
15293
  if (externalSelectedRows !== undefined) {
15243
15294
  // Controlled component - notify parent
@@ -15271,7 +15322,9 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15271
15322
  }
15272
15323
  };
15273
15324
  // Use external expansion if provided, otherwise internal
15274
- const expandedRowsSet = externalExpandedRows !== undefined ? externalExpandedRows : internalExpandedRows;
15325
+ const expandedRowsSet = externalExpandedRows !== undefined
15326
+ ? externalExpandedRows
15327
+ : internalExpandedRows;
15275
15328
  const setExpandedRows = (newSet) => {
15276
15329
  if (externalExpandedRows !== undefined) ;
15277
15330
  else {
@@ -15313,10 +15366,10 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15313
15366
  const totalCols = visibleColumns.length;
15314
15367
  // If no cell is focused, focus first data cell on first arrow key
15315
15368
  if (!focusedCell) {
15316
- if (['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
15369
+ if (["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight"].includes(e.key)) {
15317
15370
  e.preventDefault();
15318
15371
  setFocusedCell({ row: 0, col: 0 });
15319
- const colHeader = visibleColumns[0]?.header || 'first column';
15372
+ const colHeader = visibleColumns[0]?.header || "first column";
15320
15373
  setAnnouncement(`Row 1, ${colHeader}`);
15321
15374
  return;
15322
15375
  }
@@ -15324,73 +15377,73 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15324
15377
  }
15325
15378
  const { row, col } = focusedCell;
15326
15379
  switch (e.key) {
15327
- case 'ArrowDown':
15380
+ case "ArrowDown":
15328
15381
  e.preventDefault();
15329
15382
  if (row < totalRows - 1) {
15330
15383
  const newRow = row + 1;
15331
15384
  setFocusedCell({ row: newRow, col });
15332
15385
  const rowItem = data[newRow];
15333
- const colHeader = visibleColumns[col]?.header || '';
15386
+ const colHeader = visibleColumns[col]?.header || "";
15334
15387
  const cellValue = rowItem[visibleColumns[col]?.key];
15335
- setAnnouncement(`Row ${newRow + 1}, ${colHeader}: ${cellValue || 'empty'}`);
15388
+ setAnnouncement(`Row ${newRow + 1}, ${colHeader}: ${cellValue || "empty"}`);
15336
15389
  }
15337
15390
  break;
15338
- case 'ArrowUp':
15391
+ case "ArrowUp":
15339
15392
  e.preventDefault();
15340
15393
  if (row > 0) {
15341
15394
  const newRow = row - 1;
15342
15395
  setFocusedCell({ row: newRow, col });
15343
15396
  const rowItem = data[newRow];
15344
- const colHeader = visibleColumns[col]?.header || '';
15397
+ const colHeader = visibleColumns[col]?.header || "";
15345
15398
  const cellValue = rowItem[visibleColumns[col]?.key];
15346
- setAnnouncement(`Row ${newRow + 1}, ${colHeader}: ${cellValue || 'empty'}`);
15399
+ setAnnouncement(`Row ${newRow + 1}, ${colHeader}: ${cellValue || "empty"}`);
15347
15400
  }
15348
15401
  break;
15349
- case 'ArrowRight':
15402
+ case "ArrowRight":
15350
15403
  e.preventDefault();
15351
15404
  if (col < totalCols - 1) {
15352
15405
  const newCol = col + 1;
15353
15406
  setFocusedCell({ row, col: newCol });
15354
15407
  const rowItem = data[row];
15355
- const colHeader = visibleColumns[newCol]?.header || '';
15408
+ const colHeader = visibleColumns[newCol]?.header || "";
15356
15409
  const cellValue = rowItem[visibleColumns[newCol]?.key];
15357
- setAnnouncement(`${colHeader}: ${cellValue || 'empty'}`);
15410
+ setAnnouncement(`${colHeader}: ${cellValue || "empty"}`);
15358
15411
  }
15359
15412
  break;
15360
- case 'ArrowLeft':
15413
+ case "ArrowLeft":
15361
15414
  e.preventDefault();
15362
15415
  if (col > 0) {
15363
15416
  const newCol = col - 1;
15364
15417
  setFocusedCell({ row, col: newCol });
15365
15418
  const rowItem = data[row];
15366
- const colHeader = visibleColumns[newCol]?.header || '';
15419
+ const colHeader = visibleColumns[newCol]?.header || "";
15367
15420
  const cellValue = rowItem[visibleColumns[newCol]?.key];
15368
- setAnnouncement(`${colHeader}: ${cellValue || 'empty'}`);
15421
+ setAnnouncement(`${colHeader}: ${cellValue || "empty"}`);
15369
15422
  }
15370
15423
  break;
15371
- case 'Home':
15424
+ case "Home":
15372
15425
  e.preventDefault();
15373
15426
  if (e.ctrlKey) {
15374
15427
  // Ctrl+Home: Go to first cell
15375
15428
  setFocusedCell({ row: 0, col: 0 });
15376
- setAnnouncement(`First cell, Row 1, ${visibleColumns[0]?.header || ''}`);
15429
+ setAnnouncement(`First cell, Row 1, ${visibleColumns[0]?.header || ""}`);
15377
15430
  }
15378
15431
  else {
15379
15432
  // Home: Go to first cell in current row
15380
15433
  setFocusedCell({ row, col: 0 });
15381
15434
  const rowItem = data[row];
15382
15435
  const cellValue = rowItem[visibleColumns[0]?.key];
15383
- setAnnouncement(`${visibleColumns[0]?.header || ''}: ${cellValue || 'empty'}`);
15436
+ setAnnouncement(`${visibleColumns[0]?.header || ""}: ${cellValue || "empty"}`);
15384
15437
  }
15385
15438
  break;
15386
- case 'End':
15439
+ case "End":
15387
15440
  e.preventDefault();
15388
15441
  if (e.ctrlKey) {
15389
15442
  // Ctrl+End: Go to last cell
15390
15443
  const lastRow = totalRows - 1;
15391
15444
  const lastCol = totalCols - 1;
15392
15445
  setFocusedCell({ row: lastRow, col: lastCol });
15393
- setAnnouncement(`Last cell, Row ${lastRow + 1}, ${visibleColumns[lastCol]?.header || ''}`);
15446
+ setAnnouncement(`Last cell, Row ${lastRow + 1}, ${visibleColumns[lastCol]?.header || ""}`);
15394
15447
  }
15395
15448
  else {
15396
15449
  // End: Go to last cell in current row
@@ -15398,10 +15451,10 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15398
15451
  setFocusedCell({ row, col: lastCol });
15399
15452
  const rowItem = data[row];
15400
15453
  const cellValue = rowItem[visibleColumns[lastCol]?.key];
15401
- setAnnouncement(`${visibleColumns[lastCol]?.header || ''}: ${cellValue || 'empty'}`);
15454
+ setAnnouncement(`${visibleColumns[lastCol]?.header || ""}: ${cellValue || "empty"}`);
15402
15455
  }
15403
15456
  break;
15404
- case 'Enter':
15457
+ case "Enter":
15405
15458
  e.preventDefault();
15406
15459
  {
15407
15460
  const rowItem = data[row];
@@ -15409,27 +15462,27 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15409
15462
  // Priority: Edit mode > Details mode > Row double-click handler
15410
15463
  if (onEdit) {
15411
15464
  onEdit(rowItem);
15412
- setAnnouncement('Opening edit mode');
15465
+ setAnnouncement("Opening edit mode");
15413
15466
  }
15414
15467
  else if (expandedRowConfig?.edit) {
15415
- handleExpansionWithMode(rowKey, 'edit');
15416
- setAnnouncement('Opening inline edit');
15468
+ handleExpansionWithMode(rowKey, "edit");
15469
+ setAnnouncement("Opening inline edit");
15417
15470
  }
15418
15471
  else if (expandedRowConfig?.details) {
15419
- handleExpansionWithMode(rowKey, 'details');
15420
- setAnnouncement('Opening details view');
15472
+ handleExpansionWithMode(rowKey, "details");
15473
+ setAnnouncement("Opening details view");
15421
15474
  }
15422
15475
  else if (onRowDoubleClick) {
15423
15476
  onRowDoubleClick(rowItem);
15424
- setAnnouncement('Activating row');
15477
+ setAnnouncement("Activating row");
15425
15478
  }
15426
15479
  else if (onRowClick) {
15427
15480
  onRowClick(rowItem);
15428
- setAnnouncement('Row selected');
15481
+ setAnnouncement("Row selected");
15429
15482
  }
15430
15483
  }
15431
15484
  break;
15432
- case ' ':
15485
+ case " ":
15433
15486
  // Space: Toggle selection if selectable
15434
15487
  if (selectable) {
15435
15488
  e.preventDefault();
@@ -15437,60 +15490,82 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15437
15490
  const rowKey = getRowKey(rowItem);
15438
15491
  handleRowSelect(rowKey);
15439
15492
  const isNowSelected = !selectedRowsSet.has(rowKey);
15440
- setAnnouncement(isNowSelected ? 'Row selected' : 'Row deselected');
15493
+ setAnnouncement(isNowSelected ? "Row selected" : "Row deselected");
15441
15494
  }
15442
15495
  break;
15443
- case 'Escape':
15496
+ case "Escape":
15444
15497
  e.preventDefault();
15445
15498
  setFocusedCell(null);
15446
- setAnnouncement('Table navigation exited');
15499
+ setAnnouncement("Table navigation exited");
15447
15500
  // Return focus to table container
15448
- tableBodyRef.current?.closest('table')?.focus();
15501
+ tableBodyRef.current?.closest("table")?.focus();
15449
15502
  break;
15450
- case 'PageDown':
15503
+ case "PageDown":
15451
15504
  e.preventDefault();
15452
15505
  {
15453
15506
  const jumpSize = 10;
15454
15507
  const newRow = Math.min(row + jumpSize, totalRows - 1);
15455
15508
  setFocusedCell({ row: newRow, col });
15456
- const colHeader = visibleColumns[col]?.header || '';
15509
+ const colHeader = visibleColumns[col]?.header || "";
15457
15510
  setAnnouncement(`Row ${newRow + 1} of ${totalRows}, ${colHeader}`);
15458
15511
  }
15459
15512
  break;
15460
- case 'PageUp':
15513
+ case "PageUp":
15461
15514
  e.preventDefault();
15462
15515
  {
15463
15516
  const jumpSize = 10;
15464
15517
  const newRow = Math.max(row - jumpSize, 0);
15465
15518
  setFocusedCell({ row: newRow, col });
15466
- const colHeader = visibleColumns[col]?.header || '';
15519
+ const colHeader = visibleColumns[col]?.header || "";
15467
15520
  setAnnouncement(`Row ${newRow + 1} of ${totalRows}, ${colHeader}`);
15468
15521
  }
15469
15522
  break;
15470
15523
  }
15471
- }, [data, visibleColumns, focusedCell, selectable, expandedRowConfig, onEdit, onRowDoubleClick, onRowClick, getRowKey, handleExpansionWithMode, handleRowSelect, selectedRowsSet]);
15524
+ }, [
15525
+ data,
15526
+ visibleColumns,
15527
+ focusedCell,
15528
+ selectable,
15529
+ expandedRowConfig,
15530
+ onEdit,
15531
+ onRowDoubleClick,
15532
+ onRowClick,
15533
+ getRowKey,
15534
+ handleExpansionWithMode,
15535
+ handleRowSelect,
15536
+ selectedRowsSet,
15537
+ ]);
15472
15538
  // Focus the appropriate cell when focusedCell changes
15473
15539
  useEffect(() => {
15474
15540
  if (focusedCell && tableBodyRef.current) {
15475
15541
  const { row, col } = focusedCell;
15476
- const rows = tableBodyRef.current.querySelectorAll('tr[data-row-index]');
15542
+ const rows = tableBodyRef.current.querySelectorAll("tr[data-row-index]");
15477
15543
  const targetRow = rows[row];
15478
15544
  if (targetRow) {
15479
15545
  // Calculate actual column index including extra columns
15480
15546
  const hasSelectionCol = selectable;
15481
15547
  const hasExpandCol = (expandable || expandedRowConfig) && showExpandChevron;
15482
15548
  const hasActionsCol = hasAnyActions;
15483
- const extraColsBefore = (hasSelectionCol ? 1 : 0) + (hasExpandCol ? 1 : 0) + (hasActionsCol ? 1 : 0);
15484
- const cells = targetRow.querySelectorAll('td');
15549
+ const extraColsBefore = (hasSelectionCol ? 1 : 0) +
15550
+ (hasExpandCol ? 1 : 0) +
15551
+ (hasActionsCol ? 1 : 0);
15552
+ const cells = targetRow.querySelectorAll("td");
15485
15553
  const targetCell = cells[col + extraColsBefore];
15486
15554
  if (targetCell) {
15487
15555
  targetCell.focus();
15488
15556
  // Scroll into view if needed
15489
- targetCell.scrollIntoView({ block: 'nearest', inline: 'nearest' });
15557
+ targetCell.scrollIntoView({ block: "nearest", inline: "nearest" });
15490
15558
  }
15491
15559
  }
15492
15560
  }
15493
- }, [focusedCell, selectable, expandable, expandedRowConfig, showExpandChevron, hasAnyActions]);
15561
+ }, [
15562
+ focusedCell,
15563
+ selectable,
15564
+ expandable,
15565
+ expandedRowConfig,
15566
+ showExpandChevron,
15567
+ hasAnyActions,
15568
+ ]);
15494
15569
  // Handle column header click for sorting
15495
15570
  const handleSort = (column) => {
15496
15571
  if (!column.sortable || !onSortChange)
@@ -15498,8 +15573,12 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15498
15573
  const columnKey = String(column.key);
15499
15574
  // If clicking the same column, toggle direction
15500
15575
  if (currentSort?.key === columnKey) {
15501
- if (currentSort.direction === 'asc') {
15502
- onSortChange({ key: columnKey, direction: 'desc', label: column.header });
15576
+ if (currentSort.direction === "asc") {
15577
+ onSortChange({
15578
+ key: columnKey,
15579
+ direction: "desc",
15580
+ label: column.header,
15581
+ });
15503
15582
  }
15504
15583
  else {
15505
15584
  // Remove sort on third click
@@ -15508,7 +15587,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15508
15587
  }
15509
15588
  else {
15510
15589
  // New column - start with ascending
15511
- onSortChange({ key: columnKey, direction: 'asc', label: column.header });
15590
+ onSortChange({ key: columnKey, direction: "asc", label: column.header });
15512
15591
  }
15513
15592
  };
15514
15593
  // Get sort icon SVG for column (matches reference ui-preview.html)
@@ -15517,7 +15596,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15517
15596
  return null;
15518
15597
  const columnKey = String(column.key);
15519
15598
  const isActive = currentSort?.key === columnKey;
15520
- const isAscending = currentSort?.direction === 'asc';
15599
+ const isAscending = currentSort?.direction === "asc";
15521
15600
  // Inactive state - show neutral up/down arrows
15522
15601
  if (!isActive) {
15523
15602
  return (jsx("svg", { className: "ml-2 w-4 h-4 text-ink-400 group-hover:text-ink-700", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" }) }));
@@ -15530,17 +15609,23 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15530
15609
  return (jsx("svg", { className: "ml-2 w-4 h-4 text-accent-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M16 17l-4 4m0 0l-4-4m4 4V3" }) }));
15531
15610
  };
15532
15611
  // Render loading skeleton
15533
- const renderLoadingSkeleton = () => (jsx(Fragment, { children: Array.from({ length: loadingRows }, (_, i) => (jsxs("tr", { className: `animate-pulse table-row-stable ${bordered ? `border-b ${borderColor}` : ''}`, children: [selectable && (jsx("td", { className: `sticky left-0 bg-white ${currentDensity.cell} border-b ${borderColor} z-10 align-middle`, children: jsx("div", { className: "h-4 w-4 bg-paper-200 rounded" }) })), expandable && (jsx("td", { className: `sticky left-0 bg-white px-2 ${currentDensity.cell} border-b ${borderColor} z-10`, children: jsx("div", { className: "h-4 w-4 bg-paper-200 rounded" }) })), allActions.length > 0 && (jsx("td", { className: `sticky left-0 bg-white px-2 ${currentDensity.cell} border-b ${borderColor} z-10`, children: jsx("div", { className: "h-8 w-8 bg-paper-200 rounded" }) })), visibleColumns.map((column, colIndex) => {
15612
+ const renderLoadingSkeleton = () => (jsx(Fragment, { children: Array.from({ length: loadingRows }, (_, i) => (jsxs("tr", { className: `animate-pulse table-row-stable ${bordered ? `border-b ${borderColor}` : ""}`, children: [selectable && (jsx("td", { className: `sticky left-0 bg-white ${currentDensity.cell} border-b ${borderColor} z-10 align-middle`, children: jsx("div", { className: "h-4 w-4 bg-paper-200 rounded" }) })), expandable && (jsx("td", { className: `sticky left-0 bg-white px-2 ${currentDensity.cell} border-b ${borderColor} z-10`, children: jsx("div", { className: "h-4 w-4 bg-paper-200 rounded" }) })), allActions.length > 0 && (jsx("td", { className: `sticky left-0 bg-white px-2 ${currentDensity.cell} border-b ${borderColor} z-10`, children: jsx("div", { className: "h-8 w-8 bg-paper-200 rounded" }) })), visibleColumns.map((column, colIndex) => {
15534
15613
  const columnKey = String(column.key);
15535
15614
  const dynamicWidth = columnWidths[columnKey];
15536
- return (jsxs("td", { className: `${currentDensity.cell} whitespace-nowrap table-row-stable ${bordered ? `border ${borderColor}` : ''}`, style: getColumnStyle(column, dynamicWidth), children: [jsx("div", { className: "h-4 bg-paper-200 rounded mb-1" }), jsx("div", { className: "h-3 bg-paper-200 rounded w-3/4" })] }, `loading-${i}-${colIndex}`));
15615
+ return (jsxs("td", { className: `${currentDensity.cell} whitespace-nowrap table-row-stable ${bordered ? `border ${borderColor}` : ""}`, style: getColumnStyle(column, dynamicWidth), children: [jsx("div", { className: "h-4 bg-paper-200 rounded mb-1" }), jsx("div", { className: "h-3 bg-paper-200 rounded w-3/4" })] }, `loading-${i}-${colIndex}`));
15537
15616
  })] }, `loading-${i}`))) }));
15538
15617
  // Render empty state
15539
15618
  const renderEmptyStateContent = () => {
15540
15619
  if (customRenderEmptyState) {
15541
- return (jsx("tr", { children: jsx("td", { colSpan: visibleColumns.length + (allActions.length > 0 ? 1 : 0) + (selectable ? 1 : 0) + (expandable ? 1 : 0), children: customRenderEmptyState() }) }));
15620
+ return (jsx("tr", { children: jsx("td", { colSpan: visibleColumns.length +
15621
+ (allActions.length > 0 ? 1 : 0) +
15622
+ (selectable ? 1 : 0) +
15623
+ (expandable ? 1 : 0), children: customRenderEmptyState() }) }));
15542
15624
  }
15543
- return (jsx("tr", { children: jsx("td", { colSpan: visibleColumns.length + (allActions.length > 0 ? 1 : 0) + (selectable ? 1 : 0) + (expandable ? 1 : 0), className: `${currentDensity.cell} py-8 text-center text-ink-500`, children: error || emptyMessage }) }));
15625
+ return (jsx("tr", { children: jsx("td", { colSpan: visibleColumns.length +
15626
+ (allActions.length > 0 ? 1 : 0) +
15627
+ (selectable ? 1 : 0) +
15628
+ (expandable ? 1 : 0), className: `${currentDensity.cell} py-8 text-center text-ink-500`, children: error || emptyMessage }) }));
15544
15629
  };
15545
15630
  // Virtual scrolling calculations
15546
15631
  const getVisibleRange = () => {
@@ -15570,14 +15655,18 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15570
15655
  const isSelected = selectedRowsSet.has(rowKey);
15571
15656
  const isExpanded = expandedRowsSet.has(rowKey);
15572
15657
  const rowBgClass = getRowBackgroundClass(item, index);
15573
- const borderClass = bordered ? `border-b ${borderColor}` : (!visibleColumns.some(col => !!col.renderSecondary) ? `border-b ${borderColor}` : '');
15574
- const hasSecondaryRow = visibleColumns.some(col => !!col.renderSecondary);
15658
+ const borderClass = bordered
15659
+ ? `border-b ${borderColor}`
15660
+ : !visibleColumns.some((col) => !!col.renderSecondary)
15661
+ ? `border-b ${borderColor}`
15662
+ : "";
15663
+ const hasSecondaryRow = visibleColumns.some((col) => !!col.renderSecondary);
15575
15664
  // Hover state for row pair (primary + secondary)
15576
15665
  const isHovered = hoveredRowKey === rowKey;
15577
- const hoverClass = disableHover ? '' : (isHovered ? 'bg-paper-100' : '');
15666
+ const hoverClass = disableHover ? "" : isHovered ? "bg-paper-100" : "";
15578
15667
  // Check if this row is keyboard-focused
15579
15668
  const isKeyboardFocused = focusedCell?.row === index;
15580
- return (jsxs(React__default.Fragment, { children: [jsxs("tr", { "data-row-index": index, className: `table-row-stable ${onRowDoubleClick || onRowClick || onEdit || expandedRowConfig?.edit || expandedRowConfig?.details || expandedRowConfig?.addRelated?.length || expandedRowConfig?.manageRelated?.length ? 'cursor-pointer' : ''} ${isSelected ? 'bg-accent-50 border-l-2 border-accent-500' : hoverClass || rowBgClass} ${borderClass} ${isKeyboardFocused ? 'ring-2 ring-inset ring-accent-400' : ''}`, onMouseEnter: () => !disableHover && setHoveredRowKey(rowKey), onMouseLeave: () => !disableHover && setHoveredRowKey(null), onClick: () => onRowClick?.(item), onContextMenu: (e) => {
15669
+ return (jsxs(React__default.Fragment, { children: [jsxs("tr", { "data-row-index": index, className: `table-row-stable ${onRowDoubleClick || onRowClick || onEdit || expandedRowConfig?.edit || expandedRowConfig?.details || expandedRowConfig?.addRelated?.length || expandedRowConfig?.manageRelated?.length ? "cursor-pointer" : ""} ${isSelected ? "bg-accent-50 border-l-2 border-accent-500" : hoverClass || rowBgClass} ${borderClass} ${isKeyboardFocused ? "ring-2 ring-inset ring-accent-400" : ""}`, onMouseEnter: () => !disableHover && setHoveredRowKey(rowKey), onMouseLeave: () => !disableHover && setHoveredRowKey(null), onClick: () => onRowClick?.(item), onContextMenu: (e) => {
15581
15670
  if (enableContextMenu && allActions.length > 0) {
15582
15671
  e.preventDefault();
15583
15672
  e.stopPropagation();
@@ -15596,109 +15685,146 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15596
15685
  }
15597
15686
  // Priority 2: If there's an expandable edit mode, trigger it
15598
15687
  else if (expandedRowConfig?.edit) {
15599
- handleExpansionWithMode(rowKey, 'edit');
15688
+ handleExpansionWithMode(rowKey, "edit");
15600
15689
  }
15601
15690
  // Priority 3: If there's an expandable details mode, trigger it
15602
15691
  else if (expandedRowConfig?.details) {
15603
- handleExpansionWithMode(rowKey, 'details');
15692
+ handleExpansionWithMode(rowKey, "details");
15604
15693
  }
15605
15694
  // Priority 4: If there's any addRelated mode, trigger the first one
15606
- else if (expandedRowConfig?.addRelated && expandedRowConfig.addRelated.length > 0) {
15695
+ else if (expandedRowConfig?.addRelated &&
15696
+ expandedRowConfig.addRelated.length > 0) {
15607
15697
  handleExpansionWithMode(rowKey, `addRelated-${expandedRowConfig.addRelated[0].key}`);
15608
15698
  }
15609
15699
  // Priority 5: If there's any manageRelated mode, trigger the first one
15610
- else if (expandedRowConfig?.manageRelated && expandedRowConfig.manageRelated.length > 0) {
15700
+ else if (expandedRowConfig?.manageRelated &&
15701
+ expandedRowConfig.manageRelated.length > 0) {
15611
15702
  handleExpansionWithMode(rowKey, `manageRelated-${expandedRowConfig.manageRelated[0].key}`);
15612
15703
  }
15613
15704
  // Priority 6: Legacy onRowDoubleClick handler
15614
15705
  else {
15615
15706
  onRowDoubleClick?.(item);
15616
15707
  }
15617
- }, title: onEdit ? 'Double-click to edit' :
15618
- expandedRowConfig?.edit ? 'Double-click to edit inline' :
15619
- expandedRowConfig?.details ? 'Double-click to view details' :
15620
- expandedRowConfig?.addRelated && expandedRowConfig.addRelated.length > 0 ? `Double-click to ${expandedRowConfig.addRelated[0].label}` :
15621
- expandedRowConfig?.manageRelated && expandedRowConfig.manageRelated.length > 0 ? `Double-click to ${expandedRowConfig.manageRelated[0].label}` :
15622
- onRowDoubleClick ? 'Double-click for details' :
15623
- onRowClick ? 'Click to select' :
15624
- undefined, children: [selectable && (jsx("td", { className: `sticky left-0 z-10 ${bordered ? `border ${borderColor}` : ''}`, style: {
15625
- backgroundColor: 'inherit',
15626
- verticalAlign: 'middle',
15627
- padding: '0.375rem 0.75rem',
15628
- textAlign: 'center'
15629
- }, rowSpan: hasSecondaryRow ? 2 : 1, children: jsx("input", { type: "checkbox", checked: isSelected, onChange: () => handleRowSelect(rowKey), className: "w-4 h-4 text-accent-600 border-paper-300 rounded focus:ring-accent-400", "aria-label": `Select row ${rowKey}` }) })), ((expandable || expandedRowConfig) && showExpandChevron) && (jsx("td", { className: `sticky left-0 px-2 ${currentDensity.cell} z-10 ${bordered ? `border ${borderColor}` : ''}`, style: { backgroundColor: 'inherit', verticalAlign: 'middle' }, rowSpan: hasSecondaryRow ? 2 : 1, children: jsx("button", { onClick: () => {
15708
+ }, title: onEdit
15709
+ ? "Double-click to edit"
15710
+ : expandedRowConfig?.edit
15711
+ ? "Double-click to edit inline"
15712
+ : expandedRowConfig?.details
15713
+ ? "Double-click to view details"
15714
+ : expandedRowConfig?.addRelated &&
15715
+ expandedRowConfig.addRelated.length > 0
15716
+ ? `Double-click to ${expandedRowConfig.addRelated[0].label}`
15717
+ : expandedRowConfig?.manageRelated &&
15718
+ expandedRowConfig.manageRelated.length > 0
15719
+ ? `Double-click to ${expandedRowConfig.manageRelated[0].label}`
15720
+ : onRowDoubleClick
15721
+ ? "Double-click for details"
15722
+ : onRowClick
15723
+ ? "Click to select"
15724
+ : undefined, children: [selectable && (jsx("td", { className: `sticky left-0 z-10 ${bordered ? `border ${borderColor}` : ""}`, style: {
15725
+ backgroundColor: "inherit",
15726
+ verticalAlign: "middle",
15727
+ padding: "0.375rem 0.75rem",
15728
+ textAlign: "center",
15729
+ }, rowSpan: hasSecondaryRow ? 2 : 1, children: jsx("input", { type: "checkbox", checked: isSelected, onChange: () => handleRowSelect(rowKey), className: "w-4 h-4 text-accent-600 border-paper-300 rounded focus:ring-accent-400", "aria-label": `Select row ${rowKey}` }) })), (expandable || expandedRowConfig) && showExpandChevron && (jsx("td", { className: `sticky left-0 px-2 ${currentDensity.cell} z-10 ${bordered ? `border ${borderColor}` : ""}`, style: { backgroundColor: "inherit", verticalAlign: "middle" }, rowSpan: hasSecondaryRow ? 2 : 1, children: jsx("button", { onClick: () => {
15630
15730
  // NEW: Enhanced logic for expandedRowConfig
15631
- if (expandedRowConfig?.details && expandedRowConfig.details.triggerOnExpand !== false) {
15731
+ if (expandedRowConfig?.details &&
15732
+ expandedRowConfig.details.triggerOnExpand !== false) {
15632
15733
  // Trigger details mode if configured
15633
- handleExpansionWithMode(rowKey, 'details');
15734
+ handleExpansionWithMode(rowKey, "details");
15634
15735
  }
15635
- else if (expandedRowConfig?.edit && expandedRowConfig.edit.triggerOnDoubleClick !== false) {
15736
+ else if (expandedRowConfig?.edit &&
15737
+ expandedRowConfig.edit.triggerOnDoubleClick !== false) {
15636
15738
  // Fallback to edit mode if no details but edit is available
15637
- handleExpansionWithMode(rowKey, 'edit');
15739
+ handleExpansionWithMode(rowKey, "edit");
15638
15740
  }
15639
15741
  else {
15640
15742
  // Legacy: use handleRowExpand
15641
15743
  handleRowExpand(rowKey);
15642
15744
  }
15643
- }, className: "text-ink-500 hover:text-ink-900 transition-colors", "aria-label": isExpanded || (expansionState?.rowKey === rowKey) ? 'Collapse row' : 'Expand row', children: isExpanded || (expansionState?.rowKey === rowKey) ? (jsx(ChevronDown, { className: "h-4 w-4" })) : (jsx(ChevronRight, { className: "h-4 w-4" })) }) })), allActions.length > 0 && (jsx("td", { className: "sticky left-0 whitespace-nowrap shadow-[4px_0_6px_-2px_rgba(0,0,0,0.1)] z-10", style: {
15644
- width: '28px',
15645
- padding: '0',
15646
- backgroundColor: 'inherit',
15647
- verticalAlign: 'middle'
15648
- }, onClick: (e) => e.stopPropagation(), rowSpan: hasSecondaryRow ? 2 : 1, children: jsx("div", { style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '28px' }, children: jsx(ActionMenu, { actions: allActions, item: item }) }) })), visibleColumns.map((column, colIdx) => {
15745
+ }, className: "text-ink-500 hover:text-ink-900 transition-colors", "aria-label": isExpanded || expansionState?.rowKey === rowKey
15746
+ ? "Collapse row"
15747
+ : "Expand row", children: isExpanded || expansionState?.rowKey === rowKey ? (jsx(ChevronDown, { className: "h-4 w-4" })) : (jsx(ChevronRight, { className: "h-4 w-4" })) }) })), allActions.length > 0 && (jsx("td", { className: "sticky left-0 whitespace-nowrap shadow-[4px_0_6px_-2px_rgba(0,0,0,0.1)] z-10", style: {
15748
+ width: "28px",
15749
+ padding: "0",
15750
+ backgroundColor: "inherit",
15751
+ verticalAlign: "middle",
15752
+ }, onClick: (e) => e.stopPropagation(), rowSpan: hasSecondaryRow ? 2 : 1, children: jsx("div", { style: {
15753
+ display: "inline-flex",
15754
+ alignItems: "center",
15755
+ justifyContent: "center",
15756
+ width: "28px",
15757
+ }, children: jsx(ActionMenu, { actions: allActions, item: item }) }) })), visibleColumns.map((column, colIdx) => {
15649
15758
  const columnKey = String(column.key);
15650
15759
  const dynamicWidth = columnWidths[columnKey];
15651
- const value = typeof column.key === 'string'
15760
+ const value = typeof column.key === "string"
15652
15761
  ? item[column.key]
15653
15762
  : item[column.key];
15654
- const primaryContent = column.render ? column.render(item, value) : String(value || '');
15763
+ const primaryContent = column.render
15764
+ ? column.render(item, value)
15765
+ : String(value || "");
15766
+ // Tooltip: caller-provided > raw value stringified. Empty
15767
+ // strings get dropped so we don't render an empty title
15768
+ // attribute (which the browser would still show as a
15769
+ // 0-width tooltip box on hover).
15770
+ const primaryTooltipText = column.tooltip
15771
+ ? column.tooltip(item, value)
15772
+ : value !== null && value !== undefined && value !== ""
15773
+ ? String(value)
15774
+ : undefined;
15655
15775
  // Reduce left padding on first column when there are action buttons
15656
15776
  const isFirstColumn = colIdx === 0;
15657
- const paddingClass = isFirstColumn && allActions.length > 0 ? 'pl-3' : '';
15777
+ const paddingClass = isFirstColumn && allActions.length > 0 ? "pl-3" : "";
15658
15778
  // Check if this cell is keyboard-focused
15659
15779
  const isCellFocused = focusedCell?.row === index && focusedCell?.col === colIdx;
15660
- return (jsx("td", { className: `${currentDensity.cell} ${paddingClass} ${column.className || ''} ${bordered ? `border ${borderColor}` : ''} ${isCellFocused ? 'outline outline-2 outline-accent-500 outline-offset-[-2px]' : ''}`, style: getColumnStyle(column, dynamicWidth), tabIndex: isCellFocused ? 0 : -1, role: "gridcell", "aria-colindex": colIdx + 1, children: jsx("div", { className: `${currentDensity.text} leading-tight`, children: primaryContent }) }, `${item.id}-${columnKey}`));
15661
- })] }), hasSecondaryRow && (jsx("tr", { className: `secondary-row ${isSelected ? 'bg-accent-50 border-l-2 border-accent-500' : hoverClass || rowBgClass} border-b ${borderColor}`, onMouseEnter: () => !disableHover && setHoveredRowKey(rowKey), onMouseLeave: () => !disableHover && setHoveredRowKey(null), children: visibleColumns.map((column, colIdx) => {
15780
+ return (jsx("td", { className: `${currentDensity.cell} ${paddingClass} ${column.className || ""} ${bordered ? `border ${borderColor}` : ""} ${isCellFocused ? "outline outline-2 outline-accent-500 outline-offset-[-2px]" : ""}`, style: getColumnStyle(column, dynamicWidth), tabIndex: isCellFocused ? 0 : -1, role: "gridcell", "aria-colindex": colIdx + 1, children: jsx("div", { className: `${currentDensity.text} leading-tight`, title: primaryTooltipText, children: primaryContent }) }, `${item.id}-${columnKey}`));
15781
+ })] }), hasSecondaryRow && (jsx("tr", { className: `secondary-row ${isSelected ? "bg-accent-50 border-l-2 border-accent-500" : hoverClass || rowBgClass} border-b ${borderColor}`, onMouseEnter: () => !disableHover && setHoveredRowKey(rowKey), onMouseLeave: () => !disableHover && setHoveredRowKey(null), children: visibleColumns.map((column, colIdx) => {
15662
15782
  const columnKey = String(column.key);
15663
15783
  const dynamicWidth = columnWidths[columnKey];
15664
- const value = typeof column.key === 'string'
15784
+ const value = typeof column.key === "string"
15665
15785
  ? item[column.key]
15666
15786
  : item[column.key];
15667
- const secondaryContent = column.renderSecondary ? column.renderSecondary(item, value) : null;
15787
+ const secondaryContent = column.renderSecondary
15788
+ ? column.renderSecondary(item, value)
15789
+ : null;
15790
+ // Tooltip on the secondary row prefixes the field label when
15791
+ // available so the otherwise-unlabeled second row stays
15792
+ // self-describing on hover. Caller can override entirely
15793
+ // via `secondaryTooltip`.
15794
+ const hasSecondaryValue = value !== null && value !== undefined && value !== "";
15795
+ let secondaryTooltipText;
15796
+ if (column.secondaryTooltip) {
15797
+ secondaryTooltipText = column.secondaryTooltip(item, value);
15798
+ }
15799
+ else if (hasSecondaryValue) {
15800
+ secondaryTooltipText = column.secondaryHeader
15801
+ ? `${column.secondaryHeader}: ${value}`
15802
+ : String(value);
15803
+ }
15804
+ else if (column.secondaryHeader) {
15805
+ secondaryTooltipText = column.secondaryHeader;
15806
+ }
15668
15807
  // Reduce left padding on first column when there are action buttons
15669
15808
  const isFirstColumn = colIdx === 0;
15670
- const paddingClass = isFirstColumn && allActions.length > 0 ? 'pl-3' : '';
15671
- return (jsx("td", { className: `${currentDensity.cell} py-0.5 ${paddingClass} ${column.className || ''} ${bordered ? `border ${borderColor}` : ''}`, style: getColumnStyle(column, dynamicWidth), children: jsx("div", { className: "text-xs text-ink-500 leading-tight", children: secondaryContent || jsx("span", { className: "invisible", children: "\u2014" }) }) }, `${item.id}-${columnKey}-secondary`));
15809
+ const paddingClass = isFirstColumn && allActions.length > 0 ? "pl-3" : "";
15810
+ return (jsx("td", { className: `${currentDensity.cell} py-0.5 ${paddingClass} ${column.className || ""} ${bordered ? `border ${borderColor}` : ""}`, style: getColumnStyle(column, dynamicWidth), children: jsx("div", { className: "text-xs text-ink-500 leading-tight", title: secondaryTooltipText, children: secondaryContent || jsx("span", { className: "invisible", children: "\u2014" }) }) }, `${item.id}-${columnKey}-secondary`));
15672
15811
  }) })), expandable && isExpanded && renderExpandedRow && (jsx("tr", { children: jsx("td", { colSpan: visibleColumns.length +
15673
15812
  (selectable ? 1 : 0) +
15674
- (((expandable || expandedRowConfig) && showExpandChevron) ? 1 : 0) +
15675
- (allActions.length > 0 ? 1 : 0), className: `${currentDensity.cell} py-4 bg-paper-50`, children: renderExpandedRow(item) }) })), expansionState && expansionState.rowKey === rowKey && expandedRowConfig && (() => {
15676
- const mode = expansionState.mode;
15677
- let content = null;
15678
- let bgColorClass = 'bg-paper-50'; // Default
15679
- // Edit mode
15680
- if (mode === 'edit' && expandedRowConfig.edit) {
15681
- bgColorClass = 'bg-paper-100/80 border-t border-b border-paper-300/80';
15682
- content = expandedRowConfig.edit.render(item, async (_updated) => {
15683
- // Handle save
15684
- handleCollapseExpansion();
15685
- }, () => {
15686
- // Handle cancel
15687
- handleCollapseExpansion();
15688
- });
15689
- }
15690
- // Details mode
15691
- else if (mode === 'details' && expandedRowConfig.details) {
15692
- bgColorClass = 'bg-primary-50/80 border-t border-b border-primary-200/80';
15693
- content = expandedRowConfig.details.render(item);
15694
- }
15695
- // Add related modes
15696
- else if (mode.startsWith('addRelated-') && expandedRowConfig.addRelated) {
15697
- const key = mode.replace('addRelated-', '');
15698
- const config = expandedRowConfig.addRelated.find(c => c.key === key);
15699
- if (config) {
15700
- bgColorClass = 'bg-success-50/80 border-t border-b border-success-200/80';
15701
- content = config.render(item, async (_newItem) => {
15813
+ ((expandable || expandedRowConfig) && showExpandChevron
15814
+ ? 1
15815
+ : 0) +
15816
+ (allActions.length > 0 ? 1 : 0), className: `${currentDensity.cell} py-4 bg-paper-50`, children: renderExpandedRow(item) }) })), expansionState &&
15817
+ expansionState.rowKey === rowKey &&
15818
+ expandedRowConfig &&
15819
+ (() => {
15820
+ const mode = expansionState.mode;
15821
+ let content = null;
15822
+ let bgColorClass = "bg-paper-50"; // Default
15823
+ // Edit mode
15824
+ if (mode === "edit" && expandedRowConfig.edit) {
15825
+ bgColorClass =
15826
+ "bg-paper-100/80 border-t border-b border-paper-300/80";
15827
+ content = expandedRowConfig.edit.render(item, async (_updated) => {
15702
15828
  // Handle save
15703
15829
  handleCollapseExpansion();
15704
15830
  }, () => {
@@ -15706,31 +15832,57 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15706
15832
  handleCollapseExpansion();
15707
15833
  });
15708
15834
  }
15709
- }
15710
- // Manage related modes
15711
- else if (mode.startsWith('manageRelated-') && expandedRowConfig.manageRelated) {
15712
- const key = mode.replace('manageRelated-', '');
15713
- const config = expandedRowConfig.manageRelated.find(c => c.key === key);
15714
- if (config) {
15715
- bgColorClass = 'bg-slate-50/80 border-t border-b border-slate-200/80';
15716
- const handleClose = () => setExpansionState(null);
15717
- content = config.render(item, handleClose);
15835
+ // Details mode
15836
+ else if (mode === "details" && expandedRowConfig.details) {
15837
+ bgColorClass =
15838
+ "bg-primary-50/80 border-t border-b border-primary-200/80";
15839
+ content = expandedRowConfig.details.render(item);
15718
15840
  }
15719
- }
15720
- if (!content)
15721
- return null;
15722
- return (jsx("tr", { children: jsx("td", { colSpan: visibleColumns.length +
15723
- (selectable ? 1 : 0) +
15724
- (((expandable || expandedRowConfig) && showExpandChevron) ? 1 : 0) +
15725
- (allActions.length > 0 ? 1 : 0), className: `${currentDensity.cell} py-4 ${bgColorClass} animate-expand`, children: content }) }, `expanded-${rowKey}`));
15726
- })()] }, rowKey));
15841
+ // Add related modes
15842
+ else if (mode.startsWith("addRelated-") &&
15843
+ expandedRowConfig.addRelated) {
15844
+ const key = mode.replace("addRelated-", "");
15845
+ const config = expandedRowConfig.addRelated.find((c) => c.key === key);
15846
+ if (config) {
15847
+ bgColorClass =
15848
+ "bg-success-50/80 border-t border-b border-success-200/80";
15849
+ content = config.render(item, async (_newItem) => {
15850
+ // Handle save
15851
+ handleCollapseExpansion();
15852
+ }, () => {
15853
+ // Handle cancel
15854
+ handleCollapseExpansion();
15855
+ });
15856
+ }
15857
+ }
15858
+ // Manage related modes
15859
+ else if (mode.startsWith("manageRelated-") &&
15860
+ expandedRowConfig.manageRelated) {
15861
+ const key = mode.replace("manageRelated-", "");
15862
+ const config = expandedRowConfig.manageRelated.find((c) => c.key === key);
15863
+ if (config) {
15864
+ bgColorClass =
15865
+ "bg-slate-50/80 border-t border-b border-slate-200/80";
15866
+ const handleClose = () => setExpansionState(null);
15867
+ content = config.render(item, handleClose);
15868
+ }
15869
+ }
15870
+ if (!content)
15871
+ return null;
15872
+ return (jsx("tr", { children: jsx("td", { colSpan: visibleColumns.length +
15873
+ (selectable ? 1 : 0) +
15874
+ ((expandable || expandedRowConfig) && showExpandChevron
15875
+ ? 1
15876
+ : 0) +
15877
+ (allActions.length > 0 ? 1 : 0), className: `${currentDensity.cell} py-4 ${bgColorClass} animate-expand`, children: content }) }, `expanded-${rowKey}`));
15878
+ })()] }, rowKey));
15727
15879
  });
15728
15880
  };
15729
- const tableContent = (jsxs("div", { className: `bg-white rounded-lg shadow border-2 ${borderColor} ${virtualized ? 'overflow-hidden' : 'overflow-x-auto overflow-y-visible'} ${className}`, style: { position: 'relative' }, children: [loading && data.length > 0 && (jsx("div", { className: "absolute inset-0 bg-white/75 flex items-center justify-center z-20", style: { backdropFilter: 'blur(2px)' }, children: jsxs("div", { className: "flex flex-col items-center gap-3", children: [jsx("div", { className: "loading-spinner", style: { width: '32px', height: '32px', borderWidth: '3px' } }), jsx("span", { className: "text-sm font-medium text-ink-600", children: "Loading..." })] }) })), jsxs("table", { className: `table-stable w-full ${bordered ? 'border-collapse' : ''}`, role: "grid", "aria-label": "Data table", "aria-rowcount": data.length, "aria-colcount": visibleColumns.length, children: [jsxs("colgroup", { children: [selectable && jsx("col", { className: "w-12" }), ((expandable || expandedRowConfig) && showExpandChevron) && jsx("col", { className: "w-10" }), allActions.length > 0 && jsx("col", { style: { width: '28px' } }), visibleColumns.map((column, index) => {
15881
+ const tableContent = (jsxs("div", { className: `bg-white rounded-lg shadow border-2 ${borderColor} ${virtualized ? "overflow-hidden" : "overflow-x-auto overflow-y-visible"} ${className}`, style: { position: "relative" }, children: [loading && data.length > 0 && (jsx("div", { className: "absolute inset-0 bg-white/75 flex items-center justify-center z-20", style: { backdropFilter: "blur(2px)" }, children: jsxs("div", { className: "flex flex-col items-center gap-3", children: [jsx("div", { className: "loading-spinner", style: { width: "32px", height: "32px", borderWidth: "3px" } }), jsx("span", { className: "text-sm font-medium text-ink-600", children: "Loading..." })] }) })), jsxs("table", { className: `table-stable w-full ${bordered ? "border-collapse" : ""}`, role: "grid", "aria-label": "Data table", "aria-rowcount": data.length, "aria-colcount": visibleColumns.length, children: [jsxs("colgroup", { children: [selectable && jsx("col", { className: "w-12" }), (expandable || expandedRowConfig) && showExpandChevron && (jsx("col", { className: "w-10" })), allActions.length > 0 && jsx("col", { style: { width: "28px" } }), visibleColumns.map((column, index) => {
15730
15882
  const columnKey = String(column.key);
15731
15883
  const dynamicWidth = columnWidths[columnKey];
15732
15884
  return (jsx("col", { style: getColumnStyle(column, dynamicWidth) }, index));
15733
- })] }), jsx("thead", { className: `bg-paper-100 sticky top-0 z-10 ${headerClassName}`, children: jsxs("tr", { className: "table-header-row", children: [selectable && (jsx("th", { className: `sticky left-0 bg-paper-100 ${currentDensity.header} border-b ${borderColor} z-20 w-12 ${bordered ? `border ${borderColor}` : ''}`, children: jsx("input", { type: "checkbox", checked: selectedRowsSet.size === data.length && data.length > 0, onChange: handleSelectAll, className: "w-4 h-4 text-accent-600 border-paper-300 rounded focus:ring-accent-400", "aria-label": "Select all rows" }) })), ((expandable || expandedRowConfig) && showExpandChevron) && (jsx("th", { className: `sticky left-0 bg-paper-100 px-2 ${currentDensity.header} border-b ${borderColor} z-19 w-10 ${bordered ? `border ${borderColor}` : ''}` })), allActions.length > 0 && (jsx("th", { className: `sticky left-0 bg-paper-100 text-center text-xs font-medium text-ink-700 uppercase tracking-wider border-b ${borderColor} z-20 ${bordered ? `border ${borderColor}` : ''}`, style: { width: '28px', padding: '0' } })), visibleColumns.map((column, colIdx) => {
15885
+ })] }), jsx("thead", { className: `bg-paper-100 sticky top-0 z-10 ${headerClassName}`, children: jsxs("tr", { className: "table-header-row", children: [selectable && (jsx("th", { className: `sticky left-0 bg-paper-100 ${currentDensity.header} border-b ${borderColor} z-20 w-12 ${bordered ? `border ${borderColor}` : ""}`, children: jsx("input", { type: "checkbox", checked: selectedRowsSet.size === data.length && data.length > 0, onChange: handleSelectAll, className: "w-4 h-4 text-accent-600 border-paper-300 rounded focus:ring-accent-400", "aria-label": "Select all rows" }) })), (expandable || expandedRowConfig) && showExpandChevron && (jsx("th", { className: `sticky left-0 bg-paper-100 px-2 ${currentDensity.header} border-b ${borderColor} z-19 w-10 ${bordered ? `border ${borderColor}` : ""}` })), allActions.length > 0 && (jsx("th", { className: `sticky left-0 bg-paper-100 text-center text-xs font-medium text-ink-700 uppercase tracking-wider border-b ${borderColor} z-20 ${bordered ? `border ${borderColor}` : ""}`, style: { width: "28px", padding: "0" } })), visibleColumns.map((column, colIdx) => {
15734
15886
  const columnKey = String(column.key);
15735
15887
  const dynamicWidth = columnWidths[columnKey];
15736
15888
  const thRef = useRef(null);
@@ -15738,24 +15890,28 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15738
15890
  const isDragOver = dragOverColumn === columnKey;
15739
15891
  // Reduce left padding on first column when there are action buttons (match body cells)
15740
15892
  const isFirstColumn = colIdx === 0;
15741
- const headerPaddingClass = isFirstColumn && allActions.length > 0 ? 'pl-3' : '';
15893
+ const headerPaddingClass = isFirstColumn && allActions.length > 0 ? "pl-3" : "";
15742
15894
  return (jsxs("th", { ref: thRef, draggable: reorderable, onDragStart: (e) => reorderable && handleDragStart(e, columnKey), onDragOver: (e) => reorderable && handleDragOver(e, columnKey), onDragEnd: handleDragEnd, onDrop: (e) => reorderable && handleDrop(e, columnKey), className: `
15743
- ${currentDensity.header} ${headerPaddingClass} text-left border-b ${borderColor} ${bordered ? `border ${borderColor}` : ''} relative
15744
- ${reorderable ? 'cursor-move' : ''}
15745
- ${isDragging ? 'opacity-50' : ''}
15746
- ${isDragOver ? 'bg-accent-100' : ''}
15895
+ ${currentDensity.header} ${headerPaddingClass} text-left border-b ${borderColor} ${bordered ? `border ${borderColor}` : ""} relative
15896
+ ${reorderable ? "cursor-move" : ""}
15897
+ ${isDragging ? "opacity-50" : ""}
15898
+ ${isDragOver ? "bg-accent-100" : ""}
15747
15899
  `, style: getColumnStyle(column, dynamicWidth), children: [column.sortable ? (jsxs("button", { onClick: () => handleSort(column), className: "group inline-flex items-center text-xs font-medium text-ink-500 uppercase tracking-wider hover:text-ink-900 transition-colors", children: [jsx("span", { children: column.header }), getSortIcon(column)] })) : (jsx("span", { className: "text-xs font-medium text-ink-500 uppercase tracking-wider", children: column.header })), resizable && (jsx("div", { className: "absolute right-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-accent-400 group", onMouseDown: (e) => {
15748
15900
  const currentWidth = thRef.current?.offsetWidth || 100;
15749
15901
  handleResizeStart(e, columnKey, currentWidth);
15750
15902
  }, children: jsx("div", { className: "absolute right-0 top-0 bottom-0 w-1 bg-paper-300 group-hover:bg-accent-400 transition-colors" }) }))] }, columnKey));
15751
- })] }) }), jsx("tbody", { ref: tableBodyRef, className: "bg-white table-loading transition-opacity duration-200", onKeyDown: handleKeyboardNavigation, tabIndex: 0, role: "rowgroup", "aria-label": "Table data", children: loading && data.length === 0 ? (renderLoadingSkeleton()) : data.length === 0 ? (renderEmptyStateContent()) : (renderDataRows()) })] })] }));
15903
+ })] }) }), jsx("tbody", { ref: tableBodyRef, className: "bg-white table-loading transition-opacity duration-200", onKeyDown: handleKeyboardNavigation, tabIndex: 0, role: "rowgroup", "aria-label": "Table data", children: loading && data.length === 0
15904
+ ? renderLoadingSkeleton()
15905
+ : data.length === 0
15906
+ ? renderEmptyStateContent()
15907
+ : renderDataRows() })] })] }));
15752
15908
  // Wrap in scrollable container if virtualized
15753
- const finalContent = virtualized ? (jsx("div", { ref: tableContainerRef, onScroll: handleScroll, style: { height: virtualHeight, overflow: 'auto' }, className: "rounded-lg", children: tableContent })) : tableContent;
15909
+ const finalContent = virtualized ? (jsx("div", { ref: tableContainerRef, onScroll: handleScroll, style: { height: virtualHeight, overflow: "auto" }, className: "rounded-lg", children: tableContent })) : (tableContent);
15754
15910
  // Calculate pagination values
15755
15911
  const effectiveTotalItems = totalItems ?? data.length;
15756
15912
  const totalPages = Math.ceil(effectiveTotalItems / pageSize);
15757
15913
  // Page size selector options
15758
- const pageSizeSelectOptions = pageSizeOptions.map(size => ({
15914
+ const pageSizeSelectOptions = pageSizeOptions.map((size) => ({
15759
15915
  value: String(size),
15760
15916
  label: `${size} per page`,
15761
15917
  }));
@@ -15763,17 +15919,20 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15763
15919
  const renderPaginationControls = () => {
15764
15920
  if (!paginated)
15765
15921
  return null;
15766
- return (jsxs("div", { className: "flex items-center justify-between mb-4 px-1", children: [jsxs("div", { className: "flex items-center gap-4", children: [showPageSizeSelector && onPageSizeChange && (jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm text-ink-600", children: "Show:" }), jsx(Select, { options: pageSizeSelectOptions, value: String(pageSize), onChange: (value) => onPageSizeChange?.(Number(value)) })] })), jsx("span", { className: "text-sm text-ink-600", children: effectiveTotalItems > 0 ? (jsxs(Fragment, { children: ["Showing ", ((currentPage - 1) * pageSize) + 1, " - ", Math.min(currentPage * pageSize, effectiveTotalItems), " of ", effectiveTotalItems] })) : ('No items') })] }), totalPages > 1 && onPageChange && (jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: onPageChange }))] }));
15922
+ return (jsxs("div", { className: "flex items-center justify-between mb-4 px-1", children: [jsxs("div", { className: "flex items-center gap-4", children: [showPageSizeSelector && onPageSizeChange && (jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm text-ink-600", children: "Show:" }), jsx(Select, { options: pageSizeSelectOptions, value: String(pageSize), onChange: (value) => onPageSizeChange?.(Number(value)) })] })), jsx("span", { className: "text-sm text-ink-600", children: effectiveTotalItems > 0 ? (jsxs(Fragment, { children: ["Showing ", (currentPage - 1) * pageSize + 1, " -", " ", Math.min(currentPage * pageSize, effectiveTotalItems), " of", " ", effectiveTotalItems] })) : ("No items") })] }), totalPages > 1 && onPageChange && (jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: onPageChange }))] }));
15767
15923
  };
15768
15924
  // Determine if we should show card view
15769
- const shouldShowCardView = mobileView === 'card' ||
15770
- (mobileView === 'auto' && isMobileViewport);
15925
+ const shouldShowCardView = mobileView === "card" || (mobileView === "auto" && isMobileViewport);
15771
15926
  // Card view content
15772
15927
  const cardViewContent = shouldShowCardView ? (jsx(DataTableCardView, { data: data, columns: visibleColumns, cardConfig: cardConfig, loading: loading, loadingRows: loadingRows, emptyMessage: emptyMessage, onCardClick: onRowClick, onCardLongPress: (item, event) => {
15773
15928
  if (enableContextMenu && allActions.length > 0) {
15774
15929
  event.preventDefault();
15775
- const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX;
15776
- const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY;
15930
+ const clientX = "touches" in event
15931
+ ? event.touches[0].clientX
15932
+ : event.clientX;
15933
+ const clientY = "touches" in event
15934
+ ? event.touches[0].clientY
15935
+ : event.clientY;
15777
15936
  setContextMenuState({
15778
15937
  isOpen: true,
15779
15938
  position: { x: clientX, y: clientY },
@@ -15782,7 +15941,11 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
15782
15941
  }
15783
15942
  }, selectable: selectable, selectedRows: selectedRowsSet, onSelectionChange: onRowSelect ? (rows) => onRowSelect(rows) : undefined, keyExtractor: getRowKey, actions: allActions, onEdit: onEdit, onDelete: onDelete, className: className, cardClassName: cardClassName, gap: cardGap })) : null;
15784
15943
  // Render with context menu
15785
- return (jsxs(Fragment, { children: [jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: announcement }), renderPaginationControls(), shouldShowCardView ? cardViewContent : finalContent, contextMenuState.isOpen && contextMenuState.item && (jsx(Menu, { items: convertActionsToMenuItems(contextMenuState.item), position: contextMenuState.position, onClose: () => setContextMenuState({ isOpen: false, position: { x: 0, y: 0 }, item: null }) }))] }));
15944
+ return (jsxs(Fragment, { children: [jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: announcement }), renderPaginationControls(), shouldShowCardView ? cardViewContent : finalContent, contextMenuState.isOpen && contextMenuState.item && (jsx(Menu, { items: convertActionsToMenuItems(contextMenuState.item), position: contextMenuState.position, onClose: () => setContextMenuState({
15945
+ isOpen: false,
15946
+ position: { x: 0, y: 0 },
15947
+ item: null,
15948
+ }) }))] }));
15786
15949
  }
15787
15950
 
15788
15951
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};