@papernote/ui 1.10.17 → 1.10.19

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
@@ -6073,6 +6073,68 @@ function HelpTooltip({ content, icon = 'help', size = 'md', position = 'top', cl
6073
6073
  return (jsx(Tooltip, { content: content, position: position, children: jsx("span", { className: `inline-flex items-center justify-center text-ink-400 hover:text-ink-600 cursor-help transition-colors ${className}`, role: "button", "aria-label": "Help", tabIndex: 0, children: jsx(IconComponent, { className: sizeClasses$7[size] }) }) }));
6074
6074
  }
6075
6075
 
6076
+ /**
6077
+ * SkipLink - Accessibility skip link for keyboard navigation
6078
+ *
6079
+ * A skip link allows keyboard users to bypass repetitive navigation
6080
+ * and jump directly to the main content. The link is visually hidden
6081
+ * until focused, making it invisible to mouse users while remaining
6082
+ * accessible to keyboard and screen reader users.
6083
+ *
6084
+ * Place this component at the very beginning of your page layout,
6085
+ * before any navigation elements.
6086
+ *
6087
+ * @example Basic usage
6088
+ * ```tsx
6089
+ * // In your layout component:
6090
+ * <SkipLink targetId="main-content" />
6091
+ * <Navigation />
6092
+ * <main id="main-content">
6093
+ * {children}
6094
+ * </main>
6095
+ * ```
6096
+ *
6097
+ * @example Custom text
6098
+ * ```tsx
6099
+ * <SkipLink targetId="content">Skip navigation</SkipLink>
6100
+ * ```
6101
+ *
6102
+ * @example Multiple skip links
6103
+ * ```tsx
6104
+ * <div>
6105
+ * <SkipLink targetId="main-content">Skip to main content</SkipLink>
6106
+ * <SkipLink targetId="search">Skip to search</SkipLink>
6107
+ * </div>
6108
+ * ```
6109
+ */
6110
+ function SkipLink({ targetId, children = 'Skip to main content', className = '', }) {
6111
+ const handleClick = (e) => {
6112
+ e.preventDefault();
6113
+ const target = document.getElementById(targetId);
6114
+ if (target) {
6115
+ // Set tabindex to make the element focusable if it isn't already
6116
+ if (!target.hasAttribute('tabindex')) {
6117
+ target.setAttribute('tabindex', '-1');
6118
+ }
6119
+ target.focus();
6120
+ // Scroll the element into view
6121
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
6122
+ }
6123
+ };
6124
+ return (jsx("a", { href: `#${targetId}`, onClick: handleClick, className: `
6125
+ sr-only focus:not-sr-only
6126
+ focus:absolute focus:z-50
6127
+ focus:top-4 focus:left-4
6128
+ focus:px-4 focus:py-2
6129
+ focus:bg-accent-600 focus:text-white
6130
+ focus:rounded-lg focus:shadow-lg
6131
+ focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent-400
6132
+ font-medium text-sm
6133
+ transition-none
6134
+ ${className}
6135
+ `, children: children }));
6136
+ }
6137
+
6076
6138
  function Popover({ trigger: triggerElement, children, placement = 'bottom', triggerMode = 'click', showArrow = true, offset = 8, open: controlledOpen, onOpenChange, closeOnClickOutside = true, closeOnEscape = true, showDelay = 0, hideDelay = 0, className = '', disabled = false, }) {
6077
6139
  const [internalOpen, setInternalOpen] = useState(false);
6078
6140
  const [position, setPosition] = useState({ top: 0, left: 0 });
@@ -8363,7 +8425,7 @@ function Menu({ items, className = '', onClose, position, anchor = 'bottom-left'
8363
8425
  `, style: positionStyle, role: "menu", "aria-orientation": "vertical", children: items.map((item, index) => {
8364
8426
  // Handle divider
8365
8427
  if (item.divider) {
8366
- return (jsx("div", { className: "h-px bg-ink-200 my-1", role: "separator" }, `divider-${index}`));
8428
+ return (jsx("div", { className: "h-px bg-ink-200 my-1", role: "separator", "data-testid": item.dataAttributes?.['data-testid'] || `menu-divider-${index}`, ...item.dataAttributes }, `divider-${index}`));
8367
8429
  }
8368
8430
  const currentNavIndex = navIndex;
8369
8431
  navIndex++;
@@ -8378,7 +8440,7 @@ function Menu({ items, className = '', onClose, position, anchor = 'bottom-left'
8378
8440
  ? 'text-error-600 hover:bg-error-50'
8379
8441
  : 'text-ink-900 hover:bg-accent-100'}
8380
8442
  ${focusedIndex === currentNavIndex ? 'bg-accent-100' : ''}
8381
- `, role: "menuitem", "aria-haspopup": hasSubmenu, "aria-expanded": hasSubmenu && isSubmenuOpen, children: [jsxs("div", { className: "flex items-center gap-2 flex-1 min-w-0", children: [item.icon && (jsx("span", { className: "flex-shrink-0 w-4 h-4 flex items-center justify-center", children: item.icon })), jsx("span", { className: "truncate", children: item.label })] }), hasSubmenu && (jsx(ChevronRight, { className: "h-4 w-4 flex-shrink-0 text-ink-400" }))] }), hasSubmenu && isSubmenuOpen && (jsx("div", { className: "absolute left-full top-0 ml-1 z-10", children: jsx(Menu, { items: item.submenu, onClose: onClose, anchor: "top-left" }) }))] }, item.id));
8443
+ `, role: "menuitem", "aria-haspopup": hasSubmenu, "aria-expanded": hasSubmenu && isSubmenuOpen, "data-testid": item.dataAttributes?.['data-testid'] || `menu-item-${item.id}`, ...item.dataAttributes, children: [jsxs("div", { className: "flex items-center gap-2 flex-1 min-w-0", children: [item.icon && (jsx("span", { className: "flex-shrink-0 w-4 h-4 flex items-center justify-center", children: item.icon })), jsx("span", { className: "truncate", children: item.label })] }), hasSubmenu && (jsx(ChevronRight, { className: "h-4 w-4 flex-shrink-0 text-ink-400" }))] }), hasSubmenu && isSubmenuOpen && (jsx("div", { className: "absolute left-full top-0 ml-1 z-10", children: jsx(Menu, { items: item.submenu, onClose: onClose, anchor: "top-left" }) }))] }, item.id));
8382
8444
  }) }));
8383
8445
  }
8384
8446
  // Convenience component for MenuDivider
@@ -11421,7 +11483,7 @@ function TabsList({ children, className = '' }) {
11421
11483
  /**
11422
11484
  * TabsTrigger - Individual tab button
11423
11485
  */
11424
- function TabsTrigger({ children, value, disabled = false, icon, badge, badgeVariant = 'info', className = '', }) {
11486
+ function TabsTrigger({ children, value, disabled = false, icon, badge, badgeVariant = 'info', className = '', dataAttributes, }) {
11425
11487
  const { activeTab, setActiveTab, variant, orientation, size, registerTab, unregisterTab } = useTabsContext();
11426
11488
  const isActive = activeTab === value;
11427
11489
  // Register this tab on mount
@@ -11451,7 +11513,7 @@ function TabsTrigger({ children, value, disabled = false, icon, badge, badgeVari
11451
11513
  ${disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer'}
11452
11514
  focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-500 focus-visible:ring-offset-1
11453
11515
  ${className}
11454
- `, children: [icon && jsx("span", { className: `flex-shrink-0 ${sizeClasses[size].icon}`, children: icon }), jsx("span", { children: children }), badge !== undefined && (jsx(Badge, { variant: badgeVariant, size: sizeClasses[size].badgeSize, children: badge }))] }));
11516
+ `, "data-testid": dataAttributes?.['data-testid'] || `tab-${value}`, ...dataAttributes, children: [icon && jsx("span", { className: `flex-shrink-0 ${sizeClasses[size].icon}`, children: icon }), jsx("span", { children: children }), badge !== undefined && (jsx(Badge, { variant: badgeVariant, size: sizeClasses[size].badgeSize, children: badge }))] }));
11455
11517
  }
11456
11518
  /**
11457
11519
  * TabsContent - Content panel for a tab
@@ -11680,7 +11742,7 @@ function Tabs({ tabs, activeTab: controlledActiveTab, defaultTab, variant = 'und
11680
11742
  ${tab.disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer'}
11681
11743
  focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-500 focus-visible:ring-offset-1
11682
11744
  group
11683
- `, children: [tab.icon && jsx("span", { className: `flex-shrink-0 ${sizeClasses[size].icon}`, children: tab.icon }), jsx("span", { className: isTabCloseable ? 'mr-1' : '', children: tab.label }), tab.badge !== undefined && (jsx(Badge, { variant: tab.badgeVariant || 'info', size: sizeClasses[size].badgeSize, children: tab.badge })), isTabCloseable && onClose && (jsx("span", { role: "button", "aria-label": `Close ${tab.label}`, onClick: (e) => handleClose(e, tab.id), className: `
11745
+ `, "data-testid": tab.dataAttributes?.['data-testid'] || `tab-${tab.id}`, ...tab.dataAttributes, children: [tab.icon && jsx("span", { className: `flex-shrink-0 ${sizeClasses[size].icon}`, children: tab.icon }), jsx("span", { className: isTabCloseable ? 'mr-1' : '', children: tab.label }), tab.badge !== undefined && (jsx(Badge, { variant: tab.badgeVariant || 'info', size: sizeClasses[size].badgeSize, children: tab.badge })), isTabCloseable && onClose && (jsx("span", { role: "button", "aria-label": `Close ${tab.label}`, onClick: (e) => handleClose(e, tab.id), className: `
11684
11746
  flex-shrink-0 p-0.5 rounded
11685
11747
  text-ink-400 hover:text-ink-700 hover:bg-paper-200
11686
11748
  opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100
@@ -12369,7 +12431,7 @@ function SidebarNavItem({ item, onNavigate, level = 0, currentPath }) {
12369
12431
  ? 'text-ink-600 hover:text-ink-900 hover:bg-paper-50 rounded-lg transition-colors'
12370
12432
  : 'text-ink-600 hover:text-ink-900 hover:bg-paper-100 rounded-lg transition-colors'}
12371
12433
  ${paddingLeft} pr-3
12372
- `, children: [jsxs("div", { className: "flex items-center gap-3 min-w-0 flex-1", children: [item.icon && (jsx("span", { className: "flex-shrink-0 w-5 h-5 flex items-center justify-center text-ink-500", children: item.icon })), jsx("span", { className: "truncate", children: item.label }), item.badge && (jsx("span", { className: "ml-auto flex-shrink-0 px-2 py-0.5 text-xs font-medium bg-accent-100 text-accent-700 rounded-full", children: item.badge }))] }), hasChildren && (jsx("span", { className: "flex-shrink-0 ml-2", children: isExpanded ? (jsx(ChevronDown, { className: "h-4 w-4 text-ink-400" })) : (jsx(ChevronRight, { className: "h-4 w-4 text-ink-400" })) }))] }), hasChildren && isExpanded && (jsx("div", { className: "mt-1 space-y-1", children: item.children?.map((child) => (jsx(SidebarNavItem, { item: child, onNavigate: onNavigate, level: level + 1, currentPath: currentPath }, child.id))) }))] }));
12434
+ `, "data-testid": item.dataAttributes?.['data-testid'] || `sidebar-item-${item.id}`, ...item.dataAttributes, children: [jsxs("div", { className: "flex items-center gap-3 min-w-0 flex-1", children: [item.icon && (jsx("span", { className: "flex-shrink-0 w-5 h-5 flex items-center justify-center text-ink-500", children: item.icon })), jsx("span", { className: "truncate", children: item.label }), item.badge && (jsx("span", { className: "ml-auto flex-shrink-0 px-2 py-0.5 text-xs font-medium bg-accent-100 text-accent-700 rounded-full", children: item.badge }))] }), hasChildren && (jsx("span", { className: "flex-shrink-0 ml-2", children: isExpanded ? (jsx(ChevronDown, { className: "h-4 w-4 text-ink-400" })) : (jsx(ChevronRight, { className: "h-4 w-4 text-ink-400" })) }))] }), hasChildren && isExpanded && (jsx("div", { className: "mt-1 space-y-1", children: item.children?.map((child) => (jsx(SidebarNavItem, { item: child, onNavigate: onNavigate, level: level + 1, currentPath: currentPath }, child.id))) }))] }));
12373
12435
  }
12374
12436
  function SidebarGroup({ title, items, onNavigate, defaultExpanded = true, currentPath }) {
12375
12437
  const [isExpanded, setIsExpanded] = useState(defaultExpanded);
@@ -12471,7 +12533,7 @@ function Sidebar({ items, onNavigate, className = '', header, footer, currentPat
12471
12533
  const sidebarContent = (jsxs("div", { ref: sidebarRef, className: `flex flex-col h-full bg-white border-r border-paper-300 notebook-binding ${width} ${className}`, children: [mobileOpen !== undefined && (jsxs("div", { className: "flex items-center justify-between px-4 pt-4 md:hidden", children: [jsx("div", { className: "flex-1", children: header }), jsx("button", { onClick: onMobileClose, className: "\n flex items-center justify-center\n w-10 h-10 -mr-2\n text-ink-500 hover:text-ink-700\n hover:bg-paper-100 rounded-full\n transition-colors\n ", "aria-label": "Close sidebar", children: jsx(X, { className: "w-5 h-5" }) })] })), header && mobileOpen === undefined && (jsx("div", { className: "px-6 pt-6 pb-4", children: header })), header && mobileOpen !== undefined && (jsx("div", { className: "px-6 pt-2 pb-4 hidden md:block", children: header })), jsx("nav", { className: "flex-1 px-3 py-2 space-y-1 overflow-y-auto", children: items.map((item) => {
12472
12534
  // Render separator
12473
12535
  if (item.separator) {
12474
- return jsx("div", { className: "my-4 border-t border-paper-300" }, item.id);
12536
+ return (jsx("div", { className: "my-4 border-t border-paper-300", "data-testid": item.dataAttributes?.['data-testid'] || `sidebar-separator-${item.id}`, ...item.dataAttributes }, item.id));
12475
12537
  }
12476
12538
  // Render nav item
12477
12539
  return (jsx(SidebarNavItem, { item: item, onNavigate: handleNavigate, currentPath: currentPath }, item.id));
@@ -12563,7 +12625,7 @@ function BottomNavigation({ items, activeId, onNavigate, showLabels = true, clas
12563
12625
  ${isActive
12564
12626
  ? 'text-accent-600'
12565
12627
  : 'text-ink-500 hover:text-ink-700'}
12566
- `, "aria-current": isActive ? 'page' : undefined, "aria-label": item.label, children: [jsxs("div", { className: "relative", children: [jsx("div", { className: `
12628
+ `, "aria-current": isActive ? 'page' : undefined, "aria-label": item.label, "data-testid": item.dataAttributes?.['data-testid'] || `bottom-nav-${item.id}`, ...item.dataAttributes, children: [jsxs("div", { className: "relative", children: [jsx("div", { className: `
12567
12629
  w-6 h-6 flex items-center justify-center
12568
12630
  transition-transform duration-200
12569
12631
  ${isActive ? 'scale-110' : 'scale-100'}
@@ -14074,6 +14136,10 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
14074
14136
  const tableContainerRef = useRef(null);
14075
14137
  // Row hover state (for coordinating primary + secondary row highlighting)
14076
14138
  const [hoveredRowKey, setHoveredRowKey] = useState(null);
14139
+ // Keyboard navigation state
14140
+ const [focusedCell, setFocusedCell] = useState(null);
14141
+ const [announcement, setAnnouncement] = useState('');
14142
+ const tableBodyRef = useRef(null);
14077
14143
  // Temporary row highlight state (for flash animation)
14078
14144
  const [flashingRows, setFlashingRows] = useState(new Set());
14079
14145
  const flashTimeoutRef = useRef(null);
@@ -14139,6 +14205,11 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
14139
14205
  const currentDensity = densityClasses[density];
14140
14206
  // Key extractor function - defined early for use in other functions
14141
14207
  const getRowKey = keyExtractor || ((row) => String(row.id));
14208
+ // Calculate if there are any actions (for keyboard navigation column calculation)
14209
+ // This is computed early so it can be used in keyboard handlers
14210
+ const hasAnyActions = !!(onEdit || onDelete || actions.length > 0 ||
14211
+ expandedRowConfig?.edit || expandedRowConfig?.details ||
14212
+ expandedRowConfig?.addRelated?.length || expandedRowConfig?.manageRelated?.length);
14142
14213
  // Get row background class based on striping and highlighting
14143
14214
  const getRowBackgroundClass = (item, index) => {
14144
14215
  const classes = [];
@@ -14440,6 +14511,192 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
14440
14511
  const handleCollapseExpansion = () => {
14441
14512
  setExpansionState(null);
14442
14513
  };
14514
+ // Keyboard navigation handler
14515
+ const handleKeyboardNavigation = useCallback((e) => {
14516
+ if (!data.length)
14517
+ return;
14518
+ const totalRows = data.length;
14519
+ const totalCols = visibleColumns.length;
14520
+ // If no cell is focused, focus first data cell on first arrow key
14521
+ if (!focusedCell) {
14522
+ if (['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
14523
+ e.preventDefault();
14524
+ setFocusedCell({ row: 0, col: 0 });
14525
+ const colHeader = visibleColumns[0]?.header || 'first column';
14526
+ setAnnouncement(`Row 1, ${colHeader}`);
14527
+ return;
14528
+ }
14529
+ return;
14530
+ }
14531
+ const { row, col } = focusedCell;
14532
+ switch (e.key) {
14533
+ case 'ArrowDown':
14534
+ e.preventDefault();
14535
+ if (row < totalRows - 1) {
14536
+ const newRow = row + 1;
14537
+ setFocusedCell({ row: newRow, col });
14538
+ const rowItem = data[newRow];
14539
+ const colHeader = visibleColumns[col]?.header || '';
14540
+ const cellValue = rowItem[visibleColumns[col]?.key];
14541
+ setAnnouncement(`Row ${newRow + 1}, ${colHeader}: ${cellValue || 'empty'}`);
14542
+ }
14543
+ break;
14544
+ case 'ArrowUp':
14545
+ e.preventDefault();
14546
+ if (row > 0) {
14547
+ const newRow = row - 1;
14548
+ setFocusedCell({ row: newRow, col });
14549
+ const rowItem = data[newRow];
14550
+ const colHeader = visibleColumns[col]?.header || '';
14551
+ const cellValue = rowItem[visibleColumns[col]?.key];
14552
+ setAnnouncement(`Row ${newRow + 1}, ${colHeader}: ${cellValue || 'empty'}`);
14553
+ }
14554
+ break;
14555
+ case 'ArrowRight':
14556
+ e.preventDefault();
14557
+ if (col < totalCols - 1) {
14558
+ const newCol = col + 1;
14559
+ setFocusedCell({ row, col: newCol });
14560
+ const rowItem = data[row];
14561
+ const colHeader = visibleColumns[newCol]?.header || '';
14562
+ const cellValue = rowItem[visibleColumns[newCol]?.key];
14563
+ setAnnouncement(`${colHeader}: ${cellValue || 'empty'}`);
14564
+ }
14565
+ break;
14566
+ case 'ArrowLeft':
14567
+ e.preventDefault();
14568
+ if (col > 0) {
14569
+ const newCol = col - 1;
14570
+ setFocusedCell({ row, col: newCol });
14571
+ const rowItem = data[row];
14572
+ const colHeader = visibleColumns[newCol]?.header || '';
14573
+ const cellValue = rowItem[visibleColumns[newCol]?.key];
14574
+ setAnnouncement(`${colHeader}: ${cellValue || 'empty'}`);
14575
+ }
14576
+ break;
14577
+ case 'Home':
14578
+ e.preventDefault();
14579
+ if (e.ctrlKey) {
14580
+ // Ctrl+Home: Go to first cell
14581
+ setFocusedCell({ row: 0, col: 0 });
14582
+ setAnnouncement(`First cell, Row 1, ${visibleColumns[0]?.header || ''}`);
14583
+ }
14584
+ else {
14585
+ // Home: Go to first cell in current row
14586
+ setFocusedCell({ row, col: 0 });
14587
+ const rowItem = data[row];
14588
+ const cellValue = rowItem[visibleColumns[0]?.key];
14589
+ setAnnouncement(`${visibleColumns[0]?.header || ''}: ${cellValue || 'empty'}`);
14590
+ }
14591
+ break;
14592
+ case 'End':
14593
+ e.preventDefault();
14594
+ if (e.ctrlKey) {
14595
+ // Ctrl+End: Go to last cell
14596
+ const lastRow = totalRows - 1;
14597
+ const lastCol = totalCols - 1;
14598
+ setFocusedCell({ row: lastRow, col: lastCol });
14599
+ setAnnouncement(`Last cell, Row ${lastRow + 1}, ${visibleColumns[lastCol]?.header || ''}`);
14600
+ }
14601
+ else {
14602
+ // End: Go to last cell in current row
14603
+ const lastCol = totalCols - 1;
14604
+ setFocusedCell({ row, col: lastCol });
14605
+ const rowItem = data[row];
14606
+ const cellValue = rowItem[visibleColumns[lastCol]?.key];
14607
+ setAnnouncement(`${visibleColumns[lastCol]?.header || ''}: ${cellValue || 'empty'}`);
14608
+ }
14609
+ break;
14610
+ case 'Enter':
14611
+ e.preventDefault();
14612
+ {
14613
+ const rowItem = data[row];
14614
+ const rowKey = getRowKey(rowItem);
14615
+ // Priority: Edit mode > Details mode > Row double-click handler
14616
+ if (onEdit) {
14617
+ onEdit(rowItem);
14618
+ setAnnouncement('Opening edit mode');
14619
+ }
14620
+ else if (expandedRowConfig?.edit) {
14621
+ handleExpansionWithMode(rowKey, 'edit');
14622
+ setAnnouncement('Opening inline edit');
14623
+ }
14624
+ else if (expandedRowConfig?.details) {
14625
+ handleExpansionWithMode(rowKey, 'details');
14626
+ setAnnouncement('Opening details view');
14627
+ }
14628
+ else if (onRowDoubleClick) {
14629
+ onRowDoubleClick(rowItem);
14630
+ setAnnouncement('Activating row');
14631
+ }
14632
+ else if (onRowClick) {
14633
+ onRowClick(rowItem);
14634
+ setAnnouncement('Row selected');
14635
+ }
14636
+ }
14637
+ break;
14638
+ case ' ':
14639
+ // Space: Toggle selection if selectable
14640
+ if (selectable) {
14641
+ e.preventDefault();
14642
+ const rowItem = data[row];
14643
+ const rowKey = getRowKey(rowItem);
14644
+ handleRowSelect(rowKey);
14645
+ const isNowSelected = !selectedRowsSet.has(rowKey);
14646
+ setAnnouncement(isNowSelected ? 'Row selected' : 'Row deselected');
14647
+ }
14648
+ break;
14649
+ case 'Escape':
14650
+ e.preventDefault();
14651
+ setFocusedCell(null);
14652
+ setAnnouncement('Table navigation exited');
14653
+ // Return focus to table container
14654
+ tableBodyRef.current?.closest('table')?.focus();
14655
+ break;
14656
+ case 'PageDown':
14657
+ e.preventDefault();
14658
+ {
14659
+ const jumpSize = 10;
14660
+ const newRow = Math.min(row + jumpSize, totalRows - 1);
14661
+ setFocusedCell({ row: newRow, col });
14662
+ const colHeader = visibleColumns[col]?.header || '';
14663
+ setAnnouncement(`Row ${newRow + 1} of ${totalRows}, ${colHeader}`);
14664
+ }
14665
+ break;
14666
+ case 'PageUp':
14667
+ e.preventDefault();
14668
+ {
14669
+ const jumpSize = 10;
14670
+ const newRow = Math.max(row - jumpSize, 0);
14671
+ setFocusedCell({ row: newRow, col });
14672
+ const colHeader = visibleColumns[col]?.header || '';
14673
+ setAnnouncement(`Row ${newRow + 1} of ${totalRows}, ${colHeader}`);
14674
+ }
14675
+ break;
14676
+ }
14677
+ }, [data, visibleColumns, focusedCell, selectable, expandedRowConfig, onEdit, onRowDoubleClick, onRowClick, getRowKey, handleExpansionWithMode, handleRowSelect, selectedRowsSet]);
14678
+ // Focus the appropriate cell when focusedCell changes
14679
+ useEffect(() => {
14680
+ if (focusedCell && tableBodyRef.current) {
14681
+ const { row, col } = focusedCell;
14682
+ const rows = tableBodyRef.current.querySelectorAll('tr[data-row-index]');
14683
+ const targetRow = rows[row];
14684
+ if (targetRow) {
14685
+ // Calculate actual column index including extra columns
14686
+ const hasSelectionCol = selectable;
14687
+ const hasExpandCol = (expandable || expandedRowConfig) && showExpandChevron;
14688
+ const hasActionsCol = hasAnyActions;
14689
+ const extraColsBefore = (hasSelectionCol ? 1 : 0) + (hasExpandCol ? 1 : 0) + (hasActionsCol ? 1 : 0);
14690
+ const cells = targetRow.querySelectorAll('td');
14691
+ const targetCell = cells[col + extraColsBefore];
14692
+ if (targetCell) {
14693
+ targetCell.focus();
14694
+ // Scroll into view if needed
14695
+ targetCell.scrollIntoView({ block: 'nearest', inline: 'nearest' });
14696
+ }
14697
+ }
14698
+ }
14699
+ }, [focusedCell, selectable, expandable, expandedRowConfig, showExpandChevron, hasAnyActions]);
14443
14700
  // Handle column header click for sorting
14444
14701
  const handleSort = (column) => {
14445
14702
  if (!column.sortable || !onSortChange)
@@ -14524,7 +14781,9 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
14524
14781
  // Hover state for row pair (primary + secondary)
14525
14782
  const isHovered = hoveredRowKey === rowKey;
14526
14783
  const hoverClass = disableHover ? '' : (isHovered ? 'bg-paper-100' : '');
14527
- return (jsxs(React__default.Fragment, { children: [jsxs("tr", { 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}`, onMouseEnter: () => !disableHover && setHoveredRowKey(rowKey), onMouseLeave: () => !disableHover && setHoveredRowKey(null), onClick: () => onRowClick?.(item), onContextMenu: (e) => {
14784
+ // Check if this row is keyboard-focused
14785
+ const isKeyboardFocused = focusedCell?.row === index;
14786
+ 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) => {
14528
14787
  if (enableContextMenu && allActions.length > 0) {
14529
14788
  e.preventDefault();
14530
14789
  e.stopPropagation();
@@ -14602,7 +14861,9 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
14602
14861
  // Reduce left padding on first column when there are action buttons
14603
14862
  const isFirstColumn = colIdx === 0;
14604
14863
  const paddingClass = isFirstColumn && allActions.length > 0 ? 'pl-3' : '';
14605
- return (jsx("td", { className: `${currentDensity.cell} ${paddingClass} ${column.className || ''} ${bordered ? `border ${borderColor}` : ''}`, style: getColumnStyle(column, dynamicWidth), children: jsx("div", { className: `${currentDensity.text} leading-tight`, children: primaryContent }) }, `${item.id}-${columnKey}`));
14864
+ // Check if this cell is keyboard-focused
14865
+ const isCellFocused = focusedCell?.row === index && focusedCell?.col === colIdx;
14866
+ 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}`));
14606
14867
  })] }), 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) => {
14607
14868
  const columnKey = String(column.key);
14608
14869
  const dynamicWidth = columnWidths[columnKey];
@@ -14671,7 +14932,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
14671
14932
  })()] }, rowKey));
14672
14933
  });
14673
14934
  };
14674
- 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 bg-opacity-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' : ''}`, 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) => {
14935
+ 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 bg-opacity-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) => {
14675
14936
  const columnKey = String(column.key);
14676
14937
  const dynamicWidth = columnWidths[columnKey];
14677
14938
  return (jsx("col", { style: getColumnStyle(column, dynamicWidth) }, index));
@@ -14693,7 +14954,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
14693
14954
  const currentWidth = thRef.current?.offsetWidth || 100;
14694
14955
  handleResizeStart(e, columnKey, currentWidth);
14695
14956
  }, children: jsx("div", { className: "absolute right-0 top-0 bottom-0 w-1 bg-paper-300 group-hover:bg-accent-400 transition-colors" }) }))] }, columnKey));
14696
- })] }) }), jsx("tbody", { className: "bg-white table-loading transition-opacity duration-200", children: loading && data.length === 0 ? (renderLoadingSkeleton()) : data.length === 0 ? (renderEmptyStateContent()) : (renderDataRows()) })] })] }));
14957
+ })] }) }), 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()) })] })] }));
14697
14958
  // Wrap in scrollable container if virtualized
