@ahrowe/ui 0.30.0 → 0.31.0

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.
Files changed (40) hide show
  1. package/dist/esm/common/tree/tree.mjs +1 -1
  2. package/dist/esm/common/tree/tree.mjs.map +1 -1
  3. package/dist/esm/common/virtualList/useColumnResize.mjs +2 -0
  4. package/dist/esm/common/virtualList/useColumnResize.mjs.map +1 -0
  5. package/dist/esm/common/virtualList/useColumns.mjs +1 -1
  6. package/dist/esm/common/virtualList/useColumns.mjs.map +1 -1
  7. package/dist/esm/common/virtualList/useHeaderNavigation.mjs +2 -0
  8. package/dist/esm/common/virtualList/useHeaderNavigation.mjs.map +1 -0
  9. package/dist/esm/common/virtualList/useRowNavigation.mjs +2 -0
  10. package/dist/esm/common/virtualList/useRowNavigation.mjs.map +1 -0
  11. package/dist/esm/common/virtualList/useSorting.mjs +2 -0
  12. package/dist/esm/common/virtualList/useSorting.mjs.map +1 -0
  13. package/dist/esm/common/virtualList/useVirtualWindow.mjs +1 -1
  14. package/dist/esm/common/virtualList/useVirtualWindow.mjs.map +1 -1
  15. package/dist/esm/common/virtualList/virtualList.mjs +1 -1
  16. package/dist/esm/common/virtualList/virtualList.mjs.map +1 -1
  17. package/dist/esm/common/virtualList/virtualList.module.mjs +1 -1
  18. package/dist/esm/common/virtualList/virtualList.module.mjs.map +1 -1
  19. package/dist/esm/common/virtualList/virtualList.utils.mjs +1 -1
  20. package/dist/esm/common/virtualList/virtualList.utils.mjs.map +1 -1
  21. package/dist/esm/common/virtualList/virtualListHeader.mjs +2 -0
  22. package/dist/esm/common/virtualList/virtualListHeader.mjs.map +1 -0
  23. package/dist/esm/common/virtualList/virtualRow.mjs +1 -1
  24. package/dist/esm/common/virtualList/virtualRow.mjs.map +1 -1
  25. package/dist/index.cjs +3 -3
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/style.css +1 -1
  28. package/dist/types/common/virtualList/useColumnResize.d.ts +59 -0
  29. package/dist/types/common/virtualList/useColumns.d.ts +12 -2
  30. package/dist/types/common/virtualList/useHeaderNavigation.d.ts +22 -0
  31. package/dist/types/common/virtualList/useRowNavigation.d.ts +50 -0
  32. package/dist/types/common/virtualList/useSorting.d.ts +26 -0
  33. package/dist/types/common/virtualList/useVirtualWindow.d.ts +1 -8
  34. package/dist/types/common/virtualList/virtualList.d.ts +1 -1
  35. package/dist/types/common/virtualList/virtualList.types.d.ts +82 -1
  36. package/dist/types/common/virtualList/virtualList.utils.d.ts +83 -1
  37. package/dist/types/common/virtualList/virtualListHeader.d.ts +35 -0
  38. package/dist/types/common/virtualList/virtualRow.d.ts +14 -1
  39. package/docs/VirtualList.md +125 -7
  40. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"virtualList.mjs","names":[],"sources":["../../../../package/common/virtualList/virtualList.tsx"],"sourcesContent":["import React, { useMemo, useCallback, useImperativeHandle } from 'react';\nimport cx from 'classnames';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faGear } from '@fortawesome/free-solid-svg-icons';\n\nimport styles from './virtualList.module.pcss';\nimport SpinnerLoading from '../loading/spinnerLoading';\nimport Checkbox from '../checkbox';\nimport FloatingMenu, { Align } from '../floatingMenu';\nimport VirtualRow from './virtualRow';\nimport { useVirtualWindow } from './useVirtualWindow';\nimport { useSelection } from './useSelection';\nimport { useColumns } from './useColumns';\nimport { useRowDrag } from './useRowDrag';\nimport type { VirtualListProps, VirtualListHandle } from './virtualList.types';\n\n// Pure helpers live in ./virtualList.utils; re-exported here so existing\n// imports (and unit tests) keep working against the component entry point.\nexport {\n buildOffsets,\n findStartIndex,\n getVisibleRange,\n resolveReorderTarget,\n resolveTreeDropTarget,\n lastDescendantIndex,\n buildColumnTemplate,\n} from './virtualList.utils';\n\n// ─── Orchestrator ─────────────────────────────────────────────────────────────\n// VirtualListInner wires four focused hooks together and renders. Each concern\n// lives in its own module:\n// useVirtualWindow — generic scroll / measurement / windowing (all modes)\n// useSelection — multi-row selection\n// useColumns — table mode: columns, visibility, persistence, fit widths\n// useRowDrag — reorder + tree drag-and-drop (mouse + touch)\n\nfunction VirtualListInner<T>(\n {\n items,\n renderRow,\n ariaRoles,\n columns,\n height = '100%',\n estimatedRowHeight = 40,\n overscan = 3,\n rowGap = 0,\n rowPadding,\n selectedKey,\n getItemKey,\n onRowClick,\n visibleColumnKeys,\n onVisibleColumnsChange,\n persistColumnsKey,\n showColumnToggle,\n onLoadMore,\n loadMoreThreshold = 100,\n isLoading = false,\n multiSelect = false,\n selectedKeys,\n onSelectionChange,\n reorderable = false,\n onReorder,\n longPressDelay = 400,\n treeReorder = false,\n getItemDepth,\n isGroup,\n onTreeDrop,\n treeIndentPx = 24,\n dragHandle = false,\n showDivider = true,\n rowHover = true,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n }: VirtualListProps<T>,\n ref: React.ForwardedRef<VirtualListHandle>,\n) {\n const getKey = useCallback(\n (item: T, index: number): string | number => (getItemKey ? getItemKey(item, index) : index),\n [getItemKey],\n );\n const keys = useMemo(() => items.map((item, i) => getKey(item, i)), [items, getKey]);\n const depths = useMemo(\n () => (getItemDepth ? items.map((item, i) => getItemDepth(item, i)) : []),\n [items, getItemDepth],\n );\n const isGroupAt = useCallback((i: number) => (isGroup ? isGroup(items[i], i) : false), [isGroup, items]);\n\n const win = useVirtualWindow<T>({\n items,\n keys,\n getItemKey,\n estimatedRowHeight,\n rowGap,\n overscan,\n onLoadMore,\n loadMoreThreshold,\n isLoading,\n });\n const { rootRef, headerRef, rowObserver, keysRef, offsets, totalHeight, startIndex, endIndex } = win;\n\n const selection = useSelection<T>({ items, getKey, multiSelect, selectedKeys, onSelectionChange });\n const { effectiveSelectedKeys } = selection;\n\n const cols = useColumns<T>({\n columns,\n items,\n multiSelect,\n visibleColumnKeys,\n onVisibleColumnsChange,\n persistColumnsKey,\n showColumnToggle,\n rootRef,\n startIndex,\n endIndex,\n containerHeight: win.containerHeight,\n containerWidth: win.containerWidth,\n });\n const {\n hasColumns,\n visibleColumns,\n columnTemplate,\n shouldShowToggle,\n effectiveVisible,\n manuallyHiddenCount,\n toggleOpen,\n setToggleOpen,\n handleToggleColumn,\n recalculateColumns,\n } = cols;\n\n // Compose the forwarded handle from both hooks: scroll methods come from the\n // windowing engine, column controls from the column hook.\n useImperativeHandle(ref, () => ({ ...win.scrollApi, recalculateColumns }), [win.scrollApi, recalculateColumns]);\n\n const drag = useRowDrag({\n reorderable,\n treeReorder,\n onReorder,\n onTreeDrop,\n longPressDelay,\n dragHandle,\n treeIndentPx,\n keys,\n depths,\n isGroupAt,\n rootRef,\n keysRef,\n });\n\n const rowPaddingValue = typeof rowPadding === 'number' ? `${rowPadding}px` : rowPadding;\n\n const renderRowAt = (i: number) => {\n const item = items[i];\n const key = getKey(item, i);\n const isSelected = (selectedKey != null && key === selectedKey) || effectiveSelectedKeys.has(key);\n const dragProps = drag.getRowDragProps(i, key);\n\n return (\n <VirtualRow\n key={key}\n index={i}\n top={offsets[i]}\n padding={rowPaddingValue}\n columnTemplate={columnTemplate}\n isSelected={isSelected}\n showDivider={showDivider}\n hover={rowHover}\n onClick={() => {\n if (drag.consumeSuppressedClick()) return;\n onRowClick?.(item, i);\n }}\n observer={rowObserver}\n ariaRowIndex={hasColumns ? i + 2 : undefined}\n ariaPosInSet={hasColumns ? undefined : i + 1}\n ariaSetSize={hasColumns ? undefined : items.length}\n rowRole={ariaRoles?.row ?? (hasColumns ? 'row' : 'listitem')}\n reserveToggleGutter={shouldShowToggle}\n {...dragProps}\n dropIndicatorClassName={classNames?.dropIndicator}\n dropIndicatorStyle={slotStyles?.dropIndicator}\n rowClassName={classNames?.row}\n rowStyle={slotStyles?.row}\n >\n {hasColumns ? (\n <>\n {multiSelect && (\n <div\n className={cx(styles.virtualListSelectCell, classNames?.selectCell)}\n style={slotStyles?.selectCell}\n role='gridcell'\n onClick={(e) => e.stopPropagation()}\n >\n <Checkbox\n selected={effectiveSelectedKeys.has(key)}\n onToggle={() => selection.handleRowSelect(key)}\n aria-label='Select row'\n />\n </div>\n )}\n {visibleColumns.map((col) => (\n <div\n key={col.key}\n className={cx(styles.virtualListCell, classNames?.cell)}\n style={slotStyles?.cell}\n role='gridcell'\n data-fit-key={col.width.type === 'fit' ? col.key : undefined}\n >\n {col.renderCell(item, i)}\n </div>\n ))}\n </>\n ) : (\n renderRow?.(item, i)\n )}\n </VirtualRow>\n );\n };\n\n const visibleRows: React.ReactNode[] = [];\n for (let i = startIndex; i <= endIndex; i++) visibleRows.push(renderRowAt(i));\n\n // Keep the row being touch-dragged mounted even if it scrolls out of the\n // window. Touch events are dispatched to the element under the finger at\n // touchstart; if that row unmounts, the browser stops delivering touchmove\n // and the drag freezes. Pinning its DOM node keeps the gesture alive.\n const draggedIndex = drag.draggingKey != null ? keys.indexOf(drag.draggingKey) : -1;\n if (draggedIndex >= 0 && (draggedIndex < startIndex || draggedIndex > endIndex)) {\n visibleRows.push(renderRowAt(draggedIndex));\n }\n\n return (\n <div\n {...rest}\n ref={rootRef}\n className={cx(\n styles.virtualList,\n drag.draggingKey != null && styles.virtualListReordering,\n className,\n classNames?.root,\n )}\n style={{ height, ...style, ...slotStyles?.root }}\n onScroll={win.handleScroll}\n role={ariaRoles?.container ?? (hasColumns ? 'grid' : 'list')}\n aria-rowcount={hasColumns ? items.length + 1 : undefined}\n >\n {hasColumns && (\n <div\n ref={headerRef}\n className={cx(styles.virtualListHeader, classNames?.header)}\n style={{\n gridTemplateColumns: columnTemplate,\n ...(shouldShowToggle ? { paddingRight: 32 } : {}),\n ...slotStyles?.header,\n }}\n role='row'\n aria-rowindex={1}\n >\n {multiSelect && (\n <div\n className={cx(styles.virtualListSelectCell, classNames?.selectCell)}\n style={slotStyles?.selectCell}\n role='columnheader'\n >\n <Checkbox\n selected={selection.allSelected || selection.someSelected}\n onToggle={selection.handleSelectAll}\n aria-label='Select all rows'\n />\n </div>\n )}\n {visibleColumns.map((col) => (\n <div\n key={col.key}\n className={cx(styles.virtualListHeaderCell, classNames?.headerCell)}\n style={slotStyles?.headerCell}\n role='columnheader'\n data-fit-key={col.width.type === 'fit' ? col.key : undefined}\n >\n {col.label}\n </div>\n ))}\n {shouldShowToggle && (\n <div className={styles.virtualListToggleWrapper}>\n <FloatingMenu\n isOpen={toggleOpen}\n onOpenChange={setToggleOpen}\n align={Align.Right}\n dontCloseOnChildClick\n content={\n <div\n className={cx(styles.virtualListTogglePopover, classNames?.togglePopover)}\n style={slotStyles?.togglePopover}\n >\n {columns!\n .filter((col) => !col.hideFromToggle)\n .map((col) => (\n <Checkbox\n key={col.key}\n className={cx(styles.virtualListToggleItem, classNames?.toggleItem)}\n style={slotStyles?.toggleItem}\n selected={effectiveVisible.has(col.key)}\n onToggle={() => handleToggleColumn(col.key)}\n disabled={col.lockVisible}\n cursorDefault={false}\n >\n {col.toggleLabel ?? col.label}\n </Checkbox>\n ))}\n </div>\n }\n >\n <button\n type='button'\n className={cx(styles.virtualListHeaderToggle, classNames?.headerToggle)}\n style={slotStyles?.headerToggle}\n aria-label='Toggle column visibility'\n >\n <FontAwesomeIcon\n icon={faGear}\n style={slotStyles?.headerToggle as React.CSSProperties & Record<`--fa-font-${string}`, string>}\n />\n </button>\n </FloatingMenu>\n {manuallyHiddenCount > 0 && (\n <span\n className={cx(styles.virtualListColumnChip, classNames?.columnChip)}\n style={slotStyles?.columnChip}\n data-column-hidden-count={manuallyHiddenCount}\n aria-label={`${manuallyHiddenCount} columns hidden`}\n >\n {manuallyHiddenCount}\n </span>\n )}\n </div>\n )}\n </div>\n )}\n {isLoading ? (\n <div className={styles.virtualListLoadingFull}>\n <SpinnerLoading className={styles.virtualListSpinner} />\n </div>\n ) : (\n <div\n className={cx(styles.virtualListBody, classNames?.body)}\n style={{ height: totalHeight, ...slotStyles?.body }}\n >\n {visibleRows}\n </div>\n )}\n {win.showLoadMoreSpinner && (\n <div\n className={cx(styles.virtualListLoadingIndicator, classNames?.loadingIndicator)}\n style={slotStyles?.loadingIndicator}\n >\n <SpinnerLoading className={styles.virtualListSpinner} />\n </div>\n )}\n </div>\n );\n}\n\n// forwardRef erases the generic, so cast back to a generic-preserving signature.\nconst VirtualList = React.forwardRef(VirtualListInner) as <T>(\n props: VirtualListProps<T> & { ref?: React.Ref<VirtualListHandle> },\n) => React.ReactElement | null;\n\nexport default VirtualList;\n"],"mappings":"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"}
