@toolbox-web/grid 3.9.1 → 3.10.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 (148) hide show
  1. package/all.js +1 -1
  2. package/all.js.map +1 -1
  3. package/custom-elements.json +2 -2
  4. package/index.js +1 -1
  5. package/index.js.map +1 -1
  6. package/lib/core/grid.d.ts +8 -4
  7. package/lib/core/plugin/base-plugin.d.ts +7 -0
  8. package/lib/core/types.d.ts +8 -3
  9. package/lib/features/clipboard.js.map +1 -1
  10. package/lib/features/column-virtualization.js.map +1 -1
  11. package/lib/features/context-menu.js.map +1 -1
  12. package/lib/features/editing.js.map +1 -1
  13. package/lib/features/export.js.map +1 -1
  14. package/lib/features/filtering.js.map +1 -1
  15. package/lib/features/grouping-columns.js.map +1 -1
  16. package/lib/features/grouping-rows.js.map +1 -1
  17. package/lib/features/master-detail.js.map +1 -1
  18. package/lib/features/multi-sort.js.map +1 -1
  19. package/lib/features/pinned-columns.js.map +1 -1
  20. package/lib/features/pinned-rows.js.map +1 -1
  21. package/lib/features/pivot.js.map +1 -1
  22. package/lib/features/print.js.map +1 -1
  23. package/lib/features/registry.js +1 -1
  24. package/lib/features/registry.js.map +1 -1
  25. package/lib/features/reorder-columns.js.map +1 -1
  26. package/lib/features/responsive.js.map +1 -1
  27. package/lib/features/row-drag-drop.js.map +1 -1
  28. package/lib/features/selection.js.map +1 -1
  29. package/lib/features/server-side.js.map +1 -1
  30. package/lib/features/shell.js.map +1 -1
  31. package/lib/features/sticky-rows.js.map +1 -1
  32. package/lib/features/tooltip.js.map +1 -1
  33. package/lib/features/tree.js.map +1 -1
  34. package/lib/features/undo-redo.js.map +1 -1
  35. package/lib/features/visibility.js.map +1 -1
  36. package/lib/plugins/clipboard/index.js.map +1 -1
  37. package/lib/plugins/column-virtualization/index.js.map +1 -1
  38. package/lib/plugins/context-menu/context-menu-plugin.d.ts +9 -4
  39. package/lib/plugins/context-menu/index.js +1 -1
  40. package/lib/plugins/context-menu/index.js.map +1 -1
  41. package/lib/plugins/context-menu/types.d.ts +8 -4
  42. package/lib/plugins/editing/editors.d.ts +15 -0
  43. package/lib/plugins/editing/index.js +1 -1
  44. package/lib/plugins/editing/index.js.map +1 -1
  45. package/lib/plugins/editing/internal/dirty-tracking-manager.d.ts +2 -1
  46. package/lib/plugins/editing/types.d.ts +5 -1
  47. package/lib/plugins/export/index.js +1 -1
  48. package/lib/plugins/export/index.js.map +1 -1
  49. package/lib/plugins/filtering/filter-model.d.ts +22 -2
  50. package/lib/plugins/filtering/filter-panel-default.d.ts +15 -1
  51. package/lib/plugins/filtering/filtering-plugin.d.ts +5 -2
  52. package/lib/plugins/filtering/index.d.ts +1 -1
  53. package/lib/plugins/filtering/index.js +1 -1
  54. package/lib/plugins/filtering/index.js.map +1 -1
  55. package/lib/plugins/filtering/types.d.ts +58 -3
  56. package/lib/plugins/grouping-columns/index.js.map +1 -1
  57. package/lib/plugins/grouping-rows/grouping-rows-plugin.d.ts +7 -14
  58. package/lib/plugins/grouping-rows/grouping-rows.d.ts +48 -0
  59. package/lib/plugins/grouping-rows/index.d.ts +1 -1
  60. package/lib/plugins/grouping-rows/index.js +1 -1
  61. package/lib/plugins/grouping-rows/index.js.map +1 -1
  62. package/lib/plugins/grouping-rows/types.d.ts +21 -0
  63. package/lib/plugins/hierarchy/index.js.map +1 -1
  64. package/lib/plugins/master-detail/index.d.ts +1 -1
  65. package/lib/plugins/master-detail/index.js +1 -1
  66. package/lib/plugins/master-detail/index.js.map +1 -1
  67. package/lib/plugins/master-detail/master-detail-plugin.d.ts +28 -6
  68. package/lib/plugins/master-detail/master-detail.d.ts +0 -8
  69. package/lib/plugins/master-detail/types.d.ts +25 -1
  70. package/lib/plugins/multi-sort/index.js +1 -1
  71. package/lib/plugins/multi-sort/index.js.map +1 -1
  72. package/lib/plugins/pinned-columns/index.js.map +1 -1
  73. package/lib/plugins/pinned-rows/index.js.map +1 -1
  74. package/lib/plugins/pivot/index.js +1 -1
  75. package/lib/plugins/pivot/index.js.map +1 -1
  76. package/lib/plugins/pivot/pivot-plugin.d.ts +2 -0
  77. package/lib/plugins/pivot/types.d.ts +2 -0
  78. package/lib/plugins/print/index.js.map +1 -1
  79. package/lib/plugins/reorder-columns/index.js.map +1 -1
  80. package/lib/plugins/responsive/index.js.map +1 -1
  81. package/lib/plugins/row-drag-drop/index.js.map +1 -1
  82. package/lib/plugins/selection/index.js +1 -1
  83. package/lib/plugins/selection/index.js.map +1 -1
  84. package/lib/plugins/selection/selection-plugin.d.ts +8 -1
  85. package/lib/plugins/server-side/datasource-types.d.ts +28 -0
  86. package/lib/plugins/server-side/datasource.d.ts +7 -0
  87. package/lib/plugins/server-side/index.js +1 -1
  88. package/lib/plugins/server-side/index.js.map +1 -1
  89. package/lib/plugins/server-side/server-side-plugin.d.ts +61 -8
  90. package/lib/plugins/server-side/types.d.ts +9 -1
  91. package/lib/plugins/shared/form-field-target.d.ts +10 -0
  92. package/lib/plugins/shared/format-value.d.ts +12 -0
  93. package/lib/plugins/shell/index.js.map +1 -1
  94. package/lib/plugins/sticky-rows/index.js.map +1 -1
  95. package/lib/plugins/tooltip/index.js.map +1 -1
  96. package/lib/plugins/tree/index.js +1 -1
  97. package/lib/plugins/tree/index.js.map +1 -1
  98. package/lib/plugins/tree/tree-plugin.d.ts +5 -11
  99. package/lib/plugins/tree/types.d.ts +22 -6
  100. package/lib/plugins/undo-redo/history.d.ts +2 -1
  101. package/lib/plugins/undo-redo/index.js +1 -1
  102. package/lib/plugins/undo-redo/index.js.map +1 -1
  103. package/lib/plugins/undo-redo/types.d.ts +13 -1
  104. package/lib/plugins/undo-redo/undo-redo-plugin.d.ts +15 -2
  105. package/lib/plugins/visibility/index.js.map +1 -1
  106. package/package.json +1 -1
  107. package/umd/grid.all.umd.js +1 -1
  108. package/umd/grid.all.umd.js.map +1 -1
  109. package/umd/grid.umd.js +1 -1
  110. package/umd/grid.umd.js.map +1 -1
  111. package/umd/plugins/clipboard.umd.js.map +1 -1
  112. package/umd/plugins/column-virtualization.umd.js.map +1 -1
  113. package/umd/plugins/context-menu.umd.js +1 -1
  114. package/umd/plugins/context-menu.umd.js.map +1 -1
  115. package/umd/plugins/editing.umd.js +1 -1
  116. package/umd/plugins/editing.umd.js.map +1 -1
  117. package/umd/plugins/export.umd.js +1 -1
  118. package/umd/plugins/export.umd.js.map +1 -1
  119. package/umd/plugins/filtering.umd.js +1 -1
  120. package/umd/plugins/filtering.umd.js.map +1 -1
  121. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  122. package/umd/plugins/grouping-rows.umd.js +1 -1
  123. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  124. package/umd/plugins/hierarchy.umd.js.map +1 -1
  125. package/umd/plugins/master-detail.umd.js +1 -1
  126. package/umd/plugins/master-detail.umd.js.map +1 -1
  127. package/umd/plugins/multi-sort.umd.js +1 -1
  128. package/umd/plugins/multi-sort.umd.js.map +1 -1
  129. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  130. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  131. package/umd/plugins/pivot.umd.js +1 -1
  132. package/umd/plugins/pivot.umd.js.map +1 -1
  133. package/umd/plugins/print.umd.js.map +1 -1
  134. package/umd/plugins/reorder-columns.umd.js.map +1 -1
  135. package/umd/plugins/responsive.umd.js.map +1 -1
  136. package/umd/plugins/row-drag-drop.umd.js.map +1 -1
  137. package/umd/plugins/selection.umd.js +1 -1
  138. package/umd/plugins/selection.umd.js.map +1 -1
  139. package/umd/plugins/server-side.umd.js +1 -1
  140. package/umd/plugins/server-side.umd.js.map +1 -1
  141. package/umd/plugins/shell.umd.js.map +1 -1
  142. package/umd/plugins/sticky-rows.umd.js.map +1 -1
  143. package/umd/plugins/tooltip.umd.js.map +1 -1
  144. package/umd/plugins/tree.umd.js +1 -1
  145. package/umd/plugins/tree.umd.js.map +1 -1
  146. package/umd/plugins/undo-redo.umd.js +1 -1
  147. package/umd/plugins/undo-redo.umd.js.map +1 -1
  148. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../../../libs/grid/src/lib/core/constants.ts","../../../../../../libs/grid/src/lib/core/internal/utils.ts","../../../../../../libs/grid/src/lib/core/internal/keyboard.ts","../../../../../../libs/grid/src/lib/plugins/shared/drag-alternative-menu.ts","../../../../../../libs/grid/src/lib/plugins/reorder-columns/column-drag.ts","../../../../../../libs/grid/src/lib/plugins/reorder-columns/reorder.css?inline","../../../../../../libs/grid/src/lib/plugins/reorder-columns/reorder-plugin.ts"],"sourcesContent":["/**\n * Grid DOM Constants\n *\n * Centralized constants for CSS classes, data attributes, and selectors\n * used throughout the grid. Use these instead of magic strings.\n *\n * Note: Some constants here are used by plugins but defined in core because:\n * 1. The core CSS needs to style these elements (e.g., sticky positioning)\n * 2. Multiple plugins may share the same class names\n *\n * Plugins can define their own additional constants and export them.\n * See each plugin's index.ts for plugin-specific exports.\n */\n\n/**\n * CSS selector for focusable editor elements within a cell.\n * Used by EditingPlugin, UndoRedoPlugin, and keyboard navigation. Defined here\n * (a leaf module) rather than in `rows.ts` so `keyboard.ts` can consume it\n * without creating a rows ↔ keyboard import cycle.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// #region CSS Classes\n\n/**\n * CSS class names used in the grid's DOM.\n * Use these when adding/removing classes or querying elements.\n *\n * Classes are organized by:\n * - Core: Used by the grid core for structure and basic functionality\n * - Shared: Used by multiple features/plugins, styled by core CSS\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridClasses = {\n // ─── Core Structure ───────────────────────────────────────────────\n ROOT: 'tbw-grid-root',\n HEADER: 'header',\n HEADER_ROW: 'header-row',\n HEADER_CELL: 'header-cell',\n\n // Body structure\n ROWS_VIEWPORT: 'rows-viewport',\n ROWS_SPACER: 'rows-spacer',\n ROWS_CONTAINER: 'rows',\n\n // Row elements\n DATA_ROW: 'data-grid-row',\n GROUP_ROW: 'group-row',\n\n // Cell elements\n DATA_CELL: 'cell',\n\n // ─── Core States ──────────────────────────────────────────────────\n SELECTED: 'selected',\n FOCUSED: 'focused',\n EDITING: 'editing',\n EXPANDED: 'expanded',\n COLLAPSED: 'collapsed',\n DRAGGING: 'dragging',\n RESIZING: 'resizing',\n\n // Sorting (core feature)\n SORTABLE: 'sortable',\n SORTED_ASC: 'sorted-asc',\n SORTED_DESC: 'sorted-desc',\n\n // Visibility\n HIDDEN: 'hidden',\n\n // ─── Shared Classes (used by plugins, styled by core CSS) ────────\n // These are defined here because core CSS provides the base styling.\n // Plugins apply these classes; core CSS defines how they look.\n\n // Sticky positioning (PinnedColumnsPlugin applies, core CSS styles)\n STICKY_LEFT: 'sticky-left',\n STICKY_RIGHT: 'sticky-right',\n\n // Pinned rows (PinnedRowsPlugin applies, core CSS styles)\n PINNED_TOP: 'pinned-top',\n PINNED_BOTTOM: 'pinned-bottom',\n\n // Tree structure (TreePlugin applies, core CSS styles)\n TREE_TOGGLE: 'tree-toggle',\n TREE_INDENT: 'tree-indent',\n\n // Grouping (GroupingRowsPlugin applies, core CSS styles)\n GROUP_TOGGLE: 'group-toggle',\n GROUP_LABEL: 'group-label',\n GROUP_COUNT: 'group-count',\n\n // Selection (SelectionPlugin applies, core CSS styles)\n RANGE_SELECTION: 'range-selection',\n SELECTION_OVERLAY: 'selection-overlay',\n} as const;\n\n// #endregion\n\n// #region Data Attributes\n\n/**\n * Data attribute names used on grid elements.\n * Use these when getting/setting data attributes.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridDataAttrs = {\n // ─── Core Attributes ──────────────────────────────────────────────\n FIELD: 'data-field',\n\n /**\n * Marks a `title` the grid itself put on a cell to reveal ellipsised text\n * (WCAG SC 1.4.12). Anything that sets its own `title` on a cell must clear\n * this, or the grid will later mistake that title for its own and replace it.\n *\n * @since 3.6.0\n */\n TRUNCATED: 'data-tbw-truncated',\n\n // ─── Shared Attributes (used by plugins) ──────────────────────────\n GROUP_KEY: 'data-group-key', // GroupingRowsPlugin\n} as const;\n\n// #endregion\n\n// #region Selectors\n\n/**\n * Common CSS selectors for querying grid elements.\n * Built from the class constants for consistency.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridSelectors = {\n ROOT: `.${GridClasses.ROOT}`,\n HEADER: `.${GridClasses.HEADER}`,\n HEADER_ROW: `.${GridClasses.HEADER_ROW}`,\n HEADER_CELL: `.${GridClasses.HEADER_CELL}`,\n ROWS_VIEWPORT: `.${GridClasses.ROWS_VIEWPORT}`,\n ROWS_CONTAINER: `.${GridClasses.ROWS_CONTAINER}`,\n DATA_ROW: `.${GridClasses.DATA_ROW}`,\n DATA_CELL: `.${GridClasses.DATA_CELL}`,\n GROUP_ROW: `.${GridClasses.GROUP_ROW}`,\n\n // By data attribute\n CELL_BY_FIELD: (field: string) => `.${GridClasses.DATA_CELL}[${GridDataAttrs.FIELD}=\"${field}\"]`,\n\n // State selectors\n SELECTED_ROWS: `.${GridClasses.DATA_ROW}.${GridClasses.SELECTED}`,\n EDITING_CELL: `.${GridClasses.DATA_CELL}.${GridClasses.EDITING}`,\n} as const;\n\n// #endregion\n\n// #region CSS Custom Properties\n\n/**\n * CSS custom property names for theming.\n * Use these when programmatically setting styles.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridCSSVars = {\n // Colors\n COLOR_BG: '--tbw-color-bg',\n COLOR_FG: '--tbw-color-fg',\n COLOR_FG_MUTED: '--tbw-color-fg-muted',\n COLOR_BORDER: '--tbw-color-border',\n COLOR_ACCENT: '--tbw-color-accent',\n COLOR_HEADER_BG: '--tbw-color-header-bg',\n COLOR_HEADER_FG: '--tbw-color-header-fg',\n COLOR_SELECTION: '--tbw-color-selection',\n COLOR_ROW_HOVER: '--tbw-color-row-hover',\n COLOR_ROW_ALT: '--tbw-color-row-alt',\n\n // Sizing\n ROW_HEIGHT: '--tbw-row-height',\n HEADER_HEIGHT: '--tbw-header-height',\n CELL_PADDING: '--tbw-cell-padding',\n\n // Typography\n FONT_FAMILY: '--tbw-font-family',\n FONT_SIZE: '--tbw-font-size',\n\n // Borders\n BORDER_RADIUS: '--tbw-border-radius',\n FOCUS_OUTLINE: '--tbw-focus-outline',\n} as const;\n\n// #endregion\n\n// Type helpers\n\n/**\n * Union of all CSS class names used in the grid's DOM.\n * Use `GridClasses` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridClassName = (typeof GridClasses)[keyof typeof GridClasses];\n\n/**\n * Union of all `data-*` attribute names used on grid elements.\n * Use `GridDataAttrs` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridDataAttr = (typeof GridDataAttrs)[keyof typeof GridDataAttrs];\n\n/**\n * Union of all CSS custom property names (`--tbw-*`) available for theming.\n * Use `GridCSSVars` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridCSSVar = (typeof GridCSSVars)[keyof typeof GridCSSVars];\n","// #region Environment Helpers\n\n/**\n * Check if we're running in a development environment.\n * Returns true for localhost or when NODE_ENV !== 'production'.\n * Used to show warnings only in development.\n */\nexport function isDevelopment(): boolean {\n // Check for localhost (browser environment)\n if (typeof window !== 'undefined' && window.location) {\n const hostname = window.location.hostname;\n if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {\n return true;\n }\n }\n // Check for NODE_ENV (build-time or SSR)\n if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n return true;\n }\n return false;\n}\n\n// #endregion\n\n// #region Cell Rendering Helpers\n\n/**\n * Generate accessible HTML for a boolean cell.\n * Uses role=\"checkbox\" with proper aria attributes.\n */\nexport function booleanCellHTML(value: boolean): string {\n return `<span role=\"checkbox\" aria-checked=\"${value}\" aria-label=\"${value}\">${value ? '&#x1F5F9;' : '&#9744;'}</span>`;\n}\n\n/**\n * Format a date value for display.\n * Handles Date objects, timestamps, and date strings.\n * Returns empty string for invalid dates.\n */\nexport function formatDateValue(value: unknown): string {\n if (value == null || value === '') return '';\n if (value instanceof Date) {\n return isNaN(value.getTime()) ? '' : value.toLocaleDateString();\n }\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return isNaN(d.getTime()) ? '' : d.toLocaleDateString();\n }\n return '';\n}\n\n/**\n * Get the row index from a cell element's data-row attribute.\n * Falls back to calculating from parent row's DOM position if data-row is missing.\n * Returns -1 if no valid row index is found.\n */\nexport function getRowIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-row');\n if (attr) return parseInt(attr, 10);\n\n // Fallback: find the parent .data-grid-row and calculate index from siblings\n const rowEl = cell.closest('.data-grid-row');\n if (!rowEl) return -1;\n\n const parent = rowEl.parentElement;\n if (!parent) return -1;\n\n // Get all data-grid-row siblings and find this row's index\n const rows = parent.querySelectorAll(':scope > .data-grid-row');\n for (let i = 0; i < rows.length; i++) {\n if (rows[i] === rowEl) return i;\n }\n return -1;\n}\n\n/**\n * Get the column index from a cell element's data-col attribute.\n * Returns -1 if no valid column index is found.\n */\nexport function getColIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-col');\n return attr ? parseInt(attr, 10) : -1;\n}\n\n/**\n * Clear all cell-focus styling from a root element.\n * Used when changing focus or when selection plugin takes over focus management.\n */\nexport function clearCellFocus(root: Element | null): void {\n if (!root) return;\n root.querySelectorAll('.cell-focus').forEach((el) => el.classList.remove('cell-focus'));\n}\n// #endregion\n\n// #region RTL Helpers\n\n/** Text direction */\nexport type TextDirection = 'ltr' | 'rtl';\n\n/**\n * Get the text direction for an element.\n * Reads from the computed style, which respects the `dir` attribute on the element\n * or any ancestor, as well as CSS `direction` property.\n *\n * @param element - The element to check direction for\n * @returns 'ltr' or 'rtl'\n *\n * @example\n * ```typescript\n * // Detect grid's direction\n * const dir = getDirection(gridElement);\n * if (dir === 'rtl') {\n * // Handle RTL layout\n * }\n * ```\n */\nexport function getDirection(element: Element): TextDirection {\n // Try computed style first (works in real browsers)\n try {\n const computedDir = getComputedStyle(element).direction;\n if (computedDir === 'rtl') return 'rtl';\n } catch {\n // getComputedStyle may fail in some test environments\n }\n\n // Fallback: check dir attribute on element or ancestors\n // This handles test environments where getComputedStyle may not reflect dir attribute\n try {\n const dirAttr = element.closest?.('[dir]')?.getAttribute('dir');\n if (dirAttr === 'rtl') return 'rtl';\n } catch {\n // closest may not be available on mock elements\n }\n\n return 'ltr';\n}\n\n/**\n * Check if an element is in RTL mode.\n *\n * @param element - The element to check\n * @returns true if the element's text direction is right-to-left\n */\nexport function isRTL(element: Element): boolean {\n return getDirection(element) === 'rtl';\n}\n\n/**\n * Resolve a logical inline position to a physical position based on text direction.\n *\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n * - `'left'` / `'right'` → unchanged (physical values)\n *\n * @param position - Logical or physical position\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical position ('left' or 'right')\n *\n * @example\n * ```typescript\n * resolveInlinePosition('start', 'ltr'); // 'left'\n * resolveInlinePosition('start', 'rtl'); // 'right'\n * resolveInlinePosition('left', 'rtl'); // 'left' (unchanged)\n * ```\n */\nexport function resolveInlinePosition(\n position: 'left' | 'right' | 'start' | 'end',\n direction: TextDirection,\n): 'left' | 'right' {\n if (position === 'left' || position === 'right') {\n return position;\n }\n if (direction === 'rtl') {\n return position === 'start' ? 'right' : 'left';\n }\n return position === 'start' ? 'left' : 'right';\n}\n// #endregion\n","/**\n * Central keyboard handler attached to the host element. Manages navigation, paging,\n * and edit lifecycle triggers while respecting active form field interactions.\n */\nimport { FOCUSABLE_EDITOR_SELECTOR, GridClasses } from '../constants';\nimport type { GridHost } from '../types';\nimport { clearCellFocus, isRTL } from './utils';\nimport { readCellField } from './value-accessor';\nimport { fromVirtualScrollTop, getRowIndexAtOffset, toVirtualScrollTop } from './virtualization';\n\n/** Commit active row edit if the editing plugin provides this method. */\nfunction tryCommitEdit(grid: GridHost): void {\n if (typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n}\n\nfunction isFormField(el: HTMLElement | null): boolean {\n if (!el) return false;\n const tag = el.tagName;\n if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') return true;\n if (el.isContentEditable) return true;\n return false;\n}\n\n/** True while a row-scoped edit session is active. */\nfunction isRowEditing(grid: GridHost): boolean {\n return grid._activeEditRows !== undefined && grid._activeEditRows !== -1;\n}\n\n// #region Keyboard Handler\n/**\n * Guard chain deciding whether this keydown belongs to something other than\n * cell navigation and must be left alone.\n */\nfunction shouldIgnoreKeyDown(grid: GridHost, e: KeyboardEvent, target: HTMLElement | null): boolean {\n // Keyboard events whose target lives inside the grid host but OUTSIDE the\n // rows body (toolpanel inputs/buttons, shell-header controls, light-DOM\n // children of <tbw-grid>) own their own keyboard interactions. Treating\n // ArrowUp/ArrowDown/Enter on such a target as cell navigation steals the\n // user's actual interaction target on the very next render cycle.\n // Cells and the grid host itself remain valid navigation targets.\n if (target && target !== grid && !target.closest?.('.rows-body')) return true;\n\n if (isFormField(target)) {\n switch (e.key) {\n // Home/End and horizontal arrows navigate within the text input; Enter\n // and Escape are handled by the input's own handlers first.\n case 'Home':\n case 'End':\n case 'ArrowLeft':\n case 'ArrowRight':\n case 'Enter':\n case 'Escape':\n return true;\n case 'ArrowUp':\n case 'ArrowDown':\n // Number spinners own the vertical arrows; other fields fall through.\n if ((target as HTMLInputElement).tagName === 'INPUT' && (target as HTMLInputElement).type === 'number') {\n return true;\n }\n break;\n }\n }\n\n const colType = grid._visibleColumns[grid._focusCol]?.type;\n return isRowEditing(grid) && colType === 'select' && (e.key === 'ArrowDown' || e.key === 'ArrowUp');\n}\n\n/** Tab / Shift+Tab: advance one cell, wrapping to the next/previous row. */\nfunction navigateTab(grid: GridHost, e: KeyboardEvent, maxRow: number, maxCol: number): void {\n e.preventDefault();\n if (!e.shiftKey) {\n if (grid._focusCol < maxCol) {\n grid._focusCol += 1;\n return;\n }\n tryCommitEdit(grid);\n if (grid._focusRow < maxRow) {\n grid._focusRow += 1;\n grid._focusCol = 0;\n }\n return;\n }\n if (grid._focusCol > 0) {\n grid._focusCol -= 1;\n } else if (grid._focusRow > 0) {\n if (grid._activeEditRows === grid._focusRow) tryCommitEdit(grid);\n grid._focusRow -= 1;\n grid._focusCol = maxCol;\n }\n}\n\n/**\n * ArrowLeft/ArrowRight. `towardEnd` is the LOGICAL direction (ArrowRight); in\n * RTL the physical arrow maps to the opposite column index.\n */\nfunction navigateHorizontal(grid: GridHost, e: KeyboardEvent, maxCol: number, towardEnd: boolean): void {\n const forward = isRTL(grid) ? !towardEnd : towardEnd;\n grid._focusCol = forward ? Math.min(maxCol, grid._focusCol + 1) : Math.max(0, grid._focusCol - 1);\n e.preventDefault();\n}\n\n/** Commit any active row edit, then focus the first/last row (column unchanged). */\nfunction jumpToRowEdge(grid: GridHost, maxRow: number, toEnd: boolean): void {\n if (isRowEditing(grid)) tryCommitEdit(grid);\n grid._focusRow = toEnd ? maxRow : 0;\n}\n\n/** ArrowUp/ArrowDown: one row, or the first/last row with Ctrl/Cmd. */\nfunction navigateVertical(grid: GridHost, e: KeyboardEvent, maxRow: number, down: boolean): void {\n if (e.ctrlKey || e.metaKey) jumpToRowEdge(grid, maxRow, down);\n else {\n if (isRowEditing(grid)) tryCommitEdit(grid);\n grid._focusRow = down ? Math.min(maxRow, grid._focusRow + 1) : Math.max(0, grid._focusRow - 1);\n }\n e.preventDefault();\n}\n\n/**\n * PageUp/PageDown target: one viewport of rows (less any overlay band), so an\n * unconstrained grid pages straight to its edge. 20 rows until layout exists.\n */\nfunction pageTargetRow(grid: GridHost, maxRow: number, down: boolean): number {\n const from = grid._focusRow;\n const virt = grid._virtualization;\n const viewportH = virt?.viewportEl?.clientHeight ?? virt?.container?.clientHeight ?? 0;\n const offsets = grid._getVerticalScrollOffsets?.(from);\n const usable = viewportH - Math.max(0, offsets?.top ?? 0) - Math.max(0, offsets?.bottom ?? 0);\n const pc = virt?.variableHeights ? virt.positionCache : null;\n let target: number;\n if (usable > 0 && pc?.[from]) {\n target = getRowIndexAtOffset(pc, pc[from].offset + (down ? usable : -usable));\n // A row taller than the viewport would otherwise pin focus in place.\n if (target === from) target = from + (down ? 1 : -1);\n } else {\n const rowH = virt?.rowHeight ?? 0;\n const step = usable > 0 && rowH > 0 ? Math.max(1, Math.floor(usable / rowH)) : 20;\n target = from + (down ? step : -step);\n }\n return Math.max(0, Math.min(maxRow, target));\n}\n\n/** Home/End (plus Ctrl/Cmd variants which also jump to the first/last row). */\nfunction navigateRowEdge(grid: GridHost, e: KeyboardEvent, maxRow: number, maxCol: number, toEnd: boolean): void {\n if (e.ctrlKey || e.metaKey) jumpToRowEdge(grid, maxRow, toEnd);\n grid._focusCol = toEnd ? maxCol : 0;\n e.preventDefault();\n ensureCellVisible(grid, toEnd ? { forceScrollRight: true } : { forceScrollLeft: true });\n}\n\n/** Dispatch the unified `cell-activate` event. Returns true when cancelled. */\nfunction emitCellActivate(grid: GridHost, e: KeyboardEvent): boolean {\n const rowIndex = grid._focusRow;\n const colIndex = grid._focusCol;\n const column = grid._visibleColumns[colIndex];\n const row = grid._rows[rowIndex];\n const field = column?.field ?? '';\n const activateEvent = new CustomEvent('cell-activate', {\n cancelable: true,\n detail: {\n rowIndex,\n colIndex,\n column,\n field,\n value: field && row ? readCellField(row, field) : undefined,\n row,\n cellEl: grid.querySelector(`[data-row=\"${rowIndex}\"][data-col=\"${colIndex}\"]`) as HTMLElement | undefined,\n trigger: 'keyboard' as const,\n originalEvent: e,\n },\n });\n grid.dispatchEvent(activateEvent);\n return activateEvent.defaultPrevented;\n}\n\nexport function handleGridKeyDown(grid: GridHost, e: KeyboardEvent): void {\n // Dispatch to plugin system first - if any plugin handles it, stop here\n if (grid._dispatchKeyDown?.(e)) return;\n\n const path = e.composedPath?.() ?? [];\n const target = (path.length ? path[0] : e.target) as HTMLElement | null;\n if (shouldIgnoreKeyDown(grid, e, target)) return;\n\n const maxRow = grid._rows.length - 1;\n const maxCol = grid._visibleColumns.length - 1;\n\n switch (e.key) {\n case 'Tab':\n navigateTab(grid, e, maxRow, maxCol);\n break;\n case 'ArrowDown':\n navigateVertical(grid, e, maxRow, true);\n break;\n case 'ArrowUp':\n navigateVertical(grid, e, maxRow, false);\n break;\n case 'ArrowRight':\n navigateHorizontal(grid, e, maxCol, true);\n break;\n case 'ArrowLeft':\n navigateHorizontal(grid, e, maxCol, false);\n break;\n case 'Home':\n navigateRowEdge(grid, e, maxRow, maxCol, false);\n return;\n case 'End':\n navigateRowEdge(grid, e, maxRow, maxCol, true);\n return;\n case 'PageDown':\n grid._focusRow = pageTargetRow(grid, maxRow, true);\n e.preventDefault();\n break;\n case 'PageUp':\n grid._focusRow = pageTargetRow(grid, maxRow, false);\n e.preventDefault();\n break;\n // NOTE: Enter is normally handled by EditingPlugin. If no plugin handled\n // it, dispatch the unified cell-activate event for custom handling.\n case 'Enter':\n if (emitCellActivate(grid, e)) {\n e.preventDefault();\n return;\n }\n break;\n default:\n return;\n }\n ensureCellVisible(grid);\n}\n// #endregion\n\n// #region Cell Visibility\n/**\n * Options for ensureCellVisible to control scroll behavior.\n */\ninterface EnsureCellVisibleOptions {\n /** Force scroll to the leftmost position (for Home key) */\n forceScrollLeft?: boolean;\n /** Force scroll to the rightmost position (for End key) */\n forceScrollRight?: boolean;\n /** Force horizontal scroll even in edit mode (for Tab navigation) */\n forceHorizontalScroll?: boolean;\n}\n\n/**\n * Vertical: scroll the virtualized container so the focused row is in view.\n *\n * `y` is in raw row-content space (rowHeight × index). Above the browser height\n * cap (#326), `scrollEl.scrollTop` is in clamped spacer space — the two\n * coordinate systems are not interchangeable, so translate scrollTop into\n * virtual space for the comparison and back into native space when writing.\n * For sub-cap datasets the mapping is identity, so this is a no-op.\n *\n * Plugins that paint over the viewport (sticky rows) report the space they\n * obscure via `_getVerticalScrollOffsets`, so the row lands clear of the\n * overlay instead of underneath it — WCAG 2.2 SC 2.4.11 Focus Not Obscured.\n */\nfunction scrollFocusedRowIntoView(grid: GridHost): void {\n if (!grid._virtualization?.enabled) return;\n const { rowHeight, container, viewportEl, scrollMapping } = grid._virtualization;\n // container is the faux scrollbar element that handles actual scrolling;\n // viewportEl is the visible area element that has the correct height.\n const scrollEl = container as HTMLElement | undefined;\n const visibleHeight = viewportEl?.clientHeight ?? scrollEl?.clientHeight ?? 0;\n if (!scrollEl || visibleHeight <= 0) return;\n\n const offsets = grid._getVerticalScrollOffsets?.(grid._focusRow) ?? { top: 0, bottom: 0 };\n if (offsets.skipScroll) return;\n // A malformed or oversized report must never invert the visible band.\n const top = Math.max(0, offsets.top);\n const bottom = Math.max(0, offsets.bottom);\n const usableHeight = visibleHeight - top - bottom;\n if (usableHeight <= 0) return;\n\n const y = grid._focusRow * rowHeight;\n const virtualScrollTop = scrollMapping ? toVirtualScrollTop(scrollEl.scrollTop, scrollMapping) : scrollEl.scrollTop;\n if (y < virtualScrollTop + top) {\n const target = Math.max(0, y - top);\n scrollEl.scrollTop = scrollMapping ? fromVirtualScrollTop(target, scrollMapping) : target;\n } else if (y + rowHeight > virtualScrollTop + top + usableHeight) {\n const target = Math.max(0, y - usableHeight + rowHeight - top);\n scrollEl.scrollTop = scrollMapping ? fromVirtualScrollTop(target, scrollMapping) : target;\n }\n}\n\n/**\n * Resolve the rendered cell for the focused row/col, or `undefined` when the\n * focused row is outside the current virtual window.\n */\nfunction findFocusedCell(grid: GridHost): { rowEl: HTMLElement | null; cell: HTMLElement } | undefined {\n const rowIndex = grid._focusRow;\n const vStart = grid._virtualization.start ?? 0;\n const vEnd = grid._virtualization.end ?? grid._rows.length;\n if (rowIndex < vStart || rowIndex >= vEnd) return undefined;\n\n const rowEl = grid._bodyEl.querySelectorAll('.data-grid-row')[rowIndex - vStart] as HTMLElement | null;\n // Try exact column match first, then query by data-col, then fall back to the\n // first cell (full-width group rows).\n let cell = rowEl?.children[grid._focusCol] as HTMLElement | undefined;\n if (!cell || !cell.classList?.contains('cell')) {\n cell = (rowEl?.querySelector(`.cell[data-col=\"${grid._focusCol}\"]`) ?? rowEl?.querySelector('.cell[data-col]')) as\n HTMLElement | undefined;\n }\n return cell ? { rowEl: rowEl ?? null, cell } : undefined;\n}\n\n/** Horizontal: bring the focused cell into the `.tbw-scroll-area` viewport. */\nfunction scrollCellIntoViewHorizontally(\n grid: GridHost,\n rowEl: HTMLElement | null,\n cell: HTMLElement,\n options?: EnsureCellVisibleOptions,\n): void {\n const scrollArea = grid.querySelector('.tbw-scroll-area') as HTMLElement | null;\n if (!scrollArea) return;\n\n // Home/End always scroll to the edge.\n if (options?.forceScrollLeft) {\n scrollArea.scrollLeft = 0;\n return;\n }\n if (options?.forceScrollRight) {\n scrollArea.scrollLeft = scrollArea.scrollWidth - scrollArea.clientWidth;\n return;\n }\n\n // Plugins (e.g. pinned columns) report how much of the scroll area they\n // obscure, and whether the focused cell should skip scrolling entirely\n // (pinned cells are always visible).\n const offsets = grid._getHorizontalScrollOffsets?.(rowEl ?? undefined, cell) ?? { left: 0, right: 0 };\n if (offsets.skipScroll) return;\n\n const cellRect = cell.getBoundingClientRect();\n const scrollAreaRect = scrollArea.getBoundingClientRect();\n const cellLeft = cellRect.left - scrollAreaRect.left + scrollArea.scrollLeft;\n const cellRight = cellLeft + cellRect.width;\n const visibleLeft = scrollArea.scrollLeft + offsets.left;\n const visibleRight = scrollArea.scrollLeft + scrollArea.clientWidth - offsets.right;\n if (cellLeft < visibleLeft) {\n scrollArea.scrollLeft = cellLeft - offsets.left;\n } else if (cellRight > visibleRight) {\n scrollArea.scrollLeft = cellRight - scrollArea.clientWidth + offsets.right;\n }\n}\n\n/** Move real DOM focus to the editor, the cell, or the grid host. */\nfunction applyFocusTarget(grid: GridHost, cell: HTMLElement, isEditing: boolean): void {\n if (isEditing && cell.classList.contains(GridClasses.EDITING)) {\n // Editing cell: focus the editor input inside it\n const focusTarget = cell.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n if (focusTarget && document.activeElement !== focusTarget) {\n try {\n focusTarget.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n }\n return;\n }\n\n if (isEditing) {\n // Active edit row but this cell isn't the editing cell — focus it so Tab\n // navigation within the row can attach editors.\n if (cell.contains(document.activeElement)) return;\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n try {\n cell.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n return;\n }\n\n // NOT editing: keep focus on the grid element (tabindex=0) rather than\n // individual cells. In a virtualized grid, cells can be detached by\n // subsequent render cycles (e.g., SelectionPlugin's requestAfterRender\n // → RAF → row recycling). A detached focused cell causes activeElement\n // to revert to <body>, breaking keyboard navigation.\n // Visual focus is managed by the .cell-focus CSS class + data-has-focus.\n //\n // BUT: don't steal focus from a real focused descendant (toolpanel\n // input, shell-header button, registered overlay control). Cell focus\n // is virtual; if the user is actively typing in a non-cell input, we\n // must leave that input alone. We only reclaim focus when nothing\n // meaningful inside the grid currently holds it (active element is\n // outside grid, on the grid host itself, or on a bare cell).\n const active = document.activeElement;\n const meaningful =\n active instanceof HTMLElement &&\n active !== grid &&\n typeof grid.contains === 'function' &&\n grid.contains(active) &&\n !active.classList.contains('cell') &&\n !active.closest('.cell');\n if (!meaningful && active !== grid) {\n grid.focus({ preventScroll: true });\n }\n}\n\n/**\n * Scroll the viewport (virtualized or static) so the focused cell's row is visible\n * and apply visual focus styling / tabindex management.\n */\nexport function ensureCellVisible(grid: GridHost, options?: EnsureCellVisibleOptions): void {\n scrollFocusedRowIntoView(grid);\n\n const isEditing = isRowEditing(grid) || !!grid._isGridEditMode;\n // Skip refreshVirtualWindow when in edit mode to avoid wiping editors\n if (!isEditing) grid.refreshVirtualWindow(false);\n\n clearCellFocus(grid._bodyEl);\n // Clear previous aria-selected markers\n Array.from(grid._bodyEl.querySelectorAll('[aria-selected=\"true\"]')).forEach((el) => {\n el.setAttribute('aria-selected', 'false');\n });\n\n const focused = findFocusedCell(grid);\n if (!focused) return;\n const { rowEl, cell } = focused;\n\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n\n // Skip horizontal scrolling in edit mode to prevent scroll jumps when editors\n // are created — unless forceHorizontalScroll is set (Tab while editing).\n if (!isEditing || options?.forceHorizontalScroll) {\n scrollCellIntoViewHorizontally(grid, rowEl, cell, options);\n }\n\n applyFocusTarget(grid, cell, isEditing);\n}\n// #endregion\n","/**\n * Generic click-only action menu — the shared building block for WCAG 2.2\n * SC 2.5.7 \"Dragging Movements\" alternatives in plugins.\n *\n * Every drag affordance in the grid needs a path that works with plain clicks\n * or taps. A keyboard equivalent is **not** enough: SC 2.5.7 is specifically\n * about pointer input, and the W3C Understanding text calls out \"a pop-up menu\n * after tap\" as a conforming alternative. This module is that pop-up.\n *\n * Lives under `plugins/shared/` on purpose: each plugin bundle is built\n * self-contained, so this code is duplicated into the plugins that use it and\n * never lands in the core `index.js` budget.\n *\n * @since 3.6.0\n * @internal\n */\n\n/** Gap between the anchor element and the menu, in px. */\nconst ANCHOR_GAP_PX = 6;\n\n/** Runtime check — happy-dom and older browsers may lack the Popover API. */\nfunction supportsPopover(): boolean {\n return typeof HTMLElement.prototype?.showPopover === 'function';\n}\n\nlet hoverlessQuery: MediaQueryList | null | undefined;\n\n/** Whether the primary pointer cannot hover (touch, most switch devices). */\nfunction isHoverless(): boolean {\n if (hoverlessQuery === undefined) hoverlessQuery = globalThis.matchMedia?.('(hover: none)') ?? null;\n return hoverlessQuery?.matches ?? false;\n}\n\n/**\n * Whether a plugin should also expose its drag alternative as a dedicated\n * inline control, on top of the menu that is always available.\n *\n * Hover-less pointers get one unconditionally — a control revealed on hover is\n * unreachable when nothing can hover, and long-press is the wrong fallback for\n * the tremor and low-dexterity users SC 2.5.7 exists for.\n *\n * Driven by `a11y.dragAlternatives` on the grid config.\n */\nexport function prefersInlineDragAlternative(grid: {\n effectiveConfig?: { a11y?: { dragAlternatives?: 'menu' | 'inline' } };\n}): boolean {\n return grid.effectiveConfig?.a11y?.dragAlternatives === 'inline' || isHoverless();\n}\n\n/** One clickable entry in the menu. */\nexport interface DragAlternativeAction {\n /** Visible text, also the accessible name. */\n label: string;\n /** Render the entry but refuse the click (e.g. \"Move up\" on the first row). */\n disabled?: boolean;\n /** Invoked on click; the menu closes first so focus handling is predictable. */\n run(): void;\n}\n\nexport interface DragAlternativeMenu {\n /**\n * Show `actions` anchored to `anchor`, with `label` as the group's accessible\n * name. Mounted inside the anchor's grid host so it inherits theme tokens.\n */\n open(anchor: HTMLElement, label: string, actions: readonly DragAlternativeAction[]): void;\n /** Hide the menu. Safe to call when already closed. */\n close(): void;\n /** Remove the element and every document-level listener. */\n dispose(): void;\n /** The menu element, or `null` before the first {@link DragAlternativeMenu.open}. */\n readonly element: HTMLElement | null;\n}\n\n/**\n * Create a menu instance.\n *\n * @param id - DOM id for the menu element, unique per owning plugin.\n * @param className - Root class name, so each plugin can style its own menu.\n */\nexport function createDragAlternativeMenu(id: string, className: string): DragAlternativeMenu {\n let root: HTMLDivElement | null = null;\n let opened = false;\n let disposed = false;\n const abort = new AbortController();\n\n function close(): void {\n if (!opened || !root) return;\n opened = false;\n if (supportsPopover()) root.hidePopover();\n root.hidden = true;\n }\n\n function build(): HTMLDivElement {\n const el = document.createElement('div');\n el.id = id;\n el.className = className;\n el.setAttribute('popover', 'manual');\n el.setAttribute('role', 'group');\n // Without the Popover API there is no UA `display: none`, so hide it here.\n el.hidden = true;\n // Tabbing past either end dismisses instead of stranding the user — the\n // menu has no visible trigger to tab back to. A null `relatedTarget` (click\n // focus in Firefox/Safari, focus leaving the document) is ignored.\n el.addEventListener(\n 'focusout',\n (e) => {\n const next = (e as FocusEvent).relatedTarget;\n if (next instanceof Node && !el.contains(next)) close();\n },\n { signal: abort.signal },\n );\n return el;\n }\n\n /**\n * Position against the anchor in viewport coordinates. The menu lives in the\n * top layer, so its containing block is the viewport regardless of where the\n * anchor sits in the DOM.\n */\n function place(anchor: HTMLElement): void {\n if (!root) return;\n const a = anchor.getBoundingClientRect();\n const box = root.getBoundingClientRect();\n const left = Math.max(4, Math.min(a.left, window.innerWidth - box.width - 4));\n const below = a.bottom + ANCHOR_GAP_PX;\n const top = below + box.height <= window.innerHeight ? below : Math.max(4, a.top - ANCHOR_GAP_PX - box.height);\n root.style.left = `${Math.round(left)}px`;\n root.style.top = `${Math.round(top)}px`;\n }\n\n // Any pointer press outside the menu dismisses it. Capture phase so the press\n // that lands on a grid cell still reaches its own handler afterwards.\n document.addEventListener(\n 'pointerdown',\n (e) => {\n if (opened && root && !e.composedPath().includes(root)) close();\n },\n { capture: true, signal: abort.signal },\n );\n document.addEventListener(\n 'keydown',\n (e) => {\n if ((e as KeyboardEvent).key === 'Escape') close();\n },\n { capture: true, signal: abort.signal },\n );\n\n return {\n get element() {\n return root;\n },\n open(anchor, label, actions) {\n if (disposed || actions.length === 0) return;\n if (!root) root = build();\n // Mount inside the grid host (light DOM) so theme custom properties and\n // `color-scheme` cascade in; the top layer keeps it out of overflow.\n const host = anchor.closest<HTMLElement>('tbw-grid, [data-tbw-grid]') ?? document.body;\n if (root.parentNode !== host) host.appendChild(root);\n root.setAttribute('aria-label', label);\n root.replaceChildren();\n for (const action of actions) {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `${className}-item`;\n btn.textContent = action.label;\n btn.disabled = action.disabled === true;\n btn.addEventListener('click', () => {\n close();\n action.run();\n });\n root.appendChild(btn);\n }\n root.hidden = false;\n if (!opened && supportsPopover()) root.showPopover();\n opened = true;\n place(anchor);\n root.querySelector<HTMLButtonElement>('button:not([disabled])')?.focus();\n },\n close,\n dispose() {\n disposed = true;\n close();\n abort.abort();\n root?.remove();\n root = null;\n },\n };\n}\n","/**\n * Column Reordering Core Logic\n *\n * Pure functions for column drag and reordering operations.\n */\n\n/**\n * Check if a column can be moved based on its own metadata.\n * This checks column-level properties like lockPosition, utility, and suppressMovable.\n *\n * Both the top-level `lockPosition` (preferred) and legacy `meta.lockPosition` /\n * `meta.suppressMovable` are honored. `utility: true` columns (system columns\n * synthesized by plugins or authored by the app) are always treated as locked.\n *\n * Note: For full movability checks including plugin constraints (e.g., pinned columns),\n * use `grid.query<boolean>('canMoveColumn', column)` which queries all plugins that\n * declare the 'canMoveColumn' query in their manifest.\n *\n * @param column - The column configuration to check\n * @returns True if the column can be moved based on its metadata\n */\nexport function canMoveColumn(column: {\n lockPosition?: boolean;\n utility?: boolean;\n meta?: Record<string, unknown>;\n}): boolean {\n if (column.lockPosition === true) return false;\n if (column.utility === true) return false;\n const meta = column.meta ?? {};\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Move a column from one position to another in the order array.\n *\n * @param columns - Array of field names in current order\n * @param fromIndex - The current index of the column to move\n * @param toIndex - The target index to move the column to\n * @returns New array with updated order\n */\nexport function moveColumn(columns: string[], fromIndex: number, toIndex: number): string[] {\n if (fromIndex === toIndex) return columns;\n if (fromIndex < 0 || fromIndex >= columns.length) return columns;\n if (toIndex < 0 || toIndex > columns.length) return columns;\n\n const result = [...columns];\n const [removed] = result.splice(fromIndex, 1);\n result.splice(toIndex, 0, removed);\n return result;\n}\n","@layer tbw-plugins {\n .header-row > .cell[draggable='true'] {\n cursor: grab;\n position: relative;\n }\n .header-row > .cell.dragging {\n opacity: 0.5;\n cursor: grabbing;\n }\n .header-row > .cell.drop-before::before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\n z-index: 1;\n }\n .header-row > .cell.drop-after::after {\n content: '';\n position: absolute;\n right: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\n z-index: 1;\n }\n\n /* FLIP animation: cells slide smoothly during reorder */\n .cell.flip-animating {\n transition: transform var(--tbw-animation-duration, 200ms) ease-out;\n will-change: transform;\n z-index: 1;\n }\n\n /* Fade animation: crossfade columns at new position */\n @keyframes reorder-fade-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n }\n\n .cell.fade-animating {\n animation: reorder-fade-in var(--tbw-animation-duration, 200ms) ease-out backwards;\n }\n\n /* ========== Click-only move control (WCAG 2.2 SC 2.5.7) ==========\n Dragging a header is the primary gesture; the single-pointer alternative is\n always available from the header's context menu. This button is the *extra*\n affordance, and the plugin only puts it in the DOM under\n `a11y.dragAlternatives: 'inline'` or on a hover-less pointer — because as a\n flex sibling it permanently reserves ~24px of every header cell, which is\n real estate width-critical grids cannot spare.\n\n It is a sibling rather than an absolute overlay on purpose: an overlay sits\n on top of the header's own click-to-sort target, so on any column narrower\n than ~135px a click aimed at the middle of the header would land on the\n button and silently not sort.\n\n Once present it stays in the a11y tree at all times so speech- and\n switch-control users can target it, which is why it is dimmed with\n `opacity` rather than `display: none` / `visibility: hidden` (both of those\n also remove it from the accessibility tree, defeating the point). */\n .header-row > .cell > .tbw-col-move-btn,\n .header-group-row > .cell > .tbw-col-move-btn {\n flex-shrink: 0;\n min-width: var(--tbw-touch-target-min, 24px);\n min-height: var(--tbw-touch-target-min, 24px);\n padding: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: var(--tbw-color-header-bg);\n color: var(--tbw-color-header-fg, inherit);\n font: inherit;\n line-height: 1;\n border: 1px solid var(--tbw-color-border);\n border-radius: var(--tbw-border-radius, 0.25rem);\n cursor: pointer;\n opacity: 0;\n pointer-events: none;\n transition: opacity var(--tbw-transition-duration, 120ms) var(--tbw-transition-ease, ease);\n\n &:hover {\n border-color: var(--tbw-color-accent);\n }\n }\n\n .header-row > .cell:hover > .tbw-col-move-btn,\n .header-row > .cell:focus-within > .tbw-col-move-btn,\n .header-row > .cell.cell-focus > .tbw-col-move-btn,\n .header-group-row > .cell:hover > .tbw-col-move-btn,\n .header-group-row > .cell:focus-within > .tbw-col-move-btn {\n opacity: 1;\n pointer-events: auto;\n }\n\n /* No hover to reveal it (touch, most switch devices): keep it visible. */\n @media (hover: none) {\n .header-row > .cell > .tbw-col-move-btn,\n .header-group-row > .cell > .tbw-col-move-btn {\n opacity: 1;\n pointer-events: auto;\n }\n }\n\n /* Menu opened by the move button. Lives on document.body (top layer), so every\n custom property needs a literal fallback. */\n .tbw-col-move-menu {\n position: fixed;\n margin: 0;\n padding: 4px;\n display: flex;\n flex-direction: column;\n min-width: 10rem;\n background: var(--tbw-color-panel-bg, light-dark(#fff, #1e1e1e));\n color: var(--tbw-color-panel-fg, light-dark(#1a1a1a, #eaeaea));\n border: 1px solid var(--tbw-color-border, light-dark(#d0d0d0, #3a3a3a));\n border-radius: var(--tbw-border-radius, 0.25rem);\n box-shadow: var(--tbw-shadow-panel, 0 4px 12px light-dark(rgb(0 0 0 / 20%), rgb(0 0 0 / 50%)));\n z-index: 10001;\n\n &[hidden] {\n display: none;\n }\n }\n\n .tbw-col-move-menu-item {\n min-height: var(--tbw-touch-target-min, 24px);\n padding: 4px 8px;\n text-align: start;\n background: none;\n color: inherit;\n font: inherit;\n border: 0;\n /* Themes set radii for much larger surfaces (Material uses 12px), which\n turns a 24px row into a lozenge. */\n border-radius: min(var(--tbw-border-radius, 0.25rem), 6px);\n cursor: pointer;\n\n &:hover:not(:disabled) {\n background: var(--tbw-color-row-hover, light-dark(#00000010, #ffffff14));\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: default;\n }\n }\n} /* End @layer tbw-plugins */\n","/**\n * Column Reordering Plugin (Class-based)\n *\n * Provides drag-and-drop column reordering functionality for tbw-grid.\n * Supports keyboard and mouse interactions with visual feedback.\n * Uses FLIP animation technique for smooth column transitions.\n *\n * Animation respects grid-level animation.mode setting but style is plugin-configured.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport { BaseGridPlugin, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, GridHost } from '../../core/types';\nimport type { ContextMenuParams, HeaderContextMenuItem } from '../context-menu/types';\nimport {\n type DragAlternativeAction,\n type DragAlternativeMenu,\n createDragAlternativeMenu,\n prefersInlineDragAlternative,\n} from '../shared/drag-alternative-menu';\nimport { canMoveColumn, moveColumn } from './column-drag';\nimport styles from './reorder.css?inline';\nimport type { ColumnMoveDetail, ReorderConfig } from './types';\n\nconst QUERY_CAN_MOVE_COLUMN = 'canMoveColumn';\nconst QUERY_GET_CONTEXT_MENU_ITEMS = 'getContextMenuItems';\n\n/** Menu order band for column moving — after pinning (40-49). */\nconst MOVE_ITEM_ORDER = 50;\n\n/**\n * Column Reorder Plugin for tbw-grid\n *\n * Lets users rearrange columns by dragging and dropping column headers. Supports smooth\n * FLIP animations, fade transitions, or instant reordering. Animation respects the\n * grid-level `animation.mode` setting.\n *\n * ## Installation\n *\n * ```ts\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder-columns';\n * ```\n *\n * ## Keyboard Shortcuts\n *\n * | Key | Action |\n * |-----|--------|\n * | `Alt + ←` | Move focused column left |\n * | `Alt + →` | Move focused column right |\n *\n * ## Events\n *\n * | Event | Detail | Cancelable | Description |\n * |-------|--------|------------|-------------|\n * | `column-move` | `{ field, fromIndex, toIndex, columnOrder }` | Yes | Fired when a column move is attempted |\n *\n * @example Basic Drag-and-Drop Reordering\n * ```ts\n * import { queryGrid } from '@toolbox-web/grid';\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder-columns';\n *\n * const grid = queryGrid('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID' },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [new ReorderPlugin({ animation: 'flip', animationDuration: 200 })],\n * };\n *\n * // Persist column order\n * grid.on('column-move', ({ columnOrder }) => {\n * localStorage.setItem('columnOrder', JSON.stringify(columnOrder));\n * });\n * ```\n *\n * @example Prevent Moves That Break Group Boundaries\n * ```ts\n * grid.on('column-move', (detail, e) => {\n * if (!isValidMoveWithinGroup(detail.field, detail.fromIndex, detail.toIndex)) {\n * e.preventDefault(); // Column snaps back to original position\n * }\n * });\n * ```\n *\n * @see {@link ReorderConfig} for all configuration options\n * @see {@link ColumnMoveDetail} for the event detail structure\n * @see GroupingColumnsPlugin for column group integration\n *\n * @internal Extends BaseGridPlugin\n * @since 1.24.0\n */\nexport class ReorderPlugin extends BaseGridPlugin<ReorderConfig> {\n /** @internal */\n readonly name = 'reorderColumns';\n /** @internal */\n override readonly aliases = ['reorder'] as const;\n /** @internal */\n override readonly styles = styles;\n\n /**\n * Plugin manifest - declares handled queries.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n queries: [\n {\n type: QUERY_CAN_MOVE_COLUMN,\n description: 'Authoritative check for whether a column may be reordered (honors lockPosition).',\n },\n {\n type: QUERY_GET_CONTEXT_MENU_ITEMS,\n description: 'Contributes the click-only column move actions (WCAG 2.2 SC 2.5.7) to the header context menu.',\n },\n ],\n };\n\n /** @internal */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === QUERY_CAN_MOVE_COLUMN) {\n const column = query.context as ColumnConfig | undefined;\n return column ? canMoveColumn(column) : undefined;\n }\n if (query.type === QUERY_GET_CONTEXT_MENU_ITEMS) {\n const params = query.context as ContextMenuParams | undefined;\n if (!params?.isHeader) return undefined;\n return this.#moveActions(params.field)?.map((action, i): HeaderContextMenuItem => ({\n id: `reorder-${i}`,\n label: action.label,\n disabled: action.disabled,\n action: action.run,\n order: MOVE_ITEM_ORDER + i,\n }));\n }\n return undefined;\n }\n\n /** @internal */\n protected override get defaultConfig(): Partial<ReorderConfig> {\n return {\n animation: 'flip', // Plugin's own default\n };\n }\n\n /**\n * Resolve animation type from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationType(): false | 'flip' | 'fade' {\n // Check if animations are globally disabled\n if (!this.isAnimationEnabled) return false;\n\n // Plugin config (with default from defaultConfig)\n if (this.config.animation !== undefined) return this.config.animation;\n\n return 'flip'; // Plugin default\n }\n\n /**\n * Get animation duration, allowing plugin config override.\n * Uses base class animationDuration for default.\n */\n protected override get animationDuration(): number {\n // Plugin config override\n if (this.config.animationDuration !== undefined) {\n return this.config.animationDuration;\n }\n return super.animationDuration;\n }\n\n // #region Internal State\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n /** When dragging a group header, holds the field names in that fragment. */\n private draggedGroupFields: string[] = [];\n /** Initial column order (captured after order-attribute reordering). Used by resetColumnOrder(). */\n private originalColumnOrder: string[] | null = null;\n /** Lazily created click-only move menu (WCAG 2.2 SC 2.5.7). */\n private moveMenu: DragAlternativeMenu | null = null;\n\n /** Typed internal grid accessor. */\n get #internalGrid(): GridHost {\n return this.grid;\n }\n\n /**\n * Check if a column can be moved, considering both column config and plugin queries.\n *\n * The local `canMoveColumn(column)` check (lockPosition / suppressMovable) runs first;\n * cross-plugin vetoes (e.g. PinnedColumnsPlugin blocking sticky columns) are then\n * collected via the `canMoveColumn` query. Other plugins (e.g. VisibilityPlugin) get\n * the same answer by querying — ReorderPlugin's `handleQuery` returns the local check.\n */\n private canMoveColumnWithPlugins(column: ColumnConfig | undefined): boolean {\n if (!column || !canMoveColumn(column)) return false;\n const responses = this.grid.query<boolean>(QUERY_CAN_MOVE_COLUMN, column);\n return !responses.includes(false);\n }\n\n /**\n * Add the click-only move control to a movable header cell.\n *\n * WCAG 2.2 SC 2.5.7 \"Dragging Movements\" is **not** satisfied by the\n * `Alt + Arrow` shortcuts: the criterion is about pointer input, and the W3C\n * Understanding text is explicit that a keyboard equivalent only counts when\n * it also exposes controls that can be clicked or tapped. The alternative is\n * always reachable from the header's context menu; this button is the extra,\n * opt-in affordance for `a11y.dragAlternatives: 'inline'`.\n *\n * It costs ~24px of every header cell — it is a flex sibling of the label\n * rather than an overlay, because an overlay would swallow the header's own\n * click-to-sort target on narrow columns — so it is removed again as soon as\n * the mode no longer calls for it.\n */\n #syncMoveControl(headerEl: HTMLElement, field: string, column: ColumnConfig | undefined): void {\n this.#syncInlineMoveButton(headerEl, `Move column ${String(column?.header ?? field)}`, (btn) =>\n this.#openMoveMenu(btn, field),\n );\n }\n\n /**\n * Add or remove the inline `\\u2194` button on `hostEl` according to the current\n * drag-alternative mode. Shared by header cells and group header cells so the\n * two affordances can never drift apart.\n */\n #syncInlineMoveButton(hostEl: HTMLElement, ariaLabel: string, open: (btn: HTMLElement) => void): void {\n const existing = hostEl.querySelector<HTMLElement>(':scope > .tbw-col-move-btn');\n if (!prefersInlineDragAlternative(this.grid)) {\n existing?.remove();\n return;\n }\n if (existing) return;\n\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'tbw-col-move-btn';\n btn.tabIndex = -1;\n // Not draggable, or a press on the button would start an HTML5 column drag.\n btn.draggable = false;\n btn.textContent = '\\u2194';\n btn.setAttribute('aria-label', ariaLabel);\n // Stop the press from reaching the header's sort handler or the drag start.\n btn.addEventListener('pointerdown', (e) => e.stopPropagation());\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n e.preventDefault();\n open(btn);\n });\n hostEl.appendChild(btn);\n }\n\n /**\n * The single-click move actions for `field`, or `null` when the column\n * cannot be moved. Shared by the standalone menu and the `getContextMenuItems`\n * contribution so the two can never drift apart.\n */\n #moveActions(field: string): DragAlternativeAction[] | null {\n const column = this.columns.find((c) => c.field === field);\n if (!this.canMoveColumnWithPlugins(column)) return null;\n\n const order = this.getColumnOrder();\n const fromIndex = order.indexOf(field);\n if (fromIndex === -1) return null;\n\n const firstMovable = order.findIndex((f) => this.#isMoveTarget(f));\n let lastMovable = -1;\n for (let i = order.length - 1; i >= 0; i--) {\n if (this.#isMoveTarget(order[i])) {\n lastMovable = i;\n break;\n }\n }\n return [\n {\n label: 'Move left',\n disabled: !this.#canStep(order, fromIndex, -1),\n run: () => this.moveColumn(field, fromIndex - 1),\n },\n {\n label: 'Move right',\n disabled: !this.#canStep(order, fromIndex, 1),\n run: () => this.moveColumn(field, fromIndex + 1),\n },\n {\n label: 'Move to start',\n disabled: firstMovable === -1 || firstMovable === fromIndex,\n run: () => this.moveColumn(field, firstMovable),\n },\n {\n label: 'Move to end',\n disabled: lastMovable === -1 || lastMovable === fromIndex,\n run: () => this.moveColumn(field, lastMovable),\n },\n ];\n }\n\n /** Open the click-only move menu anchored to a header cell or its move button. */\n #openMoveMenu(anchor: HTMLElement, field: string): void {\n const actions = this.#moveActions(field);\n if (!actions) return;\n const column = this.columns.find((c) => c.field === field);\n\n this.moveMenu ??= createDragAlternativeMenu('tbw-col-move-menu', 'tbw-col-move-menu');\n this.moveMenu.open(anchor, `Move column ${String(column?.header ?? field)}`, actions);\n }\n\n /**\n * The group-row fragments in visual order, each as the fields it spans.\n *\n * Read back from the DOM rather than from the group config because a fragment\n * is what the user actually sees and drags: `mergeGroups` splits one configured\n * group into several cells when pinning or an interleaved utility column breaks\n * its span, and each of those cells moves independently.\n */\n #groupFragments(gridEl: HTMLElement): { id: string; fields: string[] }[] {\n const visible = this.visibleColumns;\n const fragments: { id: string; fields: string[] }[] = [];\n\n gridEl.querySelectorAll<HTMLElement>('.header-group-row > .cell[data-group]').forEach((cell) => {\n const id = cell.getAttribute('data-group');\n if (!id) return;\n const fields = this.getGroupFragmentFields(cell, id);\n if (fields.length > 0) fragments.push({ id, fields });\n });\n\n // The cells are appended in span order, but a plugin may re-parent them.\n return fragments.sort(\n (a, b) => visible.findIndex((c) => c.field === a.fields[0]) - visible.findIndex((c) => c.field === b.fields[0]),\n );\n }\n\n /**\n * The single-click move actions for a group fragment, or `null` when the\n * fragment cannot be moved.\n *\n * A step moves the whole fragment past its whole neighbour — landing *inside*\n * a neighbouring group would split that group, which dragging cannot do either.\n * A neighbour that holds even one locked column disables the step rather than\n * being skipped, since moving past it would still shift its index.\n */\n #groupMoveActions(gridEl: HTMLElement, fragmentFields: string[]): DragAlternativeAction[] | null {\n if (!fragmentFields.every((f) => this.#isMoveTarget(f))) return null;\n\n const fragments = this.#groupFragments(gridEl);\n const at = fragments.findIndex((f) => f.fields[0] === fragmentFields[0]);\n if (at === -1) return null;\n\n const movable = (fragment: { fields: string[] } | undefined): boolean =>\n !!fragment && fragment.fields.every((f) => this.#isMoveTarget(f));\n const spanMovable = (from: number, to: number): boolean =>\n from <= to && fragments.slice(from, to + 1).every(movable);\n const move = (target: { fields: string[] }, before: boolean) => () =>\n this.executeGroupBlockMove(fragmentFields, target.fields, before);\n\n return [\n {\n label: 'Move left',\n disabled: !movable(fragments[at - 1]),\n run: move(fragments[at - 1] ?? { fields: fragmentFields }, true),\n },\n {\n label: 'Move right',\n disabled: !movable(fragments[at + 1]),\n run: move(fragments[at + 1] ?? { fields: fragmentFields }, false),\n },\n {\n label: 'Move to start',\n disabled: at === 0 || !spanMovable(0, at - 1),\n run: move(fragments[0], true),\n },\n {\n label: 'Move to end',\n disabled: at === fragments.length - 1 || !spanMovable(at + 1, fragments.length - 1),\n run: move(fragments[fragments.length - 1], false),\n },\n ];\n }\n\n /** Open the click-only move menu for a group fragment. */\n #openGroupMoveMenu(anchor: HTMLElement, groupHeaderEl: HTMLElement, fragmentFields: string[]): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n const actions = this.#groupMoveActions(gridEl, fragmentFields);\n if (!actions) return;\n\n this.moveMenu ??= createDragAlternativeMenu('tbw-col-move-menu', 'tbw-col-move-menu');\n this.moveMenu.open(anchor, `Move group ${this.#groupLabel(groupHeaderEl)}`, actions);\n }\n\n /** The visible group label, falling back to its id when a renderer drew no text. */\n #groupLabel(groupHeaderEl: HTMLElement): string {\n const text = groupHeaderEl.textContent?.replace('\\u2194', '').trim();\n return text || (groupHeaderEl.getAttribute('data-group') ?? '');\n }\n\n /** Whether `field` names a column this plugin is allowed to move past or onto. */\n #isMoveTarget(field: string): boolean {\n return this.canMoveColumnWithPlugins(this.columns.find((c) => c.field === field));\n }\n\n /** Whether stepping one position toward `dir` lands on a movable neighbour. */\n #canStep(order: string[], fromIndex: number, dir: -1 | 1): boolean {\n const toIndex = fromIndex + dir;\n if (toIndex < 0 || toIndex >= order.length) return false;\n return this.#isMoveTarget(order[toIndex]);\n }\n\n /**\n * Clear all drag-related classes from header cells and group header cells.\n */\n private clearDragClasses(): void {\n this.gridElement?.querySelectorAll('.header-row > .cell, .header-group-row > .cell').forEach((h) => {\n h.classList.remove(GridClasses.DRAGGING, 'drop-target', 'drop-before', 'drop-after');\n });\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for reorder requests from other plugins (e.g., VisibilityPlugin)\n // Uses disconnectSignal for automatic cleanup - no need for manual removeEventListener\n this.gridElement.addEventListener(\n 'column-reorder-request',\n (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail?.field && typeof detail.toIndex === 'number') {\n this.moveColumn(detail.field, detail.toIndex);\n }\n },\n { signal: this.disconnectSignal },\n );\n }\n\n /** @internal */\n override detach(): void {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.draggedGroupFields = [];\n this.moveMenu?.dispose();\n this.moveMenu = null;\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Capture initial column order on first render (after order-attribute reordering)\n if (!this.originalColumnOrder) {\n this.originalColumnOrder = this.columns.map((c) => c.field);\n }\n\n const headers = gridEl.querySelectorAll('.header-row > .cell');\n\n headers.forEach((header) => {\n const headerEl = header as HTMLElement;\n const field = headerEl.getAttribute('data-field');\n if (!field) return;\n\n const column = this.columns.find((c) => c.field === field);\n if (!this.canMoveColumnWithPlugins(column)) {\n headerEl.draggable = false;\n headerEl.querySelector(':scope > .tbw-col-move-btn')?.remove();\n return;\n }\n\n headerEl.draggable = true;\n this.#syncMoveControl(headerEl, field, column);\n\n // Remove existing listeners to prevent duplicates\n if (headerEl.getAttribute('data-dragstart-bound')) return;\n headerEl.setAttribute('data-dragstart-bound', 'true');\n\n // The always-available pointer alternative (WCAG 2.2 SC 2.5.7): right-click,\n // long-press, and Shift+F10 all raise `contextmenu`. Skipped when the context\n // menu plugin is loaded, since it renders the same actions itself from the\n // `getContextMenuItems` contribution and two stacked menus help nobody.\n headerEl.addEventListener('contextmenu', (e: MouseEvent) => {\n if (this.grid.getPluginByName('contextMenu')) return;\n e.preventDefault();\n this.#openMoveMenu(headerEl, field);\n });\n\n headerEl.addEventListener('dragstart', (e: DragEvent) => {\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = orderIndex;\n this.draggedGroupFields = []; // Clear any stale group state\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n headerEl.classList.add(GridClasses.DRAGGING);\n });\n\n headerEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.draggedGroupFields = [];\n this.clearDragClasses();\n });\n\n headerEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n // Skip if dragging this same individual column\n if (this.draggedField === field && this.draggedGroupFields.length === 0) return;\n // Skip if this column is part of the dragged group fragment\n if (this.draggedGroupFields.includes(field)) return;\n\n const rect = headerEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.dropIndex = e.clientX < midX ? orderIndex : orderIndex + 1;\n\n this.clearDragClasses();\n // Re-mark dragged elements\n if (this.draggedGroupFields.length > 0) {\n for (const f of this.draggedGroupFields) {\n this.gridElement\n ?.querySelector(`.header-row > .cell[data-field=\"${f}\"]`)\n ?.classList.add(GridClasses.DRAGGING);\n }\n } else if (this.draggedField) {\n this.gridElement\n ?.querySelector(`.header-row > .cell[data-field=\"${this.draggedField}\"]`)\n ?.classList.add(GridClasses.DRAGGING);\n }\n headerEl.classList.add('drop-target');\n headerEl.classList.toggle('drop-before', e.clientX < midX);\n headerEl.classList.toggle('drop-after', e.clientX >= midX);\n });\n\n headerEl.addEventListener('dragleave', () => {\n headerEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n headerEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n\n // Group fragment drop onto individual column header\n if (this.draggedGroupFields.length > 0) {\n if (this.draggedGroupFields.includes(field)) return;\n const rect = headerEl.getBoundingClientRect();\n const before = e.clientX < rect.left + rect.width / 2;\n this.executeGroupBlockMove(this.draggedGroupFields, [field], before);\n return;\n }\n\n // Individual column drop\n const draggedField = this.draggedField;\n const draggedIndex = this.draggedIndex;\n const dropIndex = this.dropIndex;\n\n if (!this.isDragging || draggedField === null || draggedIndex === null || dropIndex === null) {\n return;\n }\n\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n const currentOrder = this.getColumnOrder();\n const newOrder = moveColumn(currentOrder, draggedIndex, effectiveToIndex);\n\n const detail: ColumnMoveDetail = {\n field: draggedField,\n fromIndex: draggedIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n };\n\n // Emit cancelable event first - only update if not cancelled\n const cancelled = this.emitCancelable('column-move', detail);\n if (!cancelled) {\n // Update the grid's column order (with optional view transition)\n this.updateColumnOrder(newOrder);\n }\n });\n });\n\n // Set up drag listeners for group header cells (if column grouping is active).\n // Deferred to a microtask because GroupingColumnsPlugin.afterRender() creates the\n // .header-group-row DOM, and it may run after this plugin in hook order.\n queueMicrotask(() => this.setupGroupHeaderDrag(gridEl));\n }\n\n /**\n * Set up drag-and-drop listeners on group header cells (.header-group-row > .cell).\n * Dragging a group header moves all columns in that fragment as a block.\n * Implicit groups (ungrouped column spans) are not draggable.\n */\n private setupGroupHeaderDrag(gridEl: HTMLElement): void {\n const groupHeaders = gridEl.querySelectorAll('.header-group-row > .cell[data-group]');\n\n groupHeaders.forEach((gh) => {\n const groupHeaderEl = gh as HTMLElement;\n const groupId = groupHeaderEl.getAttribute('data-group');\n if (!groupId || groupId.startsWith('__implicit__')) return;\n\n // Determine which columns are in this fragment by reading the grid-column style\n const fragmentFields = this.getGroupFragmentFields(groupHeaderEl, groupId);\n if (fragmentFields.length === 0) return;\n\n // Check if all columns in the fragment can be moved\n const allMovable = fragmentFields.every((f) => {\n const col = this.columns.find((c) => c.field === f);\n return this.canMoveColumnWithPlugins(col);\n });\n if (!allMovable) return;\n\n groupHeaderEl.draggable = true;\n groupHeaderEl.style.cursor = 'grab';\n\n // Re-synced every render so a change of drag-alternative mode is picked up.\n this.#syncInlineMoveButton(groupHeaderEl, `Move group ${this.#groupLabel(groupHeaderEl)}`, (btn) =>\n this.#openGroupMoveMenu(btn, groupHeaderEl, fragmentFields),\n );\n\n // Already bound?\n if (groupHeaderEl.getAttribute('data-group-drag-bound')) return;\n groupHeaderEl.setAttribute('data-group-drag-bound', 'true');\n\n // The always-available pointer alternative (WCAG 2.2 SC 2.5.7). Unlike a\n // header cell this is not skipped when the context menu plugin is loaded:\n // that plugin resolves neither a cell nor a `header-cell` part here, so it\n // already swallows the native menu on a group header without offering one.\n groupHeaderEl.addEventListener('contextmenu', (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n this.#openGroupMoveMenu(groupHeaderEl, groupHeaderEl, fragmentFields);\n });\n\n groupHeaderEl.addEventListener('dragstart', (e: DragEvent) => {\n this.isDragging = true;\n this.draggedField = null;\n this.draggedIndex = null;\n this.draggedGroupFields = [...fragmentFields];\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', `group:${groupId}`);\n }\n\n groupHeaderEl.classList.add(GridClasses.DRAGGING);\n // Also mark the individual column headers as dragging\n for (const f of fragmentFields) {\n gridEl.querySelector(`.header-row > .cell[data-field=\"${f}\"]`)?.classList.add(GridClasses.DRAGGING);\n }\n });\n\n groupHeaderEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.draggedGroupFields = [];\n this.clearDragClasses();\n });\n\n // Group header is also a drop target for other groups / individual columns\n groupHeaderEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n // If dragging the same fragment, ignore\n if (\n this.draggedGroupFields.length > 0 &&\n this.draggedGroupFields.length === fragmentFields.length &&\n this.draggedGroupFields.every((f) => fragmentFields.includes(f))\n )\n return;\n\n const rect = groupHeaderEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n const before = e.clientX < midX;\n\n this.clearDragClasses();\n groupHeaderEl.classList.add('drop-target');\n groupHeaderEl.classList.toggle('drop-before', before);\n groupHeaderEl.classList.toggle('drop-after', !before);\n });\n\n groupHeaderEl.addEventListener('dragleave', () => {\n groupHeaderEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n groupHeaderEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n\n const rect = groupHeaderEl.getBoundingClientRect();\n const before = e.clientX < rect.left + rect.width / 2;\n\n if (this.draggedGroupFields.length > 0) {\n // Group-to-group drop: move dragged fragment as block relative to this fragment\n if (\n this.draggedGroupFields.length === fragmentFields.length &&\n this.draggedGroupFields.every((f) => fragmentFields.includes(f))\n )\n return;\n this.executeGroupBlockMove(this.draggedGroupFields, fragmentFields, before);\n } else if (this.draggedField) {\n // Individual column dropped onto group header\n const currentOrder = this.getColumnOrder();\n const anchorField = before ? fragmentFields[0] : fragmentFields[fragmentFields.length - 1];\n const fromIndex = currentOrder.indexOf(this.draggedField);\n const toIndex = before ? currentOrder.indexOf(anchorField) : currentOrder.indexOf(anchorField) + 1;\n if (fromIndex === -1 || toIndex === -1) return;\n const effectiveToIndex = toIndex > fromIndex ? toIndex - 1 : toIndex;\n const newOrder = moveColumn(currentOrder, fromIndex, effectiveToIndex);\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field: this.draggedField,\n fromIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n });\n if (!cancelled) this.updateColumnOrder(newOrder);\n }\n });\n });\n }\n\n /**\n * Get the column field names that belong to a group header fragment.\n * Reads the grid-column CSS style of the header cell to determine the column range.\n */\n private getGroupFragmentFields(groupHeaderEl: HTMLElement, _groupId: string): string[] {\n // Parse grid-column (e.g., \"2 / span 3\") to get start index and span\n const gridColumn = groupHeaderEl.style.gridColumn;\n const match = /(\\d+)\\s*\\/\\s*span\\s+(\\d+)/.exec(gridColumn);\n if (!match) return [];\n\n const startCol = parseInt(match[1], 10); // 1-based CSS grid column\n const span = parseInt(match[2], 10);\n\n // Map CSS grid columns to visible column fields\n const visibleColumns = this.visibleColumns;\n const fields: string[] = [];\n for (let i = startCol - 1; i < startCol - 1 + span && i < visibleColumns.length; i++) {\n const col = visibleColumns[i];\n if (col) fields.push(col.field);\n }\n return fields;\n }\n\n /**\n * Move a group of columns as a block to a new position relative to target fields.\n */\n private executeGroupBlockMove(draggedFields: string[], targetFields: string[], before: boolean): void {\n const currentOrder = this.getColumnOrder();\n const remaining = currentOrder.filter((f) => !draggedFields.includes(f));\n\n const anchorField = before ? targetFields[0] : targetFields[targetFields.length - 1];\n const insertAt = remaining.indexOf(anchorField);\n if (insertAt === -1) return;\n\n const insertIndex = before ? insertAt : insertAt + 1;\n const draggedInOrder = currentOrder.filter((f) => draggedFields.includes(f));\n remaining.splice(insertIndex, 0, ...draggedInOrder);\n\n // Emit cancelable column-move for the first field so lockGroupOrder guard can check\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field: draggedFields[0],\n fromIndex: currentOrder.indexOf(draggedFields[0]),\n toIndex: insertIndex,\n columnOrder: remaining,\n });\n if (!cancelled) {\n this.updateColumnOrder(remaining);\n }\n }\n\n /**\n * Handle Alt+Arrow keyboard shortcuts for column reordering.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n if (!event.altKey || (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')) {\n return;\n }\n\n const grid = this.#internalGrid;\n const focusCol = grid._focusCol;\n const columns = grid._visibleColumns;\n\n if (focusCol < 0 || focusCol >= columns.length) return;\n\n const column = columns[focusCol];\n if (!this.canMoveColumnWithPlugins(column)) return;\n\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(column.field);\n if (fromIndex === -1) return;\n\n const toIndex = event.key === 'ArrowLeft' ? fromIndex - 1 : fromIndex + 1;\n\n // Check bounds\n if (toIndex < 0 || toIndex >= currentOrder.length) return;\n\n // Check if target position is allowed (e.g., not into pinned area)\n const targetColumn = columns.find((c) => c.field === currentOrder[toIndex]);\n if (!this.canMoveColumnWithPlugins(targetColumn)) return;\n\n this.moveColumn(column.field, toIndex);\n\n // Update focus to follow the moved column and refresh visual focus state\n grid._focusCol = toIndex;\n ensureCellVisible(this.#internalGrid);\n\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current column order from the grid.\n * @returns Array of field names in display order\n */\n getColumnOrder(): string[] {\n return this.grid.getColumnOrder();\n }\n\n /**\n * Move a column to a new position.\n * @param field - The field name of the column to move\n * @param toIndex - The target index\n */\n moveColumn(field: string, toIndex: number): void {\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(field);\n if (fromIndex === -1) return;\n\n const newOrder = moveColumn(currentOrder, fromIndex, toIndex);\n\n // Emit cancelable event first - only update if not cancelled\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field,\n fromIndex,\n toIndex,\n columnOrder: newOrder,\n });\n if (!cancelled) {\n // Update with view transition\n this.updateColumnOrder(newOrder);\n }\n }\n\n /**\n * Set a specific column order.\n * @param order - Array of field names in desired order\n */\n setColumnOrder(order: string[]): void {\n this.updateColumnOrder(order);\n }\n\n /**\n * Reset column order to the original configuration order.\n */\n resetColumnOrder(): void {\n // Use the captured initial order (including order-attribute reordering),\n // or fall back to current column order if not yet captured\n const originalOrder = this.originalColumnOrder ?? this.columns.map((c) => c.field);\n this.updateColumnOrder(originalOrder);\n }\n // #endregion\n\n // #region View Transition\n\n /**\n * Capture header cell positions before reorder.\n */\n private captureHeaderPositions(): Map<string, number> {\n const positions = new Map<string, number>();\n this.gridElement?.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (field) positions.set(field, cell.getBoundingClientRect().left);\n });\n return positions;\n }\n\n /**\n * Apply FLIP animation for column reorder.\n * Uses CSS transitions - JS sets initial transform and toggles class.\n * @param oldPositions - Header positions captured before DOM change\n */\n private animateFLIP(oldPositions: Map<string, number>): void {\n const gridEl = this.gridElement;\n if (!gridEl || oldPositions.size === 0) return;\n\n // Compute deltas from header cells (one per column, stable reference).\n // All cells in the same column share the same horizontal offset.\n const deltas = new Map<string, number>();\n gridEl.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n const oldLeft = oldPositions.get(field);\n if (oldLeft === undefined) return;\n const deltaX = oldLeft - cell.getBoundingClientRect().left;\n if (Math.abs(deltaX) > 1) deltas.set(field, deltaX);\n });\n\n if (deltas.size === 0) return;\n\n // Apply transforms to ALL cells (headers + body).\n // After forceLayout(), body cells are fully rebuilt in new DOM order\n // with correct data-field attributes, so header-derived deltas apply correctly.\n const cells: HTMLElement[] = [];\n gridEl.querySelectorAll('.cell[data-field]').forEach((cell) => {\n const deltaX = deltas.get(cell.getAttribute('data-field') ?? '');\n if (deltaX !== undefined) {\n const el = cell as HTMLElement;\n el.style.transform = `translateX(${deltaX}px)`;\n cells.push(el);\n }\n });\n\n if (cells.length === 0) return;\n\n // Force reflow then animate to final position via CSS transition\n void gridEl.offsetHeight;\n\n const duration = this.animationDuration;\n\n requestAnimationFrame(() => {\n cells.forEach((el) => {\n el.classList.add('flip-animating');\n el.style.transform = '';\n });\n\n // Cleanup after animation\n setTimeout(() => {\n cells.forEach((el) => {\n el.style.transform = '';\n el.classList.remove('flip-animating');\n });\n }, duration + 50);\n });\n }\n\n /**\n * Apply crossfade animation for moved columns.\n * Uses CSS keyframes - JS just toggles classes.\n */\n private animateFade(applyChange: () => void): void {\n const gridEl = this.gridElement;\n if (!gridEl) {\n applyChange();\n return;\n }\n\n // Capture old positions to detect which columns moved\n const oldPositions = this.captureHeaderPositions();\n\n // Apply the change first\n applyChange();\n\n // Find which columns changed position\n const movedFields = new Set<string>();\n gridEl.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n const oldLeft = oldPositions.get(field);\n if (oldLeft === undefined) return;\n const newLeft = cell.getBoundingClientRect().left;\n if (Math.abs(oldLeft - newLeft) > 1) {\n movedFields.add(field);\n }\n });\n\n if (movedFields.size === 0) return;\n\n // Add animation class to moved columns (headers + body cells)\n const cells: HTMLElement[] = [];\n gridEl.querySelectorAll('.cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (field && movedFields.has(field)) {\n const el = cell as HTMLElement;\n el.classList.add('fade-animating');\n cells.push(el);\n }\n });\n\n if (cells.length === 0) return;\n\n // Remove class after animation completes\n const duration = this.animationDuration;\n setTimeout(() => {\n cells.forEach((el) => el.classList.remove('fade-animating'));\n }, duration + 50);\n }\n\n /**\n * Update column order with configured animation.\n */\n private updateColumnOrder(newOrder: string[]): void {\n const animation = this.animationType;\n\n if (animation === 'flip' && this.gridElement) {\n const oldPositions = this.captureHeaderPositions();\n this.grid.setColumnOrder(newOrder);\n // setColumnOrder now requests COLUMNS phase (not just VIRTUALIZATION), so\n // processColumns() runs and bumps __rowRenderEpoch. forceLayout() waits\n // for the pending phase to flush, ensuring body cells are fully rebuilt\n // with correct data-field attributes before the FLIP animation runs.\n if (typeof this.grid.forceLayout === 'function') {\n this.grid.forceLayout().then(() => {\n this.animateFLIP(oldPositions);\n });\n } else {\n // Fallback: animate headers only (body cells may not be rebuilt yet)\n requestAnimationFrame(() => {\n this.animateFLIP(oldPositions);\n });\n }\n } else if (animation === 'fade') {\n this.animateFade(() => this.grid.setColumnOrder(newOrder));\n } else {\n this.grid.setColumnOrder(newOrder);\n }\n\n this.grid.requestStateChange?.();\n }\n // #endregion\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["styles","BaseGridPlugin","fromVirtualScrollTop","toVirtualScrollTop","GridClasses","ensureCellVisible","grid","options","_virtualization","enabled","rowHeight","container","viewportEl","scrollMapping","scrollEl","visibleHeight","clientHeight","offsets","_getVerticalScrollOffsets","_focusRow","top","bottom","skipScroll","Math","max","usableHeight","y","virtualScrollTop","scrollTop","target","scrollFocusedRowIntoView","isEditing","_activeEditRows","isRowEditing","_isGridEditMode","root","refreshVirtualWindow","_bodyEl","querySelectorAll","forEach","el","classList","remove","Array","from","setAttribute","focused","rowIndex","vStart","start","vEnd","end","_rows","length","rowEl","cell","children","_focusCol","contains","querySelector","findFocusedCell","add","forceHorizontalScroll","scrollArea","forceScrollLeft","scrollLeft","forceScrollRight","scrollWidth","clientWidth","_getHorizontalScrollOffsets","left","right","cellRect","getBoundingClientRect","scrollAreaRect","cellLeft","cellRight","width","visibleLeft","visibleRight","scrollCellIntoViewHorizontally","focusTarget","document","activeElement","focus","preventScroll","hasAttribute","active","HTMLElement","closest","applyFocusTarget","hoverlessQuery","supportsPopover","prototype","showPopover","prefersInlineDragAlternative","effectiveConfig","a11y","dragAlternatives","globalThis","matchMedia","matches","createDragAlternativeMenu","id","className","opened","disposed","abort","AbortController","close","hidePopover","hidden","addEventListener","e","composedPath","includes","capture","signal","key","element","open","anchor","label","actions","createElement","next","relatedTarget","Node","build","host","body","parentNode","appendChild","replaceChildren","action","btn","type","textContent","disabled","run","a","box","min","window","innerWidth","below","height","innerHeight","style","round","place","dispose","canMoveColumn","column","lockPosition","utility","meta","suppressMovable","moveColumn","columns","fromIndex","toIndex","result","removed","splice","QUERY_CAN_MOVE_COLUMN","QUERY_GET_CONTEXT_MENU_ITEMS","ReorderPlugin","name","aliases","styles","static","queries","description","handleQuery","query","context","params","isHeader","this","moveActions","field","map","i","order","defaultConfig","animation","animationType","isAnimationEnabled","config","animationDuration","super","isDragging","draggedField","draggedIndex","dropIndex","draggedGroupFields","originalColumnOrder","moveMenu","internalGrid","canMoveColumnWithPlugins","syncMoveControl","headerEl","syncInlineMoveButton","String","header","openMoveMenu","hostEl","ariaLabel","existing","tabIndex","draggable","stopPropagation","preventDefault","find","c","getColumnOrder","indexOf","firstMovable","findIndex","f","isMoveTarget","lastMovable","canStep","groupFragments","gridEl","visible","visibleColumns","fragments","getAttribute","fields","getGroupFragmentFields","push","sort","b","groupMoveActions","fragmentFields","every","at","movable","fragment","spanMovable","to","slice","move","before","executeGroupBlockMove","openGroupMoveMenu","groupHeaderEl","gridElement","groupLabel","replace","trim","dir","clearDragClasses","h","attach","detail","disconnectSignal","detach","afterRender","getPluginByName","orderIndex","dataTransfer","effectAllowed","setData","rect","midX","clientX","toggle","effectiveToIndex","newOrder","columnOrder","emitCancelable","updateColumnOrder","queueMicrotask","setupGroupHeaderDrag","gh","groupId","startsWith","col","cursor","currentOrder","anchorField","_groupId","gridColumn","match","exec","startCol","parseInt","span","draggedFields","targetFields","remaining","filter","insertAt","insertIndex","draggedInOrder","onKeyDown","event","altKey","focusCol","_visibleColumns","targetColumn","setColumnOrder","resetColumnOrder","originalOrder","captureHeaderPositions","positions","Map","set","animateFLIP","oldPositions","size","deltas","oldLeft","get","deltaX","abs","cells","transform","offsetHeight","duration","requestAnimationFrame","setTimeout","animateFade","applyChange","movedFields","Set","newLeft","has","forceLayout","then","requestStateChange"],"sources":["../../../../../../libs/grid/src/lib/core/constants.ts","../../../../../../libs/grid/src/lib/core/internal/utils.ts","../../../../../../libs/grid/src/lib/core/internal/keyboard.ts","../../../../../../libs/grid/src/lib/plugins/shared/drag-alternative-menu.ts","../../../../../../libs/grid/src/lib/plugins/reorder-columns/column-drag.ts","../../../../../../libs/grid/src/lib/plugins/reorder-columns/reorder.css?inline","../../../../../../libs/grid/src/lib/plugins/reorder-columns/reorder-plugin.ts"],"sourcesContent":["/**\n * Grid DOM Constants\n *\n * Centralized constants for CSS classes, data attributes, and selectors\n * used throughout the grid. Use these instead of magic strings.\n *\n * Note: Some constants here are used by plugins but defined in core because:\n * 1. The core CSS needs to style these elements (e.g., sticky positioning)\n * 2. Multiple plugins may share the same class names\n *\n * Plugins can define their own additional constants and export them.\n * See each plugin's index.ts for plugin-specific exports.\n */\n\n/**\n * CSS selector for focusable editor elements within a cell.\n * Used by EditingPlugin, UndoRedoPlugin, and keyboard navigation. Defined here\n * (a leaf module) rather than in `rows.ts` so `keyboard.ts` can consume it\n * without creating a rows ↔ keyboard import cycle.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// #region CSS Classes\n\n/**\n * CSS class names used in the grid's DOM.\n * Use these when adding/removing classes or querying elements.\n *\n * Classes are organized by:\n * - Core: Used by the grid core for structure and basic functionality\n * - Shared: Used by multiple features/plugins, styled by core CSS\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridClasses = {\n // ─── Core Structure ───────────────────────────────────────────────\n ROOT: 'tbw-grid-root',\n HEADER: 'header',\n HEADER_ROW: 'header-row',\n HEADER_CELL: 'header-cell',\n\n // Body structure\n ROWS_VIEWPORT: 'rows-viewport',\n ROWS_SPACER: 'rows-spacer',\n ROWS_CONTAINER: 'rows',\n\n // Row elements\n DATA_ROW: 'data-grid-row',\n GROUP_ROW: 'group-row',\n\n // Cell elements\n DATA_CELL: 'cell',\n\n // ─── Core States ──────────────────────────────────────────────────\n SELECTED: 'selected',\n FOCUSED: 'focused',\n EDITING: 'editing',\n EXPANDED: 'expanded',\n COLLAPSED: 'collapsed',\n DRAGGING: 'dragging',\n RESIZING: 'resizing',\n\n // Sorting (core feature)\n SORTABLE: 'sortable',\n SORTED_ASC: 'sorted-asc',\n SORTED_DESC: 'sorted-desc',\n\n // Visibility\n HIDDEN: 'hidden',\n\n // ─── Shared Classes (used by plugins, styled by core CSS) ────────\n // These are defined here because core CSS provides the base styling.\n // Plugins apply these classes; core CSS defines how they look.\n\n // Sticky positioning (PinnedColumnsPlugin applies, core CSS styles)\n STICKY_LEFT: 'sticky-left',\n STICKY_RIGHT: 'sticky-right',\n\n // Pinned rows (PinnedRowsPlugin applies, core CSS styles)\n PINNED_TOP: 'pinned-top',\n PINNED_BOTTOM: 'pinned-bottom',\n\n // Tree structure (TreePlugin applies, core CSS styles)\n TREE_TOGGLE: 'tree-toggle',\n TREE_INDENT: 'tree-indent',\n\n // Grouping (GroupingRowsPlugin applies, core CSS styles)\n GROUP_TOGGLE: 'group-toggle',\n GROUP_LABEL: 'group-label',\n GROUP_COUNT: 'group-count',\n\n // Selection (SelectionPlugin applies, core CSS styles)\n RANGE_SELECTION: 'range-selection',\n SELECTION_OVERLAY: 'selection-overlay',\n} as const;\n\n// #endregion\n\n// #region Data Attributes\n\n/**\n * Data attribute names used on grid elements.\n * Use these when getting/setting data attributes.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridDataAttrs = {\n // ─── Core Attributes ──────────────────────────────────────────────\n FIELD: 'data-field',\n\n /**\n * Marks a `title` the grid itself put on a cell to reveal ellipsised text\n * (WCAG SC 1.4.12). Anything that sets its own `title` on a cell must clear\n * this, or the grid will later mistake that title for its own and replace it.\n *\n * @since 3.6.0\n */\n TRUNCATED: 'data-tbw-truncated',\n\n // ─── Shared Attributes (used by plugins) ──────────────────────────\n GROUP_KEY: 'data-group-key', // GroupingRowsPlugin\n} as const;\n\n// #endregion\n\n// #region Selectors\n\n/**\n * Common CSS selectors for querying grid elements.\n * Built from the class constants for consistency.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridSelectors = {\n ROOT: `.${GridClasses.ROOT}`,\n HEADER: `.${GridClasses.HEADER}`,\n HEADER_ROW: `.${GridClasses.HEADER_ROW}`,\n HEADER_CELL: `.${GridClasses.HEADER_CELL}`,\n ROWS_VIEWPORT: `.${GridClasses.ROWS_VIEWPORT}`,\n ROWS_CONTAINER: `.${GridClasses.ROWS_CONTAINER}`,\n DATA_ROW: `.${GridClasses.DATA_ROW}`,\n DATA_CELL: `.${GridClasses.DATA_CELL}`,\n GROUP_ROW: `.${GridClasses.GROUP_ROW}`,\n\n // By data attribute\n CELL_BY_FIELD: (field: string) => `.${GridClasses.DATA_CELL}[${GridDataAttrs.FIELD}=\"${field}\"]`,\n\n // State selectors\n SELECTED_ROWS: `.${GridClasses.DATA_ROW}.${GridClasses.SELECTED}`,\n EDITING_CELL: `.${GridClasses.DATA_CELL}.${GridClasses.EDITING}`,\n} as const;\n\n// #endregion\n\n// #region CSS Custom Properties\n\n/**\n * CSS custom property names for theming.\n * Use these when programmatically setting styles.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridCSSVars = {\n // Colors\n COLOR_BG: '--tbw-color-bg',\n COLOR_FG: '--tbw-color-fg',\n COLOR_FG_MUTED: '--tbw-color-fg-muted',\n COLOR_BORDER: '--tbw-color-border',\n COLOR_ACCENT: '--tbw-color-accent',\n COLOR_HEADER_BG: '--tbw-color-header-bg',\n COLOR_HEADER_FG: '--tbw-color-header-fg',\n COLOR_SELECTION: '--tbw-color-selection',\n COLOR_ROW_HOVER: '--tbw-color-row-hover',\n COLOR_ROW_ALT: '--tbw-color-row-alt',\n\n // Sizing\n ROW_HEIGHT: '--tbw-row-height',\n HEADER_HEIGHT: '--tbw-header-height',\n CELL_PADDING: '--tbw-cell-padding',\n\n // Typography\n FONT_FAMILY: '--tbw-font-family',\n FONT_SIZE: '--tbw-font-size',\n\n // Borders\n BORDER_RADIUS: '--tbw-border-radius',\n FOCUS_OUTLINE: '--tbw-focus-outline',\n} as const;\n\n// #endregion\n\n// Type helpers\n\n/**\n * Union of all CSS class names used in the grid's DOM.\n * Use `GridClasses` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridClassName = (typeof GridClasses)[keyof typeof GridClasses];\n\n/**\n * Union of all `data-*` attribute names used on grid elements.\n * Use `GridDataAttrs` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridDataAttr = (typeof GridDataAttrs)[keyof typeof GridDataAttrs];\n\n/**\n * Union of all CSS custom property names (`--tbw-*`) available for theming.\n * Use `GridCSSVars` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridCSSVar = (typeof GridCSSVars)[keyof typeof GridCSSVars];\n","// #region Environment Helpers\n\n/**\n * Check if we're running in a development environment.\n * Returns true for localhost or when NODE_ENV !== 'production'.\n * Used to show warnings only in development.\n */\nexport function isDevelopment(): boolean {\n // Check for localhost (browser environment)\n if (typeof window !== 'undefined' && window.location) {\n const hostname = window.location.hostname;\n if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {\n return true;\n }\n }\n // Check for NODE_ENV (build-time or SSR)\n if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n return true;\n }\n return false;\n}\n\n// #endregion\n\n// #region Cell Rendering Helpers\n\n/**\n * Generate accessible HTML for a boolean cell.\n * Uses role=\"checkbox\" with proper aria attributes.\n */\nexport function booleanCellHTML(value: boolean): string {\n return `<span role=\"checkbox\" aria-checked=\"${value}\" aria-label=\"${value}\">${value ? '&#x1F5F9;' : '&#9744;'}</span>`;\n}\n\n/**\n * Format a date value for display.\n * Handles Date objects, timestamps, and date strings.\n * Returns empty string for invalid dates.\n */\nexport function formatDateValue(value: unknown): string {\n if (value == null || value === '') return '';\n if (value instanceof Date) {\n return isNaN(value.getTime()) ? '' : value.toLocaleDateString();\n }\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return isNaN(d.getTime()) ? '' : d.toLocaleDateString();\n }\n return '';\n}\n\n/**\n * Get the row index from a cell element's data-row attribute.\n * Falls back to calculating from parent row's DOM position if data-row is missing.\n * Returns -1 if no valid row index is found.\n */\nexport function getRowIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-row');\n if (attr) return parseInt(attr, 10);\n\n // Fallback: find the parent .data-grid-row and calculate index from siblings\n const rowEl = cell.closest('.data-grid-row');\n if (!rowEl) return -1;\n\n const parent = rowEl.parentElement;\n if (!parent) return -1;\n\n // Get all data-grid-row siblings and find this row's index\n const rows = parent.querySelectorAll(':scope > .data-grid-row');\n for (let i = 0; i < rows.length; i++) {\n if (rows[i] === rowEl) return i;\n }\n return -1;\n}\n\n/**\n * Get the column index from a cell element's data-col attribute.\n * Returns -1 if no valid column index is found.\n */\nexport function getColIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-col');\n return attr ? parseInt(attr, 10) : -1;\n}\n\n/**\n * Clear all cell-focus styling from a root element.\n * Used when changing focus or when selection plugin takes over focus management.\n */\nexport function clearCellFocus(root: Element | null): void {\n if (!root) return;\n root.querySelectorAll('.cell-focus').forEach((el) => el.classList.remove('cell-focus'));\n}\n// #endregion\n\n// #region RTL Helpers\n\n/** Text direction */\nexport type TextDirection = 'ltr' | 'rtl';\n\n/**\n * Get the text direction for an element.\n * Reads from the computed style, which respects the `dir` attribute on the element\n * or any ancestor, as well as CSS `direction` property.\n *\n * @param element - The element to check direction for\n * @returns 'ltr' or 'rtl'\n *\n * @example\n * ```typescript\n * // Detect grid's direction\n * const dir = getDirection(gridElement);\n * if (dir === 'rtl') {\n * // Handle RTL layout\n * }\n * ```\n */\nexport function getDirection(element: Element): TextDirection {\n // Try computed style first (works in real browsers)\n try {\n const computedDir = getComputedStyle(element).direction;\n if (computedDir === 'rtl') return 'rtl';\n } catch {\n // getComputedStyle may fail in some test environments\n }\n\n // Fallback: check dir attribute on element or ancestors\n // This handles test environments where getComputedStyle may not reflect dir attribute\n try {\n const dirAttr = element.closest?.('[dir]')?.getAttribute('dir');\n if (dirAttr === 'rtl') return 'rtl';\n } catch {\n // closest may not be available on mock elements\n }\n\n return 'ltr';\n}\n\n/**\n * Check if an element is in RTL mode.\n *\n * @param element - The element to check\n * @returns true if the element's text direction is right-to-left\n */\nexport function isRTL(element: Element): boolean {\n return getDirection(element) === 'rtl';\n}\n\n/**\n * Resolve a logical inline position to a physical position based on text direction.\n *\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n * - `'left'` / `'right'` → unchanged (physical values)\n *\n * @param position - Logical or physical position\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical position ('left' or 'right')\n *\n * @example\n * ```typescript\n * resolveInlinePosition('start', 'ltr'); // 'left'\n * resolveInlinePosition('start', 'rtl'); // 'right'\n * resolveInlinePosition('left', 'rtl'); // 'left' (unchanged)\n * ```\n */\nexport function resolveInlinePosition(\n position: 'left' | 'right' | 'start' | 'end',\n direction: TextDirection,\n): 'left' | 'right' {\n if (position === 'left' || position === 'right') {\n return position;\n }\n if (direction === 'rtl') {\n return position === 'start' ? 'right' : 'left';\n }\n return position === 'start' ? 'left' : 'right';\n}\n// #endregion\n","/**\n * Central keyboard handler attached to the host element. Manages navigation, paging,\n * and edit lifecycle triggers while respecting active form field interactions.\n */\nimport { FOCUSABLE_EDITOR_SELECTOR, GridClasses } from '../constants';\nimport type { GridHost } from '../types';\nimport { clearCellFocus, isRTL } from './utils';\nimport { readCellField } from './value-accessor';\nimport { fromVirtualScrollTop, getRowIndexAtOffset, toVirtualScrollTop } from './virtualization';\n\n/** Commit active row edit if the editing plugin provides this method. */\nfunction tryCommitEdit(grid: GridHost): void {\n if (typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n}\n\nfunction isFormField(el: HTMLElement | null): boolean {\n if (!el) return false;\n const tag = el.tagName;\n if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') return true;\n if (el.isContentEditable) return true;\n return false;\n}\n\n/** True while a row-scoped edit session is active. */\nfunction isRowEditing(grid: GridHost): boolean {\n return grid._activeEditRows !== undefined && grid._activeEditRows !== -1;\n}\n\n// #region Keyboard Handler\n/**\n * Guard chain deciding whether this keydown belongs to something other than\n * cell navigation and must be left alone.\n */\nfunction shouldIgnoreKeyDown(grid: GridHost, e: KeyboardEvent, target: HTMLElement | null): boolean {\n // Keyboard events whose target lives inside the grid host but OUTSIDE the\n // rows body (toolpanel inputs/buttons, shell-header controls, light-DOM\n // children of <tbw-grid>) own their own keyboard interactions. Treating\n // ArrowUp/ArrowDown/Enter on such a target as cell navigation steals the\n // user's actual interaction target on the very next render cycle.\n // Cells and the grid host itself remain valid navigation targets.\n if (target && target !== grid && !target.closest?.('.rows-body')) return true;\n\n if (isFormField(target)) {\n switch (e.key) {\n // Home/End and horizontal arrows navigate within the text input; Enter\n // and Escape are handled by the input's own handlers first.\n case 'Home':\n case 'End':\n case 'ArrowLeft':\n case 'ArrowRight':\n case 'Enter':\n case 'Escape':\n return true;\n case 'ArrowUp':\n case 'ArrowDown':\n // Number spinners own the vertical arrows; other fields fall through.\n if ((target as HTMLInputElement).tagName === 'INPUT' && (target as HTMLInputElement).type === 'number') {\n return true;\n }\n break;\n }\n }\n\n const colType = grid._visibleColumns[grid._focusCol]?.type;\n return isRowEditing(grid) && colType === 'select' && (e.key === 'ArrowDown' || e.key === 'ArrowUp');\n}\n\n/** Tab / Shift+Tab: advance one cell, wrapping to the next/previous row. */\nfunction navigateTab(grid: GridHost, e: KeyboardEvent, maxRow: number, maxCol: number): void {\n e.preventDefault();\n if (!e.shiftKey) {\n if (grid._focusCol < maxCol) {\n grid._focusCol += 1;\n return;\n }\n tryCommitEdit(grid);\n if (grid._focusRow < maxRow) {\n grid._focusRow += 1;\n grid._focusCol = 0;\n }\n return;\n }\n if (grid._focusCol > 0) {\n grid._focusCol -= 1;\n } else if (grid._focusRow > 0) {\n if (grid._activeEditRows === grid._focusRow) tryCommitEdit(grid);\n grid._focusRow -= 1;\n grid._focusCol = maxCol;\n }\n}\n\n/**\n * ArrowLeft/ArrowRight. `towardEnd` is the LOGICAL direction (ArrowRight); in\n * RTL the physical arrow maps to the opposite column index.\n */\nfunction navigateHorizontal(grid: GridHost, e: KeyboardEvent, maxCol: number, towardEnd: boolean): void {\n const forward = isRTL(grid) ? !towardEnd : towardEnd;\n grid._focusCol = forward ? Math.min(maxCol, grid._focusCol + 1) : Math.max(0, grid._focusCol - 1);\n e.preventDefault();\n}\n\n/** Commit any active row edit, then focus the first/last row (column unchanged). */\nfunction jumpToRowEdge(grid: GridHost, maxRow: number, toEnd: boolean): void {\n if (isRowEditing(grid)) tryCommitEdit(grid);\n grid._focusRow = toEnd ? maxRow : 0;\n}\n\n/** ArrowUp/ArrowDown: one row, or the first/last row with Ctrl/Cmd. */\nfunction navigateVertical(grid: GridHost, e: KeyboardEvent, maxRow: number, down: boolean): void {\n if (e.ctrlKey || e.metaKey) jumpToRowEdge(grid, maxRow, down);\n else {\n if (isRowEditing(grid)) tryCommitEdit(grid);\n grid._focusRow = down ? Math.min(maxRow, grid._focusRow + 1) : Math.max(0, grid._focusRow - 1);\n }\n e.preventDefault();\n}\n\n/**\n * PageUp/PageDown target: one viewport of rows (less any overlay band), so an\n * unconstrained grid pages straight to its edge. 20 rows until layout exists.\n */\nfunction pageTargetRow(grid: GridHost, maxRow: number, down: boolean): number {\n const from = grid._focusRow;\n const virt = grid._virtualization;\n const viewportH = virt?.viewportEl?.clientHeight ?? virt?.container?.clientHeight ?? 0;\n const offsets = grid._getVerticalScrollOffsets?.(from);\n const usable = viewportH - Math.max(0, offsets?.top ?? 0) - Math.max(0, offsets?.bottom ?? 0);\n const pc = virt?.variableHeights ? virt.positionCache : null;\n let target: number;\n if (usable > 0 && pc?.[from]) {\n target = getRowIndexAtOffset(pc, pc[from].offset + (down ? usable : -usable));\n // A row taller than the viewport would otherwise pin focus in place.\n if (target === from) target = from + (down ? 1 : -1);\n } else {\n const rowH = virt?.rowHeight ?? 0;\n const step = usable > 0 && rowH > 0 ? Math.max(1, Math.floor(usable / rowH)) : 20;\n target = from + (down ? step : -step);\n }\n return Math.max(0, Math.min(maxRow, target));\n}\n\n/** Home/End (plus Ctrl/Cmd variants which also jump to the first/last row). */\nfunction navigateRowEdge(grid: GridHost, e: KeyboardEvent, maxRow: number, maxCol: number, toEnd: boolean): void {\n if (e.ctrlKey || e.metaKey) jumpToRowEdge(grid, maxRow, toEnd);\n grid._focusCol = toEnd ? maxCol : 0;\n e.preventDefault();\n ensureCellVisible(grid, toEnd ? { forceScrollRight: true } : { forceScrollLeft: true });\n}\n\n/** Dispatch the unified `cell-activate` event. Returns true when cancelled. */\nfunction emitCellActivate(grid: GridHost, e: KeyboardEvent): boolean {\n const rowIndex = grid._focusRow;\n const colIndex = grid._focusCol;\n const column = grid._visibleColumns[colIndex];\n const row = grid._rows[rowIndex];\n const field = column?.field ?? '';\n const activateEvent = new CustomEvent('cell-activate', {\n cancelable: true,\n detail: {\n rowIndex,\n colIndex,\n column,\n field,\n value: field && row ? readCellField(row, field) : undefined,\n row,\n cellEl: grid.querySelector(`[data-row=\"${rowIndex}\"][data-col=\"${colIndex}\"]`) as HTMLElement | undefined,\n trigger: 'keyboard' as const,\n originalEvent: e,\n },\n });\n grid.dispatchEvent(activateEvent);\n return activateEvent.defaultPrevented;\n}\n\nexport function handleGridKeyDown(grid: GridHost, e: KeyboardEvent): void {\n // Dispatch to plugin system first - if any plugin handles it, stop here\n if (grid._dispatchKeyDown?.(e)) return;\n\n const path = e.composedPath?.() ?? [];\n const target = (path.length ? path[0] : e.target) as HTMLElement | null;\n if (shouldIgnoreKeyDown(grid, e, target)) return;\n\n const maxRow = grid._rows.length - 1;\n const maxCol = grid._visibleColumns.length - 1;\n\n switch (e.key) {\n case 'Tab':\n navigateTab(grid, e, maxRow, maxCol);\n break;\n case 'ArrowDown':\n navigateVertical(grid, e, maxRow, true);\n break;\n case 'ArrowUp':\n navigateVertical(grid, e, maxRow, false);\n break;\n case 'ArrowRight':\n navigateHorizontal(grid, e, maxCol, true);\n break;\n case 'ArrowLeft':\n navigateHorizontal(grid, e, maxCol, false);\n break;\n case 'Home':\n navigateRowEdge(grid, e, maxRow, maxCol, false);\n return;\n case 'End':\n navigateRowEdge(grid, e, maxRow, maxCol, true);\n return;\n case 'PageDown':\n grid._focusRow = pageTargetRow(grid, maxRow, true);\n e.preventDefault();\n break;\n case 'PageUp':\n grid._focusRow = pageTargetRow(grid, maxRow, false);\n e.preventDefault();\n break;\n // NOTE: Enter is normally handled by EditingPlugin. If no plugin handled\n // it, dispatch the unified cell-activate event for custom handling.\n case 'Enter':\n if (emitCellActivate(grid, e)) {\n e.preventDefault();\n return;\n }\n break;\n default:\n return;\n }\n ensureCellVisible(grid);\n}\n// #endregion\n\n// #region Cell Visibility\n/**\n * Options for ensureCellVisible to control scroll behavior.\n */\ninterface EnsureCellVisibleOptions {\n /** Force scroll to the leftmost position (for Home key) */\n forceScrollLeft?: boolean;\n /** Force scroll to the rightmost position (for End key) */\n forceScrollRight?: boolean;\n /** Force horizontal scroll even in edit mode (for Tab navigation) */\n forceHorizontalScroll?: boolean;\n}\n\n/**\n * Vertical: scroll the virtualized container so the focused row is in view.\n *\n * `y` is in raw row-content space (rowHeight × index). Above the browser height\n * cap (#326), `scrollEl.scrollTop` is in clamped spacer space — the two\n * coordinate systems are not interchangeable, so translate scrollTop into\n * virtual space for the comparison and back into native space when writing.\n * For sub-cap datasets the mapping is identity, so this is a no-op.\n *\n * Plugins that paint over the viewport (sticky rows) report the space they\n * obscure via `_getVerticalScrollOffsets`, so the row lands clear of the\n * overlay instead of underneath it — WCAG 2.2 SC 2.4.11 Focus Not Obscured.\n */\nfunction scrollFocusedRowIntoView(grid: GridHost): void {\n if (!grid._virtualization?.enabled) return;\n const { rowHeight, container, viewportEl, scrollMapping } = grid._virtualization;\n // container is the faux scrollbar element that handles actual scrolling;\n // viewportEl is the visible area element that has the correct height.\n const scrollEl = container as HTMLElement | undefined;\n const visibleHeight = viewportEl?.clientHeight ?? scrollEl?.clientHeight ?? 0;\n if (!scrollEl || visibleHeight <= 0) return;\n\n const offsets = grid._getVerticalScrollOffsets?.(grid._focusRow) ?? { top: 0, bottom: 0 };\n if (offsets.skipScroll) return;\n // A malformed or oversized report must never invert the visible band.\n const top = Math.max(0, offsets.top);\n const bottom = Math.max(0, offsets.bottom);\n const usableHeight = visibleHeight - top - bottom;\n if (usableHeight <= 0) return;\n\n const y = grid._focusRow * rowHeight;\n const virtualScrollTop = scrollMapping ? toVirtualScrollTop(scrollEl.scrollTop, scrollMapping) : scrollEl.scrollTop;\n if (y < virtualScrollTop + top) {\n const target = Math.max(0, y - top);\n scrollEl.scrollTop = scrollMapping ? fromVirtualScrollTop(target, scrollMapping) : target;\n } else if (y + rowHeight > virtualScrollTop + top + usableHeight) {\n const target = Math.max(0, y - usableHeight + rowHeight - top);\n scrollEl.scrollTop = scrollMapping ? fromVirtualScrollTop(target, scrollMapping) : target;\n }\n}\n\n/**\n * Resolve the rendered cell for the focused row/col, or `undefined` when the\n * focused row is outside the current virtual window.\n */\nfunction findFocusedCell(grid: GridHost): { rowEl: HTMLElement | null; cell: HTMLElement } | undefined {\n const rowIndex = grid._focusRow;\n const vStart = grid._virtualization.start ?? 0;\n const vEnd = grid._virtualization.end ?? grid._rows.length;\n if (rowIndex < vStart || rowIndex >= vEnd) return undefined;\n\n const rowEl = grid._bodyEl.querySelectorAll('.data-grid-row')[rowIndex - vStart] as HTMLElement | null;\n // Try exact column match first, then query by data-col, then fall back to the\n // first cell (full-width group rows).\n let cell = rowEl?.children[grid._focusCol] as HTMLElement | undefined;\n if (!cell || !cell.classList?.contains('cell')) {\n cell = (rowEl?.querySelector(`.cell[data-col=\"${grid._focusCol}\"]`) ?? rowEl?.querySelector('.cell[data-col]')) as\n HTMLElement | undefined;\n }\n return cell ? { rowEl: rowEl ?? null, cell } : undefined;\n}\n\n/** Horizontal: bring the focused cell into the `.tbw-scroll-area` viewport. */\nfunction scrollCellIntoViewHorizontally(\n grid: GridHost,\n rowEl: HTMLElement | null,\n cell: HTMLElement,\n options?: EnsureCellVisibleOptions,\n): void {\n const scrollArea = grid.querySelector('.tbw-scroll-area') as HTMLElement | null;\n if (!scrollArea) return;\n\n // Home/End always scroll to the edge.\n if (options?.forceScrollLeft) {\n scrollArea.scrollLeft = 0;\n return;\n }\n if (options?.forceScrollRight) {\n scrollArea.scrollLeft = scrollArea.scrollWidth - scrollArea.clientWidth;\n return;\n }\n\n // Plugins (e.g. pinned columns) report how much of the scroll area they\n // obscure, and whether the focused cell should skip scrolling entirely\n // (pinned cells are always visible).\n const offsets = grid._getHorizontalScrollOffsets?.(rowEl ?? undefined, cell) ?? { left: 0, right: 0 };\n if (offsets.skipScroll) return;\n\n const cellRect = cell.getBoundingClientRect();\n const scrollAreaRect = scrollArea.getBoundingClientRect();\n const cellLeft = cellRect.left - scrollAreaRect.left + scrollArea.scrollLeft;\n const cellRight = cellLeft + cellRect.width;\n const visibleLeft = scrollArea.scrollLeft + offsets.left;\n const visibleRight = scrollArea.scrollLeft + scrollArea.clientWidth - offsets.right;\n if (cellLeft < visibleLeft) {\n scrollArea.scrollLeft = cellLeft - offsets.left;\n } else if (cellRight > visibleRight) {\n scrollArea.scrollLeft = cellRight - scrollArea.clientWidth + offsets.right;\n }\n}\n\n/** Move real DOM focus to the editor, the cell, or the grid host. */\nfunction applyFocusTarget(grid: GridHost, cell: HTMLElement, isEditing: boolean): void {\n if (isEditing && cell.classList.contains(GridClasses.EDITING)) {\n // Editing cell: focus the editor input inside it\n const focusTarget = cell.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n if (focusTarget && document.activeElement !== focusTarget) {\n try {\n focusTarget.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n }\n return;\n }\n\n if (isEditing) {\n // Active edit row but this cell isn't the editing cell — focus it so Tab\n // navigation within the row can attach editors.\n if (cell.contains(document.activeElement)) return;\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n try {\n cell.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n return;\n }\n\n // NOT editing: keep focus on the grid element (tabindex=0) rather than\n // individual cells. In a virtualized grid, cells can be detached by\n // subsequent render cycles (e.g., SelectionPlugin's requestAfterRender\n // → RAF → row recycling). A detached focused cell causes activeElement\n // to revert to <body>, breaking keyboard navigation.\n // Visual focus is managed by the .cell-focus CSS class + data-has-focus.\n //\n // BUT: don't steal focus from a real focused descendant (toolpanel\n // input, shell-header button, registered overlay control). Cell focus\n // is virtual; if the user is actively typing in a non-cell input, we\n // must leave that input alone. We only reclaim focus when nothing\n // meaningful inside the grid currently holds it (active element is\n // outside grid, on the grid host itself, or on a bare cell).\n const active = document.activeElement;\n const meaningful =\n active instanceof HTMLElement &&\n active !== grid &&\n typeof grid.contains === 'function' &&\n grid.contains(active) &&\n !active.classList.contains('cell') &&\n !active.closest('.cell');\n if (!meaningful && active !== grid) {\n grid.focus({ preventScroll: true });\n }\n}\n\n/**\n * Scroll the viewport (virtualized or static) so the focused cell's row is visible\n * and apply visual focus styling / tabindex management.\n */\nexport function ensureCellVisible(grid: GridHost, options?: EnsureCellVisibleOptions): void {\n scrollFocusedRowIntoView(grid);\n\n const isEditing = isRowEditing(grid) || !!grid._isGridEditMode;\n // Skip refreshVirtualWindow when in edit mode to avoid wiping editors\n if (!isEditing) grid.refreshVirtualWindow(false);\n\n clearCellFocus(grid._bodyEl);\n // Clear previous aria-selected markers\n Array.from(grid._bodyEl.querySelectorAll('[aria-selected=\"true\"]')).forEach((el) => {\n el.setAttribute('aria-selected', 'false');\n });\n\n const focused = findFocusedCell(grid);\n if (!focused) return;\n const { rowEl, cell } = focused;\n\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n\n // Skip horizontal scrolling in edit mode to prevent scroll jumps when editors\n // are created — unless forceHorizontalScroll is set (Tab while editing).\n if (!isEditing || options?.forceHorizontalScroll) {\n scrollCellIntoViewHorizontally(grid, rowEl, cell, options);\n }\n\n applyFocusTarget(grid, cell, isEditing);\n}\n// #endregion\n","/**\n * Generic click-only action menu — the shared building block for WCAG 2.2\n * SC 2.5.7 \"Dragging Movements\" alternatives in plugins.\n *\n * Every drag affordance in the grid needs a path that works with plain clicks\n * or taps. A keyboard equivalent is **not** enough: SC 2.5.7 is specifically\n * about pointer input, and the W3C Understanding text calls out \"a pop-up menu\n * after tap\" as a conforming alternative. This module is that pop-up.\n *\n * Lives under `plugins/shared/` on purpose: each plugin bundle is built\n * self-contained, so this code is duplicated into the plugins that use it and\n * never lands in the core `index.js` budget.\n *\n * @since 3.6.0\n * @internal\n */\n\n/** Gap between the anchor element and the menu, in px. */\nconst ANCHOR_GAP_PX = 6;\n\n/** Runtime check — happy-dom and older browsers may lack the Popover API. */\nfunction supportsPopover(): boolean {\n return typeof HTMLElement.prototype?.showPopover === 'function';\n}\n\nlet hoverlessQuery: MediaQueryList | null | undefined;\n\n/** Whether the primary pointer cannot hover (touch, most switch devices). */\nfunction isHoverless(): boolean {\n if (hoverlessQuery === undefined) hoverlessQuery = globalThis.matchMedia?.('(hover: none)') ?? null;\n return hoverlessQuery?.matches ?? false;\n}\n\n/**\n * Whether a plugin should also expose its drag alternative as a dedicated\n * inline control, on top of the menu that is always available.\n *\n * Hover-less pointers get one unconditionally — a control revealed on hover is\n * unreachable when nothing can hover, and long-press is the wrong fallback for\n * the tremor and low-dexterity users SC 2.5.7 exists for.\n *\n * Driven by `a11y.dragAlternatives` on the grid config.\n */\nexport function prefersInlineDragAlternative(grid: {\n effectiveConfig?: { a11y?: { dragAlternatives?: 'menu' | 'inline' } };\n}): boolean {\n return grid.effectiveConfig?.a11y?.dragAlternatives === 'inline' || isHoverless();\n}\n\n/** One clickable entry in the menu. */\nexport interface DragAlternativeAction {\n /** Visible text, also the accessible name. */\n label: string;\n /** Render the entry but refuse the click (e.g. \"Move up\" on the first row). */\n disabled?: boolean;\n /** Invoked on click; the menu closes first so focus handling is predictable. */\n run(): void;\n}\n\nexport interface DragAlternativeMenu {\n /**\n * Show `actions` anchored to `anchor`, with `label` as the group's accessible\n * name. Mounted inside the anchor's grid host so it inherits theme tokens.\n */\n open(anchor: HTMLElement, label: string, actions: readonly DragAlternativeAction[]): void;\n /** Hide the menu. Safe to call when already closed. */\n close(): void;\n /** Remove the element and every document-level listener. */\n dispose(): void;\n /** The menu element, or `null` before the first {@link DragAlternativeMenu.open}. */\n readonly element: HTMLElement | null;\n}\n\n/**\n * Create a menu instance.\n *\n * @param id - DOM id for the menu element, unique per owning plugin.\n * @param className - Root class name, so each plugin can style its own menu.\n */\nexport function createDragAlternativeMenu(id: string, className: string): DragAlternativeMenu {\n let root: HTMLDivElement | null = null;\n let opened = false;\n let disposed = false;\n const abort = new AbortController();\n\n function close(): void {\n if (!opened || !root) return;\n opened = false;\n if (supportsPopover()) root.hidePopover();\n root.hidden = true;\n }\n\n function build(): HTMLDivElement {\n const el = document.createElement('div');\n el.id = id;\n el.className = className;\n el.setAttribute('popover', 'manual');\n el.setAttribute('role', 'group');\n // Without the Popover API there is no UA `display: none`, so hide it here.\n el.hidden = true;\n // Tabbing past either end dismisses instead of stranding the user — the\n // menu has no visible trigger to tab back to. A null `relatedTarget` (click\n // focus in Firefox/Safari, focus leaving the document) is ignored.\n el.addEventListener(\n 'focusout',\n (e) => {\n const next = (e as FocusEvent).relatedTarget;\n if (next instanceof Node && !el.contains(next)) close();\n },\n { signal: abort.signal },\n );\n return el;\n }\n\n /**\n * Position against the anchor in viewport coordinates. The menu lives in the\n * top layer, so its containing block is the viewport regardless of where the\n * anchor sits in the DOM.\n */\n function place(anchor: HTMLElement): void {\n if (!root) return;\n const a = anchor.getBoundingClientRect();\n const box = root.getBoundingClientRect();\n const left = Math.max(4, Math.min(a.left, window.innerWidth - box.width - 4));\n const below = a.bottom + ANCHOR_GAP_PX;\n const top = below + box.height <= window.innerHeight ? below : Math.max(4, a.top - ANCHOR_GAP_PX - box.height);\n root.style.left = `${Math.round(left)}px`;\n root.style.top = `${Math.round(top)}px`;\n }\n\n // Any pointer press outside the menu dismisses it. Capture phase so the press\n // that lands on a grid cell still reaches its own handler afterwards.\n document.addEventListener(\n 'pointerdown',\n (e) => {\n if (opened && root && !e.composedPath().includes(root)) close();\n },\n { capture: true, signal: abort.signal },\n );\n document.addEventListener(\n 'keydown',\n (e) => {\n if ((e as KeyboardEvent).key === 'Escape') close();\n },\n { capture: true, signal: abort.signal },\n );\n\n return {\n get element() {\n return root;\n },\n open(anchor, label, actions) {\n if (disposed || actions.length === 0) return;\n if (!root) root = build();\n // Mount inside the grid host (light DOM) so theme custom properties and\n // `color-scheme` cascade in; the top layer keeps it out of overflow.\n const host = anchor.closest<HTMLElement>('tbw-grid, [data-tbw-grid]') ?? document.body;\n if (root.parentNode !== host) host.appendChild(root);\n root.setAttribute('aria-label', label);\n root.replaceChildren();\n for (const action of actions) {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `${className}-item`;\n btn.textContent = action.label;\n btn.disabled = action.disabled === true;\n btn.addEventListener('click', () => {\n close();\n action.run();\n });\n root.appendChild(btn);\n }\n root.hidden = false;\n if (!opened && supportsPopover()) root.showPopover();\n opened = true;\n place(anchor);\n root.querySelector<HTMLButtonElement>('button:not([disabled])')?.focus();\n },\n close,\n dispose() {\n disposed = true;\n close();\n abort.abort();\n root?.remove();\n root = null;\n },\n };\n}\n","/**\n * Column Reordering Core Logic\n *\n * Pure functions for column drag and reordering operations.\n */\n\n/**\n * Check if a column can be moved based on its own metadata.\n * This checks column-level properties like lockPosition, utility, and suppressMovable.\n *\n * Both the top-level `lockPosition` (preferred) and legacy `meta.lockPosition` /\n * `meta.suppressMovable` are honored. `utility: true` columns (system columns\n * synthesized by plugins or authored by the app) are always treated as locked.\n *\n * Note: For full movability checks including plugin constraints (e.g., pinned columns),\n * use `grid.query<boolean>('canMoveColumn', column)` which queries all plugins that\n * declare the 'canMoveColumn' query in their manifest.\n *\n * @param column - The column configuration to check\n * @returns True if the column can be moved based on its metadata\n */\nexport function canMoveColumn(column: {\n lockPosition?: boolean;\n utility?: boolean;\n meta?: Record<string, unknown>;\n}): boolean {\n if (column.lockPosition === true) return false;\n if (column.utility === true) return false;\n const meta = column.meta ?? {};\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Move a column from one position to another in the order array.\n *\n * @param columns - Array of field names in current order\n * @param fromIndex - The current index of the column to move\n * @param toIndex - The target index to move the column to\n * @returns New array with updated order\n */\nexport function moveColumn(columns: string[], fromIndex: number, toIndex: number): string[] {\n if (fromIndex === toIndex) return columns;\n if (fromIndex < 0 || fromIndex >= columns.length) return columns;\n if (toIndex < 0 || toIndex > columns.length) return columns;\n\n const result = [...columns];\n const [removed] = result.splice(fromIndex, 1);\n result.splice(toIndex, 0, removed);\n return result;\n}\n","@layer tbw-plugins {\n .header-row > .cell[draggable='true'] {\n cursor: grab;\n position: relative;\n }\n .header-row > .cell.dragging {\n opacity: 0.5;\n cursor: grabbing;\n }\n .header-row > .cell.drop-before::before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\n z-index: 1;\n }\n .header-row > .cell.drop-after::after {\n content: '';\n position: absolute;\n right: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\n z-index: 1;\n }\n\n /* FLIP animation: cells slide smoothly during reorder */\n .cell.flip-animating {\n transition: transform var(--tbw-animation-duration, 200ms) ease-out;\n will-change: transform;\n z-index: 1;\n }\n\n /* Fade animation: crossfade columns at new position */\n @keyframes reorder-fade-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n }\n\n .cell.fade-animating {\n animation: reorder-fade-in var(--tbw-animation-duration, 200ms) ease-out backwards;\n }\n\n /* ========== Click-only move control (WCAG 2.2 SC 2.5.7) ==========\n Dragging a header is the primary gesture; the single-pointer alternative is\n always available from the header's context menu. This button is the *extra*\n affordance, and the plugin only puts it in the DOM under\n `a11y.dragAlternatives: 'inline'` or on a hover-less pointer — because as a\n flex sibling it permanently reserves ~24px of every header cell, which is\n real estate width-critical grids cannot spare.\n\n It is a sibling rather than an absolute overlay on purpose: an overlay sits\n on top of the header's own click-to-sort target, so on any column narrower\n than ~135px a click aimed at the middle of the header would land on the\n button and silently not sort.\n\n Once present it stays in the a11y tree at all times so speech- and\n switch-control users can target it, which is why it is dimmed with\n `opacity` rather than `display: none` / `visibility: hidden` (both of those\n also remove it from the accessibility tree, defeating the point). */\n .header-row > .cell > .tbw-col-move-btn,\n .header-group-row > .cell > .tbw-col-move-btn {\n flex-shrink: 0;\n min-width: var(--tbw-touch-target-min, 24px);\n min-height: var(--tbw-touch-target-min, 24px);\n padding: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: var(--tbw-color-header-bg);\n color: var(--tbw-color-header-fg, inherit);\n font: inherit;\n line-height: 1;\n border: 1px solid var(--tbw-color-border);\n border-radius: var(--tbw-border-radius, 0.25rem);\n cursor: pointer;\n opacity: 0;\n pointer-events: none;\n transition: opacity var(--tbw-transition-duration, 120ms) var(--tbw-transition-ease, ease);\n\n &:hover {\n border-color: var(--tbw-color-accent);\n }\n }\n\n .header-row > .cell:hover > .tbw-col-move-btn,\n .header-row > .cell:focus-within > .tbw-col-move-btn,\n .header-row > .cell.cell-focus > .tbw-col-move-btn,\n .header-group-row > .cell:hover > .tbw-col-move-btn,\n .header-group-row > .cell:focus-within > .tbw-col-move-btn {\n opacity: 1;\n pointer-events: auto;\n }\n\n /* No hover to reveal it (touch, most switch devices): keep it visible. */\n @media (hover: none) {\n .header-row > .cell > .tbw-col-move-btn,\n .header-group-row > .cell > .tbw-col-move-btn {\n opacity: 1;\n pointer-events: auto;\n }\n }\n\n /* Menu opened by the move button. Lives on document.body (top layer), so every\n custom property needs a literal fallback. */\n .tbw-col-move-menu {\n position: fixed;\n margin: 0;\n padding: 4px;\n display: flex;\n flex-direction: column;\n min-width: 10rem;\n background: var(--tbw-color-panel-bg, light-dark(#fff, #1e1e1e));\n color: var(--tbw-color-panel-fg, light-dark(#1a1a1a, #eaeaea));\n border: 1px solid var(--tbw-color-border, light-dark(#d0d0d0, #3a3a3a));\n border-radius: var(--tbw-border-radius, 0.25rem);\n box-shadow: var(--tbw-shadow-panel, 0 4px 12px light-dark(rgb(0 0 0 / 20%), rgb(0 0 0 / 50%)));\n z-index: 10001;\n\n &[hidden] {\n display: none;\n }\n }\n\n .tbw-col-move-menu-item {\n min-height: var(--tbw-touch-target-min, 24px);\n padding: 4px 8px;\n text-align: start;\n background: none;\n color: inherit;\n font: inherit;\n border: 0;\n /* Themes set radii for much larger surfaces (Material uses 12px), which\n turns a 24px row into a lozenge. */\n border-radius: min(var(--tbw-border-radius, 0.25rem), 6px);\n cursor: pointer;\n\n &:hover:not(:disabled) {\n background: var(--tbw-color-row-hover, light-dark(#00000010, #ffffff14));\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: default;\n }\n }\n} /* End @layer tbw-plugins */\n","/**\n * Column Reordering Plugin (Class-based)\n *\n * Provides drag-and-drop column reordering functionality for tbw-grid.\n * Supports keyboard and mouse interactions with visual feedback.\n * Uses FLIP animation technique for smooth column transitions.\n *\n * Animation respects grid-level animation.mode setting but style is plugin-configured.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport { BaseGridPlugin, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, GridHost } from '../../core/types';\nimport type { ContextMenuParams, HeaderContextMenuItem } from '../context-menu/types';\nimport {\n type DragAlternativeAction,\n type DragAlternativeMenu,\n createDragAlternativeMenu,\n prefersInlineDragAlternative,\n} from '../shared/drag-alternative-menu';\nimport { canMoveColumn, moveColumn } from './column-drag';\nimport styles from './reorder.css?inline';\nimport type { ColumnMoveDetail, ReorderConfig } from './types';\n\nconst QUERY_CAN_MOVE_COLUMN = 'canMoveColumn';\nconst QUERY_GET_CONTEXT_MENU_ITEMS = 'getContextMenuItems';\n\n/** Menu order band for column moving — after pinning (40-49). */\nconst MOVE_ITEM_ORDER = 50;\n\n/**\n * Column Reorder Plugin for tbw-grid\n *\n * Lets users rearrange columns by dragging and dropping column headers. Supports smooth\n * FLIP animations, fade transitions, or instant reordering. Animation respects the\n * grid-level `animation.mode` setting.\n *\n * ## Installation\n *\n * ```ts\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder-columns';\n * ```\n *\n * ## Keyboard Shortcuts\n *\n * | Key | Action |\n * |-----|--------|\n * | `Alt + ←` | Move focused column left |\n * | `Alt + →` | Move focused column right |\n *\n * ## Events\n *\n * | Event | Detail | Cancelable | Description |\n * |-------|--------|------------|-------------|\n * | `column-move` | `{ field, fromIndex, toIndex, columnOrder }` | Yes | Fired when a column move is attempted |\n *\n * @example Basic Drag-and-Drop Reordering\n * ```ts\n * import { queryGrid } from '@toolbox-web/grid';\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder-columns';\n *\n * const grid = queryGrid('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID' },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [new ReorderPlugin({ animation: 'flip', animationDuration: 200 })],\n * };\n *\n * // Persist column order\n * grid.on('column-move', ({ columnOrder }) => {\n * localStorage.setItem('columnOrder', JSON.stringify(columnOrder));\n * });\n * ```\n *\n * @example Prevent Moves That Break Group Boundaries\n * ```ts\n * grid.on('column-move', (detail, e) => {\n * if (!isValidMoveWithinGroup(detail.field, detail.fromIndex, detail.toIndex)) {\n * e.preventDefault(); // Column snaps back to original position\n * }\n * });\n * ```\n *\n * @see {@link ReorderConfig} for all configuration options\n * @see {@link ColumnMoveDetail} for the event detail structure\n * @see GroupingColumnsPlugin for column group integration\n *\n * @internal Extends BaseGridPlugin\n * @since 1.24.0\n */\nexport class ReorderPlugin extends BaseGridPlugin<ReorderConfig> {\n /** @internal */\n readonly name = 'reorderColumns';\n /** @internal */\n override readonly aliases = ['reorder'] as const;\n /** @internal */\n override readonly styles = styles;\n\n /**\n * Plugin manifest - declares handled queries.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n queries: [\n {\n type: QUERY_CAN_MOVE_COLUMN,\n description: 'Authoritative check for whether a column may be reordered (honors lockPosition).',\n },\n {\n type: QUERY_GET_CONTEXT_MENU_ITEMS,\n description: 'Contributes the click-only column move actions (WCAG 2.2 SC 2.5.7) to the header context menu.',\n },\n ],\n };\n\n /** @internal */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === QUERY_CAN_MOVE_COLUMN) {\n const column = query.context as ColumnConfig | undefined;\n return column ? canMoveColumn(column) : undefined;\n }\n if (query.type === QUERY_GET_CONTEXT_MENU_ITEMS) {\n const params = query.context as ContextMenuParams | undefined;\n if (!params?.isHeader) return undefined;\n return this.#moveActions(params.field)?.map((action, i): HeaderContextMenuItem => ({\n id: `reorder-${i}`,\n label: action.label,\n disabled: action.disabled,\n action: action.run,\n order: MOVE_ITEM_ORDER + i,\n }));\n }\n return undefined;\n }\n\n /** @internal */\n protected override get defaultConfig(): Partial<ReorderConfig> {\n return {\n animation: 'flip', // Plugin's own default\n };\n }\n\n /**\n * Resolve animation type from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationType(): false | 'flip' | 'fade' {\n // Check if animations are globally disabled\n if (!this.isAnimationEnabled) return false;\n\n // Plugin config (with default from defaultConfig)\n if (this.config.animation !== undefined) return this.config.animation;\n\n return 'flip'; // Plugin default\n }\n\n /**\n * Get animation duration, allowing plugin config override.\n * Uses base class animationDuration for default.\n */\n protected override get animationDuration(): number {\n // Plugin config override\n if (this.config.animationDuration !== undefined) {\n return this.config.animationDuration;\n }\n return super.animationDuration;\n }\n\n // #region Internal State\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n /** When dragging a group header, holds the field names in that fragment. */\n private draggedGroupFields: string[] = [];\n /** Initial column order (captured after order-attribute reordering). Used by resetColumnOrder(). */\n private originalColumnOrder: string[] | null = null;\n /** Lazily created click-only move menu (WCAG 2.2 SC 2.5.7). */\n private moveMenu: DragAlternativeMenu | null = null;\n\n /** Typed internal grid accessor. */\n get #internalGrid(): GridHost {\n return this.grid;\n }\n\n /**\n * Check if a column can be moved, considering both column config and plugin queries.\n *\n * The local `canMoveColumn(column)` check (lockPosition / suppressMovable) runs first;\n * cross-plugin vetoes (e.g. PinnedColumnsPlugin blocking sticky columns) are then\n * collected via the `canMoveColumn` query. Other plugins (e.g. VisibilityPlugin) get\n * the same answer by querying — ReorderPlugin's `handleQuery` returns the local check.\n */\n private canMoveColumnWithPlugins(column: ColumnConfig | undefined): boolean {\n if (!column || !canMoveColumn(column)) return false;\n const responses = this.grid.query<boolean>(QUERY_CAN_MOVE_COLUMN, column);\n return !responses.includes(false);\n }\n\n /**\n * Add the click-only move control to a movable header cell.\n *\n * WCAG 2.2 SC 2.5.7 \"Dragging Movements\" is **not** satisfied by the\n * `Alt + Arrow` shortcuts: the criterion is about pointer input, and the W3C\n * Understanding text is explicit that a keyboard equivalent only counts when\n * it also exposes controls that can be clicked or tapped. The alternative is\n * always reachable from the header's context menu; this button is the extra,\n * opt-in affordance for `a11y.dragAlternatives: 'inline'`.\n *\n * It costs ~24px of every header cell — it is a flex sibling of the label\n * rather than an overlay, because an overlay would swallow the header's own\n * click-to-sort target on narrow columns — so it is removed again as soon as\n * the mode no longer calls for it.\n */\n #syncMoveControl(headerEl: HTMLElement, field: string, column: ColumnConfig | undefined): void {\n this.#syncInlineMoveButton(headerEl, `Move column ${String(column?.header ?? field)}`, (btn) =>\n this.#openMoveMenu(btn, field),\n );\n }\n\n /**\n * Add or remove the inline `\\u2194` button on `hostEl` according to the current\n * drag-alternative mode. Shared by header cells and group header cells so the\n * two affordances can never drift apart.\n */\n #syncInlineMoveButton(hostEl: HTMLElement, ariaLabel: string, open: (btn: HTMLElement) => void): void {\n const existing = hostEl.querySelector<HTMLElement>(':scope > .tbw-col-move-btn');\n if (!prefersInlineDragAlternative(this.grid)) {\n existing?.remove();\n return;\n }\n if (existing) return;\n\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'tbw-col-move-btn';\n btn.tabIndex = -1;\n // Not draggable, or a press on the button would start an HTML5 column drag.\n btn.draggable = false;\n btn.textContent = '\\u2194';\n btn.setAttribute('aria-label', ariaLabel);\n // Stop the press from reaching the header's sort handler or the drag start.\n btn.addEventListener('pointerdown', (e) => e.stopPropagation());\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n e.preventDefault();\n open(btn);\n });\n hostEl.appendChild(btn);\n }\n\n /**\n * The single-click move actions for `field`, or `null` when the column\n * cannot be moved. Shared by the standalone menu and the `getContextMenuItems`\n * contribution so the two can never drift apart.\n */\n #moveActions(field: string): DragAlternativeAction[] | null {\n const column = this.columns.find((c) => c.field === field);\n if (!this.canMoveColumnWithPlugins(column)) return null;\n\n const order = this.getColumnOrder();\n const fromIndex = order.indexOf(field);\n if (fromIndex === -1) return null;\n\n const firstMovable = order.findIndex((f) => this.#isMoveTarget(f));\n let lastMovable = -1;\n for (let i = order.length - 1; i >= 0; i--) {\n if (this.#isMoveTarget(order[i])) {\n lastMovable = i;\n break;\n }\n }\n return [\n {\n label: 'Move left',\n disabled: !this.#canStep(order, fromIndex, -1),\n run: () => this.moveColumn(field, fromIndex - 1),\n },\n {\n label: 'Move right',\n disabled: !this.#canStep(order, fromIndex, 1),\n run: () => this.moveColumn(field, fromIndex + 1),\n },\n {\n label: 'Move to start',\n disabled: firstMovable === -1 || firstMovable === fromIndex,\n run: () => this.moveColumn(field, firstMovable),\n },\n {\n label: 'Move to end',\n disabled: lastMovable === -1 || lastMovable === fromIndex,\n run: () => this.moveColumn(field, lastMovable),\n },\n ];\n }\n\n /** Open the click-only move menu anchored to a header cell or its move button. */\n #openMoveMenu(anchor: HTMLElement, field: string): void {\n const actions = this.#moveActions(field);\n if (!actions) return;\n const column = this.columns.find((c) => c.field === field);\n\n this.moveMenu ??= createDragAlternativeMenu('tbw-col-move-menu', 'tbw-col-move-menu');\n this.moveMenu.open(anchor, `Move column ${String(column?.header ?? field)}`, actions);\n }\n\n /**\n * The group-row fragments in visual order, each as the fields it spans.\n *\n * Read back from the DOM rather than from the group config because a fragment\n * is what the user actually sees and drags: `mergeGroups` splits one configured\n * group into several cells when pinning or an interleaved utility column breaks\n * its span, and each of those cells moves independently.\n */\n #groupFragments(gridEl: HTMLElement): { id: string; fields: string[] }[] {\n const visible = this.visibleColumns;\n const fragments: { id: string; fields: string[] }[] = [];\n\n gridEl.querySelectorAll<HTMLElement>('.header-group-row > .cell[data-group]').forEach((cell) => {\n const id = cell.getAttribute('data-group');\n if (!id) return;\n const fields = this.getGroupFragmentFields(cell, id);\n if (fields.length > 0) fragments.push({ id, fields });\n });\n\n // The cells are appended in span order, but a plugin may re-parent them.\n return fragments.sort(\n (a, b) => visible.findIndex((c) => c.field === a.fields[0]) - visible.findIndex((c) => c.field === b.fields[0]),\n );\n }\n\n /**\n * The single-click move actions for a group fragment, or `null` when the\n * fragment cannot be moved.\n *\n * A step moves the whole fragment past its whole neighbour — landing *inside*\n * a neighbouring group would split that group, which dragging cannot do either.\n * A neighbour that holds even one locked column disables the step rather than\n * being skipped, since moving past it would still shift its index.\n */\n #groupMoveActions(gridEl: HTMLElement, fragmentFields: string[]): DragAlternativeAction[] | null {\n if (!fragmentFields.every((f) => this.#isMoveTarget(f))) return null;\n\n const fragments = this.#groupFragments(gridEl);\n const at = fragments.findIndex((f) => f.fields[0] === fragmentFields[0]);\n if (at === -1) return null;\n\n const movable = (fragment: { fields: string[] } | undefined): boolean =>\n !!fragment && fragment.fields.every((f) => this.#isMoveTarget(f));\n const spanMovable = (from: number, to: number): boolean =>\n from <= to && fragments.slice(from, to + 1).every(movable);\n const move = (target: { fields: string[] }, before: boolean) => () =>\n this.executeGroupBlockMove(fragmentFields, target.fields, before);\n\n return [\n {\n label: 'Move left',\n disabled: !movable(fragments[at - 1]),\n run: move(fragments[at - 1] ?? { fields: fragmentFields }, true),\n },\n {\n label: 'Move right',\n disabled: !movable(fragments[at + 1]),\n run: move(fragments[at + 1] ?? { fields: fragmentFields }, false),\n },\n {\n label: 'Move to start',\n disabled: at === 0 || !spanMovable(0, at - 1),\n run: move(fragments[0], true),\n },\n {\n label: 'Move to end',\n disabled: at === fragments.length - 1 || !spanMovable(at + 1, fragments.length - 1),\n run: move(fragments[fragments.length - 1], false),\n },\n ];\n }\n\n /** Open the click-only move menu for a group fragment. */\n #openGroupMoveMenu(anchor: HTMLElement, groupHeaderEl: HTMLElement, fragmentFields: string[]): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n const actions = this.#groupMoveActions(gridEl, fragmentFields);\n if (!actions) return;\n\n this.moveMenu ??= createDragAlternativeMenu('tbw-col-move-menu', 'tbw-col-move-menu');\n this.moveMenu.open(anchor, `Move group ${this.#groupLabel(groupHeaderEl)}`, actions);\n }\n\n /** The visible group label, falling back to its id when a renderer drew no text. */\n #groupLabel(groupHeaderEl: HTMLElement): string {\n const text = groupHeaderEl.textContent?.replace('\\u2194', '').trim();\n return text || (groupHeaderEl.getAttribute('data-group') ?? '');\n }\n\n /** Whether `field` names a column this plugin is allowed to move past or onto. */\n #isMoveTarget(field: string): boolean {\n return this.canMoveColumnWithPlugins(this.columns.find((c) => c.field === field));\n }\n\n /** Whether stepping one position toward `dir` lands on a movable neighbour. */\n #canStep(order: string[], fromIndex: number, dir: -1 | 1): boolean {\n const toIndex = fromIndex + dir;\n if (toIndex < 0 || toIndex >= order.length) return false;\n return this.#isMoveTarget(order[toIndex]);\n }\n\n /**\n * Clear all drag-related classes from header cells and group header cells.\n */\n private clearDragClasses(): void {\n this.gridElement?.querySelectorAll('.header-row > .cell, .header-group-row > .cell').forEach((h) => {\n h.classList.remove(GridClasses.DRAGGING, 'drop-target', 'drop-before', 'drop-after');\n });\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for reorder requests from other plugins (e.g., VisibilityPlugin)\n // Uses disconnectSignal for automatic cleanup - no need for manual removeEventListener\n this.gridElement.addEventListener(\n 'column-reorder-request',\n (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail?.field && typeof detail.toIndex === 'number') {\n this.moveColumn(detail.field, detail.toIndex);\n }\n },\n { signal: this.disconnectSignal },\n );\n }\n\n /** @internal */\n override detach(): void {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.draggedGroupFields = [];\n this.moveMenu?.dispose();\n this.moveMenu = null;\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Capture initial column order on first render (after order-attribute reordering)\n if (!this.originalColumnOrder) {\n this.originalColumnOrder = this.columns.map((c) => c.field);\n }\n\n const headers = gridEl.querySelectorAll('.header-row > .cell');\n\n headers.forEach((header) => {\n const headerEl = header as HTMLElement;\n const field = headerEl.getAttribute('data-field');\n if (!field) return;\n\n const column = this.columns.find((c) => c.field === field);\n if (!this.canMoveColumnWithPlugins(column)) {\n headerEl.draggable = false;\n headerEl.querySelector(':scope > .tbw-col-move-btn')?.remove();\n return;\n }\n\n headerEl.draggable = true;\n this.#syncMoveControl(headerEl, field, column);\n\n // Remove existing listeners to prevent duplicates\n if (headerEl.getAttribute('data-dragstart-bound')) return;\n headerEl.setAttribute('data-dragstart-bound', 'true');\n\n // The always-available pointer alternative (WCAG 2.2 SC 2.5.7): right-click,\n // long-press, and Shift+F10 all raise `contextmenu`. Skipped when the context\n // menu plugin is loaded, since it renders the same actions itself from the\n // `getContextMenuItems` contribution and two stacked menus help nobody.\n headerEl.addEventListener('contextmenu', (e: MouseEvent) => {\n if (this.grid.getPluginByName('contextMenu')) return;\n e.preventDefault();\n this.#openMoveMenu(headerEl, field);\n });\n\n headerEl.addEventListener('dragstart', (e: DragEvent) => {\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = orderIndex;\n this.draggedGroupFields = []; // Clear any stale group state\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n headerEl.classList.add(GridClasses.DRAGGING);\n });\n\n headerEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.draggedGroupFields = [];\n this.clearDragClasses();\n });\n\n headerEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n // Skip if dragging this same individual column\n if (this.draggedField === field && this.draggedGroupFields.length === 0) return;\n // Skip if this column is part of the dragged group fragment\n if (this.draggedGroupFields.includes(field)) return;\n\n const rect = headerEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.dropIndex = e.clientX < midX ? orderIndex : orderIndex + 1;\n\n this.clearDragClasses();\n // Re-mark dragged elements\n if (this.draggedGroupFields.length > 0) {\n for (const f of this.draggedGroupFields) {\n this.gridElement\n ?.querySelector(`.header-row > .cell[data-field=\"${f}\"]`)\n ?.classList.add(GridClasses.DRAGGING);\n }\n } else if (this.draggedField) {\n this.gridElement\n ?.querySelector(`.header-row > .cell[data-field=\"${this.draggedField}\"]`)\n ?.classList.add(GridClasses.DRAGGING);\n }\n headerEl.classList.add('drop-target');\n headerEl.classList.toggle('drop-before', e.clientX < midX);\n headerEl.classList.toggle('drop-after', e.clientX >= midX);\n });\n\n headerEl.addEventListener('dragleave', () => {\n headerEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n headerEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n\n // Group fragment drop onto individual column header\n if (this.draggedGroupFields.length > 0) {\n if (this.draggedGroupFields.includes(field)) return;\n const rect = headerEl.getBoundingClientRect();\n const before = e.clientX < rect.left + rect.width / 2;\n this.executeGroupBlockMove(this.draggedGroupFields, [field], before);\n return;\n }\n\n // Individual column drop\n const draggedField = this.draggedField;\n const draggedIndex = this.draggedIndex;\n const dropIndex = this.dropIndex;\n\n if (!this.isDragging || draggedField === null || draggedIndex === null || dropIndex === null) {\n return;\n }\n\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n const currentOrder = this.getColumnOrder();\n const newOrder = moveColumn(currentOrder, draggedIndex, effectiveToIndex);\n\n const detail: ColumnMoveDetail = {\n field: draggedField,\n fromIndex: draggedIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n };\n\n // Emit cancelable event first - only update if not cancelled\n const cancelled = this.emitCancelable('column-move', detail);\n if (!cancelled) {\n // Update the grid's column order (with optional view transition)\n this.updateColumnOrder(newOrder);\n }\n });\n });\n\n // Set up drag listeners for group header cells (if column grouping is active).\n // Deferred to a microtask because GroupingColumnsPlugin.afterRender() creates the\n // .header-group-row DOM, and it may run after this plugin in hook order.\n queueMicrotask(() => this.setupGroupHeaderDrag(gridEl));\n }\n\n /**\n * Set up drag-and-drop listeners on group header cells (.header-group-row > .cell).\n * Dragging a group header moves all columns in that fragment as a block.\n * Implicit groups (ungrouped column spans) are not draggable.\n */\n private setupGroupHeaderDrag(gridEl: HTMLElement): void {\n const groupHeaders = gridEl.querySelectorAll('.header-group-row > .cell[data-group]');\n\n groupHeaders.forEach((gh) => {\n const groupHeaderEl = gh as HTMLElement;\n const groupId = groupHeaderEl.getAttribute('data-group');\n if (!groupId || groupId.startsWith('__implicit__')) return;\n\n // Determine which columns are in this fragment by reading the grid-column style\n const fragmentFields = this.getGroupFragmentFields(groupHeaderEl, groupId);\n if (fragmentFields.length === 0) return;\n\n // Check if all columns in the fragment can be moved\n const allMovable = fragmentFields.every((f) => {\n const col = this.columns.find((c) => c.field === f);\n return this.canMoveColumnWithPlugins(col);\n });\n if (!allMovable) return;\n\n groupHeaderEl.draggable = true;\n groupHeaderEl.style.cursor = 'grab';\n\n // Re-synced every render so a change of drag-alternative mode is picked up.\n this.#syncInlineMoveButton(groupHeaderEl, `Move group ${this.#groupLabel(groupHeaderEl)}`, (btn) =>\n this.#openGroupMoveMenu(btn, groupHeaderEl, fragmentFields),\n );\n\n // Already bound?\n if (groupHeaderEl.getAttribute('data-group-drag-bound')) return;\n groupHeaderEl.setAttribute('data-group-drag-bound', 'true');\n\n // The always-available pointer alternative (WCAG 2.2 SC 2.5.7). Unlike a\n // header cell this is not skipped when the context menu plugin is loaded:\n // that plugin resolves neither a cell nor a `header-cell` part here, so it\n // already swallows the native menu on a group header without offering one.\n groupHeaderEl.addEventListener('contextmenu', (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n this.#openGroupMoveMenu(groupHeaderEl, groupHeaderEl, fragmentFields);\n });\n\n groupHeaderEl.addEventListener('dragstart', (e: DragEvent) => {\n this.isDragging = true;\n this.draggedField = null;\n this.draggedIndex = null;\n this.draggedGroupFields = [...fragmentFields];\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', `group:${groupId}`);\n }\n\n groupHeaderEl.classList.add(GridClasses.DRAGGING);\n // Also mark the individual column headers as dragging\n for (const f of fragmentFields) {\n gridEl.querySelector(`.header-row > .cell[data-field=\"${f}\"]`)?.classList.add(GridClasses.DRAGGING);\n }\n });\n\n groupHeaderEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.draggedGroupFields = [];\n this.clearDragClasses();\n });\n\n // Group header is also a drop target for other groups / individual columns\n groupHeaderEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n // If dragging the same fragment, ignore\n if (\n this.draggedGroupFields.length > 0 &&\n this.draggedGroupFields.length === fragmentFields.length &&\n this.draggedGroupFields.every((f) => fragmentFields.includes(f))\n )\n return;\n\n const rect = groupHeaderEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n const before = e.clientX < midX;\n\n this.clearDragClasses();\n groupHeaderEl.classList.add('drop-target');\n groupHeaderEl.classList.toggle('drop-before', before);\n groupHeaderEl.classList.toggle('drop-after', !before);\n });\n\n groupHeaderEl.addEventListener('dragleave', () => {\n groupHeaderEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n groupHeaderEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n\n const rect = groupHeaderEl.getBoundingClientRect();\n const before = e.clientX < rect.left + rect.width / 2;\n\n if (this.draggedGroupFields.length > 0) {\n // Group-to-group drop: move dragged fragment as block relative to this fragment\n if (\n this.draggedGroupFields.length === fragmentFields.length &&\n this.draggedGroupFields.every((f) => fragmentFields.includes(f))\n )\n return;\n this.executeGroupBlockMove(this.draggedGroupFields, fragmentFields, before);\n } else if (this.draggedField) {\n // Individual column dropped onto group header\n const currentOrder = this.getColumnOrder();\n const anchorField = before ? fragmentFields[0] : fragmentFields[fragmentFields.length - 1];\n const fromIndex = currentOrder.indexOf(this.draggedField);\n const toIndex = before ? currentOrder.indexOf(anchorField) : currentOrder.indexOf(anchorField) + 1;\n if (fromIndex === -1 || toIndex === -1) return;\n const effectiveToIndex = toIndex > fromIndex ? toIndex - 1 : toIndex;\n const newOrder = moveColumn(currentOrder, fromIndex, effectiveToIndex);\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field: this.draggedField,\n fromIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n });\n if (!cancelled) this.updateColumnOrder(newOrder);\n }\n });\n });\n }\n\n /**\n * Get the column field names that belong to a group header fragment.\n * Reads the grid-column CSS style of the header cell to determine the column range.\n */\n private getGroupFragmentFields(groupHeaderEl: HTMLElement, _groupId: string): string[] {\n // Parse grid-column (e.g., \"2 / span 3\") to get start index and span\n const gridColumn = groupHeaderEl.style.gridColumn;\n const match = /(\\d+)\\s*\\/\\s*span\\s+(\\d+)/.exec(gridColumn);\n if (!match) return [];\n\n const startCol = parseInt(match[1], 10); // 1-based CSS grid column\n const span = parseInt(match[2], 10);\n\n // Map CSS grid columns to visible column fields\n const visibleColumns = this.visibleColumns;\n const fields: string[] = [];\n for (let i = startCol - 1; i < startCol - 1 + span && i < visibleColumns.length; i++) {\n const col = visibleColumns[i];\n if (col) fields.push(col.field);\n }\n return fields;\n }\n\n /**\n * Move a group of columns as a block to a new position relative to target fields.\n */\n private executeGroupBlockMove(draggedFields: string[], targetFields: string[], before: boolean): void {\n const currentOrder = this.getColumnOrder();\n const remaining = currentOrder.filter((f) => !draggedFields.includes(f));\n\n const anchorField = before ? targetFields[0] : targetFields[targetFields.length - 1];\n const insertAt = remaining.indexOf(anchorField);\n if (insertAt === -1) return;\n\n const insertIndex = before ? insertAt : insertAt + 1;\n const draggedInOrder = currentOrder.filter((f) => draggedFields.includes(f));\n remaining.splice(insertIndex, 0, ...draggedInOrder);\n\n // Emit cancelable column-move for the first field so lockGroupOrder guard can check\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field: draggedFields[0],\n fromIndex: currentOrder.indexOf(draggedFields[0]),\n toIndex: insertIndex,\n columnOrder: remaining,\n });\n if (!cancelled) {\n this.updateColumnOrder(remaining);\n }\n }\n\n /**\n * Handle Alt+Arrow keyboard shortcuts for column reordering.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n if (!event.altKey || (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')) {\n return;\n }\n\n const grid = this.#internalGrid;\n const focusCol = grid._focusCol;\n const columns = grid._visibleColumns;\n\n if (focusCol < 0 || focusCol >= columns.length) return;\n\n const column = columns[focusCol];\n if (!this.canMoveColumnWithPlugins(column)) return;\n\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(column.field);\n if (fromIndex === -1) return;\n\n const toIndex = event.key === 'ArrowLeft' ? fromIndex - 1 : fromIndex + 1;\n\n // Check bounds\n if (toIndex < 0 || toIndex >= currentOrder.length) return;\n\n // Check if target position is allowed (e.g., not into pinned area)\n const targetColumn = columns.find((c) => c.field === currentOrder[toIndex]);\n if (!this.canMoveColumnWithPlugins(targetColumn)) return;\n\n this.moveColumn(column.field, toIndex);\n\n // Update focus to follow the moved column and refresh visual focus state\n grid._focusCol = toIndex;\n ensureCellVisible(this.#internalGrid);\n\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current column order from the grid.\n * @returns Array of field names in display order\n */\n getColumnOrder(): string[] {\n return this.grid.getColumnOrder();\n }\n\n /**\n * Move a column to a new position.\n * @param field - The field name of the column to move\n * @param toIndex - The target index\n */\n moveColumn(field: string, toIndex: number): void {\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(field);\n if (fromIndex === -1) return;\n\n const newOrder = moveColumn(currentOrder, fromIndex, toIndex);\n\n // Emit cancelable event first - only update if not cancelled\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field,\n fromIndex,\n toIndex,\n columnOrder: newOrder,\n });\n if (!cancelled) {\n // Update with view transition\n this.updateColumnOrder(newOrder);\n }\n }\n\n /**\n * Set a specific column order.\n * @param order - Array of field names in desired order\n */\n setColumnOrder(order: string[]): void {\n this.updateColumnOrder(order);\n }\n\n /**\n * Reset column order to the original configuration order.\n */\n resetColumnOrder(): void {\n // Use the captured initial order (including order-attribute reordering),\n // or fall back to current column order if not yet captured\n const originalOrder = this.originalColumnOrder ?? this.columns.map((c) => c.field);\n this.updateColumnOrder(originalOrder);\n }\n // #endregion\n\n // #region View Transition\n\n /**\n * Capture header cell positions before reorder.\n */\n private captureHeaderPositions(): Map<string, number> {\n const positions = new Map<string, number>();\n this.gridElement?.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (field) positions.set(field, cell.getBoundingClientRect().left);\n });\n return positions;\n }\n\n /**\n * Apply FLIP animation for column reorder.\n * Uses CSS transitions - JS sets initial transform and toggles class.\n * @param oldPositions - Header positions captured before DOM change\n */\n private animateFLIP(oldPositions: Map<string, number>): void {\n const gridEl = this.gridElement;\n if (!gridEl || oldPositions.size === 0) return;\n\n // Compute deltas from header cells (one per column, stable reference).\n // All cells in the same column share the same horizontal offset.\n const deltas = new Map<string, number>();\n gridEl.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n const oldLeft = oldPositions.get(field);\n if (oldLeft === undefined) return;\n const deltaX = oldLeft - cell.getBoundingClientRect().left;\n if (Math.abs(deltaX) > 1) deltas.set(field, deltaX);\n });\n\n if (deltas.size === 0) return;\n\n // Apply transforms to ALL cells (headers + body).\n // After forceLayout(), body cells are fully rebuilt in new DOM order\n // with correct data-field attributes, so header-derived deltas apply correctly.\n const cells: HTMLElement[] = [];\n gridEl.querySelectorAll('.cell[data-field]').forEach((cell) => {\n const deltaX = deltas.get(cell.getAttribute('data-field') ?? '');\n if (deltaX !== undefined) {\n const el = cell as HTMLElement;\n el.style.transform = `translateX(${deltaX}px)`;\n cells.push(el);\n }\n });\n\n if (cells.length === 0) return;\n\n // Force reflow then animate to final position via CSS transition\n void gridEl.offsetHeight;\n\n const duration = this.animationDuration;\n\n requestAnimationFrame(() => {\n cells.forEach((el) => {\n el.classList.add('flip-animating');\n el.style.transform = '';\n });\n\n // Cleanup after animation\n setTimeout(() => {\n cells.forEach((el) => {\n el.style.transform = '';\n el.classList.remove('flip-animating');\n });\n }, duration + 50);\n });\n }\n\n /**\n * Apply crossfade animation for moved columns.\n * Uses CSS keyframes - JS just toggles classes.\n */\n private animateFade(applyChange: () => void): void {\n const gridEl = this.gridElement;\n if (!gridEl) {\n applyChange();\n return;\n }\n\n // Capture old positions to detect which columns moved\n const oldPositions = this.captureHeaderPositions();\n\n // Apply the change first\n applyChange();\n\n // Find which columns changed position\n const movedFields = new Set<string>();\n gridEl.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n const oldLeft = oldPositions.get(field);\n if (oldLeft === undefined) return;\n const newLeft = cell.getBoundingClientRect().left;\n if (Math.abs(oldLeft - newLeft) > 1) {\n movedFields.add(field);\n }\n });\n\n if (movedFields.size === 0) return;\n\n // Add animation class to moved columns (headers + body cells)\n const cells: HTMLElement[] = [];\n gridEl.querySelectorAll('.cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (field && movedFields.has(field)) {\n const el = cell as HTMLElement;\n el.classList.add('fade-animating');\n cells.push(el);\n }\n });\n\n if (cells.length === 0) return;\n\n // Remove class after animation completes\n const duration = this.animationDuration;\n setTimeout(() => {\n cells.forEach((el) => el.classList.remove('fade-animating'));\n }, duration + 50);\n }\n\n /**\n * Update column order with configured animation.\n */\n private updateColumnOrder(newOrder: string[]): void {\n const animation = this.animationType;\n\n if (animation === 'flip' && this.gridElement) {\n const oldPositions = this.captureHeaderPositions();\n this.grid.setColumnOrder(newOrder);\n // setColumnOrder now requests COLUMNS phase (not just VIRTUALIZATION), so\n // processColumns() runs and bumps __rowRenderEpoch. forceLayout() waits\n // for the pending phase to flush, ensuring body cells are fully rebuilt\n // with correct data-field attributes before the FLIP animation runs.\n if (typeof this.grid.forceLayout === 'function') {\n this.grid.forceLayout().then(() => {\n this.animateFLIP(oldPositions);\n });\n } else {\n // Fallback: animate headers only (body cells may not be rebuilt yet)\n requestAnimationFrame(() => {\n this.animateFLIP(oldPositions);\n });\n }\n } else if (animation === 'fade') {\n this.animateFade(() => this.grid.setColumnOrder(newOrder));\n } else {\n this.grid.setColumnOrder(newOrder);\n }\n\n this.grid.requestStateChange?.();\n }\n // #endregion\n}\n"],"mappings":"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"}