14698
14959
  const finalContent = virtualized ? (jsx("div", { ref: tableContainerRef, onScroll: handleScroll, style: { height: virtualHeight, overflow: 'auto' }, className: "rounded-lg", children: tableContent })) : tableContent;
14699
14960
  // Calculate pagination values
@@ -14727,7 +14988,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
14727
14988
  }
14728
14989
  }, 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;
14729
14990
  // Render with context menu
14730
- return (jsxs(Fragment, { children: [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 }) }))] }));
14991
+ 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 }) }))] }));
14731
14992
  }
14732
14993
 
14733
14994
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
@@ -61361,5 +61622,5 @@ function Responsive({ mobile, tablet, desktop, }) {
61361
61622
  return jsx(Fragment, { children: mobile || tablet || desktop });
61362
61623
  }
61363
61624
 
61364
- export { Accordion, AchievementBadge, AchievementUnlock, ActionBar, ActionBarCenter, ActionBarLeft, ActionBarRight, ActionButton, ActivityFeed, AdminModal, Alert, AlertDialog, AppLayout, Autocomplete, Avatar, BREAKPOINTS, Badge, BottomNavigation, BottomNavigationSpacer, BottomSheet, BottomSheetActions, BottomSheetContent, BottomSheetHeader, Box, Breadcrumbs, Button, ButtonGroup, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CardView, Carousel, Celebration, Checkbox, CheckboxList, Chip, ChipGroup, CollaboratorAvatars, Collapsible, CollapsibleSection, ColorPicker, Combobox, ComingSoon, CommandPalette, CompactStat, ConfirmDialog, ContextMenu, ControlBar, CurrencyDisplay, CurrencyInput, Dashboard, DashboardContent, DashboardHeader, DataGrid, DataTable, DataTableCardView, DateDisplay, DatePicker, DateRangePicker, DateTimePicker, DesktopOnly, Drawer, DrawerFooter, DropZone, Dropdown, DropdownTrigger, EmptyState, ErrorBoundary, ExpandablePanel, ExpandablePanelContainer, ExpandablePanelSpacer, ExpandableRowButton, ExpandableToolbar, ExpandedRowEditForm, ExportButton, FORMULA_CATEGORIES, FORMULA_DEFINITIONS, FORMULA_NAMES, FieldArray, FileUpload, FilterBar, FilterControls, FilterStatusBanner, FloatingActionButton, Form, FormContext, FormControl, FormWizard, Grid, GridItem, HelpTooltip, Hide, HorizontalScroll, HoverCard, InfiniteScroll, Input, InviteCard, KanbanBoard, Layout, Loading, LoadingOverlay, Logo, MarkdownEditor, MaskedInput, Menu, MenuDivider, MobileHeader, MobileHeaderSpacer, MobileLayout, MobileOnly, MobileProvider, Modal, ModalFooter, MotivationalMessage, MultiSelect, NotificationBanner, NotificationBar, NotificationBell, NotificationIndicator, NumberInput, Page, PageHeader, PageLayout, PageNavigation, Pagination, PasswordInput, PermissionBadge, Popover, PriorityAlertBanner, Progress, ProgressCelebration, PullToRefresh, QueryTransparency, RadioGroup, Rating, Responsive, RichTextEditor, SearchBar, SearchableList, Select, Separator, SharedBadge, Show, Sidebar, SidebarGroup, Skeleton, SkeletonCard$1 as SkeletonCard, SkeletonTable, Slider, Spreadsheet, SpreadsheetReport, Stack, StatCard, StatItem, StatsCardGrid, StatsGrid, StatusBadge, StatusBar, StepIndicator, Stepper, StreakBadge, SuccessCheck, SummaryCard, SwipeActions, SwipeableCard, SwipeableListItem, Switch, Tabs, TabsContent, TabsList, TabsRoot, TabsTrigger, Text, Textarea, ThemeToggle, TimePicker, Timeline, TimezoneSelector, Toast, ToastContainer, Tooltip, Transfer, TreeView, TwoColumnContent, UserProfileButton, addErrorMessage, addInfoMessage, addSuccessMessage, addWarningMessage, calculateColumnWidth, createActionsSection, createFiltersSection, createMultiSheetExcel, createPageControlsSection, createQueryDetailsSection, exportDataTableToExcel, exportToExcel, formatStatisticValue, formatStatistics, getFormula, getFormulasByCategory, getLocalTimezone, isValidTimezone, loadColumnOrder, loadColumnWidths, reorderArray, saveColumnOrder, saveColumnWidths, searchFormulas, statusManager, useBreadcrumbReset, useBreakpoint, useBreakpointValue, useCelebration, useColumnReorder, useColumnResize, useCommandPalette, useConfirmDialog, useDelighters, useFABScroll, useFormContext, useIsDesktop, useIsMobile, useIsTablet, useIsTouchDevice, useMediaQuery, useMobileContext, useOrientation, usePrefersMobile, useResponsiveCallback, useSafeAreaInsets, useViewportSize, withMobileContext };
61625
+ export { Accordion, AchievementBadge, AchievementUnlock, ActionBar, ActionBarCenter, ActionBarLeft, ActionBarRight, ActionButton, ActivityFeed, AdminModal, Alert, AlertDialog, AppLayout, Autocomplete, Avatar, BREAKPOINTS, Badge, BottomNavigation, BottomNavigationSpacer, BottomSheet, BottomSheetActions, BottomSheetContent, BottomSheetHeader, Box, Breadcrumbs, Button, ButtonGroup, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CardView, Carousel, Celebration, Checkbox, CheckboxList, Chip, ChipGroup, CollaboratorAvatars, Collapsible, CollapsibleSection, ColorPicker, Combobox, ComingSoon, CommandPalette, CompactStat, ConfirmDialog, ContextMenu, ControlBar, CurrencyDisplay, CurrencyInput, Dashboard, DashboardContent, DashboardHeader, DataGrid, DataTable, DataTableCardView, DateDisplay, DatePicker, DateRangePicker, DateTimePicker, DesktopOnly, Drawer, DrawerFooter, DropZone, Dropdown, DropdownTrigger, EmptyState, ErrorBoundary, ExpandablePanel, ExpandablePanelContainer, ExpandablePanelSpacer, ExpandableRowButton, ExpandableToolbar, ExpandedRowEditForm, ExportButton, FORMULA_CATEGORIES, FORMULA_DEFINITIONS, FORMULA_NAMES, FieldArray, FileUpload, FilterBar, FilterControls, FilterStatusBanner, FloatingActionButton, Form, FormContext, FormControl, FormWizard, Grid, GridItem, HelpTooltip, Hide, HorizontalScroll, HoverCard, InfiniteScroll, Input, InviteCard, KanbanBoard, Layout, Loading, LoadingOverlay, Logo, MarkdownEditor, MaskedInput, Menu, MenuDivider, MobileHeader, MobileHeaderSpacer, MobileLayout, MobileOnly, MobileProvider, Modal, ModalFooter, MotivationalMessage, MultiSelect, NotificationBanner, NotificationBar, NotificationBell, NotificationIndicator, NumberInput, Page, PageHeader, PageLayout, PageNavigation, Pagination, PasswordInput, PermissionBadge, Popover, PriorityAlertBanner, Progress, ProgressCelebration, PullToRefresh, QueryTransparency, RadioGroup, Rating, Responsive, RichTextEditor, SearchBar, SearchableList, Select, Separator, SharedBadge, Show, Sidebar, SidebarGroup, Skeleton, SkeletonCard$1 as SkeletonCard, SkeletonTable, SkipLink, Slider, Spreadsheet, SpreadsheetReport, Stack, StatCard, StatItem, StatsCardGrid, StatsGrid, StatusBadge, StatusBar, StepIndicator, Stepper, StreakBadge, SuccessCheck, SummaryCard, SwipeActions, SwipeableCard, SwipeableListItem, Switch, Tabs, TabsContent, TabsList, TabsRoot, TabsTrigger, Text, Textarea, ThemeToggle, TimePicker, Timeline, TimezoneSelector, Toast, ToastContainer, Tooltip, Transfer, TreeView, TwoColumnContent, UserProfileButton, addErrorMessage, addInfoMessage, addSuccessMessage, addWarningMessage, calculateColumnWidth, createActionsSection, createFiltersSection, createMultiSheetExcel, createPageControlsSection, createQueryDetailsSection, exportDataTableToExcel, exportToExcel, formatStatisticValue, formatStatistics, getFormula, getFormulasByCategory, getLocalTimezone, isValidTimezone, loadColumnOrder, loadColumnWidths, reorderArray, saveColumnOrder, saveColumnWidths, searchFormulas, statusManager, useBreadcrumbReset, useBreakpoint, useBreakpointValue, useCelebration, useColumnReorder, useColumnResize, useCommandPalette, useConfirmDialog, useDelighters, useFABScroll, useFormContext, useIsDesktop, useIsMobile, useIsTablet, useIsTouchDevice, useMediaQuery, useMobileContext, useOrientation, usePrefersMobile, useResponsiveCallback, useSafeAreaInsets, useViewportSize, withMobileContext };
61365
61626
  //# sourceMappingURL=index.esm.js.map