1
+ {"version":3,"file":"virtualList.mjs","names":[],"sources":["../../../../package/common/virtualList/virtualList.tsx"],"sourcesContent":["import React, { useMemo, useCallback, useEffect, useId, useImperativeHandle, useRef } from 'react';\nimport cx from 'classnames';\nimport styles from './virtualList.module.pcss';\nimport SpinnerLoading from '../loading/spinnerLoading';\nimport Checkbox from '../checkbox';\nimport VirtualRow from './virtualRow';\nimport VirtualListHeader from './virtualListHeader';\nimport { useVirtualWindow } from './useVirtualWindow';\nimport { useSelection } from './useSelection';\nimport { useColumns } from './useColumns';\nimport { useRowDrag } from './useRowDrag';\nimport { useSorting } from './useSorting';\nimport { useColumnResize } from './useColumnResize';\nimport { useRowNavigation } from './useRowNavigation';\nimport type { VirtualListColumn, VirtualListProps, VirtualListHandle } from './virtualList.types';\n\n// Pure helpers live in ./virtualList.utils; re-exported here so existing\n// imports (and unit tests) keep working against the component entry point.\nexport {\n buildOffsets,\n findStartIndex,\n getVisibleRange,\n resolveReorderTarget,\n resolveTreeDropTarget,\n lastDescendantIndex,\n buildColumnTemplate,\n compareSortValues,\n isColumnSortable,\n isEmptySortValue,\n nextSort,\n sortItems,\n} from './virtualList.utils';\n\n// ─── Orchestrator ─────────────────────────────────────────────────────────────\n// VirtualListInner wires the hooks together and renders the two pieces of\n// markup. Each concern lives in its own module:\n// useSorting — table mode: header sort state, and the sorted row order\n// useVirtualWindow — generic scroll / measurement / windowing (all modes)\n// useColumnResize — table mode: user column widths, and the drag that sets them\n// useSelection — multi-row selection\n// useColumns — table mode: columns, visibility, persistence, fit widths\n// useRowDrag — reorder + tree drag-and-drop (mouse + touch)\n// useRowNavigation — arrow-key navigation over the rows\n// virtualListHeader — the header row: sorting, resizing, the column menu\n// virtualRow — one positioned row, with its drag wiring\n//\n// The hook order is not free: useColumnResize decides whether the layout is\n// frozen, which useColumns needs to build the grid template.\n\nfunction VirtualListInner<T>(\n {\n items,\n renderRow,\n ariaRoles,\n columns,\n height = '100%',\n estimatedRowHeight = 40,\n overscan = 3,\n rowGap = 0,\n rowPadding,\n selectedKey,\n getItemKey,\n onRowClick,\n keyboardNavigation = true,\n visibleColumnKeys,\n onVisibleColumnsChange,\n persistColumnsKey,\n resizableColumns = false,\n columnWidths,\n onColumnWidthsChange,\n resetColumnWidthsLabel = 'Reset column widths',\n sort,\n defaultSort,\n onSortChange,\n showColumnToggle,\n onLoadMore,\n loadMoreThreshold = 100,\n isLoading = false,\n multiSelect = false,\n selectedKeys,\n onSelectionChange,\n reorderable = false,\n onReorder,\n longPressDelay = 400,\n treeReorder = false,\n getItemDepth,\n isGroup,\n onTreeDrop,\n treeIndentPx = 24,\n dragHandle = false,\n showDivider = true,\n rowHover = true,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n }: VirtualListProps<T>,\n ref: React.ForwardedRef<VirtualListHandle>,\n) {\n const { rows, effectiveSort, isSorted, handleHeaderSort } = useSorting<T>({\n items,\n columns,\n sort,\n defaultSort,\n onSortChange,\n });\n\n const getKey = useCallback(\n (item: T, index: number): string | number => (getItemKey ? getItemKey(item, index) : index),\n [getItemKey],\n );\n const keys = useMemo(() => rows.map((item, i) => getKey(item, i)), [rows, getKey]);\n const depths = useMemo(\n () => (getItemDepth ? rows.map((item, i) => getItemDepth(item, i)) : []),\n [rows, getItemDepth],\n );\n const isGroupAt = useCallback((i: number) => (isGroup ? isGroup(rows[i], i) : false), [isGroup, rows]);\n\n const win = useVirtualWindow<T>({\n items: rows,\n keys,\n getItemKey,\n estimatedRowHeight,\n rowGap,\n overscan,\n onLoadMore,\n loadMoreThreshold,\n isLoading,\n });\n const { rootRef, headerRef, rowObserver, keysRef, offsets, totalHeight, startIndex, endIndex } = win;\n\n // Filled from `cols` below: the resize hook only reads the visible set when a\n // handle is actually used, which is what lets it run before the hook that\n // derives it.\n const visibleColumnsRef = useRef<VirtualListColumn<T>[]>([]);\n const columnResize = useColumnResize<T>({\n columns,\n visibleColumnsRef,\n resizableColumns,\n columnWidths,\n onColumnWidthsChange,\n persistColumnsKey,\n rootRef,\n });\n\n const selection = useSelection<T>({ items: rows, getKey, multiSelect, selectedKeys, onSelectionChange });\n const { effectiveSelectedKeys } = selection;\n\n const cols = useColumns<T>({\n columns,\n items: rows,\n multiSelect,\n visibleColumnKeys,\n onVisibleColumnsChange,\n persistColumnsKey,\n showColumnToggle,\n userWidths: columnResize.widths,\n isFrozen: columnResize.isFrozen,\n rootRef,\n startIndex,\n endIndex,\n containerHeight: win.containerHeight,\n containerWidth: win.containerWidth,\n });\n // The rest of `cols` is the header's business and goes there whole.\n const { hasColumns, visibleColumns, columnTemplate, widthVars, shouldShowToggle, recalculateColumns } = cols;\n visibleColumnsRef.current = visibleColumns;\n\n // Compose the forwarded handle from the hooks that own each piece: scroll\n // methods from the windowing engine, column controls from the column hooks.\n useImperativeHandle(ref, () => ({ ...win.scrollApi, recalculateColumns, resetColumnWidths: columnResize.reset }), [\n win.scrollApi,\n recalculateColumns,\n columnResize.reset,\n ]);\n\n // Dragging a row to position N is meaningless while the list decides the\n // order: the drop would be undone by the next sort pass.\n const dragBlockedBySort = isSorted && (reorderable || treeReorder);\n useEffect(() => {\n if (dragBlockedBySort)\n console.warn('VirtualList: row reordering is disabled while the list is sorted. Clear the sort to drag rows.');\n }, [dragBlockedBySort]);\n\n const drag = useRowDrag({\n reorderable: reorderable && !isSorted,\n treeReorder: treeReorder && !isSorted,\n onReorder,\n onTreeDrop,\n longPressDelay,\n dragHandle,\n treeIndentPx,\n keys,\n depths,\n isGroupAt,\n rootRef,\n keysRef,\n });\n\n const listId = useId();\n const rowDomId = (index: number) => `${listId}-row-${index}`;\n const nav = useRowNavigation({\n enabled: keyboardNavigation,\n rowCount: rows.length,\n offsets,\n rootRef,\n headerRef,\n scrollToIndex: win.scrollApi.scrollToIndex,\n initialIndex: selectedKey != null ? Math.max(0, keys.indexOf(selectedKey)) : 0,\n multiSelect,\n onActivate: onRowClick && ((index: number) => onRowClick(rows[index], index)),\n onToggleSelect: (index) => selection.handleRowSelect(keys[index]),\n });\n\n const rowPaddingValue = typeof rowPadding === 'number' ? `${rowPadding}px` : rowPadding;\n\n const renderRowAt = (i: number) => {\n const item = rows[i];\n const key = getKey(item, i);\n const isSelected = (selectedKey != null && key === selectedKey) || effectiveSelectedKeys.has(key);\n const dragProps = drag.getRowDragProps(i, key);\n\n return (\n <VirtualRow\n key={key}\n id={rowDomId(i)}\n index={i}\n top={offsets[i]}\n padding={rowPaddingValue}\n columnTemplate={columnTemplate}\n isSelected={isSelected}\n isActive={i === nav.activeIndex}\n manageTabStops={keyboardNavigation}\n isEntered={i === nav.enteredIndex}\n activeClassName={classNames?.activeRow}\n activeStyle={slotStyles?.activeRow}\n showDivider={showDivider}\n hover={rowHover}\n onClick={() => {\n if (drag.consumeSuppressedClick()) return;\n // Keep the arrows where the pointer last was.\n nav.setActiveIndex(i);\n onRowClick?.(item, i);\n }}\n observer={rowObserver}\n ariaRowIndex={hasColumns ? i + 2 : undefined}\n ariaPosInSet={hasColumns ? undefined : i + 1}\n ariaSetSize={hasColumns ? undefined : rows.length}\n rowRole={ariaRoles?.row ?? (hasColumns ? 'row' : 'listitem')}\n reserveToggleGutter={shouldShowToggle}\n {...dragProps}\n dropIndicatorClassName={classNames?.dropIndicator}\n dropIndicatorStyle={slotStyles?.dropIndicator}\n rowClassName={classNames?.row}\n rowStyle={slotStyles?.row}\n >\n {hasColumns ? (\n <>\n {multiSelect && (\n <div\n className={cx(styles.virtualListSelectCell, classNames?.selectCell)}\n style={slotStyles?.selectCell}\n role='gridcell'\n onClick={(e) => e.stopPropagation()}\n >\n <Checkbox\n selected={effectiveSelectedKeys.has(key)}\n onToggle={() => selection.handleRowSelect(key)}\n aria-label='Select row'\n // One tab stop per rendered row, changing as the list scrolls,\n // would strand the focus the moment a row unmounts. Space on\n // the active row is the keyboard path instead.\n tabIndex={-1}\n />\n </div>\n )}\n {visibleColumns.map((col) => (\n <div\n key={col.key}\n className={cx(styles.virtualListCell, classNames?.cell)}\n style={slotStyles?.cell}\n role='gridcell'\n data-col-key={col.key}\n data-fit-key={col.width.type === 'fit' ? col.key : undefined}\n >\n {col.renderCell(item, i)}\n </div>\n ))}\n </>\n ) : (\n renderRow?.(item, i)\n )}\n </VirtualRow>\n );\n };\n\n const visibleRows: React.ReactNode[] = [];\n for (let i = startIndex; i <= endIndex; i++) visibleRows.push(renderRowAt(i));\n\n // Keep the row being touch-dragged mounted even if it scrolls out of the\n // window. Touch events are dispatched to the element under the finger at\n // touchstart; if that row unmounts, the browser stops delivering touchmove\n // and the drag freezes. Pinning its DOM node keeps the gesture alive.\n const draggedIndex = drag.draggingKey != null ? keys.indexOf(drag.draggingKey) : -1;\n if (draggedIndex >= 0 && (draggedIndex < startIndex || draggedIndex > endIndex)) {\n visibleRows.push(renderRowAt(draggedIndex));\n }\n\n // Same reason, one step further: the row being used holds the focus, and\n // unmounting it would drop that focus to the document body mid-scroll.\n if (nav.enteredIndex >= 0 && (nav.enteredIndex < startIndex || nav.enteredIndex > endIndex)) {\n visibleRows.push(renderRowAt(nav.enteredIndex));\n }\n\n return (\n <div\n {...rest}\n {...nav.containerProps}\n ref={rootRef}\n className={cx(\n styles.virtualList,\n drag.draggingKey != null && styles.virtualListReordering,\n columnResize.isResizing && styles.virtualListResizing,\n className,\n classNames?.root,\n )}\n style={{ height, ...widthVars, ...style, ...slotStyles?.root }}\n onScroll={win.handleScroll}\n role={ariaRoles?.container ?? (hasColumns ? 'grid' : 'list')}\n aria-rowcount={hasColumns ? rows.length + 1 : undefined}\n // Only a composite role may point at an active descendant, and the default\n // `list` is not one. In list mode, pass `ariaRoles` (e.g. listbox/option)\n // to have the active row announced.\n aria-activedescendant={\n nav.activeIndex >= 0 && (ariaRoles?.container ?? (hasColumns ? 'grid' : 'list')) !== 'list'\n ? rowDomId(nav.activeIndex)\n : undefined\n }\n >\n {hasColumns && (\n <VirtualListHeader<T>\n headerRef={headerRef}\n columns={columns!}\n cols={cols}\n resize={columnResize}\n sort={effectiveSort}\n onSort={handleHeaderSort}\n multiSelect={multiSelect}\n allSelected={selection.allSelected}\n someSelected={selection.someSelected}\n onSelectAll={selection.handleSelectAll}\n resetColumnWidthsLabel={resetColumnWidthsLabel}\n classNames={classNames}\n styles={slotStyles}\n />\n )}\n {isLoading ? (\n <div className={styles.virtualListLoadingFull}>\n <SpinnerLoading className={styles.virtualListSpinner} />\n </div>\n ) : (\n <div\n className={cx(styles.virtualListBody, classNames?.body)}\n style={{ height: totalHeight, ...slotStyles?.body }}\n >\n {visibleRows}\n </div>\n )}\n {win.showLoadMoreSpinner && (\n <div\n className={cx(styles.virtualListLoadingIndicator, classNames?.loadingIndicator)}\n style={slotStyles?.loadingIndicator}\n >\n <SpinnerLoading className={styles.virtualListSpinner} />\n </div>\n )}\n </div>\n );\n}\n\n// forwardRef erases the generic, so cast back to a generic-preserving signature.\nconst VirtualList = React.forwardRef(VirtualListInner) as <T>(\n props: VirtualListProps<T> & { ref?: React.Ref<VirtualListHandle> },\n) => React.ReactElement | null;\n\nexport default VirtualList;\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- var e=`virtualList_qBPNc`,t=`virtualList-drag-clone_artnC`,n=`virtualList-header_0Ellb`,r=`virtualList-header-cell_c8NGV`,i=`virtualList-header-toggle_pYFxD`,a=`virtualList-toggle-wrapper_JP32M`,o=`virtualList-toggle-popover_gbpd7`,s=`virtualList-toggle-item_5gu5d`,c=`virtualList-column-chip_nOqr6`,l=`virtualList-select-cell_s9kAy`,u=`virtualList-body_k-fRr`,d=`virtualList-row_TTY50`,f=`virtualList-row-no-divider_Ct0gV`,p=`virtualList-row-no-hover_2d3mH`,m=`virtualList-row-selected_hwEN-`,h=`virtualList-row-draggable_I86ha`,g=`virtualList-row-dragging_6T5tQ`,_=`virtualList-row-drop-inside_Uhi9n`,v=`virtualList-drop-line_EM-g4`,y=`virtualList-drop-line-before_n-0iZ`,b=`virtualList-drop-line-after_2dld-`,x=`virtualList-reordering_Rt9E3`,S=`virtualList-cell_H0zBv`,C=`virtualList-loading-full_wzZNM`,w=`virtualList-loading-indicator_EJced`,T=`virtualList-spinner_apClW`,E={virtualList:e,"virtualList-drag-clone":`virtualList-drag-clone_artnC`,virtualListDragClone:t,"virtualList-header":`virtualList-header_0Ellb`,virtualListHeader:n,"virtualList-header-cell":`virtualList-header-cell_c8NGV`,virtualListHeaderCell:r,"virtualList-header-toggle":`virtualList-header-toggle_pYFxD`,virtualListHeaderToggle:i,"virtualList-toggle-wrapper":`virtualList-toggle-wrapper_JP32M`,virtualListToggleWrapper:a,"virtualList-toggle-popover":`virtualList-toggle-popover_gbpd7`,virtualListTogglePopover:o,"virtualList-toggle-item":`virtualList-toggle-item_5gu5d`,virtualListToggleItem:s,"virtualList-column-chip":`virtualList-column-chip_nOqr6`,virtualListColumnChip:c,"virtualList-select-cell":`virtualList-select-cell_s9kAy`,virtualListSelectCell:l,"virtualList-body":`virtualList-body_k-fRr`,virtualListBody:u,"virtualList-row":`virtualList-row_TTY50`,virtualListRow:d,"virtualList-row-no-divider":`virtualList-row-no-divider_Ct0gV`,virtualListRowNoDivider:f,"virtualList-row-no-hover":`virtualList-row-no-hover_2d3mH`,virtualListRowNoHover:p,"virtualList-row-selected":`virtualList-row-selected_hwEN-`,virtualListRowSelected:m,"virtualList-row-draggable":`virtualList-row-draggable_I86ha`,virtualListRowDraggable:h,"virtualList-row-dragging":`virtualList-row-dragging_6T5tQ`,virtualListRowDragging:g,"virtualList-row-drop-inside":`virtualList-row-drop-inside_Uhi9n`,virtualListRowDropInside:_,"virtualList-drop-line":`virtualList-drop-line_EM-g4`,virtualListDropLine:v,"virtualList-drop-line-before":`virtualList-drop-line-before_n-0iZ`,virtualListDropLineBefore:y,"virtualList-drop-line-after":`virtualList-drop-line-after_2dld-`,virtualListDropLineAfter:b,"virtualList-reordering":`virtualList-reordering_Rt9E3`,virtualListReordering:x,"virtualList-cell":`virtualList-cell_H0zBv`,virtualListCell:S,"virtualList-loading-full":`virtualList-loading-full_wzZNM`,virtualListLoadingFull:C,"virtualList-loading-indicator":`virtualList-loading-indicator_EJced`,virtualListLoadingIndicator:w,"virtualList-spinner":`virtualList-spinner_apClW`,virtualListSpinner:T};export{E as default,e as virtualList,u as virtualListBody,S as virtualListCell,c as virtualListColumnChip,t as virtualListDragClone,v as virtualListDropLine,b as virtualListDropLineAfter,y as virtualListDropLineBefore,n as virtualListHeader,r as virtualListHeaderCell,i as virtualListHeaderToggle,C as virtualListLoadingFull,w as virtualListLoadingIndicator,x as virtualListReordering,d as virtualListRow,h as virtualListRowDraggable,g as virtualListRowDragging,_ as virtualListRowDropInside,f as virtualListRowNoDivider,p as virtualListRowNoHover,m as virtualListRowSelected,l as virtualListSelectCell,T as virtualListSpinner,s as virtualListToggleItem,o as virtualListTogglePopover,a as virtualListToggleWrapper};
1
+ var e=`virtualList_qBPNc`,t=`virtualList-drag-clone_artnC`,n=`virtualList-header_0Ellb`,r=`virtualList-header-cell_c8NGV`,i=`virtualList-header-resize_cbYFc`,a=`virtualList-header-sort_Cdpe3`,o=`virtualList-header-sort-label_6MaZ3`,s=`virtualList-header-sort-icon_Z-Nrd`,c=`virtualList-header-sort-icon-active_g7qrb`,l=`virtualList-header-toggle_pYFxD`,u=`virtualList-toggle-wrapper_JP32M`,d=`virtualList-toggle-popover_gbpd7`,f=`virtualList-toggle-item_5gu5d`,p=`virtualList-toggle-reset_3UANJ`,m=`virtualList-column-chip_nOqr6`,h=`virtualList-select-cell_s9kAy`,g=`virtualList-body_k-fRr`,_=`virtualList-row-active_H5r5-`,v=`virtualList-resizing_28Mn4`,y=`virtualList-row_TTY50`,b=`virtualList-row-no-divider_Ct0gV`,x=`virtualList-row-no-hover_2d3mH`,S=`virtualList-row-selected_hwEN-`,C=`virtualList-row-draggable_I86ha`,w=`virtualList-row-dragging_6T5tQ`,T=`virtualList-row-drop-inside_Uhi9n`,E=`virtualList-drop-line_EM-g4`,D=`virtualList-drop-line-before_n-0iZ`,O=`virtualList-drop-line-after_2dld-`,k=`virtualList-reordering_Rt9E3`,A=`virtualList-cell_H0zBv`,j=`virtualList-loading-full_wzZNM`,M=`virtualList-loading-indicator_EJced`,N=`virtualList-spinner_apClW`,P={virtualList:e,"virtualList-drag-clone":`virtualList-drag-clone_artnC`,virtualListDragClone:t,"virtualList-header":`virtualList-header_0Ellb`,virtualListHeader:n,"virtualList-header-cell":`virtualList-header-cell_c8NGV`,virtualListHeaderCell:r,"virtualList-header-resize":`virtualList-header-resize_cbYFc`,virtualListHeaderResize:i,"virtualList-header-sort":`virtualList-header-sort_Cdpe3`,virtualListHeaderSort:a,"virtualList-header-sort-label":`virtualList-header-sort-label_6MaZ3`,virtualListHeaderSortLabel:o,"virtualList-header-sort-icon":`virtualList-header-sort-icon_Z-Nrd`,virtualListHeaderSortIcon:s,"virtualList-header-sort-icon-active":`virtualList-header-sort-icon-active_g7qrb`,virtualListHeaderSortIconActive:c,"virtualList-header-toggle":`virtualList-header-toggle_pYFxD`,virtualListHeaderToggle:l,"virtualList-toggle-wrapper":`virtualList-toggle-wrapper_JP32M`,virtualListToggleWrapper:u,"virtualList-toggle-popover":`virtualList-toggle-popover_gbpd7`,virtualListTogglePopover:d,"virtualList-toggle-item":`virtualList-toggle-item_5gu5d`,virtualListToggleItem:f,"virtualList-toggle-reset":`virtualList-toggle-reset_3UANJ`,virtualListToggleReset:p,"virtualList-column-chip":`virtualList-column-chip_nOqr6`,virtualListColumnChip:m,"virtualList-select-cell":`virtualList-select-cell_s9kAy`,virtualListSelectCell:h,"virtualList-body":`virtualList-body_k-fRr`,virtualListBody:g,"virtualList-row-active":`virtualList-row-active_H5r5-`,virtualListRowActive:_,"virtualList-resizing":`virtualList-resizing_28Mn4`,virtualListResizing:v,"virtualList-row":`virtualList-row_TTY50`,virtualListRow:y,"virtualList-row-no-divider":`virtualList-row-no-divider_Ct0gV`,virtualListRowNoDivider:b,"virtualList-row-no-hover":`virtualList-row-no-hover_2d3mH`,virtualListRowNoHover:x,"virtualList-row-selected":`virtualList-row-selected_hwEN-`,virtualListRowSelected:S,"virtualList-row-draggable":`virtualList-row-draggable_I86ha`,virtualListRowDraggable:C,"virtualList-row-dragging":`virtualList-row-dragging_6T5tQ`,virtualListRowDragging:w,"virtualList-row-drop-inside":`virtualList-row-drop-inside_Uhi9n`,virtualListRowDropInside:T,"virtualList-drop-line":`virtualList-drop-line_EM-g4`,virtualListDropLine:E,"virtualList-drop-line-before":`virtualList-drop-line-before_n-0iZ`,virtualListDropLineBefore:D,"virtualList-drop-line-after":`virtualList-drop-line-after_2dld-`,virtualListDropLineAfter:O,"virtualList-reordering":`virtualList-reordering_Rt9E3`,virtualListReordering:k,"virtualList-cell":`virtualList-cell_H0zBv`,virtualListCell:A,"virtualList-loading-full":`virtualList-loading-full_wzZNM`,virtualListLoadingFull:j,"virtualList-loading-indicator":`virtualList-loading-indicator_EJced`,virtualListLoadingIndicator:M,"virtualList-spinner":`virtualList-spinner_apClW`,virtualListSpinner:N};export{P as default,e as virtualList,g as virtualListBody,A as virtualListCell,m as virtualListColumnChip,t as virtualListDragClone,E as virtualListDropLine,O as virtualListDropLineAfter,D as virtualListDropLineBefore,n as virtualListHeader,r as virtualListHeaderCell,i as virtualListHeaderResize,a as virtualListHeaderSort,s as virtualListHeaderSortIcon,c as virtualListHeaderSortIconActive,o as virtualListHeaderSortLabel,l as virtualListHeaderToggle,j as virtualListLoadingFull,M as virtualListLoadingIndicator,k as virtualListReordering,v as virtualListResizing,y as virtualListRow,_ as virtualListRowActive,C as virtualListRowDraggable,w as virtualListRowDragging,T as virtualListRowDropInside,b as virtualListRowNoDivider,x as virtualListRowNoHover,S as virtualListRowSelected,h as virtualListSelectCell,N as virtualListSpinner,f as virtualListToggleItem,d as virtualListTogglePopover,p as virtualListToggleReset,u as virtualListToggleWrapper};
2
2
  //# sourceMappingURL=virtualList.module.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"virtualList.module.mjs","names":[],"sources":["../../../../package/common/virtualList/virtualList.module.pcss"],"sourcesContent":[".virtualList {\n overflow-y: auto;\n box-sizing: border-box;\n color: var(--text-color);\n font-family: var(--default-font);\n\n /* Disable the browser's own scroll anchoring — the component does its own\n scroll-position correction when row heights change, and the two fighting\n each other causes the thumb to desync while dragging. */\n overflow-anchor: none;\n\n /* Reserve the scrollbar gutter so rows don't re-wrap (and re-measure) when the\n vertical scrollbar appears/disappears as content grows. */\n scrollbar-gutter: stable;\n\n /* Establish a stacking context so every z-index inside the list (sticky\n header, toggle popover, drag clone) stays scoped to this component and\n can't paint over outside content like a modal. */\n isolation: isolate;\n\n &-drag-clone {\n box-sizing: border-box;\n contain: layout paint style;\n cursor: grabbing;\n\n /* It tracks the finger via transform — never animate that. */\n transition: none;\n }\n\n &-header {\n display: grid;\n position: sticky;\n top: 0;\n\n /* Above focused rows (z-index 1) so a focused row scrolled under the header\n never paints over it; the drag clone (z-index 3) still floats above both. */\n z-index: 2;\n background: var(--background-accent);\n border-bottom: 1px solid var(--border-color);\n box-sizing: border-box;\n\n &-cell {\n padding: 8px 12px;\n font-weight: 600;\n font-size: 13px;\n color: var(--text-dark);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n box-sizing: border-box;\n }\n\n &-toggle {\n background: none;\n border: none;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n padding: 4px 8px;\n color: var(--text-dark);\n display: flex;\n align-items: center;\n border-radius: var(--default-border-radius);\n transition: background 0.15s;\n\n &:hover {\n background: var(--background-accent-light);\n color: var(--text-color);\n }\n }\n }\n\n &-toggle {\n &-wrapper {\n position: absolute;\n right: 0;\n top: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n padding: 0 4px;\n }\n\n /* Chrome (background, shadow, radius, positioning) is provided by the\n FloatingMenu container this renders inside — keep only sizing here. */\n &-popover {\n display: flex;\n flex-direction: column;\n min-width: 160px;\n }\n\n &-item {\n padding: 4px 12px;\n white-space: nowrap;\n transition: background 0.1s;\n font-size: 14px;\n\n &:hover {\n background: var(--background-accent-light);\n }\n }\n }\n\n /* Count badge on the column-toggle cog: how many columns the user has hidden.\n Positioned at the cog's upper-right corner; non-interactive. */\n &-column-chip {\n position: absolute;\n top: 0;\n right: 0;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--primary-color);\n color: var(--text-on-primary);\n font-size: 10px;\n font-weight: 600;\n line-height: 1;\n border-radius: 8px;\n pointer-events: none;\n }\n\n &-select-cell {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 4px;\n box-sizing: border-box;\n }\n\n &-body {\n position: relative;\n box-sizing: border-box;\n }\n\n &-row {\n border-bottom: 1px solid var(--border-color);\n box-sizing: border-box;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition: background 0.1s;\n\n /* Isolate each row's layout/style so measuring or restyling one row can't force\n the whole list to re-layout — keeps fast scrolling smooth. */\n contain: layout style;\n\n &-no-divider {\n border-bottom: none;\n }\n\n &:hover {\n background: var(--background-accent-light);\n }\n\n /* Opt out of the hover highlight (e.g. when each row is a Card that handles\n its own hover). Declared before the `-selected` rule below, so a selected\n row's highlight still wins on hover. */\n &-no-hover:hover {\n background: transparent;\n }\n\n &-selected {\n background: var(--primary-lighter);\n\n &:hover {\n background: var(--primary-lighter);\n }\n }\n\n &-draggable {\n cursor: grab;\n\n /* Stop the mobile long-press from selecting text or showing the callout. */\n user-select: none;\n -webkit-touch-callout: none;\n\n &:active {\n cursor: grabbing;\n }\n }\n\n &-dragging {\n opacity: 0.4;\n }\n\n /* Tree-reorder `inside` drop: the dragged node becomes a child of this row,\n so highlight the whole row instead of drawing a between-rows line. */\n &-drop-inside {\n background: var(--primary-lighter);\n box-shadow: inset 0 0 0 2px var(--primary-color);\n }\n }\n\n /* Tree-reorder `before`/`after` drop line. Positioned inside the (relatively\n positioned absolute) row; `left` is set inline to the target's indent so it\n starts where the dropped content will. */\n &-drop-line {\n position: absolute;\n right: 0;\n height: 2px;\n background: var(--primary-color);\n pointer-events: none;\n z-index: 1;\n\n /* A small knob at the line's start, the usual tree drop-indicator look. */\n &::before {\n content: '';\n position: absolute;\n left: 0;\n top: 50%;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--primary-color);\n transform: translate(-50%, -50%);\n }\n\n &-before {\n top: -1px;\n }\n\n &-after {\n bottom: -1px;\n }\n }\n\n /* While a row is being dragged, suppress hover backgrounds so the pointer\n moving across rows doesn't light up the wrong one, and stop text selection.\n `touch-action: none` stops the touch drag from also scrolling the list\n (only active once the long-press has armed the drag). */\n &-reordering {\n touch-action: none;\n\n .virtualList-row {\n user-select: none;\n\n &:hover {\n background: transparent;\n }\n\n &.virtualList-row-selected:hover {\n background: var(--primary-lighter);\n }\n\n &.virtualList-row-dragging {\n background: transparent;\n }\n }\n }\n\n &-cell {\n padding: 8px 12px;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 14px;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n min-width: 0;\n\n /* `fit` columns are content-sized — keep their cells on a single line so the\n content can't wrap, and so the width measurement reads the true intrinsic\n width. (Other cells are free to wrap for variable-height rows.) */\n &[data-fit-key] {\n white-space: nowrap;\n }\n }\n\n &-loading {\n &-full {\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n }\n\n &-indicator {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 8px 0;\n }\n }\n\n &-spinner {\n width: 32px;\n height: 32px;\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"virtualList.module.mjs","names":[],"sources":["../../../../package/common/virtualList/virtualList.module.pcss"],"sourcesContent":[".virtualList {\n overflow-y: auto;\n box-sizing: border-box;\n color: var(--text-color);\n font-family: var(--default-font);\n\n /* Disable the browser's own scroll anchoring — the component does its own\n scroll-position correction when row heights change, and the two fighting\n each other causes the thumb to desync while dragging. */\n overflow-anchor: none;\n\n /* Reserve the scrollbar gutter so rows don't re-wrap (and re-measure) when the\n vertical scrollbar appears/disappears as content grows. */\n scrollbar-gutter: stable;\n\n /* Establish a stacking context so every z-index inside the list (sticky\n header, toggle popover, drag clone) stays scoped to this component and\n can't paint over outside content like a modal. */\n isolation: isolate;\n\n &-drag-clone {\n box-sizing: border-box;\n contain: layout paint style;\n cursor: grabbing;\n\n /* It tracks the finger via transform — never animate that. */\n transition: none;\n }\n\n &-header {\n display: grid;\n position: sticky;\n top: 0;\n\n /* Frozen layouts are exact pixels and may total more than the viewport, so\n the header and the row canvas below both take that total and the list\n scrolls sideways. `--vl-total` is only set once a column has been\n resized; until then this resolves to 100% and nothing changes. */\n width: max(100%, var(--vl-total, 0px));\n\n /* Above focused rows (z-index 1) so a focused row scrolled under the header\n never paints over it; the drag clone (z-index 3) still floats above both. */\n z-index: 2;\n background: var(--background-accent);\n border-bottom: 1px solid var(--border-color);\n box-sizing: border-box;\n\n &-cell {\n padding: 8px 12px;\n font-weight: 600;\n font-size: 13px;\n color: var(--text-dark);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n box-sizing: border-box;\n\n /* Anchors the resize handle to the cell's trailing edge. */\n position: relative;\n\n /* The cell, not the button inside it, carries the header's tab stop, so\n this is the ring a keyboard user follows. Inset because the cell clips\n its overflow and an outward outline would be cut off. */\n &:focus-visible {\n outline: 1px solid var(--focus-border-color, var(--primary-color));\n outline-offset: -1px;\n }\n }\n\n /* Resize handle: a grab strip on the column's trailing edge, wider than the\n line it sits on because a 1px target is unhittable. It stays inside the\n cell rather than straddling the edge, since the cell clips its overflow\n to keep a long header out of its neighbour. */\n &-resize {\n position: absolute;\n top: 0;\n bottom: 0;\n right: 0;\n width: 10px;\n cursor: col-resize;\n -webkit-tap-highlight-color: transparent;\n touch-action: none;\n\n /* The line itself, drawn on the trailing edge and revealed on hover. */\n &::after {\n content: '';\n position: absolute;\n top: 4px;\n bottom: 4px;\n right: 0;\n width: 2px;\n border-radius: 1px;\n background: var(--primary-color);\n opacity: 0;\n transition: opacity 0.12s;\n }\n\n &:hover::after,\n &:active::after {\n opacity: 1;\n }\n }\n\n /* The clickable part of a sortable header cell. Strips the button chrome and\n inherits the cell's typography, so a sortable header looks identical to a\n plain one until it's hovered. */\n &-sort {\n font: inherit;\n color: inherit;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n max-width: 100%;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n\n &:focus-visible {\n outline: 1px solid var(--focus-border-color, var(--primary-color));\n outline-offset: 2px;\n }\n\n /* The cell's own ellipsis can't apply once its content is a flex button,\n so the truncation moves onto the label. */\n &-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &-icon {\n flex: none;\n font-size: 11px;\n opacity: 0.35;\n transition:\n opacity 0.15s,\n color 0.15s;\n\n &-active {\n opacity: 1;\n color: var(--primary-color);\n }\n }\n\n &:hover &-icon {\n opacity: 0.75;\n }\n\n &:hover &-icon-active {\n opacity: 1;\n }\n }\n\n &-toggle {\n background: none;\n border: none;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n padding: 4px 8px;\n color: var(--text-dark);\n display: flex;\n align-items: center;\n border-radius: var(--default-border-radius);\n transition: background 0.15s;\n\n &:hover {\n background: var(--background-accent-light);\n color: var(--text-color);\n }\n }\n }\n\n &-toggle {\n &-wrapper {\n position: absolute;\n right: 0;\n top: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n padding: 0 4px;\n }\n\n /* Chrome (background, shadow, radius, positioning) is provided by the\n FloatingMenu container this renders inside — keep only sizing here. */\n &-popover {\n display: flex;\n flex-direction: column;\n min-width: 160px;\n }\n\n &-item {\n padding: 4px 12px;\n white-space: nowrap;\n transition: background 0.1s;\n font-size: 14px;\n\n &:hover {\n background: var(--background-accent-light);\n }\n }\n\n /* Only rendered once a column has actually been resized, so it reads as an\n undo for something the user just did rather than a permanent option. */\n &-reset {\n margin-top: 4px;\n padding: 6px 12px;\n border: none;\n border-top: 1px solid var(--border-color);\n background: none;\n font: inherit;\n font-size: 13px;\n color: var(--text-color);\n text-align: left;\n white-space: nowrap;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition: background 0.1s;\n\n &:hover {\n background: var(--background-accent-light);\n }\n }\n }\n\n /* Count badge on the column-toggle cog: how many columns the user has hidden.\n Positioned at the cog's upper-right corner; non-interactive. */\n &-column-chip {\n position: absolute;\n top: 0;\n right: 0;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--primary-color);\n color: var(--text-on-primary);\n font-size: 10px;\n font-weight: 600;\n line-height: 1;\n border-radius: 8px;\n pointer-events: none;\n }\n\n &-select-cell {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 4px;\n box-sizing: border-box;\n\n /* Only the header's select-all cell is focusable; the ones in rows are not. */\n &:focus-visible {\n outline: 1px solid var(--focus-border-color, var(--primary-color));\n outline-offset: -1px;\n }\n }\n\n &-body {\n position: relative;\n box-sizing: border-box;\n\n /* Rows are absolute with left/right 0, so they inherit this width and stay\n aligned with the header when the table is wider than the viewport. */\n width: max(100%, var(--vl-total, 0px));\n }\n\n /* The list itself holds the tab stop, so the active row cannot use a\n `:focus-visible` of its own. Ringing the list instead would mark the whole\n table rather than the row the arrow keys are on, so the list drops its own\n outline and lends it to that row. Scoped to `:focus-visible`, a mouse user\n never sees it. */\n &:focus-visible {\n outline: none;\n\n & .virtualList-row-active {\n outline: 2px solid var(--focus-border-color, var(--primary-color));\n outline-offset: -2px;\n }\n }\n\n /* A resize drag travels over the rows, and their hover highlight following the\n pointer is noise. Inert rather than a `:hover` override, which would have to\n out-specify the selected-row rule to work. The drag itself is unaffected:\n pointer capture bypasses hit testing. */\n &-resizing &-body {\n pointer-events: none;\n }\n\n &-row {\n border-bottom: 1px solid var(--border-color);\n box-sizing: border-box;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition: background 0.1s;\n\n /* Isolate each row's layout/style so measuring or restyling one row can't force\n the whole list to re-layout — keeps fast scrolling smooth. */\n contain: layout style;\n\n &-no-divider {\n border-bottom: none;\n }\n\n &:hover {\n background: var(--background-accent-light);\n }\n\n /* Opt out of the hover highlight (e.g. when each row is a Card that handles\n its own hover). Declared before the `-selected` rule below, so a selected\n row's highlight still wins on hover. */\n &-no-hover:hover {\n background: transparent;\n }\n\n &-selected {\n background: var(--primary-lighter);\n\n &:hover {\n background: var(--primary-lighter);\n }\n }\n\n &-draggable {\n cursor: grab;\n\n /* Stop the mobile long-press from selecting text or showing the callout. */\n user-select: none;\n -webkit-touch-callout: none;\n\n &:active {\n cursor: grabbing;\n }\n }\n\n &-dragging {\n opacity: 0.4;\n }\n\n /* Tree-reorder `inside` drop: the dragged node becomes a child of this row,\n so highlight the whole row instead of drawing a between-rows line. */\n &-drop-inside {\n background: var(--primary-lighter);\n box-shadow: inset 0 0 0 2px var(--primary-color);\n }\n }\n\n /* Tree-reorder `before`/`after` drop line. Positioned inside the (relatively\n positioned absolute) row; `left` is set inline to the target's indent so it\n starts where the dropped content will. */\n &-drop-line {\n position: absolute;\n right: 0;\n height: 2px;\n background: var(--primary-color);\n pointer-events: none;\n z-index: 1;\n\n /* A small knob at the line's start, the usual tree drop-indicator look. */\n &::before {\n content: '';\n position: absolute;\n left: 0;\n top: 50%;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--primary-color);\n transform: translate(-50%, -50%);\n }\n\n &-before {\n top: -1px;\n }\n\n &-after {\n bottom: -1px;\n }\n }\n\n /* While a row is being dragged, suppress hover backgrounds so the pointer\n moving across rows doesn't light up the wrong one, and stop text selection.\n `touch-action: none` stops the touch drag from also scrolling the list\n (only active once the long-press has armed the drag). */\n &-reordering {\n touch-action: none;\n\n .virtualList-row {\n user-select: none;\n\n &:hover {\n background: transparent;\n }\n\n &.virtualList-row-selected:hover {\n background: var(--primary-lighter);\n }\n\n &.virtualList-row-dragging {\n background: transparent;\n }\n }\n }\n\n &-cell {\n padding: 8px 12px;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 14px;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n min-width: 0;\n\n /* `fit` columns are content-sized — keep their cells on a single line so the\n content can't wrap, and so the width measurement reads the true intrinsic\n width. (Other cells are free to wrap for variable-height rows.) */\n &[data-fit-key] {\n white-space: nowrap;\n }\n }\n\n &-loading {\n &-full {\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n }\n\n &-indicator {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 8px 0;\n }\n }\n\n &-spinner {\n width: 32px;\n height: 32px;\n }\n}\n"],"mappings":""}
@@ -1,2 +1,2 @@
1
- function e(e,t,n,r=0){let i=e.length,a=Array(i+1);a[0]=0;for(let o=0;o<i;o++){let s=o<i-1?r:0;a[o+1]=a[o]+(t.get(e[o])??n)+s}return{offsets:a,totalHeight:a[i]}}function t(e,t,n){if(n<=0)return 0;let r=0,i=n-1;for(;r<i;){let n=r+i>>1;e[n+1]<=t?r=n+1:i=n}return r}function n(e,n,r,i,a){if(a===0)return{startIndex:0,endIndex:-1};let o=t(e,n,a),s=o;for(;s<a&&e[s]<n+r;)s++;return{startIndex:Math.max(0,o-i),endIndex:Math.min(a-1,s+i-1)}}function r(e,t,n,r,i){if(e===-1||e===t)return null;let a=r+i/2;return(t>e?n>=a:n<=a)?t:null}function i(e,t,n,r){if(n<=0)return`before`;let i=(e-t)/n;return r?i<1/3?`before`:i>2/3?`after`:`inside`:i<.5?`before`:`after`}function a(e,t){let n=t[e],r=e+1;for(;r<t.length&&t[r]>n;)r++;return r-1}function o(e,t){return e.length===0?`1fr`:e.map(e=>{switch(e.width.type){case`fixed`:return`${e.width.px}px`;case`flex`:return`${e.width.weight??1}fr`;case`fit`:{let n=t?.[e.key];return n==null?`fit-content(100%)`:`${n}px`}}}).join(` `)}var s=`ahroweui:virtuallist:columns:`;function c(e){if(!e||typeof window>`u`)return null;try{let t=window.localStorage.getItem(s+e);if(!t)return null;let n=JSON.parse(t);if(n&&Array.isArray(n.visible)&&Array.isArray(n.known))return{visible:n.visible.filter(e=>typeof e==`string`),known:n.known.filter(e=>typeof e==`string`)}}catch{}return null}function l(e,t){return e.lockVisible?!0:t&&t.known.includes(e.key)?t.visible.includes(e.key):!e.defaultHidden}export{s as COLUMN_STORAGE_PREFIX,o as buildColumnTemplate,e as buildOffsets,t as findStartIndex,n as getVisibleRange,l as isColumnInitiallyVisible,a as lastDescendantIndex,c as loadPersistedColumns,r as resolveReorderTarget,i as resolveTreeDropTarget};
1
+ function e(e,t,n,r=0){let i=e.length,a=Array(i+1);a[0]=0;for(let o=0;o<i;o++){let s=o<i-1?r:0;a[o+1]=a[o]+(t.get(e[o])??n)+s}return{offsets:a,totalHeight:a[i]}}function t(e,t,n){if(n<=0)return 0;let r=0,i=n-1;for(;r<i;){let n=r+i>>1;e[n+1]<=t?r=n+1:i=n}return r}function n(e,n,r,i,a){if(a===0)return{startIndex:0,endIndex:-1};let o=t(e,n,a),s=o;for(;s<a&&e[s]<n+r;)s++;return{startIndex:Math.max(0,o-i),endIndex:Math.min(a-1,s+i-1)}}function r(e,t,n,r,i){if(e===-1||e===t)return null;let a=r+i/2;return(t>e?n>=a:n<=a)?t:null}function i(e,t,n,r){if(n<=0)return`before`;let i=(e-t)/n;return r?i<1/3?`before`:i>2/3?`after`:`inside`:i<.5?`before`:`after`}function a(e,t){let n=t[e],r=e+1;for(;r<t.length&&t[r]>n;)r++;return r-1}function o(e,t){return e.length===0?`1fr`:e.map(e=>{switch(e.width.type){case`fixed`:return`${e.width.px}px`;case`flex`:return`${e.width.weight??1}fr`;case`fit`:{let n=t?.[e.key];return n==null?`fit-content(100%)`:`${n}px`}}}).join(` `)}var s=`a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])`,c=`data-vl-parked`,l=`${s}, [${c}]`;function u(e){let t=e.scrollWidth>e.clientWidth,n=e.scrollWidth;if(!t&&!e.firstElementChild)return n;let r=getComputedStyle(e),i=parseFloat(r.paddingLeft)||0,a=parseFloat(r.paddingRight)||0;if(t&&(n+=a),e.firstElementChild&&!t&&e.scrollWidth<=i+a+1){let t=1/0,r=-1/0;if(e.querySelectorAll(`*`).forEach(e=>{let n=e.getBoundingClientRect();n.left<t&&(t=n.left),n.right>r&&(r=n.right)}),r>t){let e=Math.ceil(r-t)+i+a;e>n&&(n=e)}}return n}function d(e){return`[data-col-key="${e.replace(/["\\]/g,`\\$&`)}"]`}function f(e){return`--vl-col-${e.replace(/[^a-zA-Z0-9_-]/g,`_`)}`}function p(e){return e.width.type===`fixed`?e.width.px:e.width.type===`fit`?160:160*(e.width.weight??1)}function m(e){return e.length===0?`1fr`:e.map(e=>`var(${f(e.key)}, 160px)`).join(` `)}var h=`ahroweui:virtuallist:columns:`;function g(e){if(!e||typeof window>`u`)return null;try{let t=window.localStorage.getItem(h+e);if(!t)return null;let n=JSON.parse(t);if(n&&Array.isArray(n.visible)&&Array.isArray(n.known)){let e={};if(n.widths&&typeof n.widths==`object`)for(let[t,r]of Object.entries(n.widths))typeof r==`number`&&Number.isFinite(r)&&r>0&&(e[t]=r);return{visible:n.visible.filter(e=>typeof e==`string`),known:n.known.filter(e=>typeof e==`string`),widths:e}}}catch{}return null}function _(e,t){return e.lockVisible?!0:t&&t.known.includes(e.key)?t.visible.includes(e.key):!e.defaultHidden}var v;function y(){return v??=new Intl.Collator(void 0,{numeric:!0,sensitivity:`base`}),v}function b(e){return e==null||typeof e==`number`&&Number.isNaN(e)}function x(e,t){let n=e instanceof Date?e.getTime():typeof e==`boolean`?Number(e):e,r=t instanceof Date?t.getTime():typeof t==`boolean`?Number(t):t;return typeof n==`string`||typeof r==`string`?y().compare(String(n),String(r)):n<r?-1:+(n>r)}function S(e){return e.sortValue!=null||e.compare!=null||e.sortable===!0}function C(e,t){return!e||e.key!==t?{key:t,direction:`asc`}:e.direction===`asc`?{key:t,direction:`desc`}:null}function w(e,t,n){if(!t||!n)return e;let r=n.find(e=>e.key===t.key);if(!r||r.compare==null&&r.sortValue==null)return e;let i=t.direction===`asc`?1:-1,{compare:a,sortValue:o}=r,s=e.map((e,t)=>({item:e,index:t}));return s.sort((e,t)=>{let n;if(a)n=i*a(e.item,t.item);else{let r=o(e.item),a=o(t.item),s=b(r),c=b(a);n=s||c?s===c?0:s?1:-1:i*x(r,a)}return n===0?e.index-t.index:n}),s.map(e=>e.item)}export{h as COLUMN_STORAGE_PREFIX,l as ROW_ENTER_SELECTOR,s as ROW_FOCUSABLE_SELECTOR,c as ROW_PARKED_ATTRIBUTE,o as buildColumnTemplate,m as buildFrozenTemplate,e as buildOffsets,d as columnCellSelector,f as columnWidthVar,x as compareSortValues,t as findStartIndex,n as getVisibleRange,_ as isColumnInitiallyVisible,S as isColumnSortable,b as isEmptySortValue,a as lastDescendantIndex,g as loadPersistedColumns,u as measureCellWidth,C as nextSort,p as resolveFrozenWidth,r as resolveReorderTarget,i as resolveTreeDropTarget,w as sortItems};
2
2
  //# sourceMappingURL=virtualList.utils.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"virtualList.utils.mjs","names":[],"sources":["../../../../package/common/virtualList/virtualList.utils.ts"],"sourcesContent":["import type { ColumnWidthSpec, VirtualListColumn } from './virtualList.types';\n\n// ─── Layout / windowing math ──────────────────────────────────────────────────\n\nexport function buildOffsets(\n keys: (string | number)[],\n heightsMap: Map<string | number, number>,\n estimatedRowHeight: number,\n rowGap = 0,\n): { offsets: number[]; totalHeight: number } {\n const itemCount = keys.length;\n const offsets = new Array<number>(itemCount + 1);\n offsets[0] = 0;\n for (let i = 0; i < itemCount; i++) {\n const gap = i < itemCount - 1 ? rowGap : 0;\n // Heights are cached by item key, not index, so a measured row keeps its\n // height across reorders/filters instead of inheriting its neighbour's.\n offsets[i + 1] = offsets[i] + (heightsMap.get(keys[i]) ?? estimatedRowHeight) + gap;\n }\n return { offsets, totalHeight: offsets[itemCount] };\n}\n\n/** Index of the row that sits at `scrollTop` — i.e. the first row whose bottom edge is below it. */\nexport function findStartIndex(offsets: number[], scrollTop: number, itemCount: number): number {\n if (itemCount <= 0) return 0;\n let lo = 0;\n let hi = itemCount - 1;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (offsets[mid + 1] <= scrollTop) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n}\n\nexport function getVisibleRange(\n offsets: number[],\n scrollTop: number,\n containerHeight: number,\n overscan: number,\n itemCount: number,\n): { startIndex: number; endIndex: number } {\n if (itemCount === 0) return { startIndex: 0, endIndex: -1 };\n\n const lo = findStartIndex(offsets, scrollTop, itemCount);\n\n let end = lo;\n while (end < itemCount && offsets[end] < scrollTop + containerHeight) end++;\n\n return {\n startIndex: Math.max(0, lo - overscan),\n endIndex: Math.min(itemCount - 1, end + overscan - 1),\n };\n}\n\n// ─── Reorder / tree-drop math ─────────────────────────────────────────────────\n\n/**\n * Decides where a dragged row should move to, applying midpoint hysteresis: a\n * move only happens once the pointer has crossed the centre of the row it is\n * over (downwards when moving down, upwards when moving up). Returns the target\n * index, or null if no move should occur. Pure for easy unit testing.\n */\nexport function resolveReorderTarget(\n fromIndex: number,\n overIndex: number,\n pointerY: number,\n rowTop: number,\n rowHeight: number,\n): number | null {\n if (fromIndex === -1 || fromIndex === overIndex) return null;\n const midpoint = rowTop + rowHeight / 2;\n const movingDown = overIndex > fromIndex;\n if (movingDown ? pointerY >= midpoint : pointerY <= midpoint) return overIndex;\n return null;\n}\n\n/**\n * Decides where a tree-reorder drop should land relative to the row under the\n * pointer. Group rows expose three zones (top third → `before`, middle →\n * `inside`, bottom third → `after`); leaf rows split in half (`before`/`after`)\n * since they can't receive children. Pure for easy unit testing.\n */\nexport function resolveTreeDropTarget(\n pointerY: number,\n rowTop: number,\n rowHeight: number,\n isGroup: boolean,\n): 'before' | 'inside' | 'after' {\n if (rowHeight <= 0) return 'before';\n const rel = (pointerY - rowTop) / rowHeight;\n if (isGroup) {\n if (rel < 1 / 3) return 'before';\n if (rel > 2 / 3) return 'after';\n return 'inside';\n }\n return rel < 0.5 ? 'before' : 'after';\n}\n\n/**\n * Index of the last descendant of the node at `index` in a depth-tagged flat\n * list (a contiguous run of following rows with a greater depth). Returns\n * `index` itself when the node has no children. A drop is forbidden anywhere in\n * `[index, lastDescendantIndex]` — that's the node and its own subtree.\n */\nexport function lastDescendantIndex(index: number, depths: number[]): number {\n const d = depths[index];\n let i = index + 1;\n while (i < depths.length && depths[i] > d) i++;\n return i - 1;\n}\n\n// ─── Column template ──────────────────────────────────────────────────────────\n\nexport function buildColumnTemplate(\n columns: { key: string; width: ColumnWidthSpec }[],\n fitWidths?: Record<string, number>,\n): string {\n if (columns.length === 0) return '1fr';\n return columns\n .map((col) => {\n switch (col.width.type) {\n case 'fixed':\n return `${col.width.px}px`;\n case 'flex':\n return `${col.width.weight ?? 1}fr`;\n // A `fit` track is content-sized, so it resolves independently in each\n // grid (the header and every row are separate grids) and would drift\n // out of alignment. Once we've measured the column's natural width\n // across all those grids, pin it to that exact px so every grid uses an\n // identical track. Until then, fall back to content-sizing.\n case 'fit': {\n const w = fitWidths?.[col.key];\n return w != null ? `${w}px` : 'fit-content(100%)';\n }\n }\n })\n .join(' ');\n}\n\n// ─── Column-visibility persistence ────────────────────────────────────────────\n\nexport const COLUMN_STORAGE_PREFIX = 'ahroweui:virtuallist:columns:';\n\n// We persist both the visible keys and the full set of columns that existed at\n// save time. That lets us tell \"the user hid this column\" from \"this column\n// didn't exist yet\" when columns are added later — the former stays hidden, the\n// latter falls back to its defaultHidden value.\nexport interface PersistedColumns {\n visible: string[];\n known: string[];\n}\n\nexport function loadPersistedColumns(persistKey: string | undefined): PersistedColumns | null {\n if (!persistKey || typeof window === 'undefined') return null;\n try {\n const raw = window.localStorage.getItem(COLUMN_STORAGE_PREFIX + persistKey);\n if (!raw) return null;\n const parsed = JSON.parse(raw);\n if (parsed && Array.isArray(parsed.visible) && Array.isArray(parsed.known)) {\n return {\n visible: parsed.visible.filter((k: unknown): k is string => typeof k === 'string'),\n known: parsed.known.filter((k: unknown): k is string => typeof k === 'string'),\n };\n }\n } catch {\n // Corrupt/unavailable storage — fall back to defaults.\n }\n return null;\n}\n\n/** Whether a column should start visible, honouring saved settings then `defaultHidden`. */\nexport function isColumnInitiallyVisible<T>(col: VirtualListColumn<T>, persisted: PersistedColumns | null): boolean {\n // A locked column is always visible and can't be hidden — ignore saved state.\n if (col.lockVisible) return true;\n if (persisted && persisted.known.includes(col.key)) return persisted.visible.includes(col.key);\n return !col.defaultHidden;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"virtualList.utils.mjs","names":[],"sources":["../../../../package/common/virtualList/virtualList.utils.ts"],"sourcesContent":["import type { ColumnWidthSpec, SortValue, VirtualListColumn, VirtualListSort } from './virtualList.types';\n\n// ─── Layout / windowing math ──────────────────────────────────────────────────\n\nexport function buildOffsets(\n keys: (string | number)[],\n heightsMap: Map<string | number, number>,\n estimatedRowHeight: number,\n rowGap = 0,\n): { offsets: number[]; totalHeight: number } {\n const itemCount = keys.length;\n const offsets = new Array<number>(itemCount + 1);\n offsets[0] = 0;\n for (let i = 0; i < itemCount; i++) {\n const gap = i < itemCount - 1 ? rowGap : 0;\n // Heights are cached by item key, not index, so a measured row keeps its\n // height across reorders/filters instead of inheriting its neighbour's.\n offsets[i + 1] = offsets[i] + (heightsMap.get(keys[i]) ?? estimatedRowHeight) + gap;\n }\n return { offsets, totalHeight: offsets[itemCount] };\n}\n\n/** Index of the row that sits at `scrollTop` — i.e. the first row whose bottom edge is below it. */\nexport function findStartIndex(offsets: number[], scrollTop: number, itemCount: number): number {\n if (itemCount <= 0) return 0;\n let lo = 0;\n let hi = itemCount - 1;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (offsets[mid + 1] <= scrollTop) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n}\n\nexport function getVisibleRange(\n offsets: number[],\n scrollTop: number,\n containerHeight: number,\n overscan: number,\n itemCount: number,\n): { startIndex: number; endIndex: number } {\n if (itemCount === 0) return { startIndex: 0, endIndex: -1 };\n\n const lo = findStartIndex(offsets, scrollTop, itemCount);\n\n let end = lo;\n while (end < itemCount && offsets[end] < scrollTop + containerHeight) end++;\n\n return {\n startIndex: Math.max(0, lo - overscan),\n endIndex: Math.min(itemCount - 1, end + overscan - 1),\n };\n}\n\n// ─── Reorder / tree-drop math ─────────────────────────────────────────────────\n\n/**\n * Decides where a dragged row should move to, applying midpoint hysteresis: a\n * move only happens once the pointer has crossed the centre of the row it is\n * over (downwards when moving down, upwards when moving up). Returns the target\n * index, or null if no move should occur. Pure for easy unit testing.\n */\nexport function resolveReorderTarget(\n fromIndex: number,\n overIndex: number,\n pointerY: number,\n rowTop: number,\n rowHeight: number,\n): number | null {\n if (fromIndex === -1 || fromIndex === overIndex) return null;\n const midpoint = rowTop + rowHeight / 2;\n const movingDown = overIndex > fromIndex;\n if (movingDown ? pointerY >= midpoint : pointerY <= midpoint) return overIndex;\n return null;\n}\n\n/**\n * Decides where a tree-reorder drop should land relative to the row under the\n * pointer. Group rows expose three zones (top third → `before`, middle →\n * `inside`, bottom third → `after`); leaf rows split in half (`before`/`after`)\n * since they can't receive children. Pure for easy unit testing.\n */\nexport function resolveTreeDropTarget(\n pointerY: number,\n rowTop: number,\n rowHeight: number,\n isGroup: boolean,\n): 'before' | 'inside' | 'after' {\n if (rowHeight <= 0) return 'before';\n const rel = (pointerY - rowTop) / rowHeight;\n if (isGroup) {\n if (rel < 1 / 3) return 'before';\n if (rel > 2 / 3) return 'after';\n return 'inside';\n }\n return rel < 0.5 ? 'before' : 'after';\n}\n\n/**\n * Index of the last descendant of the node at `index` in a depth-tagged flat\n * list (a contiguous run of following rows with a greater depth). Returns\n * `index` itself when the node has no children. A drop is forbidden anywhere in\n * `[index, lastDescendantIndex]` — that's the node and its own subtree.\n */\nexport function lastDescendantIndex(index: number, depths: number[]): number {\n const d = depths[index];\n let i = index + 1;\n while (i < depths.length && depths[i] > d) i++;\n return i - 1;\n}\n\n// ─── Column template ──────────────────────────────────────────────────────────\n\nexport function buildColumnTemplate(\n columns: { key: string; width: ColumnWidthSpec }[],\n fitWidths?: Record<string, number>,\n): string {\n if (columns.length === 0) return '1fr';\n return columns\n .map((col) => {\n switch (col.width.type) {\n case 'fixed':\n return `${col.width.px}px`;\n case 'flex':\n return `${col.width.weight ?? 1}fr`;\n // A `fit` track is content-sized, so it resolves independently in each\n // grid (the header and every row are separate grids) and would drift\n // out of alignment. Once we've measured the column's natural width\n // across all those grids, pin it to that exact px so every grid uses an\n // identical track. Until then, fall back to content-sizing.\n case 'fit': {\n const w = fitWidths?.[col.key];\n return w != null ? `${w}px` : 'fit-content(100%)';\n }\n }\n })\n .join(' ');\n}\n\n// ─── Row focus ────────────────────────────────────────────────────────────────\n\n/**\n * What counts as a control inside a row, and so what gets held out of the tab\n * order while its row is not the one being used.\n *\n * `[tabindex=\"-1\"]` is deliberately excluded. The row's own select checkbox sits\n * there and is reached with Space, and an element the consumer has already taken\n * out of the tab order stays out.\n */\nexport const ROW_FOCUSABLE_SELECTOR =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n/** Marks an element parked at `tabindex=\"-1\"` by the list rather than by the consumer. */\nexport const ROW_PARKED_ATTRIBUTE = 'data-vl-parked';\n\n/**\n * What `F2` steps into: the controls of a row, including the ones parked out of\n * the tab order. Without the second half it would find nothing at all in a row\n * whose controls are custom elements carrying a `tabindex`, because parking them\n * is exactly what takes them out of the selector above.\n */\nexport const ROW_ENTER_SELECTOR = `${ROW_FOCUSABLE_SELECTOR}, [${ROW_PARKED_ATTRIBUTE}]`;\n\n// ─── Column width measurement ─────────────────────────────────────────────────\n\n/**\n * The content width of one cell, in px. `scrollWidth` reports the true content\n * width even when the cell is pinned narrower than its content, which is what\n * lets a column recover from having been measured while it was still empty.\n *\n * Two corrections on top of it, both only resolved when they can apply, so the\n * hot scroll path stays cheap for a plain in-flow cell:\n *\n * - `scrollWidth` includes the left padding but drops the right one once the\n * content overflows, so add it back.\n * - A cell whose only children are FontAwesome icons reports just its padding:\n * their boxes overflow via `overflow: visible`, carry no intrinsic width, and\n * so never count towards `scrollWidth` (right-aligned, they even overflow\n * into negative offsets). Only then walk the descendants for their true span.\n * Once a real width pins, the cell is no longer collapsed and this stops.\n */\nexport function measureCellWidth(el: HTMLElement): number {\n const overflowing = el.scrollWidth > el.clientWidth;\n let width = el.scrollWidth;\n if (!overflowing && !el.firstElementChild) return width;\n\n const cs = getComputedStyle(el);\n const padLeft = parseFloat(cs.paddingLeft) || 0;\n const padRight = parseFloat(cs.paddingRight) || 0;\n if (overflowing) width += padRight;\n\n if (el.firstElementChild && !overflowing && el.scrollWidth <= padLeft + padRight + 1) {\n let minLeft = Infinity;\n let maxRight = -Infinity;\n el.querySelectorAll('*').forEach((d) => {\n const r = d.getBoundingClientRect();\n if (r.left < minLeft) minLeft = r.left;\n if (r.right > maxRight) maxRight = r.right;\n });\n if (maxRight > minLeft) {\n const span = Math.ceil(maxRight - minLeft) + padLeft + padRight;\n if (span > width) width = span;\n }\n }\n return width;\n}\n\n/** Attribute selector for one column's cells. Column keys are arbitrary strings. */\nexport function columnCellSelector(key: string): string {\n return `[data-col-key=\"${key.replace(/[\"\\\\]/g, '\\\\$&')}\"]`;\n}\n\n// ─── Column resizing ──────────────────────────────────────────────────────────\n\n/** Floor for a user-resized column, unless the column raises it with `minWidth`. */\nexport const MIN_COLUMN_WIDTH = 48;\n\n/**\n * Width given to a column that gains one while the layout is already frozen,\n * i.e. one revealed from the gear menu after the first resize. Every visible\n * column has to be an exact px track for the total width (and so the horizontal\n * scroll range) to be exact, and there is nothing measured to go on yet.\n */\nexport const DEFAULT_FROZEN_WIDTH = 160;\n\n/**\n * Custom property carrying one column's width. Column keys are arbitrary\n * strings, so anything a custom property can't contain is folded to `_`; two\n * keys that differ only in those characters would share a track.\n */\nexport function columnWidthVar(key: string): string {\n return `--vl-col-${key.replace(/[^a-zA-Z0-9_-]/g, '_')}`;\n}\n\n/** Px width for a column the user hasn't sized, at the moment the layout freezes. */\nexport function resolveFrozenWidth<T>(col: VirtualListColumn<T>): number {\n if (col.width.type === 'fixed') return col.width.px;\n if (col.width.type === 'fit') return DEFAULT_FROZEN_WIDTH;\n return DEFAULT_FROZEN_WIDTH * (col.width.weight ?? 1);\n}\n\n/**\n * Grid template for a frozen layout: every column reads its own custom property,\n * so a drag can rewrite one width with a single DOM write and have the header\n * and every row follow in the same layout pass, without React rendering.\n */\nexport function buildFrozenTemplate<T>(columns: VirtualListColumn<T>[]): string {\n if (columns.length === 0) return '1fr';\n return columns.map((col) => `var(${columnWidthVar(col.key)}, ${DEFAULT_FROZEN_WIDTH}px)`).join(' ');\n}\n\n// ─── Column-visibility persistence ────────────────────────────────────────────\n\nexport const COLUMN_STORAGE_PREFIX = 'ahroweui:virtuallist:columns:';\n\n// We persist both the visible keys and the full set of columns that existed at\n// save time. That lets us tell \"the user hid this column\" from \"this column\n// didn't exist yet\" when columns are added later — the former stays hidden, the\n// latter falls back to its defaultHidden value.\nexport interface PersistedColumns {\n visible: string[];\n known: string[];\n /** User-resized column widths in px. Absent in entries saved before resizing existed. */\n widths?: Record<string, number>;\n}\n\nexport function loadPersistedColumns(persistKey: string | undefined): PersistedColumns | null {\n if (!persistKey || typeof window === 'undefined') return null;\n try {\n const raw = window.localStorage.getItem(COLUMN_STORAGE_PREFIX + persistKey);\n if (!raw) return null;\n const parsed = JSON.parse(raw);\n if (parsed && Array.isArray(parsed.visible) && Array.isArray(parsed.known)) {\n const widths: Record<string, number> = {};\n if (parsed.widths && typeof parsed.widths === 'object')\n for (const [key, value] of Object.entries(parsed.widths))\n if (typeof value === 'number' && Number.isFinite(value) && value > 0) widths[key] = value;\n return {\n visible: parsed.visible.filter((k: unknown): k is string => typeof k === 'string'),\n known: parsed.known.filter((k: unknown): k is string => typeof k === 'string'),\n widths,\n };\n }\n } catch {\n // Corrupt/unavailable storage — fall back to defaults.\n }\n return null;\n}\n\n/** Whether a column should start visible, honouring saved settings then `defaultHidden`. */\nexport function isColumnInitiallyVisible<T>(col: VirtualListColumn<T>, persisted: PersistedColumns | null): boolean {\n // A locked column is always visible and can't be hidden — ignore saved state.\n if (col.lockVisible) return true;\n if (persisted && persisted.known.includes(col.key)) return persisted.visible.includes(col.key);\n return !col.defaultHidden;\n}\n\n// ─── Sorting ──────────────────────────────────────────────────────────────────\n\n// `numeric` so \"Order 10\" lands after \"Order 2\" instead of before it, and\n// `sensitivity: 'base'` so case and accents don't split otherwise-equal values.\n// Built lazily and reused: constructing a Collator is expensive relative to the\n// comparisons it then runs.\nlet collator: Intl.Collator | undefined;\nfunction getCollator(): Intl.Collator {\n collator ??= new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });\n return collator;\n}\n\n/** Values with no order of their own, parked at the end of the list either way. */\nexport function isEmptySortValue(value: SortValue): boolean {\n return value == null || (typeof value === 'number' && Number.isNaN(value));\n}\n\n/**\n * Ascending comparison of two non-empty `sortValue` results. Dates and booleans\n * are normalised to numbers; anything with a string on either side goes through\n * the collator, which is also what makes a mixed-type column produce a stable\n * (if arbitrary) order rather than a random one.\n */\nexport function compareSortValues(a: SortValue, b: SortValue): number {\n const na = a instanceof Date ? a.getTime() : typeof a === 'boolean' ? Number(a) : a;\n const nb = b instanceof Date ? b.getTime() : typeof b === 'boolean' ? Number(b) : b;\n if (typeof na === 'string' || typeof nb === 'string') return getCollator().compare(String(na), String(nb));\n return (na as number) < (nb as number) ? -1 : (na as number) > (nb as number) ? 1 : 0;\n}\n\n/** Whether a header click on this column should do anything at all. */\nexport function isColumnSortable<T>(col: VirtualListColumn<T>): boolean {\n return col.sortValue != null || col.compare != null || col.sortable === true;\n}\n\n/** The asc → desc → unsorted cycle a header click steps through. */\nexport function nextSort(current: VirtualListSort | null, key: string): VirtualListSort | null {\n if (!current || current.key !== key) return { key, direction: 'asc' };\n return current.direction === 'asc' ? { key, direction: 'desc' } : null;\n}\n\n/**\n * Returns `items` reordered by `sort`, or the same array reference when nothing\n * applies: no sort, no such column, or a `sortable` column that only reports\n * (its data is sorted by the consumer). Callers rely on that identity to tell\n * \"the list sorted this\" from \"the list left it alone\".\n */\nexport function sortItems<T>(\n items: T[],\n sort: VirtualListSort | null | undefined,\n columns: VirtualListColumn<T>[] | undefined,\n): T[] {\n if (!sort || !columns) return items;\n const col = columns.find((c) => c.key === sort.key);\n if (!col || (col.compare == null && col.sortValue == null)) return items;\n\n const dir = sort.direction === 'asc' ? 1 : -1;\n const { compare, sortValue } = col;\n // Decorated with the source index so equal values keep their original order.\n // Array.prototype.sort is stable per spec, but the tiebreaker also stops a\n // `compare` that returns 0 for distinct rows from reshuffling them.\n const decorated = items.map((item, index) => ({ item, index }));\n decorated.sort((a, b) => {\n let result: number;\n if (compare) {\n result = dir * compare(a.item, b.item);\n } else {\n const av = sortValue!(a.item);\n const bv = sortValue!(b.item);\n const aEmpty = isEmptySortValue(av);\n const bEmpty = isEmptySortValue(bv);\n // Deliberately not multiplied by `dir`: an empty cell belongs at the\n // bottom of the table whichever way the column is pointing.\n if (aEmpty || bEmpty) result = aEmpty === bEmpty ? 0 : aEmpty ? 1 : -1;\n else result = dir * compareSortValues(av, bv);\n }\n return result !== 0 ? result : a.index - b.index;\n });\n return decorated.map((d) => d.item);\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{Align as e}from"../floatingMenu/floatingMenu.types.mjs";import t from"../floatingMenu/floatingMenu.mjs";import n from"../checkbox/checkbox.mjs";import r from"./virtualList.module.mjs";import{isColumnSortable as i}from"./virtualList.utils.mjs";import{useHeaderNavigation as a}from"./useHeaderNavigation.mjs";import"react";import o from"classnames";import{FontAwesomeIcon as s}from"@fortawesome/react-fontawesome";import{faGear as c,faSort as l,faSortDown as u,faSortUp as d}from"@fortawesome/free-solid-svg-icons";import{jsx as f,jsxs as p}from"react/jsx-runtime";var m=10;function h({headerRef:e,columns:t,cols:c,resize:h,sort:_,onSort:v,multiSelect:y,allSelected:b,someSelected:x,onSelectAll:S,resetColumnWidthsLabel:C,classNames:w,styles:T}){let E=+!!y,D=E+c.visibleColumns.length+ +!!c.shouldShowToggle,O=a(D);return p(`div`,{ref:e,className:o(r.virtualListHeader,w?.header),style:{gridTemplateColumns:c.columnTemplate,...c.shouldShowToggle?{paddingRight:32}:{},...T?.header},role:`row`,"aria-rowindex":1,children:[y&&f(`div`,{...O.getStationProps(0),className:o(r.virtualListSelectCell,w?.selectCell),style:T?.selectCell,role:`columnheader`,onKeyDown:e=>{O.handleNavigationKey(e,0)||e.target===e.currentTarget&&(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),S())},children:f(n,{selected:b||x,onToggle:S,"aria-label":`Select all rows`,tabIndex:-1})}),c.visibleColumns.map((e,t)=>{let n=i(e),a=_&&_.key===e.key?_.direction:null,c=h.getHandleProps(e),g=E+t;return p(`div`,{...O.getStationProps(g),onKeyDown:t=>{if(!O.handleNavigationKey(t,g)){if(t.altKey&&h.isResizable(e)){let n=t.shiftKey?1:m;if(t.key===`ArrowRight`)h.resizeBy(e.key,n);else if(t.key===`ArrowLeft`)h.resizeBy(e.key,-n);else if(t.key===`Home`)h.resizeToMin(e.key);else if(t.key===`End`)h.autoFit(e.key);else return;t.preventDefault();return}n&&t.target===t.currentTarget&&(t.key===`Enter`||t.key===` `)&&(t.preventDefault(),v(e.key))}},className:o(r.virtualListHeaderCell,w?.headerCell),style:T?.headerCell,role:`columnheader`,"data-col-key":e.key,"aria-sort":n?a===`asc`?`ascending`:a===`desc`?`descending`:`none`:void 0,"data-fit-key":e.width.type===`fit`?e.key:void 0,children:[n?p(`button`,{type:`button`,className:o(r.virtualListHeaderSort,w?.headerSort),style:T?.headerSort,onClick:()=>v(e.key),tabIndex:-1,children:[f(`span`,{className:r.virtualListHeaderSortLabel,children:e.label}),f(s,{className:o(r.virtualListHeaderSortIcon,a&&r.virtualListHeaderSortIconActive,w?.sortIcon),style:T?.sortIcon,icon:a===`asc`?d:a===`desc`?u:l})]}):e.label,c&&f(`div`,{...c,className:o(r.virtualListHeaderResize,w?.resizeHandle),style:T?.resizeHandle})]},e.key)}),c.shouldShowToggle&&f(g,{station:D-1,nav:O,columns:t,cols:c,resize:h,resetColumnWidthsLabel:C,classNames:w,styles:T})]})}function g({station:i,nav:a,columns:l,cols:u,resize:d,resetColumnWidthsLabel:m,classNames:h,styles:g}){return p(`div`,{className:r.virtualListToggleWrapper,children:[f(t,{isOpen:u.toggleOpen,onOpenChange:u.setToggleOpen,align:e.Right,dontCloseOnChildClick:!0,content:p(`div`,{className:o(r.virtualListTogglePopover,h?.togglePopover),style:g?.togglePopover,children:[l.filter(e=>!e.hideFromToggle).map(e=>f(n,{className:o(r.virtualListToggleItem,h?.toggleItem),style:g?.toggleItem,selected:u.effectiveVisible.has(e.key),onToggle:()=>u.handleToggleColumn(e.key),disabled:e.lockVisible,cursorDefault:!1,children:e.toggleLabel??e.label},e.key)),d.isFrozen&&f(`button`,{type:`button`,className:o(r.virtualListToggleReset,h?.toggleReset),style:g?.toggleReset,onClick:()=>{d.reset(),u.setToggleOpen(!1)},children:m})]}),children:f(`button`,{...a.getStationProps(i),type:`button`,className:o(r.virtualListHeaderToggle,h?.headerToggle),style:g?.headerToggle,"aria-label":`Toggle column visibility`,onKeyDown:e=>a.handleNavigationKey(e,i),children:f(s,{icon:c,style:g?.headerToggle})})}),u.manuallyHiddenCount>0&&f(`span`,{className:o(r.virtualListColumnChip,h?.columnChip),style:g?.columnChip,"data-column-hidden-count":u.manuallyHiddenCount,"aria-label":`${u.manuallyHiddenCount} columns hidden`,children:u.manuallyHiddenCount})]})}export{h as default};
2
+ //# sourceMappingURL=virtualListHeader.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtualListHeader.mjs","names":[],"sources":["../../../../package/common/virtualList/virtualListHeader.tsx"],"sourcesContent":["import React from 'react';\nimport cx from 'classnames';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faGear, faSort, faSortDown, faSortUp } from '@fortawesome/free-solid-svg-icons';\n\nimport styles from './virtualList.module.pcss';\nimport Checkbox from '../checkbox';\nimport FloatingMenu, { Align } from '../floatingMenu';\nimport { isColumnSortable } from './virtualList.utils';\nimport { useHeaderNavigation } from './useHeaderNavigation';\nimport type { UseColumnsResult } from './useColumns';\nimport type { UseColumnResizeResult } from './useColumnResize';\nimport type { SlotClassNames, SlotStyles } from '../types/slots.types';\nimport type { VirtualListColumn, VirtualListSlots, VirtualListSort } from './virtualList.types';\n\n/** Width change of one Alt+arrow press, in px. Alt+Shift steps by one instead. */\nconst KEYBOARD_RESIZE_STEP = 10;\n\n/** FontAwesome's own style type, which carries the `--fa-font-*` custom properties. */\ntype FaStyle = React.CSSProperties & Record<`--fa-font-${string}`, string>;\n\ninterface VirtualListHeaderProps<T> {\n headerRef: React.RefObject<HTMLDivElement | null>;\n /** Every column, including hidden ones: the toggle menu lists them all. */\n columns: VirtualListColumn<T>[];\n cols: UseColumnsResult<T>;\n resize: UseColumnResizeResult<T>;\n sort: VirtualListSort | null;\n onSort: (key: string) => void;\n multiSelect: boolean;\n allSelected: boolean;\n someSelected: boolean;\n onSelectAll: () => void;\n resetColumnWidthsLabel: React.ReactNode;\n classNames?: SlotClassNames<VirtualListSlots>;\n styles?: SlotStyles<VirtualListSlots>;\n}\n\n/**\n * The table-mode header row: one cell per visible column, each optionally a sort\n * button and a resize handle, plus the column-visibility menu pinned to the\n * right. It is a grid with the same template as every row, which is what keeps\n * the columns aligned across the separate grids.\n *\n * The whole header is a single tab stop (see `useHeaderNavigation`). Focus lands\n * on a header cell rather than on the button inside it, so every key a column\n * understands is handled in one place: Enter or Space sorts, Alt plus the arrow\n * keys resizes, Alt+Home and Alt+End take the column to its minimum or to its\n * content width.\n */\nfunction VirtualListHeader<T>({\n headerRef,\n columns,\n cols,\n resize,\n sort,\n onSort,\n multiSelect,\n allSelected,\n someSelected,\n onSelectAll,\n resetColumnWidthsLabel,\n classNames,\n styles: slotStyles,\n}: VirtualListHeaderProps<T>) {\n // Station order is the reading order: the select-all box, the columns, the menu.\n const columnOffset = multiSelect ? 1 : 0;\n const stationCount = columnOffset + cols.visibleColumns.length + (cols.shouldShowToggle ? 1 : 0);\n const nav = useHeaderNavigation(stationCount);\n\n return (\n <div\n ref={headerRef}\n className={cx(styles.virtualListHeader, classNames?.header)}\n style={{\n gridTemplateColumns: cols.columnTemplate,\n ...(cols.shouldShowToggle ? { paddingRight: 32 } : {}),\n ...slotStyles?.header,\n }}\n role='row'\n aria-rowindex={1}\n >\n {multiSelect && (\n <div\n {...nav.getStationProps(0)}\n className={cx(styles.virtualListSelectCell, classNames?.selectCell)}\n style={slotStyles?.selectCell}\n role='columnheader'\n onKeyDown={(event) => {\n if (nav.handleNavigationKey(event, 0)) return;\n if (event.target !== event.currentTarget) return;\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n onSelectAll();\n }}\n >\n <Checkbox\n selected={allSelected || someSelected}\n onToggle={onSelectAll}\n aria-label='Select all rows'\n tabIndex={-1}\n />\n </div>\n )}\n\n {cols.visibleColumns.map((col, columnIndex) => {\n const sortable = isColumnSortable(col);\n const direction = sort && sort.key === col.key ? sort.direction : null;\n const handleProps = resize.getHandleProps(col);\n const station = columnOffset + columnIndex;\n return (\n <div\n key={col.key}\n {...nav.getStationProps(station)}\n onKeyDown={(event) => {\n if (nav.handleNavigationKey(event, station)) return;\n if (event.altKey && resize.isResizable(col)) {\n const step = event.shiftKey ? 1 : KEYBOARD_RESIZE_STEP;\n if (event.key === 'ArrowRight') resize.resizeBy(col.key, step);\n else if (event.key === 'ArrowLeft') resize.resizeBy(col.key, -step);\n else if (event.key === 'Home') resize.resizeToMin(col.key);\n else if (event.key === 'End') resize.autoFit(col.key);\n else return;\n event.preventDefault();\n return;\n }\n // Only when the cell itself holds the focus: after a mouse click\n // the focus sits on the sort button, which activates on its own.\n if (!sortable || event.target !== event.currentTarget) return;\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n onSort(col.key);\n }}\n className={cx(styles.virtualListHeaderCell, classNames?.headerCell)}\n style={slotStyles?.headerCell}\n role='columnheader'\n data-col-key={col.key}\n aria-sort={\n sortable ? (direction === 'asc' ? 'ascending' : direction === 'desc' ? 'descending' : 'none') : undefined\n }\n data-fit-key={col.width.type === 'fit' ? col.key : undefined}\n >\n {sortable ? (\n <button\n type='button'\n className={cx(styles.virtualListHeaderSort, classNames?.headerSort)}\n style={slotStyles?.headerSort}\n onClick={() => onSort(col.key)}\n tabIndex={-1}\n >\n <span className={styles.virtualListHeaderSortLabel}>{col.label}</span>\n {/* Rendered in every state, inactive included: the header cell is\n measured for `fit` columns and those pins only ever grow, so an\n icon that appears on click would widen the column for good. */}\n <FontAwesomeIcon\n className={cx(\n styles.virtualListHeaderSortIcon,\n direction && styles.virtualListHeaderSortIconActive,\n classNames?.sortIcon,\n )}\n style={slotStyles?.sortIcon as FaStyle}\n icon={direction === 'asc' ? faSortUp : direction === 'desc' ? faSortDown : faSort}\n />\n </button>\n ) : (\n col.label\n )}\n {handleProps && (\n <div\n {...handleProps}\n className={cx(styles.virtualListHeaderResize, classNames?.resizeHandle)}\n style={slotStyles?.resizeHandle}\n />\n )}\n </div>\n );\n })}\n\n {cols.shouldShowToggle && (\n <ColumnToggle\n station={stationCount - 1}\n nav={nav}\n columns={columns}\n cols={cols}\n resize={resize}\n resetColumnWidthsLabel={resetColumnWidthsLabel}\n classNames={classNames}\n styles={slotStyles}\n />\n )}\n </div>\n );\n}\n\ntype ColumnToggleProps<T> = Pick<\n VirtualListHeaderProps<T>,\n 'columns' | 'cols' | 'resize' | 'resetColumnWidthsLabel' | 'classNames' | 'styles'\n> & {\n station: number;\n nav: ReturnType<typeof useHeaderNavigation>;\n};\n\n/**\n * The gear at the header's trailing edge: one checkbox per column, the reset for\n * user-set widths, and a badge counting the columns the user has hidden.\n */\nfunction ColumnToggle<T>({\n station,\n nav,\n columns,\n cols,\n resize,\n resetColumnWidthsLabel,\n classNames,\n styles: slotStyles,\n}: ColumnToggleProps<T>) {\n return (\n <div className={styles.virtualListToggleWrapper}>\n <FloatingMenu\n isOpen={cols.toggleOpen}\n onOpenChange={cols.setToggleOpen}\n align={Align.Right}\n dontCloseOnChildClick\n content={\n <div\n className={cx(styles.virtualListTogglePopover, classNames?.togglePopover)}\n style={slotStyles?.togglePopover}\n >\n {columns\n .filter((col) => !col.hideFromToggle)\n .map((col) => (\n <Checkbox\n key={col.key}\n className={cx(styles.virtualListToggleItem, classNames?.toggleItem)}\n style={slotStyles?.toggleItem}\n selected={cols.effectiveVisible.has(col.key)}\n onToggle={() => cols.handleToggleColumn(col.key)}\n disabled={col.lockVisible}\n cursorDefault={false}\n >\n {col.toggleLabel ?? col.label}\n </Checkbox>\n ))}\n {resize.isFrozen && (\n <button\n type='button'\n className={cx(styles.virtualListToggleReset, classNames?.toggleReset)}\n style={slotStyles?.toggleReset}\n onClick={() => {\n resize.reset();\n cols.setToggleOpen(false);\n }}\n >\n {resetColumnWidthsLabel}\n </button>\n )}\n </div>\n }\n >\n <button\n {...nav.getStationProps(station)}\n type='button'\n className={cx(styles.virtualListHeaderToggle, classNames?.headerToggle)}\n style={slotStyles?.headerToggle}\n aria-label='Toggle column visibility'\n // Enter and Space are the button's own; only the arrows are ours.\n onKeyDown={(event) => nav.handleNavigationKey(event, station)}\n >\n <FontAwesomeIcon icon={faGear} style={slotStyles?.headerToggle as FaStyle} />\n </button>\n </FloatingMenu>\n {cols.manuallyHiddenCount > 0 && (\n <span\n className={cx(styles.virtualListColumnChip, classNames?.columnChip)}\n style={slotStyles?.columnChip}\n data-column-hidden-count={cols.manuallyHiddenCount}\n aria-label={`${cols.manuallyHiddenCount} columns hidden`}\n >\n {cols.manuallyHiddenCount}\n </span>\n )}\n </div>\n );\n}\n\nexport default VirtualListHeader;\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import e from"./virtualList.module.mjs";import{useLayoutEffect as t,useRef as n}from"react";import r from"classnames";import{jsx as i,jsxs as a}from"react/jsx-runtime";function o({index:o,top:s,padding:c,columnTemplate:l,isSelected:u,showDivider:d,hover:f,onClick:p,observer:m,ariaRowIndex:h,ariaPosInSet:g,ariaSetSize:_,rowRole:v,reorderable:y,mouseDragEnabled:b,draggable:x,isDragging:S,reserveToggleGutter:C,onMouseDown:w,onMouseOver:T,onMouseLeave:E,onDragStart:D,onDragOver:O,onDragEnd:k,onDrop:A,onTouchStart:j,dropPosition:M,dropIndentPx:N,dropIndicatorClassName:P,dropIndicatorStyle:F,rowClassName:I,rowStyle:L,children:R}){let z=n(null);return t(()=>{let e=z.current;if(e&&m)return m.observe(e),()=>m.unobserve(e)},[m]),a(`div`,{ref:z,"data-index":o,className:r(e.virtualListRow,u&&e.virtualListRowSelected,!d&&e.virtualListRowNoDivider,!f&&e.virtualListRowNoHover,y&&e.virtualListRowDraggable,S&&e.virtualListRowDragging,M===`inside`&&e.virtualListRowDropInside,I),style:{position:`absolute`,top:0,left:0,right:0,transform:`translateY(${s}px)`,...c==null?{}:{padding:c},...l?{display:`grid`,gridTemplateColumns:l}:{},...C?{paddingRight:32}:{},...L},onClick:p,draggable:x,onMouseDown:w,onMouseOver:T,onMouseLeave:E,onDragStart:D,onDragOver:O,onDragEnd:k,onDrop:A??(b?e=>e.preventDefault():void 0),onTouchStart:j,onContextMenu:b?e=>e.preventDefault():void 0,"aria-selected":u,"aria-rowindex":h,"aria-posinset":g,"aria-setsize":_,role:v,children:[(M===`before`||M===`after`)&&i(`div`,{"data-drop-line":M,"aria-hidden":!0,className:r(e.virtualListDropLine,M===`before`?e.virtualListDropLineBefore:e.virtualListDropLineAfter,P),style:{left:N??0,...F}}),R]})}export{o as default};
1
+ import e from"./virtualList.module.mjs";import{ROW_FOCUSABLE_SELECTOR as t,ROW_PARKED_ATTRIBUTE as n}from"./virtualList.utils.mjs";import{useLayoutEffect as r,useRef as i}from"react";import a from"classnames";import{jsx as o,jsxs as s}from"react/jsx-runtime";function c({index:c,id:l,top:u,padding:d,columnTemplate:f,isSelected:p,isActive:m,manageTabStops:h,isEntered:g,activeClassName:_,activeStyle:v,showDivider:y,hover:b,onClick:x,observer:S,ariaRowIndex:C,ariaPosInSet:w,ariaSetSize:T,rowRole:E,reorderable:D,mouseDragEnabled:O,draggable:k,isDragging:A,reserveToggleGutter:j,onMouseDown:M,onMouseOver:N,onMouseLeave:P,onDragStart:F,onDragOver:I,onDragEnd:L,onDrop:R,onTouchStart:z,dropPosition:B,dropIndentPx:V,dropIndicatorClassName:H,dropIndicatorStyle:U,rowClassName:W,rowStyle:G,children:K}){let q=i(null);return r(()=>{let e=q.current;if(e&&S)return S.observe(e),()=>S.unobserve(e)},[S]),r(()=>{let e=q.current;if(!e||!h||g)return;let r=[];return e.querySelectorAll(t).forEach(e=>{r.push([e,e.getAttribute(`tabindex`)]),e.setAttribute(`tabindex`,`-1`),e.setAttribute(n,``)}),()=>{for(let[e,t]of r)t===null?e.removeAttribute(`tabindex`):e.setAttribute(`tabindex`,t),e.removeAttribute(n)}}),s(`div`,{ref:q,id:l,"data-index":c,className:a(e.virtualListRow,p&&e.virtualListRowSelected,m&&e.virtualListRowActive,m&&_,!y&&e.virtualListRowNoDivider,!b&&e.virtualListRowNoHover,D&&e.virtualListRowDraggable,A&&e.virtualListRowDragging,B===`inside`&&e.virtualListRowDropInside,W),style:{position:`absolute`,top:0,left:0,right:0,transform:`translateY(${u}px)`,...d==null?{}:{padding:d},...f?{display:`grid`,gridTemplateColumns:f}:{},...j?{paddingRight:32}:{},...G,...m?v:void 0},onClick:x,draggable:k,onMouseDown:M,onMouseOver:N,onMouseLeave:P,onDragStart:F,onDragOver:I,onDragEnd:L,onDrop:R??(O?e=>e.preventDefault():void 0),onTouchStart:z,onContextMenu:O?e=>e.preventDefault():void 0,"aria-selected":p,"aria-rowindex":C,"aria-posinset":w,"aria-setsize":T,role:E,children:[(B===`before`||B===`after`)&&o(`div`,{"data-drop-line":B,"aria-hidden":!0,className:a(e.virtualListDropLine,B===`before`?e.virtualListDropLineBefore:e.virtualListDropLineAfter,H),style:{left:V??0,...U}}),K]})}export{c as default};
2
2
  //# sourceMappingURL=virtualRow.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"virtualRow.mjs","names":[],"sources":["../../../../package/common/virtualList/virtualRow.tsx"],"sourcesContent":["import React, { useRef, useLayoutEffect } from 'react';\nimport cx from 'classnames';\n\nimport styles from './virtualList.module.pcss';\nimport type { TreeDropPosition } from './virtualList.types';\n\nexport interface VirtualRowProps {\n index: number;\n top: number;\n /** Padding applied to the row (already normalised to a CSS string). */\n padding?: string;\n columnTemplate: string | undefined;\n isSelected: boolean;\n showDivider: boolean;\n hover: boolean;\n onClick: () => void;\n observer: ResizeObserver | null;\n ariaRowIndex?: number;\n ariaPosInSet?: number;\n ariaSetSize?: number;\n rowRole: string;\n reorderable?: boolean;\n /**\n * Native mouse DnD is active for the list. Distinct from `draggable`, which in\n * `dragHandle` mode is only set on the row the pointer has armed.\n */\n mouseDragEnabled?: boolean;\n draggable?: boolean;\n isDragging?: boolean;\n reserveToggleGutter?: boolean;\n onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;\n onMouseOver?: (e: React.MouseEvent<HTMLDivElement>) => void;\n onMouseLeave?: (e: React.MouseEvent<HTMLDivElement>) => void;\n onDragStart?: (e: React.DragEvent<HTMLDivElement>) => void;\n onDragOver?: (e: React.DragEvent<HTMLDivElement>) => void;\n onDragEnd?: () => void;\n onDrop?: (e: React.DragEvent<HTMLDivElement>) => void;\n onTouchStart?: (e: React.TouchEvent<HTMLDivElement>) => void;\n dropPosition?: TreeDropPosition;\n dropIndentPx?: number;\n dropIndicatorClassName?: string;\n dropIndicatorStyle?: React.CSSProperties;\n rowClassName?: string;\n rowStyle?: React.CSSProperties;\n children: React.ReactNode;\n}\n\n/**\n * A single virtualized row: absolutely positioned at its `top`, optionally a\n * grid (table mode), registering itself with the shared ResizeObserver and\n * carrying all the drag / drop-indicator wiring the parent assigns it.\n */\nfunction VirtualRow({\n index,\n top,\n padding,\n columnTemplate,\n isSelected,\n showDivider,\n hover,\n onClick,\n observer,\n ariaRowIndex,\n ariaPosInSet,\n ariaSetSize,\n rowRole,\n reorderable,\n mouseDragEnabled,\n draggable,\n isDragging,\n reserveToggleGutter,\n onMouseDown,\n onMouseOver,\n onMouseLeave,\n onDragStart,\n onDragOver,\n onDragEnd,\n onDrop,\n onTouchStart,\n dropPosition,\n dropIndentPx,\n dropIndicatorClassName,\n dropIndicatorStyle,\n rowClassName,\n rowStyle,\n children,\n}: VirtualRowProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n // A single observer is shared across all rows (created by the parent); each\n // row just registers/unregisters its element. The parent reads the row index\n // back from the `data-index` attribute when the observer fires.\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || !observer) return;\n observer.observe(el);\n return () => observer.unobserve(el);\n }, [observer]);\n\n return (\n <div\n ref={ref}\n data-index={index}\n className={cx(\n styles.virtualListRow,\n isSelected && styles.virtualListRowSelected,\n !showDivider && styles.virtualListRowNoDivider,\n !hover && styles.virtualListRowNoHover,\n reorderable && styles.virtualListRowDraggable,\n isDragging && styles.virtualListRowDragging,\n dropPosition === 'inside' && styles.virtualListRowDropInside,\n rowClassName,\n )}\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n transform: `translateY(${top}px)`,\n ...(padding != null ? { padding } : {}),\n ...(columnTemplate ? { display: 'grid', gridTemplateColumns: columnTemplate } : {}),\n // Match the header's reserved gutter for the column-toggle cog so row\n // cells line up with their header cells instead of drifting under it.\n // Applied after `padding` so the alignment gutter always wins on the right.\n ...(reserveToggleGutter ? { paddingRight: 32 } : {}),\n ...rowStyle,\n }}\n onClick={onClick}\n draggable={draggable}\n onMouseDown={onMouseDown}\n onMouseOver={onMouseOver}\n onMouseLeave={onMouseLeave}\n onDragStart={onDragStart}\n onDragOver={onDragOver}\n onDragEnd={onDragEnd}\n onDrop={onDrop ?? (mouseDragEnabled ? (e) => e.preventDefault() : undefined)}\n onTouchStart={onTouchStart}\n onContextMenu={mouseDragEnabled ? (e) => e.preventDefault() : undefined}\n aria-selected={isSelected}\n aria-rowindex={ariaRowIndex}\n aria-posinset={ariaPosInSet}\n aria-setsize={ariaSetSize}\n role={rowRole}\n >\n {(dropPosition === 'before' || dropPosition === 'after') && (\n <div\n data-drop-line={dropPosition}\n aria-hidden\n className={cx(\n styles.virtualListDropLine,\n dropPosition === 'before' ? styles.virtualListDropLineBefore : styles.virtualListDropLineAfter,\n dropIndicatorClassName,\n )}\n style={{ left: dropIndentPx ?? 0, ...dropIndicatorStyle }}\n />\n )}\n {children}\n </div>\n );\n}\n\nexport default VirtualRow;\n"],"mappings":"wKAoDA,SAAS,EAAW,CAClB,QACA,MACA,UACA,iBACA,aACA,cACA,QACA,UACA,WACA,eACA,eACA,cACA,UACA,cACA,mBACA,YACA,aACA,sBACA,cACA,cACA,eACA,cACA,aACA,YACA,SACA,eACA,eACA,eACA,yBACA,qBACA,eACA,WACA,YACkB,CAClB,IAAM,EAAM,EAAuB,IAAI,EAYvC,OAPA,MAAsB,CACpB,IAAM,EAAK,EAAI,QACX,GAAC,GAAO,EAEZ,OADA,EAAS,QAAQ,CAAE,MACN,EAAS,UAAU,CAAE,CACpC,EAAG,CAAC,CAAQ,CAAC,EAGX,EAAC,MAAD,CACO,MACL,aAAY,EACZ,UAAW,EACT,EAAO,eACP,GAAc,EAAO,uBACrB,CAAC,GAAe,EAAO,wBACvB,CAAC,GAAS,EAAO,sBACjB,GAAe,EAAO,wBACtB,GAAc,EAAO,uBACrB,IAAiB,UAAY,EAAO,yBACpC,CACF,EACA,MAAO,CACL,SAAU,WACV,IAAK,EACL,KAAM,EACN,MAAO,EACP,UAAW,cAAc,EAAI,KAC7B,GAAI,GAAW,KAAqB,CAAC,EAAf,CAAE,SAAQ,EAChC,GAAI,EAAiB,CAAE,QAAS,OAAQ,oBAAqB,CAAe,EAAI,CAAC,EAIjF,GAAI,EAAsB,CAAE,aAAc,EAAG,EAAI,CAAC,EAClD,GAAG,CACL,EACS,UACE,YACE,cACA,cACC,eACD,cACD,aACD,YACX,OAAQ,IAAW,EAAoB,GAAM,EAAE,eAAe,EAAI,IAAA,IACpD,eACd,cAAe,EAAoB,GAAM,EAAE,eAAe,EAAI,IAAA,GAC9D,gBAAe,EACf,gBAAe,EACf,gBAAe,EACf,eAAc,EACd,KAAM,EA1CR,SAAA,EA4CI,IAAiB,UAAY,IAAiB,UAC9C,EAAC,MAAD,CACE,iBAAgB,EAChB,cAAA,GACA,UAAW,EACT,EAAO,oBACP,IAAiB,SAAW,EAAO,0BAA4B,EAAO,yBACtE,CACF,EACA,MAAO,CAAE,KAAM,GAAgB,EAAG,GAAG,CAAmB,CACzD,CAAA,EAEF,CACE,GAET"}
1
+ {"version":3,"file":"virtualRow.mjs","names":[],"sources":["../../../../package/common/virtualList/virtualRow.tsx"],"sourcesContent":["import React, { useRef, useLayoutEffect } from 'react';\nimport cx from 'classnames';\n\nimport styles from './virtualList.module.pcss';\nimport { ROW_FOCUSABLE_SELECTOR, ROW_PARKED_ATTRIBUTE } from './virtualList.utils';\nimport type { TreeDropPosition } from './virtualList.types';\n\nexport interface VirtualRowProps {\n index: number;\n /** Referenced by the list's `aria-activedescendant` while this row is active. */\n id?: string;\n top: number;\n /** Padding applied to the row (already normalised to a CSS string). */\n padding?: string;\n columnTemplate: string | undefined;\n isSelected: boolean;\n /** The row the arrow keys are on. Distinct from selection. */\n isActive?: boolean;\n /**\n * Hold this row's own controls out of the tab order unless it is the row being\n * used. Off when the list leaves the keyboard to its rows.\n */\n manageTabStops?: boolean;\n /** This is the row being used, so its controls are reachable with Tab. */\n isEntered?: boolean;\n activeClassName?: string;\n activeStyle?: React.CSSProperties;\n showDivider: boolean;\n hover: boolean;\n onClick: () => void;\n observer: ResizeObserver | null;\n ariaRowIndex?: number;\n ariaPosInSet?: number;\n ariaSetSize?: number;\n rowRole: string;\n reorderable?: boolean;\n /**\n * Native mouse DnD is active for the list. Distinct from `draggable`, which in\n * `dragHandle` mode is only set on the row the pointer has armed.\n */\n mouseDragEnabled?: boolean;\n draggable?: boolean;\n isDragging?: boolean;\n reserveToggleGutter?: boolean;\n onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;\n onMouseOver?: (e: React.MouseEvent<HTMLDivElement>) => void;\n onMouseLeave?: (e: React.MouseEvent<HTMLDivElement>) => void;\n onDragStart?: (e: React.DragEvent<HTMLDivElement>) => void;\n onDragOver?: (e: React.DragEvent<HTMLDivElement>) => void;\n onDragEnd?: () => void;\n onDrop?: (e: React.DragEvent<HTMLDivElement>) => void;\n onTouchStart?: (e: React.TouchEvent<HTMLDivElement>) => void;\n dropPosition?: TreeDropPosition;\n dropIndentPx?: number;\n dropIndicatorClassName?: string;\n dropIndicatorStyle?: React.CSSProperties;\n rowClassName?: string;\n rowStyle?: React.CSSProperties;\n children: React.ReactNode;\n}\n\n/**\n * A single virtualized row: absolutely positioned at its `top`, optionally a\n * grid (table mode), registering itself with the shared ResizeObserver and\n * carrying all the drag / drop-indicator wiring the parent assigns it.\n */\nfunction VirtualRow({\n index,\n id,\n top,\n padding,\n columnTemplate,\n isSelected,\n isActive,\n manageTabStops,\n isEntered,\n activeClassName,\n activeStyle,\n showDivider,\n hover,\n onClick,\n observer,\n ariaRowIndex,\n ariaPosInSet,\n ariaSetSize,\n rowRole,\n reorderable,\n mouseDragEnabled,\n draggable,\n isDragging,\n reserveToggleGutter,\n onMouseDown,\n onMouseOver,\n onMouseLeave,\n onDragStart,\n onDragOver,\n onDragEnd,\n onDrop,\n onTouchStart,\n dropPosition,\n dropIndentPx,\n dropIndicatorClassName,\n dropIndicatorStyle,\n rowClassName,\n rowStyle,\n children,\n}: VirtualRowProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n // A single observer is shared across all rows (created by the parent); each\n // row just registers/unregisters its element. The parent reads the row index\n // back from the `data-index` attribute when the observer fires.\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || !observer) return;\n observer.observe(el);\n return () => observer.unobserve(el);\n }, [observer]);\n\n // One row at a time carries tab stops. Without this every control in every\n // rendered row is one, the set changes as the list scrolls, and tabbing\n // through a windowed list leaves it at an arbitrary point: the next row does\n // not exist in the DOM yet when the browser looks for the next stop.\n //\n // Runs on every commit of the row rather than on a dependency list, because\n // the cells are the consumer's and can change without anything here changing.\n // The selector only matches what is in the tab order to begin with, and an\n // attribute write invalidates no layout, so a scroll frame pays a query per\n // row and nothing else.\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || !manageTabStops || isEntered) return;\n const restore: [HTMLElement, string | null][] = [];\n el.querySelectorAll<HTMLElement>(ROW_FOCUSABLE_SELECTOR).forEach((node) => {\n restore.push([node, node.getAttribute('tabindex')]);\n node.setAttribute('tabindex', '-1');\n node.setAttribute(ROW_PARKED_ATTRIBUTE, '');\n });\n return () => {\n for (const [node, previous] of restore) {\n if (previous === null) node.removeAttribute('tabindex');\n else node.setAttribute('tabindex', previous);\n node.removeAttribute(ROW_PARKED_ATTRIBUTE);\n }\n };\n });\n\n return (\n <div\n ref={ref}\n id={id}\n data-index={index}\n className={cx(\n styles.virtualListRow,\n isSelected && styles.virtualListRowSelected,\n isActive && styles.virtualListRowActive,\n isActive && activeClassName,\n !showDivider && styles.virtualListRowNoDivider,\n !hover && styles.virtualListRowNoHover,\n reorderable && styles.virtualListRowDraggable,\n isDragging && styles.virtualListRowDragging,\n dropPosition === 'inside' && styles.virtualListRowDropInside,\n rowClassName,\n )}\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n transform: `translateY(${top}px)`,\n ...(padding != null ? { padding } : {}),\n ...(columnTemplate ? { display: 'grid', gridTemplateColumns: columnTemplate } : {}),\n // Match the header's reserved gutter for the column-toggle cog so row\n // cells line up with their header cells instead of drifting under it.\n // Applied after `padding` so the alignment gutter always wins on the right.\n ...(reserveToggleGutter ? { paddingRight: 32 } : {}),\n ...rowStyle,\n ...(isActive ? activeStyle : undefined),\n }}\n onClick={onClick}\n draggable={draggable}\n onMouseDown={onMouseDown}\n onMouseOver={onMouseOver}\n onMouseLeave={onMouseLeave}\n onDragStart={onDragStart}\n onDragOver={onDragOver}\n onDragEnd={onDragEnd}\n onDrop={onDrop ?? (mouseDragEnabled ? (e) => e.preventDefault() : undefined)}\n onTouchStart={onTouchStart}\n onContextMenu={mouseDragEnabled ? (e) => e.preventDefault() : undefined}\n aria-selected={isSelected}\n aria-rowindex={ariaRowIndex}\n aria-posinset={ariaPosInSet}\n aria-setsize={ariaSetSize}\n role={rowRole}\n >\n {(dropPosition === 'before' || dropPosition === 'after') && (\n <div\n data-drop-line={dropPosition}\n aria-hidden\n className={cx(\n styles.virtualListDropLine,\n dropPosition === 'before' ? styles.virtualListDropLineBefore : styles.virtualListDropLineAfter,\n dropIndicatorClassName,\n )}\n style={{ left: dropIndentPx ?? 0, ...dropIndicatorStyle }}\n />\n )}\n {children}\n </div>\n );\n}\n\nexport default VirtualRow;\n"],"mappings":"mQAkEA,SAAS,EAAW,CAClB,QACA,KACA,MACA,UACA,iBACA,aACA,WACA,iBACA,YACA,kBACA,cACA,cACA,QACA,UACA,WACA,eACA,eACA,cACA,UACA,cACA,mBACA,YACA,aACA,sBACA,cACA,cACA,eACA,cACA,aACA,YACA,SACA,eACA,eACA,eACA,yBACA,qBACA,eACA,WACA,YACkB,CAClB,IAAM,EAAM,EAAuB,IAAI,EAwCvC,OAnCA,MAAsB,CACpB,IAAM,EAAK,EAAI,QACX,GAAC,GAAO,EAEZ,OADA,EAAS,QAAQ,CAAE,MACN,EAAS,UAAU,CAAE,CACpC,EAAG,CAAC,CAAQ,CAAC,EAYb,MAAsB,CACpB,IAAM,EAAK,EAAI,QACf,GAAI,CAAC,GAAM,CAAC,GAAkB,EAAW,OACzC,IAAM,EAA0C,CAAC,EAMjD,OALA,EAAG,iBAA8B,CAAsB,CAAC,CAAC,QAAS,GAAS,CACzE,EAAQ,KAAK,CAAC,EAAM,EAAK,aAAa,UAAU,CAAC,CAAC,EAClD,EAAK,aAAa,WAAY,IAAI,EAClC,EAAK,aAAa,EAAsB,EAAE,CAC5C,CAAC,MACY,CACX,IAAK,GAAM,CAAC,EAAM,KAAa,EACzB,IAAa,KAAM,EAAK,gBAAgB,UAAU,EACjD,EAAK,aAAa,WAAY,CAAQ,EAC3C,EAAK,gBAAgB,CAAoB,CAE7C,CACF,CAAC,EAGC,EAAC,MAAD,CACO,MACD,KACJ,aAAY,EACZ,UAAW,EACT,EAAO,eACP,GAAc,EAAO,uBACrB,GAAY,EAAO,qBACnB,GAAY,EACZ,CAAC,GAAe,EAAO,wBACvB,CAAC,GAAS,EAAO,sBACjB,GAAe,EAAO,wBACtB,GAAc,EAAO,uBACrB,IAAiB,UAAY,EAAO,yBACpC,CACF,EACA,MAAO,CACL,SAAU,WACV,IAAK,EACL,KAAM,EACN,MAAO,EACP,UAAW,cAAc,EAAI,KAC7B,GAAI,GAAW,KAAqB,CAAC,EAAf,CAAE,SAAQ,EAChC,GAAI,EAAiB,CAAE,QAAS,OAAQ,oBAAqB,CAAe,EAAI,CAAC,EAIjF,GAAI,EAAsB,CAAE,aAAc,EAAG,EAAI,CAAC,EAClD,GAAG,EACH,GAAI,EAAW,EAAc,IAAA,EAC/B,EACS,UACE,YACE,cACA,cACC,eACD,cACD,aACD,YACX,OAAQ,IAAW,EAAoB,GAAM,EAAE,eAAe,EAAI,IAAA,IACpD,eACd,cAAe,EAAoB,GAAM,EAAE,eAAe,EAAI,IAAA,GAC9D,gBAAe,EACf,gBAAe,EACf,gBAAe,EACf,eAAc,EACd,KAAM,EA9CR,SAAA,EAgDI,IAAiB,UAAY,IAAiB,UAC9C,EAAC,MAAD,CACE,iBAAgB,EAChB,cAAA,GACA,UAAW,EACT,EAAO,oBACP,IAAiB,SAAW,EAAO,0BAA4B,EAAO,yBACtE,CACF,EACA,MAAO,CAAE,KAAM,GAAgB,EAAG,GAAG,CAAmB,CACzD,CAAA,EAEF,CACE,GAET"}