@toolbox-web/grid 0.4.2 → 0.6.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 (123) hide show
  1. package/README.md +2 -3
  2. package/all.js +1063 -1024
  3. package/all.js.map +1 -1
  4. package/index.js +1078 -912
  5. package/index.js.map +1 -1
  6. package/lib/core/grid.d.ts +28 -0
  7. package/lib/core/grid.d.ts.map +1 -1
  8. package/lib/core/internal/dom-builder.d.ts +2 -0
  9. package/lib/core/internal/dom-builder.d.ts.map +1 -1
  10. package/lib/core/internal/event-delegation.d.ts +21 -0
  11. package/lib/core/internal/event-delegation.d.ts.map +1 -1
  12. package/lib/core/internal/header.d.ts.map +1 -1
  13. package/lib/core/internal/resize.d.ts.map +1 -1
  14. package/lib/core/internal/rows.d.ts +1 -1
  15. package/lib/core/internal/rows.d.ts.map +1 -1
  16. package/lib/core/internal/shell.d.ts +19 -13
  17. package/lib/core/internal/shell.d.ts.map +1 -1
  18. package/lib/core/plugin/base-plugin.d.ts +13 -2
  19. package/lib/core/plugin/base-plugin.d.ts.map +1 -1
  20. package/lib/core/plugin/expander-column.d.ts.map +1 -1
  21. package/lib/core/plugin/plugin-manager.d.ts +6 -2
  22. package/lib/core/plugin/plugin-manager.d.ts.map +1 -1
  23. package/lib/core/types.d.ts +41 -3
  24. package/lib/core/types.d.ts.map +1 -1
  25. package/lib/plugins/clipboard/index.js +22 -11
  26. package/lib/plugins/clipboard/index.js.map +1 -1
  27. package/lib/plugins/column-virtualization/index.js +59 -48
  28. package/lib/plugins/column-virtualization/index.js.map +1 -1
  29. package/lib/plugins/context-menu/index.js +71 -60
  30. package/lib/plugins/context-menu/index.js.map +1 -1
  31. package/lib/plugins/editing/EditingPlugin.d.ts +1 -0
  32. package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
  33. package/lib/plugins/editing/index.js +93 -80
  34. package/lib/plugins/editing/index.js.map +1 -1
  35. package/lib/plugins/export/index.js +29 -18
  36. package/lib/plugins/export/index.js.map +1 -1
  37. package/lib/plugins/filtering/FilteringPlugin.d.ts +9 -1
  38. package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
  39. package/lib/plugins/filtering/index.js +199 -165
  40. package/lib/plugins/filtering/index.js.map +1 -1
  41. package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts +1 -0
  42. package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts.map +1 -1
  43. package/lib/plugins/grouping-columns/index.js +79 -49
  44. package/lib/plugins/grouping-columns/index.js.map +1 -1
  45. package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts.map +1 -1
  46. package/lib/plugins/grouping-rows/index.js +98 -87
  47. package/lib/plugins/grouping-rows/index.js.map +1 -1
  48. package/lib/plugins/master-detail/index.js +70 -57
  49. package/lib/plugins/master-detail/index.js.map +1 -1
  50. package/lib/plugins/multi-sort/index.js +48 -37
  51. package/lib/plugins/multi-sort/index.js.map +1 -1
  52. package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts.map +1 -1
  53. package/lib/plugins/pinned-columns/index.js +71 -66
  54. package/lib/plugins/pinned-columns/index.js.map +1 -1
  55. package/lib/plugins/pinned-columns/pinned-columns.d.ts +2 -2
  56. package/lib/plugins/pinned-columns/pinned-columns.d.ts.map +1 -1
  57. package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts.map +1 -1
  58. package/lib/plugins/pinned-rows/index.js +63 -52
  59. package/lib/plugins/pinned-rows/index.js.map +1 -1
  60. package/lib/plugins/pivot/PivotPlugin.d.ts.map +1 -1
  61. package/lib/plugins/pivot/index.js +310 -299
  62. package/lib/plugins/pivot/index.js.map +1 -1
  63. package/lib/plugins/reorder/ReorderPlugin.d.ts.map +1 -1
  64. package/lib/plugins/reorder/index.d.ts +1 -1
  65. package/lib/plugins/reorder/index.d.ts.map +1 -1
  66. package/lib/plugins/reorder/index.js +79 -68
  67. package/lib/plugins/reorder/index.js.map +1 -1
  68. package/lib/plugins/selection/SelectionPlugin.d.ts.map +1 -1
  69. package/lib/plugins/selection/index.js +115 -105
  70. package/lib/plugins/selection/index.js.map +1 -1
  71. package/lib/plugins/server-side/index.js +15 -4
  72. package/lib/plugins/server-side/index.js.map +1 -1
  73. package/lib/plugins/tree/TreePlugin.d.ts.map +1 -1
  74. package/lib/plugins/tree/index.js +41 -30
  75. package/lib/plugins/tree/index.js.map +1 -1
  76. package/lib/plugins/undo-redo/index.js +29 -18
  77. package/lib/plugins/undo-redo/index.js.map +1 -1
  78. package/lib/plugins/visibility/VisibilityPlugin.d.ts.map +1 -1
  79. package/lib/plugins/visibility/index.js +59 -47
  80. package/lib/plugins/visibility/index.js.map +1 -1
  81. package/package.json +6 -6
  82. package/public.d.ts +42 -0
  83. package/public.d.ts.map +1 -1
  84. package/themes/dg-theme-bootstrap.css +55 -53
  85. package/themes/dg-theme-contrast.css +42 -40
  86. package/themes/dg-theme-large.css +38 -37
  87. package/themes/dg-theme-material.css +54 -52
  88. package/themes/dg-theme-standard.css +19 -17
  89. package/themes/dg-theme-vibrant.css +16 -14
  90. package/umd/grid.all.umd.js +23 -22
  91. package/umd/grid.all.umd.js.map +1 -1
  92. package/umd/grid.umd.js +15 -14
  93. package/umd/grid.umd.js.map +1 -1
  94. package/umd/plugins/column-virtualization.umd.js +1 -1
  95. package/umd/plugins/column-virtualization.umd.js.map +1 -1
  96. package/umd/plugins/context-menu.umd.js +1 -1
  97. package/umd/plugins/context-menu.umd.js.map +1 -1
  98. package/umd/plugins/editing.umd.js +1 -1
  99. package/umd/plugins/editing.umd.js.map +1 -1
  100. package/umd/plugins/filtering.umd.js +1 -1
  101. package/umd/plugins/filtering.umd.js.map +1 -1
  102. package/umd/plugins/grouping-columns.umd.js +1 -1
  103. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  104. package/umd/plugins/grouping-rows.umd.js +1 -1
  105. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  106. package/umd/plugins/master-detail.umd.js +1 -1
  107. package/umd/plugins/master-detail.umd.js.map +1 -1
  108. package/umd/plugins/multi-sort.umd.js +1 -1
  109. package/umd/plugins/multi-sort.umd.js.map +1 -1
  110. package/umd/plugins/pinned-columns.umd.js +1 -1
  111. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  112. package/umd/plugins/pinned-rows.umd.js +1 -1
  113. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  114. package/umd/plugins/pivot.umd.js +1 -1
  115. package/umd/plugins/pivot.umd.js.map +1 -1
  116. package/umd/plugins/reorder.umd.js +1 -1
  117. package/umd/plugins/reorder.umd.js.map +1 -1
  118. package/umd/plugins/selection.umd.js +1 -1
  119. package/umd/plugins/selection.umd.js.map +1 -1
  120. package/umd/plugins/tree.umd.js +1 -1
  121. package/umd/plugins/tree.umd.js.map +1 -1
  122. package/umd/plugins/visibility.umd.js +1 -1
  123. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"selection.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/selection/row-selection.ts","../../../../../libs/grid/src/lib/plugins/selection/range-selection.ts","../../../../../libs/grid/src/lib/plugins/selection/SelectionPlugin.ts"],"sourcesContent":["/**\n * Row Selection Core Logic\n *\n * Pure functions for row selection operations.\n */\n\nimport type { SelectionMode, SelectionState } from './types';\n\n/** Click modifier keys state */\nexport interface ClickModifiers {\n shiftKey: boolean;\n ctrlKey: boolean;\n metaKey: boolean;\n}\n\n/** Result from handling a row click for selection */\nexport interface RowClickResult {\n selected: Set<number>;\n lastSelected: number;\n anchor: number | null;\n}\n\n/**\n * Handle a row click event for selection purposes.\n *\n * In single mode: always selects the clicked row, clearing others.\n * In multiple mode:\n * - Plain click: clears selection and selects clicked row\n * - Ctrl/Cmd+click: toggles the clicked row\n * - Shift+click: range select from anchor to clicked row\n *\n * @param state - Current selection state\n * @param rowIndex - The clicked row index\n * @param mode - Selection mode ('single' or 'multiple')\n * @param modifiers - Keyboard modifiers held during click\n * @returns Updated selection state values\n */\nexport function handleRowClick(\n state: SelectionState,\n rowIndex: number,\n mode: SelectionMode,\n modifiers: ClickModifiers\n): RowClickResult {\n const newSelected = new Set(state.selected);\n let anchor = state.anchor;\n\n if ((mode as string) === 'single') {\n newSelected.clear();\n newSelected.add(rowIndex);\n anchor = rowIndex;\n } else if ((mode as string) === 'multiple') {\n const ctrlOrMeta = modifiers.ctrlKey || modifiers.metaKey;\n\n if (modifiers.shiftKey && state.anchor !== null) {\n // Range selection from anchor\n const start = Math.min(state.anchor, rowIndex);\n const end = Math.max(state.anchor, rowIndex);\n for (let i = start; i <= end; i++) {\n newSelected.add(i);\n }\n } else if (ctrlOrMeta) {\n // Toggle selection\n if (newSelected.has(rowIndex)) {\n newSelected.delete(rowIndex);\n } else {\n newSelected.add(rowIndex);\n }\n anchor = rowIndex;\n } else {\n // Clear and select single\n newSelected.clear();\n newSelected.add(rowIndex);\n anchor = rowIndex;\n }\n }\n\n return { selected: newSelected, lastSelected: rowIndex, anchor };\n}\n\n/**\n * Create a set containing all row indices (for select all).\n *\n * @param rowCount - Total number of rows\n * @returns Set containing indices 0 to rowCount-1\n */\nexport function selectAll(rowCount: number): Set<number> {\n const selected = new Set<number>();\n for (let i = 0; i < rowCount; i++) {\n selected.add(i);\n }\n return selected;\n}\n\n/**\n * Compute the difference between two selection states.\n *\n * @param oldSelected - Previous selection set\n * @param newSelected - New selection set\n * @returns Object with added and removed row indices\n */\nexport function computeSelectionDiff(\n oldSelected: Set<number>,\n newSelected: Set<number>\n): { added: number[]; removed: number[] } {\n const added: number[] = [];\n const removed: number[] = [];\n\n for (const idx of newSelected) {\n if (!oldSelected.has(idx)) added.push(idx);\n }\n for (const idx of oldSelected) {\n if (!newSelected.has(idx)) removed.push(idx);\n }\n\n return { added, removed };\n}\n","/**\n * Cell Range Selection Core Logic\n *\n * Pure functions for cell range selection operations.\n */\n\nimport type { InternalCellRange, CellRange } from './types';\n\n/**\n * Normalize a range so startRow/startCol are always <= endRow/endCol.\n * This handles cases where user drags from bottom-right to top-left.\n *\n * @param range - The range to normalize\n * @returns Normalized range with start <= end for both dimensions\n */\nexport function normalizeRange(range: InternalCellRange): InternalCellRange {\n return {\n startRow: Math.min(range.startRow, range.endRow),\n startCol: Math.min(range.startCol, range.endCol),\n endRow: Math.max(range.startRow, range.endRow),\n endCol: Math.max(range.startCol, range.endCol),\n };\n}\n\n/**\n * Convert an internal range to the public event format.\n *\n * @param range - The internal range to convert\n * @returns Public CellRange format with from/to coordinates\n */\nexport function toPublicRange(range: InternalCellRange): CellRange {\n const normalized = normalizeRange(range);\n return {\n from: { row: normalized.startRow, col: normalized.startCol },\n to: { row: normalized.endRow, col: normalized.endCol },\n };\n}\n\n/**\n * Convert multiple internal ranges to public format.\n *\n * @param ranges - Array of internal ranges\n * @returns Array of public CellRange format\n */\nexport function toPublicRanges(ranges: InternalCellRange[]): CellRange[] {\n return ranges.map(toPublicRange);\n}\n\n/**\n * Check if a cell is within a specific range.\n *\n * @param row - The row index to check\n * @param col - The column index to check\n * @param range - The range to check against\n * @returns True if the cell is within the range\n */\nexport function isCellInRange(row: number, col: number, range: InternalCellRange): boolean {\n const normalized = normalizeRange(range);\n return (\n row >= normalized.startRow && row <= normalized.endRow && col >= normalized.startCol && col <= normalized.endCol\n );\n}\n\n/**\n * Check if a cell is within any of the provided ranges.\n *\n * @param row - The row index to check\n * @param col - The column index to check\n * @param ranges - Array of ranges to check against\n * @returns True if the cell is within any range\n */\nexport function isCellInAnyRange(row: number, col: number, ranges: InternalCellRange[]): boolean {\n return ranges.some((range) => isCellInRange(row, col, range));\n}\n\n/**\n * Get all cells within a range as an array of {row, col} objects.\n *\n * @param range - The range to enumerate\n * @returns Array of all cell coordinates in the range\n */\nexport function getCellsInRange(range: InternalCellRange): Array<{ row: number; col: number }> {\n const cells: Array<{ row: number; col: number }> = [];\n const normalized = normalizeRange(range);\n\n for (let row = normalized.startRow; row <= normalized.endRow; row++) {\n for (let col = normalized.startCol; col <= normalized.endCol; col++) {\n cells.push({ row, col });\n }\n }\n\n return cells;\n}\n\n/**\n * Get all unique cells across multiple ranges.\n * Deduplicates cells that appear in overlapping ranges.\n *\n * @param ranges - Array of ranges to enumerate\n * @returns Array of unique cell coordinates\n */\nexport function getAllCellsInRanges(ranges: InternalCellRange[]): Array<{ row: number; col: number }> {\n const cellMap = new Map<string, { row: number; col: number }>();\n\n for (const range of ranges) {\n for (const cell of getCellsInRange(range)) {\n cellMap.set(`${cell.row},${cell.col}`, cell);\n }\n }\n\n return [...cellMap.values()];\n}\n\n/**\n * Merge overlapping or adjacent ranges into fewer ranges.\n * Simple implementation - returns ranges as-is for now.\n * More complex merging logic can be added later for optimization.\n *\n * @param ranges - Array of ranges to merge\n * @returns Merged array of ranges\n */\nexport function mergeRanges(ranges: InternalCellRange[]): InternalCellRange[] {\n // Simple implementation - more complex merging can be added later\n return ranges;\n}\n\n/**\n * Create a range from an anchor cell to a current cell position.\n * The range is not normalized - it preserves the direction of selection.\n *\n * @param anchor - The anchor cell (where selection started)\n * @param current - The current cell (where selection ends)\n * @returns An InternalCellRange from anchor to current\n */\nexport function createRangeFromAnchor(\n anchor: { row: number; col: number },\n current: { row: number; col: number }\n): InternalCellRange {\n return {\n startRow: anchor.row,\n startCol: anchor.col,\n endRow: current.row,\n endCol: current.col,\n };\n}\n\n/**\n * Calculate the number of cells in a range.\n *\n * @param range - The range to measure\n * @returns Total number of cells in the range\n */\nexport function getRangeCellCount(range: InternalCellRange): number {\n const normalized = normalizeRange(range);\n const rowCount = normalized.endRow - normalized.startRow + 1;\n const colCount = normalized.endCol - normalized.startCol + 1;\n return rowCount * colCount;\n}\n\n/**\n * Check if two ranges are equal (same boundaries).\n *\n * @param a - First range\n * @param b - Second range\n * @returns True if ranges have same boundaries after normalization\n */\nexport function rangesEqual(a: InternalCellRange, b: InternalCellRange): boolean {\n const normA = normalizeRange(a);\n const normB = normalizeRange(b);\n return (\n normA.startRow === normB.startRow &&\n normA.startCol === normB.startCol &&\n normA.endRow === normB.endRow &&\n normA.endCol === normB.endCol\n );\n}\n\n/**\n * Check if a range is a single cell (1x1).\n *\n * @param range - The range to check\n * @returns True if the range is exactly one cell\n */\nexport function isSingleCell(range: InternalCellRange): boolean {\n const normalized = normalizeRange(range);\n return normalized.startRow === normalized.endRow && normalized.startCol === normalized.endCol;\n}\n","/**\n * Selection Plugin (Class-based)\n *\n * Provides selection functionality for tbw-grid.\n * Supports three modes:\n * - 'cell': Single cell selection (default). No border, just focus highlight.\n * - 'row': Row selection. Clicking a cell selects the entire row.\n * - 'range': Range selection. Shift+click or drag to select rectangular cell ranges.\n */\n\nimport { clearCellFocus, getRowIndexFromCell } from '../../core/internal/utils';\nimport { BaseGridPlugin, CellClickEvent, CellMouseEvent } from '../../core/plugin/base-plugin';\nimport { isUtilityColumn } from '../../core/plugin/expander-column';\nimport {\n createRangeFromAnchor,\n getAllCellsInRanges,\n isCellInAnyRange,\n normalizeRange,\n toPublicRanges,\n} from './range-selection';\nimport styles from './selection.css?inline';\nimport type {\n CellRange,\n InternalCellRange,\n SelectionChangeDetail,\n SelectionConfig,\n SelectionMode,\n SelectionResult,\n} from './types';\n\n/**\n * Build the selection change event detail for the current state.\n */\nfunction buildSelectionEvent(\n mode: SelectionMode,\n state: {\n selectedCell: { row: number; col: number } | null;\n selected: Set<number>;\n ranges: InternalCellRange[];\n },\n colCount: number,\n): SelectionChangeDetail {\n if (mode === 'cell' && state.selectedCell) {\n return {\n mode,\n ranges: [\n {\n from: { row: state.selectedCell.row, col: state.selectedCell.col },\n to: { row: state.selectedCell.row, col: state.selectedCell.col },\n },\n ],\n };\n }\n\n if (mode === 'row' && state.selected.size > 0) {\n const ranges = [...state.selected].map((rowIndex) => ({\n from: { row: rowIndex, col: 0 },\n to: { row: rowIndex, col: colCount - 1 },\n }));\n return { mode, ranges };\n }\n\n if (mode === 'range' && state.ranges.length > 0) {\n return { mode, ranges: toPublicRanges(state.ranges) };\n }\n\n return { mode, ranges: [] };\n}\n\n/**\n * Selection Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new SelectionPlugin({ mode: 'range' })\n * ```\n */\nexport class SelectionPlugin extends BaseGridPlugin<SelectionConfig> {\n readonly name = 'selection';\n override readonly styles = styles;\n\n protected override get defaultConfig(): Partial<SelectionConfig> {\n return {\n mode: 'cell',\n };\n }\n\n // #region Internal State\n /** Row selection state (row mode) */\n private selected = new Set<number>();\n private lastSelected: number | null = null;\n private anchor: number | null = null;\n\n /** Range selection state (range mode) */\n private ranges: InternalCellRange[] = [];\n private activeRange: InternalCellRange | null = null;\n private cellAnchor: { row: number; col: number } | null = null;\n private isDragging = false;\n\n /** Pending keyboard navigation update (processed in afterRender) */\n private pendingKeyboardUpdate: { shiftKey: boolean } | null = null;\n\n /** Cell selection state (cell mode) */\n private selectedCell: { row: number; col: number } | null = null;\n\n // #endregion\n\n // #region Lifecycle\n\n override detach(): void {\n this.selected.clear();\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.isDragging = false;\n this.selectedCell = null;\n this.pendingKeyboardUpdate = null;\n }\n\n // #endregion\n\n // #region Event Handlers\n\n override onCellClick(event: CellClickEvent): boolean {\n const { rowIndex, colIndex, originalEvent } = event;\n const { mode } = this.config;\n\n // Check if this is a utility column (expander columns, etc.)\n const column = this.columns[colIndex];\n const isUtility = column && isUtilityColumn(column);\n\n // CELL MODE: Single cell selection - skip utility columns\n if (mode === 'cell') {\n if (isUtility) {\n return false; // Allow event to propagate, but don't select utility cells\n }\n this.selectedCell = { row: rowIndex, col: colIndex };\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return false;\n }\n\n // ROW MODE: Select entire row - utility column clicks still select the row\n if (mode === 'row') {\n this.selected.clear();\n this.selected.add(rowIndex);\n this.lastSelected = rowIndex;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return false;\n }\n\n // RANGE MODE: Shift+click extends selection, click starts new\n if (mode === 'range') {\n // Skip utility columns in range mode - don't start selection from them\n if (isUtility) {\n return false;\n }\n\n const shiftKey = originalEvent.shiftKey;\n const ctrlKey = originalEvent.ctrlKey || originalEvent.metaKey;\n\n if (shiftKey && this.cellAnchor) {\n // Extend selection from anchor\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: rowIndex, col: colIndex });\n\n if (ctrlKey) {\n if (this.ranges.length > 0) {\n this.ranges[this.ranges.length - 1] = newRange;\n } else {\n this.ranges.push(newRange);\n }\n } else {\n this.ranges = [newRange];\n }\n this.activeRange = newRange;\n } else if (ctrlKey) {\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges.push(newRange);\n this.activeRange = newRange;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n } else {\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges = [newRange];\n this.activeRange = newRange;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n }\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n\n this.requestAfterRender();\n return false;\n }\n\n return false;\n }\n\n override onKeyDown(event: KeyboardEvent): boolean {\n const { mode } = this.config;\n const navKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End', 'PageUp', 'PageDown'];\n const isNavKey = navKeys.includes(event.key);\n\n // Escape clears selection in all modes\n if (event.key === 'Escape') {\n if (mode === 'cell') {\n this.selectedCell = null;\n } else if (mode === 'row') {\n this.selected.clear();\n this.anchor = null;\n } else if (mode === 'range') {\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n }\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n // CELL MODE: Selection follows focus\n if (mode === 'cell' && isNavKey) {\n // Use queueMicrotask so grid's handler runs first and updates focusRow/focusCol\n queueMicrotask(() => {\n this.selectedCell = { row: this.grid._focusRow, col: this.grid._focusCol };\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n });\n return false; // Let grid handle navigation\n }\n\n // ROW MODE: Only Up/Down arrows move row selection\n if (mode === 'row' && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {\n // Let grid move focus first, then sync row selection\n queueMicrotask(() => {\n this.selected.clear();\n this.selected.add(this.grid._focusRow);\n this.lastSelected = this.grid._focusRow;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n });\n return false; // Let grid handle navigation\n }\n\n // RANGE MODE: Shift+Arrow extends, plain Arrow resets\n // Tab key always navigates without extending (even with Shift)\n if (mode === 'range' && isNavKey) {\n // Tab should not extend selection - it just navigates to the next/previous cell\n const isTabKey = event.key === 'Tab';\n const shouldExtend = event.shiftKey && !isTabKey;\n\n // Capture anchor BEFORE grid moves focus (synchronous)\n // This ensures the anchor is the starting point, not the destination\n if (shouldExtend && !this.cellAnchor) {\n this.cellAnchor = { row: this.grid._focusRow, col: this.grid._focusCol };\n }\n\n // Mark pending update - will be processed in afterRender when grid updates focus\n this.pendingKeyboardUpdate = { shiftKey: shouldExtend };\n\n // Schedule afterRender to run after grid's keyboard handler completes\n // Grid's refreshVirtualWindow(false) skips afterRender for performance,\n // so we explicitly request it to process pendingKeyboardUpdate\n queueMicrotask(() => this.requestAfterRender());\n\n return false; // Let grid handle navigation\n }\n\n // Ctrl+A selects all in range mode\n if (mode === 'range' && event.key === 'a' && (event.ctrlKey || event.metaKey)) {\n const rowCount = this.rows.length;\n const colCount = this.columns.length;\n if (rowCount > 0 && colCount > 0) {\n const allRange: InternalCellRange = {\n startRow: 0,\n startCol: 0,\n endRow: rowCount - 1,\n endCol: colCount - 1,\n };\n this.ranges = [allRange];\n this.activeRange = allRange;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n }\n\n return false;\n }\n\n override onCellMouseDown(event: CellMouseEvent): boolean | void {\n if (this.config.mode !== 'range') return;\n if (event.rowIndex === undefined || event.colIndex === undefined) return;\n if (event.rowIndex < 0) return; // Header\n\n // Skip utility columns (expander columns, etc.)\n const column = this.columns[event.colIndex];\n if (column && isUtilityColumn(column)) {\n return; // Don't start selection on utility columns\n }\n\n // Let onCellClick handle shift+click for range extension\n if (event.originalEvent.shiftKey && this.cellAnchor) {\n return;\n }\n\n // Start drag selection\n this.isDragging = true;\n const rowIndex = event.rowIndex;\n const colIndex = event.colIndex;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n\n const ctrlKey = event.originalEvent.ctrlKey || event.originalEvent.metaKey;\n if (!ctrlKey) {\n this.ranges = [];\n }\n\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges.push(newRange);\n this.activeRange = newRange;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n override onCellMouseMove(event: CellMouseEvent): boolean | void {\n if (this.config.mode !== 'range') return;\n if (!this.isDragging || !this.cellAnchor) return;\n if (event.rowIndex === undefined || event.colIndex === undefined) return;\n if (event.rowIndex < 0) return;\n\n // When dragging, clamp to first data column (skip utility columns)\n let targetCol = event.colIndex;\n const column = this.columns[targetCol];\n if (column && isUtilityColumn(column)) {\n // Find the first non-utility column\n const firstDataCol = this.columns.findIndex((col) => !isUtilityColumn(col));\n if (firstDataCol >= 0) {\n targetCol = firstDataCol;\n }\n }\n\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: event.rowIndex, col: targetCol });\n\n if (this.ranges.length > 0) {\n this.ranges[this.ranges.length - 1] = newRange;\n } else {\n this.ranges.push(newRange);\n }\n this.activeRange = newRange;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n override onCellMouseUp(_event: CellMouseEvent): boolean | void {\n if (this.config.mode !== 'range') return;\n if (this.isDragging) {\n this.isDragging = false;\n return true;\n }\n }\n\n /**\n * Apply selection classes to visible cells/rows.\n * Shared by afterRender and onScrollRender.\n */\n #applySelectionClasses(): void {\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n const { mode } = this.config;\n\n // Clear all selection classes first\n const allCells = shadowRoot.querySelectorAll('.cell');\n allCells.forEach((cell) => {\n cell.classList.remove('selected', 'top', 'bottom', 'first', 'last');\n });\n\n const allRows = shadowRoot.querySelectorAll('.data-grid-row');\n allRows.forEach((row) => {\n row.classList.remove('selected', 'row-focus');\n });\n\n // ROW MODE: Add row-focus class to selected rows, disable cell-focus\n if (mode === 'row') {\n // In row mode, disable ALL cell-focus styling - row selection takes precedence\n clearCellFocus(shadowRoot);\n\n allRows.forEach((row) => {\n const firstCell = row.querySelector('.cell[data-row]');\n const rowIndex = getRowIndexFromCell(firstCell);\n if (rowIndex >= 0 && this.selected.has(rowIndex)) {\n row.classList.add('selected', 'row-focus');\n }\n });\n }\n\n // RANGE MODE: Add selected and edge classes to cells\n if (mode === 'range' && this.ranges.length > 0) {\n // Clear all cell-focus first - selection plugin manages focus styling in range mode\n clearCellFocus(shadowRoot);\n\n const normalized = this.activeRange ? normalizeRange(this.activeRange) : null;\n\n // Find the first non-utility column index for proper .first class application\n const firstDataColIndex = this.columns.findIndex((col) => !isUtilityColumn(col));\n const lastDataColIndex = this.columns.length - 1; // Last column is always data\n\n const cells = shadowRoot.querySelectorAll('.cell[data-row][data-col]');\n cells.forEach((cell) => {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n if (rowIndex >= 0 && colIndex >= 0) {\n // Skip utility columns entirely - don't add any selection classes\n const column = this.columns[colIndex];\n if (column && isUtilityColumn(column)) {\n return;\n }\n\n const inRange = isCellInAnyRange(rowIndex, colIndex, this.ranges);\n\n if (inRange) {\n cell.classList.add('selected');\n\n if (normalized) {\n if (rowIndex === normalized.startRow) cell.classList.add('top');\n if (rowIndex === normalized.endRow) cell.classList.add('bottom');\n // Apply .first to the first data column in range (skip utility columns)\n const effectiveStartCol = Math.max(normalized.startCol, firstDataColIndex);\n if (colIndex === effectiveStartCol) cell.classList.add('first');\n if (colIndex === normalized.endCol) cell.classList.add('last');\n }\n }\n }\n });\n }\n\n // CELL MODE: Remove cell-focus when selection plugin handles focus\n if (mode === 'cell' && this.selectedCell) {\n // Remove all cell-focus - the selection plugin manages focus styling\n clearCellFocus(shadowRoot);\n }\n }\n\n override afterRender(): void {\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n const container = shadowRoot.children[0];\n const { mode } = this.config;\n\n // Process pending keyboard navigation update (range mode)\n // This runs AFTER the grid has updated focusRow/focusCol\n if (this.pendingKeyboardUpdate && mode === 'range') {\n const { shiftKey } = this.pendingKeyboardUpdate;\n this.pendingKeyboardUpdate = null;\n\n const currentRow = this.grid._focusRow;\n const currentCol = this.grid._focusCol;\n\n if (shiftKey && this.cellAnchor) {\n // Extend selection from anchor to current focus\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: currentRow, col: currentCol });\n this.ranges = [newRange];\n this.activeRange = newRange;\n } else if (!shiftKey) {\n // Without shift, clear selection (cell-focus will show instead)\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = { row: currentRow, col: currentCol }; // Reset anchor to current position\n }\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n }\n\n // Set data attribute on host for CSS variable scoping\n (this.grid as unknown as Element).setAttribute('data-selection-mode', mode);\n\n // Toggle .selecting class during drag to prevent text selection\n if (container) {\n container.classList.toggle('selecting', this.isDragging);\n }\n\n this.#applySelectionClasses();\n }\n\n /**\n * Called after scroll-triggered row rendering.\n * Reapplies selection classes to recycled DOM elements.\n */\n override onScrollRender(): void {\n this.#applySelectionClasses();\n }\n\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current selection as a unified result.\n * Works for all selection modes and always returns ranges.\n *\n * @example\n * ```ts\n * const selection = plugin.getSelection();\n * if (selection.ranges.length > 0) {\n * const { from, to } = selection.ranges[0];\n * // For cell mode: from === to (single cell)\n * // For row mode: from.col = 0, to.col = lastCol (full row)\n * // For range mode: rectangular selection\n * }\n * ```\n */\n getSelection(): SelectionResult {\n return {\n mode: this.config.mode,\n ranges: this.#buildEvent().ranges,\n anchor: this.cellAnchor,\n };\n }\n\n /**\n * Get the selected cell (cell mode only).\n * @deprecated Use `getSelection()` instead for a unified API across all modes.\n */\n getSelectedCell(): { row: number; col: number } | null {\n const { mode, ranges } = this.getSelection();\n if (mode === 'cell' && ranges.length > 0) {\n return ranges[0].from;\n }\n return null;\n }\n\n /**\n * Get all selected row indices (row mode).\n * @deprecated Use `getSelection().ranges` instead - each range represents a full row.\n */\n getSelectedRows(): number[] {\n const { mode, ranges } = this.getSelection();\n if (mode === 'row') {\n return ranges.map((r) => r.from.row);\n }\n return [];\n }\n\n /**\n * Get all selected cell ranges in public format.\n * @deprecated Use `getSelection().ranges` instead.\n */\n getRanges(): CellRange[] {\n return this.getSelection().ranges;\n }\n\n /**\n * Get all selected cells across all ranges.\n */\n getSelectedCells(): Array<{ row: number; col: number }> {\n return getAllCellsInRanges(this.ranges);\n }\n\n /**\n * Check if a specific cell is in range selection.\n */\n isCellSelected(row: number, col: number): boolean {\n return isCellInAnyRange(row, col, this.ranges);\n }\n\n /**\n * Clear all selection.\n */\n clearSelection(): void {\n this.selectedCell = null;\n this.selected.clear();\n this.anchor = null;\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.emit<SelectionChangeDetail>('selection-change', { mode: this.config.mode, ranges: [] });\n this.requestAfterRender();\n }\n\n /**\n * Set selected ranges programmatically.\n */\n setRanges(ranges: CellRange[]): void {\n this.ranges = ranges.map((r) => ({\n startRow: r.from.row,\n startCol: r.from.col,\n endRow: r.to.row,\n endCol: r.to.col,\n }));\n this.activeRange = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null;\n this.emit<SelectionChangeDetail>('selection-change', {\n mode: this.config.mode,\n ranges: toPublicRanges(this.ranges),\n });\n this.requestAfterRender();\n }\n\n // #endregion\n\n // #region Private Helpers\n\n #buildEvent(): SelectionChangeDetail {\n return buildSelectionEvent(\n this.config.mode,\n {\n selectedCell: this.selectedCell,\n selected: this.selected,\n ranges: this.ranges,\n },\n this.columns.length,\n );\n }\n\n // #endregion\n}\n"],"names":["handleRowClick","state","rowIndex","mode","modifiers","newSelected","anchor","ctrlOrMeta","start","end","i","selectAll","rowCount","selected","computeSelectionDiff","oldSelected","added","removed","idx","normalizeRange","range","toPublicRange","normalized","toPublicRanges","ranges","isCellInRange","row","col","isCellInAnyRange","getCellsInRange","cells","getAllCellsInRanges","cellMap","cell","createRangeFromAnchor","current","buildSelectionEvent","colCount","SelectionPlugin","BaseGridPlugin","styles","event","colIndex","originalEvent","column","isUtility","isUtilityColumn","#buildEvent","shiftKey","ctrlKey","newRange","isNavKey","isTabKey","shouldExtend","allRange","targetCol","firstDataCol","_event","#applySelectionClasses","shadowRoot","allRows","clearCellFocus","firstCell","getRowIndexFromCell","firstDataColIndex","effectiveStartCol","container","currentRow","currentCol","r"],"mappings":"+eAqCO,SAASA,EACdC,EACAC,EACAC,EACAC,EACgB,CAChB,MAAMC,EAAc,IAAI,IAAIJ,EAAM,QAAQ,EAC1C,IAAIK,EAASL,EAAM,OAEnB,GAAKE,IAAoB,SACvBE,EAAY,MAAA,EACZA,EAAY,IAAIH,CAAQ,EACxBI,EAASJ,UACCC,IAAoB,WAAY,CAC1C,MAAMI,EAAaH,EAAU,SAAWA,EAAU,QAElD,GAAIA,EAAU,UAAYH,EAAM,SAAW,KAAM,CAE/C,MAAMO,EAAQ,KAAK,IAAIP,EAAM,OAAQC,CAAQ,EACvCO,EAAM,KAAK,IAAIR,EAAM,OAAQC,CAAQ,EAC3C,QAASQ,EAAIF,EAAOE,GAAKD,EAAKC,IAC5BL,EAAY,IAAIK,CAAC,CAErB,MAAWH,GAELF,EAAY,IAAIH,CAAQ,EAC1BG,EAAY,OAAOH,CAAQ,EAE3BG,EAAY,IAAIH,CAAQ,EAE1BI,EAASJ,IAGTG,EAAY,MAAA,EACZA,EAAY,IAAIH,CAAQ,EACxBI,EAASJ,EAEb,CAEA,MAAO,CAAE,SAAUG,EAAa,aAAcH,EAAU,OAAAI,CAAA,CAC1D,CAQO,SAASK,EAAUC,EAA+B,CACvD,MAAMC,MAAe,IACrB,QAASH,EAAI,EAAGA,EAAIE,EAAUF,IAC5BG,EAAS,IAAIH,CAAC,EAEhB,OAAOG,CACT,CASO,SAASC,EACdC,EACAV,EACwC,CACxC,MAAMW,EAAkB,CAAA,EAClBC,EAAoB,CAAA,EAE1B,UAAWC,KAAOb,EACXU,EAAY,IAAIG,CAAG,GAAGF,EAAM,KAAKE,CAAG,EAE3C,UAAWA,KAAOH,EACXV,EAAY,IAAIa,CAAG,GAAGD,EAAQ,KAAKC,CAAG,EAG7C,MAAO,CAAE,MAAAF,EAAO,QAAAC,CAAA,CAClB,CCpGO,SAASE,EAAeC,EAA6C,CAC1E,MAAO,CACL,SAAU,KAAK,IAAIA,EAAM,SAAUA,EAAM,MAAM,EAC/C,SAAU,KAAK,IAAIA,EAAM,SAAUA,EAAM,MAAM,EAC/C,OAAQ,KAAK,IAAIA,EAAM,SAAUA,EAAM,MAAM,EAC7C,OAAQ,KAAK,IAAIA,EAAM,SAAUA,EAAM,MAAM,CAAA,CAEjD,CAQO,SAASC,EAAcD,EAAqC,CACjE,MAAME,EAAaH,EAAeC,CAAK,EACvC,MAAO,CACL,KAAM,CAAE,IAAKE,EAAW,SAAU,IAAKA,EAAW,QAAA,EAClD,GAAI,CAAE,IAAKA,EAAW,OAAQ,IAAKA,EAAW,MAAA,CAAO,CAEzD,CAQO,SAASC,EAAeC,EAA0C,CACvE,OAAOA,EAAO,IAAIH,CAAa,CACjC,CAUO,SAASI,EAAcC,EAAaC,EAAaP,EAAmC,CACzF,MAAME,EAAaH,EAAeC,CAAK,EACvC,OACEM,GAAOJ,EAAW,UAAYI,GAAOJ,EAAW,QAAUK,GAAOL,EAAW,UAAYK,GAAOL,EAAW,MAE9G,CAUO,SAASM,EAAiBF,EAAaC,EAAaH,EAAsC,CAC/F,OAAOA,EAAO,KAAMJ,GAAUK,EAAcC,EAAKC,EAAKP,CAAK,CAAC,CAC9D,CAQO,SAASS,EAAgBT,EAA+D,CAC7F,MAAMU,EAA6C,CAAA,EAC7CR,EAAaH,EAAeC,CAAK,EAEvC,QAASM,EAAMJ,EAAW,SAAUI,GAAOJ,EAAW,OAAQI,IAC5D,QAASC,EAAML,EAAW,SAAUK,GAAOL,EAAW,OAAQK,IAC5DG,EAAM,KAAK,CAAE,IAAAJ,EAAK,IAAAC,CAAA,CAAK,EAI3B,OAAOG,CACT,CASO,SAASC,EAAoBP,EAAkE,CACpG,MAAMQ,MAAc,IAEpB,UAAWZ,KAASI,EAClB,UAAWS,KAAQJ,EAAgBT,CAAK,EACtCY,EAAQ,IAAI,GAAGC,EAAK,GAAG,IAAIA,EAAK,GAAG,GAAIA,CAAI,EAI/C,MAAO,CAAC,GAAGD,EAAQ,QAAQ,CAC7B,CAuBO,SAASE,EACd5B,EACA6B,EACmB,CACnB,MAAO,CACL,SAAU7B,EAAO,IACjB,SAAUA,EAAO,IACjB,OAAQ6B,EAAQ,IAChB,OAAQA,EAAQ,GAAA,CAEpB,g1BC/GA,SAASC,EACPjC,EACAF,EAKAoC,EACuB,CACvB,GAAIlC,IAAS,QAAUF,EAAM,aAC3B,MAAO,CACL,KAAAE,EACA,OAAQ,CACN,CACE,KAAM,CAAE,IAAKF,EAAM,aAAa,IAAK,IAAKA,EAAM,aAAa,GAAA,EAC7D,GAAI,CAAE,IAAKA,EAAM,aAAa,IAAK,IAAKA,EAAM,aAAa,GAAA,CAAI,CACjE,CACF,EAIJ,GAAIE,IAAS,OAASF,EAAM,SAAS,KAAO,EAAG,CAC7C,MAAMuB,EAAS,CAAC,GAAGvB,EAAM,QAAQ,EAAE,IAAKC,IAAc,CACpD,KAAM,CAAE,IAAKA,EAAU,IAAK,CAAA,EAC5B,GAAI,CAAE,IAAKA,EAAU,IAAKmC,EAAW,CAAA,CAAE,EACvC,EACF,MAAO,CAAE,KAAAlC,EAAM,OAAAqB,CAAA,CACjB,CAEA,OAAIrB,IAAS,SAAWF,EAAM,OAAO,OAAS,EACrC,CAAE,KAAAE,EAAM,OAAQoB,EAAetB,EAAM,MAAM,CAAA,EAG7C,CAAE,KAAAE,EAAM,OAAQ,EAAC,CAC1B,CAUO,MAAMmC,UAAwBC,EAAAA,cAAgC,CAC1D,KAAO,YACE,OAASC,EAE3B,IAAuB,eAA0C,CAC/D,MAAO,CACL,KAAM,MAAA,CAEV,CAIQ,aAAe,IACf,aAA8B,KAC9B,OAAwB,KAGxB,OAA8B,CAAA,EAC9B,YAAwC,KACxC,WAAkD,KAClD,WAAa,GAGb,sBAAsD,KAGtD,aAAoD,KAMnD,QAAe,CACtB,KAAK,SAAS,MAAA,EACd,KAAK,OAAS,CAAA,EACd,KAAK,YAAc,KACnB,KAAK,WAAa,KAClB,KAAK,WAAa,GAClB,KAAK,aAAe,KACpB,KAAK,sBAAwB,IAC/B,CAMS,YAAYC,EAAgC,CACnD,KAAM,CAAE,SAAAvC,EAAU,SAAAwC,EAAU,cAAAC,CAAA,EAAkBF,EACxC,CAAE,KAAAtC,GAAS,KAAK,OAGhByC,EAAS,KAAK,QAAQF,CAAQ,EAC9BG,EAAYD,GAAUE,EAAAA,gBAAgBF,CAAM,EAGlD,GAAIzC,IAAS,OACX,OAAI0C,IAGJ,KAAK,aAAe,CAAE,IAAK3C,EAAU,IAAKwC,CAAA,EAC1C,KAAK,KAA4B,mBAAoB,KAAKK,GAAA,CAAa,EACvE,KAAK,mBAAA,GACE,GAIT,GAAI5C,IAAS,MACX,YAAK,SAAS,MAAA,EACd,KAAK,SAAS,IAAID,CAAQ,EAC1B,KAAK,aAAeA,EAEpB,KAAK,KAA4B,mBAAoB,KAAK6C,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,GAIT,GAAI5C,IAAS,QAAS,CAEpB,GAAI0C,EACF,MAAO,GAGT,MAAMG,EAAWL,EAAc,SACzBM,EAAUN,EAAc,SAAWA,EAAc,QAEvD,GAAIK,GAAY,KAAK,WAAY,CAE/B,MAAME,EAAWhB,EAAsB,KAAK,WAAY,CAAE,IAAKhC,EAAU,IAAKwC,EAAU,EAEpFO,EACE,KAAK,OAAO,OAAS,EACvB,KAAK,OAAO,KAAK,OAAO,OAAS,CAAC,EAAIC,EAEtC,KAAK,OAAO,KAAKA,CAAQ,EAG3B,KAAK,OAAS,CAACA,CAAQ,EAEzB,KAAK,YAAcA,CACrB,SAAWD,EAAS,CAClB,MAAMC,EAA8B,CAClC,SAAUhD,EACV,SAAUwC,EACV,OAAQxC,EACR,OAAQwC,CAAA,EAEV,KAAK,OAAO,KAAKQ,CAAQ,EACzB,KAAK,YAAcA,EACnB,KAAK,WAAa,CAAE,IAAKhD,EAAU,IAAKwC,CAAA,CAC1C,KAAO,CACL,MAAMQ,EAA8B,CAClC,SAAUhD,EACV,SAAUwC,EACV,OAAQxC,EACR,OAAQwC,CAAA,EAEV,KAAK,OAAS,CAACQ,CAAQ,EACvB,KAAK,YAAcA,EACnB,KAAK,WAAa,CAAE,IAAKhD,EAAU,IAAKwC,CAAA,CAC1C,CAEA,YAAK,KAA4B,mBAAoB,KAAKK,GAAA,CAAa,EAEvE,KAAK,mBAAA,EACE,EACT,CAEA,MAAO,EACT,CAES,UAAUN,EAA+B,CAChD,KAAM,CAAE,KAAAtC,GAAS,KAAK,OAEhBgD,EADU,CAAC,UAAW,YAAa,YAAa,aAAc,MAAO,OAAQ,MAAO,SAAU,UAAU,EACrF,SAASV,EAAM,GAAG,EAG3C,GAAIA,EAAM,MAAQ,SAChB,OAAItC,IAAS,OACX,KAAK,aAAe,KACXA,IAAS,OAClB,KAAK,SAAS,MAAA,EACd,KAAK,OAAS,MACLA,IAAS,UAClB,KAAK,OAAS,CAAA,EACd,KAAK,YAAc,KACnB,KAAK,WAAa,MAEpB,KAAK,KAA4B,mBAAoB,KAAK4C,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,GAIT,GAAI5C,IAAS,QAAUgD,EAErB,sBAAe,IAAM,CACnB,KAAK,aAAe,CAAE,IAAK,KAAK,KAAK,UAAW,IAAK,KAAK,KAAK,SAAA,EAC/D,KAAK,KAA4B,mBAAoB,KAAKJ,GAAA,CAAa,EACvE,KAAK,mBAAA,CACP,CAAC,EACM,GAIT,GAAI5C,IAAS,QAAUsC,EAAM,MAAQ,WAAaA,EAAM,MAAQ,aAE9D,sBAAe,IAAM,CACnB,KAAK,SAAS,MAAA,EACd,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,EACrC,KAAK,aAAe,KAAK,KAAK,UAC9B,KAAK,KAA4B,mBAAoB,KAAKM,GAAA,CAAa,EACvE,KAAK,mBAAA,CACP,CAAC,EACM,GAKT,GAAI5C,IAAS,SAAWgD,EAAU,CAEhC,MAAMC,EAAWX,EAAM,MAAQ,MACzBY,EAAeZ,EAAM,UAAY,CAACW,EAIxC,OAAIC,GAAgB,CAAC,KAAK,aACxB,KAAK,WAAa,CAAE,IAAK,KAAK,KAAK,UAAW,IAAK,KAAK,KAAK,SAAA,GAI/D,KAAK,sBAAwB,CAAE,SAAUA,CAAA,EAKzC,eAAe,IAAM,KAAK,oBAAoB,EAEvC,EACT,CAGA,GAAIlD,IAAS,SAAWsC,EAAM,MAAQ,MAAQA,EAAM,SAAWA,EAAM,SAAU,CAC7E,MAAM7B,EAAW,KAAK,KAAK,OACrByB,EAAW,KAAK,QAAQ,OAC9B,GAAIzB,EAAW,GAAKyB,EAAW,EAAG,CAChC,MAAMiB,EAA8B,CAClC,SAAU,EACV,SAAU,EACV,OAAQ1C,EAAW,EACnB,OAAQyB,EAAW,CAAA,EAErB,YAAK,OAAS,CAACiB,CAAQ,EACvB,KAAK,YAAcA,EACnB,KAAK,KAA4B,mBAAoB,KAAKP,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,EACT,CACF,CAEA,MAAO,EACT,CAES,gBAAgBN,EAAuC,CAG9D,GAFI,KAAK,OAAO,OAAS,SACrBA,EAAM,WAAa,QAAaA,EAAM,WAAa,QACnDA,EAAM,SAAW,EAAG,OAGxB,MAAMG,EAAS,KAAK,QAAQH,EAAM,QAAQ,EAM1C,GALIG,GAAUE,kBAAgBF,CAAM,GAKhCH,EAAM,cAAc,UAAY,KAAK,WACvC,OAIF,KAAK,WAAa,GAClB,MAAMvC,EAAWuC,EAAM,SACjBC,EAAWD,EAAM,SACvB,KAAK,WAAa,CAAE,IAAKvC,EAAU,IAAKwC,CAAA,EAExBD,EAAM,cAAc,SAAWA,EAAM,cAAc,UAEjE,KAAK,OAAS,CAAA,GAGhB,MAAMS,EAA8B,CAClC,SAAUhD,EACV,SAAUwC,EACV,OAAQxC,EACR,OAAQwC,CAAA,EAEV,YAAK,OAAO,KAAKQ,CAAQ,EACzB,KAAK,YAAcA,EAEnB,KAAK,KAA4B,mBAAoB,KAAKH,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,EACT,CAES,gBAAgBN,EAAuC,CAI9D,GAHI,KAAK,OAAO,OAAS,SACrB,CAAC,KAAK,YAAc,CAAC,KAAK,YAC1BA,EAAM,WAAa,QAAaA,EAAM,WAAa,QACnDA,EAAM,SAAW,EAAG,OAGxB,IAAIc,EAAYd,EAAM,SACtB,MAAMG,EAAS,KAAK,QAAQW,CAAS,EACrC,GAAIX,GAAUE,kBAAgBF,CAAM,EAAG,CAErC,MAAMY,EAAe,KAAK,QAAQ,UAAW7B,GAAQ,CAACmB,kBAAgBnB,CAAG,CAAC,EACtE6B,GAAgB,IAClBD,EAAYC,EAEhB,CAEA,MAAMN,EAAWhB,EAAsB,KAAK,WAAY,CAAE,IAAKO,EAAM,SAAU,IAAKc,EAAW,EAE/F,OAAI,KAAK,OAAO,OAAS,EACvB,KAAK,OAAO,KAAK,OAAO,OAAS,CAAC,EAAIL,EAEtC,KAAK,OAAO,KAAKA,CAAQ,EAE3B,KAAK,YAAcA,EAEnB,KAAK,KAA4B,mBAAoB,KAAKH,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,EACT,CAES,cAAcU,EAAwC,CAC7D,GAAI,KAAK,OAAO,OAAS,SACrB,KAAK,WACP,YAAK,WAAa,GACX,EAEX,CAMAC,IAA+B,CAC7B,MAAMC,EAAa,KAAK,WACxB,GAAI,CAACA,EAAY,OAEjB,KAAM,CAAE,KAAAxD,GAAS,KAAK,OAGLwD,EAAW,iBAAiB,OAAO,EAC3C,QAAS1B,GAAS,CACzBA,EAAK,UAAU,OAAO,WAAY,MAAO,SAAU,QAAS,MAAM,CACpE,CAAC,EAED,MAAM2B,EAAUD,EAAW,iBAAiB,gBAAgB,EAoB5D,GAnBAC,EAAQ,QAASlC,GAAQ,CACvBA,EAAI,UAAU,OAAO,WAAY,WAAW,CAC9C,CAAC,EAGGvB,IAAS,QAEX0D,EAAAA,eAAeF,CAAU,EAEzBC,EAAQ,QAASlC,GAAQ,CACvB,MAAMoC,EAAYpC,EAAI,cAAc,iBAAiB,EAC/CxB,EAAW6D,EAAAA,oBAAoBD,CAAS,EAC1C5D,GAAY,GAAK,KAAK,SAAS,IAAIA,CAAQ,GAC7CwB,EAAI,UAAU,IAAI,WAAY,WAAW,CAE7C,CAAC,GAICvB,IAAS,SAAW,KAAK,OAAO,OAAS,EAAG,CAE9C0D,EAAAA,eAAeF,CAAU,EAEzB,MAAMrC,EAAa,KAAK,YAAcH,EAAe,KAAK,WAAW,EAAI,KAGnE6C,EAAoB,KAAK,QAAQ,UAAWrC,GAAQ,CAACmB,kBAAgBnB,CAAG,CAAC,EACtD,KAAK,QAAQ,OAAS,EAEjCgC,EAAW,iBAAiB,2BAA2B,EAC/D,QAAS1B,GAAS,CACtB,MAAM/B,EAAW,SAAS+B,EAAK,aAAa,UAAU,GAAK,KAAM,EAAE,EAC7DS,EAAW,SAAST,EAAK,aAAa,UAAU,GAAK,KAAM,EAAE,EACnE,GAAI/B,GAAY,GAAKwC,GAAY,EAAG,CAElC,MAAME,EAAS,KAAK,QAAQF,CAAQ,EACpC,GAAIE,GAAUE,kBAAgBF,CAAM,EAClC,OAKF,GAFgBhB,EAAiB1B,EAAUwC,EAAU,KAAK,MAAM,IAG9DT,EAAK,UAAU,IAAI,UAAU,EAEzBX,GAAY,CACVpB,IAAaoB,EAAW,UAAUW,EAAK,UAAU,IAAI,KAAK,EAC1D/B,IAAaoB,EAAW,QAAQW,EAAK,UAAU,IAAI,QAAQ,EAE/D,MAAMgC,EAAoB,KAAK,IAAI3C,EAAW,SAAU0C,CAAiB,EACrEtB,IAAauB,GAAmBhC,EAAK,UAAU,IAAI,OAAO,EAC1DS,IAAapB,EAAW,QAAQW,EAAK,UAAU,IAAI,MAAM,CAC/D,CAEJ,CACF,CAAC,CACH,CAGI9B,IAAS,QAAU,KAAK,cAE1B0D,EAAAA,eAAeF,CAAU,CAE7B,CAES,aAAoB,CAC3B,MAAMA,EAAa,KAAK,WACxB,GAAI,CAACA,EAAY,OAEjB,MAAMO,EAAYP,EAAW,SAAS,CAAC,EACjC,CAAE,KAAAxD,GAAS,KAAK,OAItB,GAAI,KAAK,uBAAyBA,IAAS,QAAS,CAClD,KAAM,CAAE,SAAA6C,GAAa,KAAK,sBAC1B,KAAK,sBAAwB,KAE7B,MAAMmB,EAAa,KAAK,KAAK,UACvBC,EAAa,KAAK,KAAK,UAE7B,GAAIpB,GAAY,KAAK,WAAY,CAE/B,MAAME,EAAWhB,EAAsB,KAAK,WAAY,CAAE,IAAKiC,EAAY,IAAKC,EAAY,EAC5F,KAAK,OAAS,CAAClB,CAAQ,EACvB,KAAK,YAAcA,CACrB,MAAYF,IAEV,KAAK,OAAS,CAAA,EACd,KAAK,YAAc,KACnB,KAAK,WAAa,CAAE,IAAKmB,EAAY,IAAKC,CAAA,GAG5C,KAAK,KAA4B,mBAAoB,KAAKrB,GAAA,CAAa,CACzE,CAGC,KAAK,KAA4B,aAAa,sBAAuB5C,CAAI,EAGtE+D,GACFA,EAAU,UAAU,OAAO,YAAa,KAAK,UAAU,EAGzD,KAAKR,GAAA,CACP,CAMS,gBAAuB,CAC9B,KAAKA,GAAA,CACP,CAqBA,cAAgC,CAC9B,MAAO,CACL,KAAM,KAAK,OAAO,KAClB,OAAQ,KAAKX,GAAA,EAAc,OAC3B,OAAQ,KAAK,UAAA,CAEjB,CAMA,iBAAuD,CACrD,KAAM,CAAE,KAAA5C,EAAM,OAAAqB,GAAW,KAAK,aAAA,EAC9B,OAAIrB,IAAS,QAAUqB,EAAO,OAAS,EAC9BA,EAAO,CAAC,EAAE,KAEZ,IACT,CAMA,iBAA4B,CAC1B,KAAM,CAAE,KAAArB,EAAM,OAAAqB,GAAW,KAAK,aAAA,EAC9B,OAAIrB,IAAS,MACJqB,EAAO,IAAK6C,GAAMA,EAAE,KAAK,GAAG,EAE9B,CAAA,CACT,CAMA,WAAyB,CACvB,OAAO,KAAK,eAAe,MAC7B,CAKA,kBAAwD,CACtD,OAAOtC,EAAoB,KAAK,MAAM,CACxC,CAKA,eAAeL,EAAaC,EAAsB,CAChD,OAAOC,EAAiBF,EAAKC,EAAK,KAAK,MAAM,CAC/C,CAKA,gBAAuB,CACrB,KAAK,aAAe,KACpB,KAAK,SAAS,MAAA,EACd,KAAK,OAAS,KACd,KAAK,OAAS,CAAA,EACd,KAAK,YAAc,KACnB,KAAK,WAAa,KAClB,KAAK,KAA4B,mBAAoB,CAAE,KAAM,KAAK,OAAO,KAAM,OAAQ,CAAA,EAAI,EAC3F,KAAK,mBAAA,CACP,CAKA,UAAUH,EAA2B,CACnC,KAAK,OAASA,EAAO,IAAK6C,IAAO,CAC/B,SAAUA,EAAE,KAAK,IACjB,SAAUA,EAAE,KAAK,IACjB,OAAQA,EAAE,GAAG,IACb,OAAQA,EAAE,GAAG,GAAA,EACb,EACF,KAAK,YAAc,KAAK,OAAO,OAAS,EAAI,KAAK,OAAO,KAAK,OAAO,OAAS,CAAC,EAAI,KAClF,KAAK,KAA4B,mBAAoB,CACnD,KAAM,KAAK,OAAO,KAClB,OAAQ9C,EAAe,KAAK,MAAM,CAAA,CACnC,EACD,KAAK,mBAAA,CACP,CAMAwB,IAAqC,CACnC,OAAOX,EACL,KAAK,OAAO,KACZ,CACE,aAAc,KAAK,aACnB,SAAU,KAAK,SACf,OAAQ,KAAK,MAAA,EAEf,KAAK,QAAQ,MAAA,CAEjB,CAGF"}
1
+ {"version":3,"file":"selection.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/selection/row-selection.ts","../../../../../libs/grid/src/lib/plugins/selection/range-selection.ts","../../../../../libs/grid/src/lib/plugins/selection/SelectionPlugin.ts"],"sourcesContent":["/**\n * Row Selection Core Logic\n *\n * Pure functions for row selection operations.\n */\n\nimport type { SelectionMode, SelectionState } from './types';\n\n/** Click modifier keys state */\nexport interface ClickModifiers {\n shiftKey: boolean;\n ctrlKey: boolean;\n metaKey: boolean;\n}\n\n/** Result from handling a row click for selection */\nexport interface RowClickResult {\n selected: Set<number>;\n lastSelected: number;\n anchor: number | null;\n}\n\n/**\n * Handle a row click event for selection purposes.\n *\n * In single mode: always selects the clicked row, clearing others.\n * In multiple mode:\n * - Plain click: clears selection and selects clicked row\n * - Ctrl/Cmd+click: toggles the clicked row\n * - Shift+click: range select from anchor to clicked row\n *\n * @param state - Current selection state\n * @param rowIndex - The clicked row index\n * @param mode - Selection mode ('single' or 'multiple')\n * @param modifiers - Keyboard modifiers held during click\n * @returns Updated selection state values\n */\nexport function handleRowClick(\n state: SelectionState,\n rowIndex: number,\n mode: SelectionMode,\n modifiers: ClickModifiers\n): RowClickResult {\n const newSelected = new Set(state.selected);\n let anchor = state.anchor;\n\n if ((mode as string) === 'single') {\n newSelected.clear();\n newSelected.add(rowIndex);\n anchor = rowIndex;\n } else if ((mode as string) === 'multiple') {\n const ctrlOrMeta = modifiers.ctrlKey || modifiers.metaKey;\n\n if (modifiers.shiftKey && state.anchor !== null) {\n // Range selection from anchor\n const start = Math.min(state.anchor, rowIndex);\n const end = Math.max(state.anchor, rowIndex);\n for (let i = start; i <= end; i++) {\n newSelected.add(i);\n }\n } else if (ctrlOrMeta) {\n // Toggle selection\n if (newSelected.has(rowIndex)) {\n newSelected.delete(rowIndex);\n } else {\n newSelected.add(rowIndex);\n }\n anchor = rowIndex;\n } else {\n // Clear and select single\n newSelected.clear();\n newSelected.add(rowIndex);\n anchor = rowIndex;\n }\n }\n\n return { selected: newSelected, lastSelected: rowIndex, anchor };\n}\n\n/**\n * Create a set containing all row indices (for select all).\n *\n * @param rowCount - Total number of rows\n * @returns Set containing indices 0 to rowCount-1\n */\nexport function selectAll(rowCount: number): Set<number> {\n const selected = new Set<number>();\n for (let i = 0; i < rowCount; i++) {\n selected.add(i);\n }\n return selected;\n}\n\n/**\n * Compute the difference between two selection states.\n *\n * @param oldSelected - Previous selection set\n * @param newSelected - New selection set\n * @returns Object with added and removed row indices\n */\nexport function computeSelectionDiff(\n oldSelected: Set<number>,\n newSelected: Set<number>\n): { added: number[]; removed: number[] } {\n const added: number[] = [];\n const removed: number[] = [];\n\n for (const idx of newSelected) {\n if (!oldSelected.has(idx)) added.push(idx);\n }\n for (const idx of oldSelected) {\n if (!newSelected.has(idx)) removed.push(idx);\n }\n\n return { added, removed };\n}\n","/**\n * Cell Range Selection Core Logic\n *\n * Pure functions for cell range selection operations.\n */\n\nimport type { InternalCellRange, CellRange } from './types';\n\n/**\n * Normalize a range so startRow/startCol are always <= endRow/endCol.\n * This handles cases where user drags from bottom-right to top-left.\n *\n * @param range - The range to normalize\n * @returns Normalized range with start <= end for both dimensions\n */\nexport function normalizeRange(range: InternalCellRange): InternalCellRange {\n return {\n startRow: Math.min(range.startRow, range.endRow),\n startCol: Math.min(range.startCol, range.endCol),\n endRow: Math.max(range.startRow, range.endRow),\n endCol: Math.max(range.startCol, range.endCol),\n };\n}\n\n/**\n * Convert an internal range to the public event format.\n *\n * @param range - The internal range to convert\n * @returns Public CellRange format with from/to coordinates\n */\nexport function toPublicRange(range: InternalCellRange): CellRange {\n const normalized = normalizeRange(range);\n return {\n from: { row: normalized.startRow, col: normalized.startCol },\n to: { row: normalized.endRow, col: normalized.endCol },\n };\n}\n\n/**\n * Convert multiple internal ranges to public format.\n *\n * @param ranges - Array of internal ranges\n * @returns Array of public CellRange format\n */\nexport function toPublicRanges(ranges: InternalCellRange[]): CellRange[] {\n return ranges.map(toPublicRange);\n}\n\n/**\n * Check if a cell is within a specific range.\n *\n * @param row - The row index to check\n * @param col - The column index to check\n * @param range - The range to check against\n * @returns True if the cell is within the range\n */\nexport function isCellInRange(row: number, col: number, range: InternalCellRange): boolean {\n const normalized = normalizeRange(range);\n return (\n row >= normalized.startRow && row <= normalized.endRow && col >= normalized.startCol && col <= normalized.endCol\n );\n}\n\n/**\n * Check if a cell is within any of the provided ranges.\n *\n * @param row - The row index to check\n * @param col - The column index to check\n * @param ranges - Array of ranges to check against\n * @returns True if the cell is within any range\n */\nexport function isCellInAnyRange(row: number, col: number, ranges: InternalCellRange[]): boolean {\n return ranges.some((range) => isCellInRange(row, col, range));\n}\n\n/**\n * Get all cells within a range as an array of {row, col} objects.\n *\n * @param range - The range to enumerate\n * @returns Array of all cell coordinates in the range\n */\nexport function getCellsInRange(range: InternalCellRange): Array<{ row: number; col: number }> {\n const cells: Array<{ row: number; col: number }> = [];\n const normalized = normalizeRange(range);\n\n for (let row = normalized.startRow; row <= normalized.endRow; row++) {\n for (let col = normalized.startCol; col <= normalized.endCol; col++) {\n cells.push({ row, col });\n }\n }\n\n return cells;\n}\n\n/**\n * Get all unique cells across multiple ranges.\n * Deduplicates cells that appear in overlapping ranges.\n *\n * @param ranges - Array of ranges to enumerate\n * @returns Array of unique cell coordinates\n */\nexport function getAllCellsInRanges(ranges: InternalCellRange[]): Array<{ row: number; col: number }> {\n const cellMap = new Map<string, { row: number; col: number }>();\n\n for (const range of ranges) {\n for (const cell of getCellsInRange(range)) {\n cellMap.set(`${cell.row},${cell.col}`, cell);\n }\n }\n\n return [...cellMap.values()];\n}\n\n/**\n * Merge overlapping or adjacent ranges into fewer ranges.\n * Simple implementation - returns ranges as-is for now.\n * More complex merging logic can be added later for optimization.\n *\n * @param ranges - Array of ranges to merge\n * @returns Merged array of ranges\n */\nexport function mergeRanges(ranges: InternalCellRange[]): InternalCellRange[] {\n // Simple implementation - more complex merging can be added later\n return ranges;\n}\n\n/**\n * Create a range from an anchor cell to a current cell position.\n * The range is not normalized - it preserves the direction of selection.\n *\n * @param anchor - The anchor cell (where selection started)\n * @param current - The current cell (where selection ends)\n * @returns An InternalCellRange from anchor to current\n */\nexport function createRangeFromAnchor(\n anchor: { row: number; col: number },\n current: { row: number; col: number }\n): InternalCellRange {\n return {\n startRow: anchor.row,\n startCol: anchor.col,\n endRow: current.row,\n endCol: current.col,\n };\n}\n\n/**\n * Calculate the number of cells in a range.\n *\n * @param range - The range to measure\n * @returns Total number of cells in the range\n */\nexport function getRangeCellCount(range: InternalCellRange): number {\n const normalized = normalizeRange(range);\n const rowCount = normalized.endRow - normalized.startRow + 1;\n const colCount = normalized.endCol - normalized.startCol + 1;\n return rowCount * colCount;\n}\n\n/**\n * Check if two ranges are equal (same boundaries).\n *\n * @param a - First range\n * @param b - Second range\n * @returns True if ranges have same boundaries after normalization\n */\nexport function rangesEqual(a: InternalCellRange, b: InternalCellRange): boolean {\n const normA = normalizeRange(a);\n const normB = normalizeRange(b);\n return (\n normA.startRow === normB.startRow &&\n normA.startCol === normB.startCol &&\n normA.endRow === normB.endRow &&\n normA.endCol === normB.endCol\n );\n}\n\n/**\n * Check if a range is a single cell (1x1).\n *\n * @param range - The range to check\n * @returns True if the range is exactly one cell\n */\nexport function isSingleCell(range: InternalCellRange): boolean {\n const normalized = normalizeRange(range);\n return normalized.startRow === normalized.endRow && normalized.startCol === normalized.endCol;\n}\n","/**\n * Selection Plugin (Class-based)\n *\n * Provides selection functionality for tbw-grid.\n * Supports three modes:\n * - 'cell': Single cell selection (default). No border, just focus highlight.\n * - 'row': Row selection. Clicking a cell selects the entire row.\n * - 'range': Range selection. Shift+click or drag to select rectangular cell ranges.\n */\n\nimport { clearCellFocus, getRowIndexFromCell } from '../../core/internal/utils';\nimport { BaseGridPlugin, CellClickEvent, CellMouseEvent } from '../../core/plugin/base-plugin';\nimport { isUtilityColumn } from '../../core/plugin/expander-column';\nimport {\n createRangeFromAnchor,\n getAllCellsInRanges,\n isCellInAnyRange,\n normalizeRange,\n toPublicRanges,\n} from './range-selection';\nimport styles from './selection.css?inline';\nimport type {\n CellRange,\n InternalCellRange,\n SelectionChangeDetail,\n SelectionConfig,\n SelectionMode,\n SelectionResult,\n} from './types';\n\n/**\n * Build the selection change event detail for the current state.\n */\nfunction buildSelectionEvent(\n mode: SelectionMode,\n state: {\n selectedCell: { row: number; col: number } | null;\n selected: Set<number>;\n ranges: InternalCellRange[];\n },\n colCount: number,\n): SelectionChangeDetail {\n if (mode === 'cell' && state.selectedCell) {\n return {\n mode,\n ranges: [\n {\n from: { row: state.selectedCell.row, col: state.selectedCell.col },\n to: { row: state.selectedCell.row, col: state.selectedCell.col },\n },\n ],\n };\n }\n\n if (mode === 'row' && state.selected.size > 0) {\n const ranges = [...state.selected].map((rowIndex) => ({\n from: { row: rowIndex, col: 0 },\n to: { row: rowIndex, col: colCount - 1 },\n }));\n return { mode, ranges };\n }\n\n if (mode === 'range' && state.ranges.length > 0) {\n return { mode, ranges: toPublicRanges(state.ranges) };\n }\n\n return { mode, ranges: [] };\n}\n\n/**\n * Selection Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new SelectionPlugin({ mode: 'range' })\n * ```\n */\nexport class SelectionPlugin extends BaseGridPlugin<SelectionConfig> {\n readonly name = 'selection';\n override readonly styles = styles;\n\n protected override get defaultConfig(): Partial<SelectionConfig> {\n return {\n mode: 'cell',\n };\n }\n\n // #region Internal State\n /** Row selection state (row mode) */\n private selected = new Set<number>();\n private lastSelected: number | null = null;\n private anchor: number | null = null;\n\n /** Range selection state (range mode) */\n private ranges: InternalCellRange[] = [];\n private activeRange: InternalCellRange | null = null;\n private cellAnchor: { row: number; col: number } | null = null;\n private isDragging = false;\n\n /** Pending keyboard navigation update (processed in afterRender) */\n private pendingKeyboardUpdate: { shiftKey: boolean } | null = null;\n\n /** Cell selection state (cell mode) */\n private selectedCell: { row: number; col: number } | null = null;\n\n // #endregion\n\n // #region Lifecycle\n\n override detach(): void {\n this.selected.clear();\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.isDragging = false;\n this.selectedCell = null;\n this.pendingKeyboardUpdate = null;\n }\n\n // #endregion\n\n // #region Event Handlers\n\n override onCellClick(event: CellClickEvent): boolean {\n const { rowIndex, colIndex, originalEvent } = event;\n const { mode } = this.config;\n\n // Check if this is a utility column (expander columns, etc.)\n const column = this.columns[colIndex];\n const isUtility = column && isUtilityColumn(column);\n\n // CELL MODE: Single cell selection - skip utility columns\n if (mode === 'cell') {\n if (isUtility) {\n return false; // Allow event to propagate, but don't select utility cells\n }\n this.selectedCell = { row: rowIndex, col: colIndex };\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return false;\n }\n\n // ROW MODE: Select entire row - utility column clicks still select the row\n if (mode === 'row') {\n this.selected.clear();\n this.selected.add(rowIndex);\n this.lastSelected = rowIndex;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return false;\n }\n\n // RANGE MODE: Shift+click extends selection, click starts new\n if (mode === 'range') {\n // Skip utility columns in range mode - don't start selection from them\n if (isUtility) {\n return false;\n }\n\n const shiftKey = originalEvent.shiftKey;\n const ctrlKey = originalEvent.ctrlKey || originalEvent.metaKey;\n\n if (shiftKey && this.cellAnchor) {\n // Extend selection from anchor\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: rowIndex, col: colIndex });\n\n if (ctrlKey) {\n if (this.ranges.length > 0) {\n this.ranges[this.ranges.length - 1] = newRange;\n } else {\n this.ranges.push(newRange);\n }\n } else {\n this.ranges = [newRange];\n }\n this.activeRange = newRange;\n } else if (ctrlKey) {\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges.push(newRange);\n this.activeRange = newRange;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n } else {\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges = [newRange];\n this.activeRange = newRange;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n }\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n\n this.requestAfterRender();\n return false;\n }\n\n return false;\n }\n\n override onKeyDown(event: KeyboardEvent): boolean {\n const { mode } = this.config;\n const navKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End', 'PageUp', 'PageDown'];\n const isNavKey = navKeys.includes(event.key);\n\n // Escape clears selection in all modes\n if (event.key === 'Escape') {\n if (mode === 'cell') {\n this.selectedCell = null;\n } else if (mode === 'row') {\n this.selected.clear();\n this.anchor = null;\n } else if (mode === 'range') {\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n }\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n // CELL MODE: Selection follows focus\n if (mode === 'cell' && isNavKey) {\n // Use queueMicrotask so grid's handler runs first and updates focusRow/focusCol\n queueMicrotask(() => {\n this.selectedCell = { row: this.grid._focusRow, col: this.grid._focusCol };\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n });\n return false; // Let grid handle navigation\n }\n\n // ROW MODE: Only Up/Down arrows move row selection\n if (mode === 'row' && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {\n // Let grid move focus first, then sync row selection\n queueMicrotask(() => {\n this.selected.clear();\n this.selected.add(this.grid._focusRow);\n this.lastSelected = this.grid._focusRow;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n });\n return false; // Let grid handle navigation\n }\n\n // RANGE MODE: Shift+Arrow extends, plain Arrow resets\n // Tab key always navigates without extending (even with Shift)\n if (mode === 'range' && isNavKey) {\n // Tab should not extend selection - it just navigates to the next/previous cell\n const isTabKey = event.key === 'Tab';\n const shouldExtend = event.shiftKey && !isTabKey;\n\n // Capture anchor BEFORE grid moves focus (synchronous)\n // This ensures the anchor is the starting point, not the destination\n if (shouldExtend && !this.cellAnchor) {\n this.cellAnchor = { row: this.grid._focusRow, col: this.grid._focusCol };\n }\n\n // Mark pending update - will be processed in afterRender when grid updates focus\n this.pendingKeyboardUpdate = { shiftKey: shouldExtend };\n\n // Schedule afterRender to run after grid's keyboard handler completes\n // Grid's refreshVirtualWindow(false) skips afterRender for performance,\n // so we explicitly request it to process pendingKeyboardUpdate\n queueMicrotask(() => this.requestAfterRender());\n\n return false; // Let grid handle navigation\n }\n\n // Ctrl+A selects all in range mode\n if (mode === 'range' && event.key === 'a' && (event.ctrlKey || event.metaKey)) {\n const rowCount = this.rows.length;\n const colCount = this.columns.length;\n if (rowCount > 0 && colCount > 0) {\n const allRange: InternalCellRange = {\n startRow: 0,\n startCol: 0,\n endRow: rowCount - 1,\n endCol: colCount - 1,\n };\n this.ranges = [allRange];\n this.activeRange = allRange;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n }\n\n return false;\n }\n\n override onCellMouseDown(event: CellMouseEvent): boolean | void {\n if (this.config.mode !== 'range') return;\n if (event.rowIndex === undefined || event.colIndex === undefined) return;\n if (event.rowIndex < 0) return; // Header\n\n // Skip utility columns (expander columns, etc.)\n const column = this.columns[event.colIndex];\n if (column && isUtilityColumn(column)) {\n return; // Don't start selection on utility columns\n }\n\n // Let onCellClick handle shift+click for range extension\n if (event.originalEvent.shiftKey && this.cellAnchor) {\n return;\n }\n\n // Start drag selection\n this.isDragging = true;\n const rowIndex = event.rowIndex;\n const colIndex = event.colIndex;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n\n const ctrlKey = event.originalEvent.ctrlKey || event.originalEvent.metaKey;\n if (!ctrlKey) {\n this.ranges = [];\n }\n\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges.push(newRange);\n this.activeRange = newRange;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n override onCellMouseMove(event: CellMouseEvent): boolean | void {\n if (this.config.mode !== 'range') return;\n if (!this.isDragging || !this.cellAnchor) return;\n if (event.rowIndex === undefined || event.colIndex === undefined) return;\n if (event.rowIndex < 0) return;\n\n // When dragging, clamp to first data column (skip utility columns)\n let targetCol = event.colIndex;\n const column = this.columns[targetCol];\n if (column && isUtilityColumn(column)) {\n // Find the first non-utility column\n const firstDataCol = this.columns.findIndex((col) => !isUtilityColumn(col));\n if (firstDataCol >= 0) {\n targetCol = firstDataCol;\n }\n }\n\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: event.rowIndex, col: targetCol });\n\n if (this.ranges.length > 0) {\n this.ranges[this.ranges.length - 1] = newRange;\n } else {\n this.ranges.push(newRange);\n }\n this.activeRange = newRange;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n override onCellMouseUp(_event: CellMouseEvent): boolean | void {\n if (this.config.mode !== 'range') return;\n if (this.isDragging) {\n this.isDragging = false;\n return true;\n }\n }\n\n /**\n * Apply selection classes to visible cells/rows.\n * Shared by afterRender and onScrollRender.\n */\n #applySelectionClasses(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const { mode } = this.config;\n\n // Clear all selection classes first\n const allCells = gridEl.querySelectorAll('.cell');\n allCells.forEach((cell) => {\n cell.classList.remove('selected', 'top', 'bottom', 'first', 'last');\n });\n\n const allRows = gridEl.querySelectorAll('.data-grid-row');\n allRows.forEach((row) => {\n row.classList.remove('selected', 'row-focus');\n });\n\n // ROW MODE: Add row-focus class to selected rows, disable cell-focus\n if (mode === 'row') {\n // In row mode, disable ALL cell-focus styling - row selection takes precedence\n clearCellFocus(gridEl);\n\n allRows.forEach((row) => {\n const firstCell = row.querySelector('.cell[data-row]');\n const rowIndex = getRowIndexFromCell(firstCell);\n if (rowIndex >= 0 && this.selected.has(rowIndex)) {\n row.classList.add('selected', 'row-focus');\n }\n });\n }\n\n // RANGE MODE: Add selected and edge classes to cells\n if (mode === 'range' && this.ranges.length > 0) {\n // Clear all cell-focus first - selection plugin manages focus styling in range mode\n clearCellFocus(gridEl);\n\n const normalized = this.activeRange ? normalizeRange(this.activeRange) : null;\n\n // Find the first non-utility column index for proper .first class application\n const firstDataColIndex = this.columns.findIndex((col) => !isUtilityColumn(col));\n const lastDataColIndex = this.columns.length - 1; // Last column is always data\n\n const cells = gridEl.querySelectorAll('.cell[data-row][data-col]');\n cells.forEach((cell) => {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n if (rowIndex >= 0 && colIndex >= 0) {\n // Skip utility columns entirely - don't add any selection classes\n const column = this.columns[colIndex];\n if (column && isUtilityColumn(column)) {\n return;\n }\n\n const inRange = isCellInAnyRange(rowIndex, colIndex, this.ranges);\n\n if (inRange) {\n cell.classList.add('selected');\n\n if (normalized) {\n if (rowIndex === normalized.startRow) cell.classList.add('top');\n if (rowIndex === normalized.endRow) cell.classList.add('bottom');\n // Apply .first to the first data column in range (skip utility columns)\n const effectiveStartCol = Math.max(normalized.startCol, firstDataColIndex);\n if (colIndex === effectiveStartCol) cell.classList.add('first');\n if (colIndex === normalized.endCol) cell.classList.add('last');\n }\n }\n }\n });\n }\n\n // CELL MODE: Let the grid's native .cell-focus styling handle cell highlighting\n // No additional action needed - the grid already manages focus styling\n }\n\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const container = gridEl.children[0];\n const { mode } = this.config;\n\n // Process pending keyboard navigation update (range mode)\n // This runs AFTER the grid has updated focusRow/focusCol\n if (this.pendingKeyboardUpdate && mode === 'range') {\n const { shiftKey } = this.pendingKeyboardUpdate;\n this.pendingKeyboardUpdate = null;\n\n const currentRow = this.grid._focusRow;\n const currentCol = this.grid._focusCol;\n\n if (shiftKey && this.cellAnchor) {\n // Extend selection from anchor to current focus\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: currentRow, col: currentCol });\n this.ranges = [newRange];\n this.activeRange = newRange;\n } else if (!shiftKey) {\n // Without shift, clear selection (cell-focus will show instead)\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = { row: currentRow, col: currentCol }; // Reset anchor to current position\n }\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n }\n\n // Set data attribute on host for CSS variable scoping\n (this.grid as unknown as Element).setAttribute('data-selection-mode', mode);\n\n // Toggle .selecting class during drag to prevent text selection\n if (container) {\n container.classList.toggle('selecting', this.isDragging);\n }\n\n this.#applySelectionClasses();\n }\n\n /**\n * Called after scroll-triggered row rendering.\n * Reapplies selection classes to recycled DOM elements.\n */\n override onScrollRender(): void {\n this.#applySelectionClasses();\n }\n\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current selection as a unified result.\n * Works for all selection modes and always returns ranges.\n *\n * @example\n * ```ts\n * const selection = plugin.getSelection();\n * if (selection.ranges.length > 0) {\n * const { from, to } = selection.ranges[0];\n * // For cell mode: from === to (single cell)\n * // For row mode: from.col = 0, to.col = lastCol (full row)\n * // For range mode: rectangular selection\n * }\n * ```\n */\n getSelection(): SelectionResult {\n return {\n mode: this.config.mode,\n ranges: this.#buildEvent().ranges,\n anchor: this.cellAnchor,\n };\n }\n\n /**\n * Get the selected cell (cell mode only).\n * @deprecated Use `getSelection()` instead for a unified API across all modes.\n */\n getSelectedCell(): { row: number; col: number } | null {\n const { mode, ranges } = this.getSelection();\n if (mode === 'cell' && ranges.length > 0) {\n return ranges[0].from;\n }\n return null;\n }\n\n /**\n * Get all selected row indices (row mode).\n * @deprecated Use `getSelection().ranges` instead - each range represents a full row.\n */\n getSelectedRows(): number[] {\n const { mode, ranges } = this.getSelection();\n if (mode === 'row') {\n return ranges.map((r) => r.from.row);\n }\n return [];\n }\n\n /**\n * Get all selected cell ranges in public format.\n * @deprecated Use `getSelection().ranges` instead.\n */\n getRanges(): CellRange[] {\n return this.getSelection().ranges;\n }\n\n /**\n * Get all selected cells across all ranges.\n */\n getSelectedCells(): Array<{ row: number; col: number }> {\n return getAllCellsInRanges(this.ranges);\n }\n\n /**\n * Check if a specific cell is in range selection.\n */\n isCellSelected(row: number, col: number): boolean {\n return isCellInAnyRange(row, col, this.ranges);\n }\n\n /**\n * Clear all selection.\n */\n clearSelection(): void {\n this.selectedCell = null;\n this.selected.clear();\n this.anchor = null;\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.emit<SelectionChangeDetail>('selection-change', { mode: this.config.mode, ranges: [] });\n this.requestAfterRender();\n }\n\n /**\n * Set selected ranges programmatically.\n */\n setRanges(ranges: CellRange[]): void {\n this.ranges = ranges.map((r) => ({\n startRow: r.from.row,\n startCol: r.from.col,\n endRow: r.to.row,\n endCol: r.to.col,\n }));\n this.activeRange = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null;\n this.emit<SelectionChangeDetail>('selection-change', {\n mode: this.config.mode,\n ranges: toPublicRanges(this.ranges),\n });\n this.requestAfterRender();\n }\n\n // #endregion\n\n // #region Private Helpers\n\n #buildEvent(): SelectionChangeDetail {\n return buildSelectionEvent(\n this.config.mode,\n {\n selectedCell: this.selectedCell,\n selected: this.selected,\n ranges: this.ranges,\n },\n this.columns.length,\n );\n }\n\n // #endregion\n}\n"],"names":["handleRowClick","state","rowIndex","mode","modifiers","newSelected","anchor","ctrlOrMeta","start","end","i","selectAll","rowCount","selected","computeSelectionDiff","oldSelected","added","removed","idx","normalizeRange","range","toPublicRange","normalized","toPublicRanges","ranges","isCellInRange","row","col","isCellInAnyRange","getCellsInRange","cells","getAllCellsInRanges","cellMap","cell","createRangeFromAnchor","current","buildSelectionEvent","colCount","SelectionPlugin","BaseGridPlugin","styles","event","colIndex","originalEvent","column","isUtility","isUtilityColumn","#buildEvent","shiftKey","ctrlKey","newRange","isNavKey","isTabKey","shouldExtend","allRange","targetCol","firstDataCol","_event","#applySelectionClasses","gridEl","allRows","clearCellFocus","firstCell","getRowIndexFromCell","firstDataColIndex","effectiveStartCol","container","currentRow","currentCol","r"],"mappings":"+eAqCO,SAASA,EACdC,EACAC,EACAC,EACAC,EACgB,CAChB,MAAMC,EAAc,IAAI,IAAIJ,EAAM,QAAQ,EAC1C,IAAIK,EAASL,EAAM,OAEnB,GAAKE,IAAoB,SACvBE,EAAY,MAAA,EACZA,EAAY,IAAIH,CAAQ,EACxBI,EAASJ,UACCC,IAAoB,WAAY,CAC1C,MAAMI,EAAaH,EAAU,SAAWA,EAAU,QAElD,GAAIA,EAAU,UAAYH,EAAM,SAAW,KAAM,CAE/C,MAAMO,EAAQ,KAAK,IAAIP,EAAM,OAAQC,CAAQ,EACvCO,EAAM,KAAK,IAAIR,EAAM,OAAQC,CAAQ,EAC3C,QAASQ,EAAIF,EAAOE,GAAKD,EAAKC,IAC5BL,EAAY,IAAIK,CAAC,CAErB,MAAWH,GAELF,EAAY,IAAIH,CAAQ,EAC1BG,EAAY,OAAOH,CAAQ,EAE3BG,EAAY,IAAIH,CAAQ,EAE1BI,EAASJ,IAGTG,EAAY,MAAA,EACZA,EAAY,IAAIH,CAAQ,EACxBI,EAASJ,EAEb,CAEA,MAAO,CAAE,SAAUG,EAAa,aAAcH,EAAU,OAAAI,CAAA,CAC1D,CAQO,SAASK,EAAUC,EAA+B,CACvD,MAAMC,MAAe,IACrB,QAASH,EAAI,EAAGA,EAAIE,EAAUF,IAC5BG,EAAS,IAAIH,CAAC,EAEhB,OAAOG,CACT,CASO,SAASC,EACdC,EACAV,EACwC,CACxC,MAAMW,EAAkB,CAAA,EAClBC,EAAoB,CAAA,EAE1B,UAAWC,KAAOb,EACXU,EAAY,IAAIG,CAAG,GAAGF,EAAM,KAAKE,CAAG,EAE3C,UAAWA,KAAOH,EACXV,EAAY,IAAIa,CAAG,GAAGD,EAAQ,KAAKC,CAAG,EAG7C,MAAO,CAAE,MAAAF,EAAO,QAAAC,CAAA,CAClB,CCpGO,SAASE,EAAeC,EAA6C,CAC1E,MAAO,CACL,SAAU,KAAK,IAAIA,EAAM,SAAUA,EAAM,MAAM,EAC/C,SAAU,KAAK,IAAIA,EAAM,SAAUA,EAAM,MAAM,EAC/C,OAAQ,KAAK,IAAIA,EAAM,SAAUA,EAAM,MAAM,EAC7C,OAAQ,KAAK,IAAIA,EAAM,SAAUA,EAAM,MAAM,CAAA,CAEjD,CAQO,SAASC,EAAcD,EAAqC,CACjE,MAAME,EAAaH,EAAeC,CAAK,EACvC,MAAO,CACL,KAAM,CAAE,IAAKE,EAAW,SAAU,IAAKA,EAAW,QAAA,EAClD,GAAI,CAAE,IAAKA,EAAW,OAAQ,IAAKA,EAAW,MAAA,CAAO,CAEzD,CAQO,SAASC,EAAeC,EAA0C,CACvE,OAAOA,EAAO,IAAIH,CAAa,CACjC,CAUO,SAASI,EAAcC,EAAaC,EAAaP,EAAmC,CACzF,MAAME,EAAaH,EAAeC,CAAK,EACvC,OACEM,GAAOJ,EAAW,UAAYI,GAAOJ,EAAW,QAAUK,GAAOL,EAAW,UAAYK,GAAOL,EAAW,MAE9G,CAUO,SAASM,EAAiBF,EAAaC,EAAaH,EAAsC,CAC/F,OAAOA,EAAO,KAAMJ,GAAUK,EAAcC,EAAKC,EAAKP,CAAK,CAAC,CAC9D,CAQO,SAASS,EAAgBT,EAA+D,CAC7F,MAAMU,EAA6C,CAAA,EAC7CR,EAAaH,EAAeC,CAAK,EAEvC,QAASM,EAAMJ,EAAW,SAAUI,GAAOJ,EAAW,OAAQI,IAC5D,QAASC,EAAML,EAAW,SAAUK,GAAOL,EAAW,OAAQK,IAC5DG,EAAM,KAAK,CAAE,IAAAJ,EAAK,IAAAC,CAAA,CAAK,EAI3B,OAAOG,CACT,CASO,SAASC,EAAoBP,EAAkE,CACpG,MAAMQ,MAAc,IAEpB,UAAWZ,KAASI,EAClB,UAAWS,KAAQJ,EAAgBT,CAAK,EACtCY,EAAQ,IAAI,GAAGC,EAAK,GAAG,IAAIA,EAAK,GAAG,GAAIA,CAAI,EAI/C,MAAO,CAAC,GAAGD,EAAQ,QAAQ,CAC7B,CAuBO,SAASE,EACd5B,EACA6B,EACmB,CACnB,MAAO,CACL,SAAU7B,EAAO,IACjB,SAAUA,EAAO,IACjB,OAAQ6B,EAAQ,IAChB,OAAQA,EAAQ,GAAA,CAEpB,q5BC/GA,SAASC,EACPjC,EACAF,EAKAoC,EACuB,CACvB,GAAIlC,IAAS,QAAUF,EAAM,aAC3B,MAAO,CACL,KAAAE,EACA,OAAQ,CACN,CACE,KAAM,CAAE,IAAKF,EAAM,aAAa,IAAK,IAAKA,EAAM,aAAa,GAAA,EAC7D,GAAI,CAAE,IAAKA,EAAM,aAAa,IAAK,IAAKA,EAAM,aAAa,GAAA,CAAI,CACjE,CACF,EAIJ,GAAIE,IAAS,OAASF,EAAM,SAAS,KAAO,EAAG,CAC7C,MAAMuB,EAAS,CAAC,GAAGvB,EAAM,QAAQ,EAAE,IAAKC,IAAc,CACpD,KAAM,CAAE,IAAKA,EAAU,IAAK,CAAA,EAC5B,GAAI,CAAE,IAAKA,EAAU,IAAKmC,EAAW,CAAA,CAAE,EACvC,EACF,MAAO,CAAE,KAAAlC,EAAM,OAAAqB,CAAA,CACjB,CAEA,OAAIrB,IAAS,SAAWF,EAAM,OAAO,OAAS,EACrC,CAAE,KAAAE,EAAM,OAAQoB,EAAetB,EAAM,MAAM,CAAA,EAG7C,CAAE,KAAAE,EAAM,OAAQ,EAAC,CAC1B,CAUO,MAAMmC,UAAwBC,EAAAA,cAAgC,CAC1D,KAAO,YACE,OAASC,EAE3B,IAAuB,eAA0C,CAC/D,MAAO,CACL,KAAM,MAAA,CAEV,CAIQ,aAAe,IACf,aAA8B,KAC9B,OAAwB,KAGxB,OAA8B,CAAA,EAC9B,YAAwC,KACxC,WAAkD,KAClD,WAAa,GAGb,sBAAsD,KAGtD,aAAoD,KAMnD,QAAe,CACtB,KAAK,SAAS,MAAA,EACd,KAAK,OAAS,CAAA,EACd,KAAK,YAAc,KACnB,KAAK,WAAa,KAClB,KAAK,WAAa,GAClB,KAAK,aAAe,KACpB,KAAK,sBAAwB,IAC/B,CAMS,YAAYC,EAAgC,CACnD,KAAM,CAAE,SAAAvC,EAAU,SAAAwC,EAAU,cAAAC,CAAA,EAAkBF,EACxC,CAAE,KAAAtC,GAAS,KAAK,OAGhByC,EAAS,KAAK,QAAQF,CAAQ,EAC9BG,EAAYD,GAAUE,EAAAA,gBAAgBF,CAAM,EAGlD,GAAIzC,IAAS,OACX,OAAI0C,IAGJ,KAAK,aAAe,CAAE,IAAK3C,EAAU,IAAKwC,CAAA,EAC1C,KAAK,KAA4B,mBAAoB,KAAKK,GAAA,CAAa,EACvE,KAAK,mBAAA,GACE,GAIT,GAAI5C,IAAS,MACX,YAAK,SAAS,MAAA,EACd,KAAK,SAAS,IAAID,CAAQ,EAC1B,KAAK,aAAeA,EAEpB,KAAK,KAA4B,mBAAoB,KAAK6C,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,GAIT,GAAI5C,IAAS,QAAS,CAEpB,GAAI0C,EACF,MAAO,GAGT,MAAMG,EAAWL,EAAc,SACzBM,EAAUN,EAAc,SAAWA,EAAc,QAEvD,GAAIK,GAAY,KAAK,WAAY,CAE/B,MAAME,EAAWhB,EAAsB,KAAK,WAAY,CAAE,IAAKhC,EAAU,IAAKwC,EAAU,EAEpFO,EACE,KAAK,OAAO,OAAS,EACvB,KAAK,OAAO,KAAK,OAAO,OAAS,CAAC,EAAIC,EAEtC,KAAK,OAAO,KAAKA,CAAQ,EAG3B,KAAK,OAAS,CAACA,CAAQ,EAEzB,KAAK,YAAcA,CACrB,SAAWD,EAAS,CAClB,MAAMC,EAA8B,CAClC,SAAUhD,EACV,SAAUwC,EACV,OAAQxC,EACR,OAAQwC,CAAA,EAEV,KAAK,OAAO,KAAKQ,CAAQ,EACzB,KAAK,YAAcA,EACnB,KAAK,WAAa,CAAE,IAAKhD,EAAU,IAAKwC,CAAA,CAC1C,KAAO,CACL,MAAMQ,EAA8B,CAClC,SAAUhD,EACV,SAAUwC,EACV,OAAQxC,EACR,OAAQwC,CAAA,EAEV,KAAK,OAAS,CAACQ,CAAQ,EACvB,KAAK,YAAcA,EACnB,KAAK,WAAa,CAAE,IAAKhD,EAAU,IAAKwC,CAAA,CAC1C,CAEA,YAAK,KAA4B,mBAAoB,KAAKK,GAAA,CAAa,EAEvE,KAAK,mBAAA,EACE,EACT,CAEA,MAAO,EACT,CAES,UAAUN,EAA+B,CAChD,KAAM,CAAE,KAAAtC,GAAS,KAAK,OAEhBgD,EADU,CAAC,UAAW,YAAa,YAAa,aAAc,MAAO,OAAQ,MAAO,SAAU,UAAU,EACrF,SAASV,EAAM,GAAG,EAG3C,GAAIA,EAAM,MAAQ,SAChB,OAAItC,IAAS,OACX,KAAK,aAAe,KACXA,IAAS,OAClB,KAAK,SAAS,MAAA,EACd,KAAK,OAAS,MACLA,IAAS,UAClB,KAAK,OAAS,CAAA,EACd,KAAK,YAAc,KACnB,KAAK,WAAa,MAEpB,KAAK,KAA4B,mBAAoB,KAAK4C,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,GAIT,GAAI5C,IAAS,QAAUgD,EAErB,sBAAe,IAAM,CACnB,KAAK,aAAe,CAAE,IAAK,KAAK,KAAK,UAAW,IAAK,KAAK,KAAK,SAAA,EAC/D,KAAK,KAA4B,mBAAoB,KAAKJ,GAAA,CAAa,EACvE,KAAK,mBAAA,CACP,CAAC,EACM,GAIT,GAAI5C,IAAS,QAAUsC,EAAM,MAAQ,WAAaA,EAAM,MAAQ,aAE9D,sBAAe,IAAM,CACnB,KAAK,SAAS,MAAA,EACd,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,EACrC,KAAK,aAAe,KAAK,KAAK,UAC9B,KAAK,KAA4B,mBAAoB,KAAKM,GAAA,CAAa,EACvE,KAAK,mBAAA,CACP,CAAC,EACM,GAKT,GAAI5C,IAAS,SAAWgD,EAAU,CAEhC,MAAMC,EAAWX,EAAM,MAAQ,MACzBY,EAAeZ,EAAM,UAAY,CAACW,EAIxC,OAAIC,GAAgB,CAAC,KAAK,aACxB,KAAK,WAAa,CAAE,IAAK,KAAK,KAAK,UAAW,IAAK,KAAK,KAAK,SAAA,GAI/D,KAAK,sBAAwB,CAAE,SAAUA,CAAA,EAKzC,eAAe,IAAM,KAAK,oBAAoB,EAEvC,EACT,CAGA,GAAIlD,IAAS,SAAWsC,EAAM,MAAQ,MAAQA,EAAM,SAAWA,EAAM,SAAU,CAC7E,MAAM7B,EAAW,KAAK,KAAK,OACrByB,EAAW,KAAK,QAAQ,OAC9B,GAAIzB,EAAW,GAAKyB,EAAW,EAAG,CAChC,MAAMiB,EAA8B,CAClC,SAAU,EACV,SAAU,EACV,OAAQ1C,EAAW,EACnB,OAAQyB,EAAW,CAAA,EAErB,YAAK,OAAS,CAACiB,CAAQ,EACvB,KAAK,YAAcA,EACnB,KAAK,KAA4B,mBAAoB,KAAKP,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,EACT,CACF,CAEA,MAAO,EACT,CAES,gBAAgBN,EAAuC,CAG9D,GAFI,KAAK,OAAO,OAAS,SACrBA,EAAM,WAAa,QAAaA,EAAM,WAAa,QACnDA,EAAM,SAAW,EAAG,OAGxB,MAAMG,EAAS,KAAK,QAAQH,EAAM,QAAQ,EAM1C,GALIG,GAAUE,kBAAgBF,CAAM,GAKhCH,EAAM,cAAc,UAAY,KAAK,WACvC,OAIF,KAAK,WAAa,GAClB,MAAMvC,EAAWuC,EAAM,SACjBC,EAAWD,EAAM,SACvB,KAAK,WAAa,CAAE,IAAKvC,EAAU,IAAKwC,CAAA,EAExBD,EAAM,cAAc,SAAWA,EAAM,cAAc,UAEjE,KAAK,OAAS,CAAA,GAGhB,MAAMS,EAA8B,CAClC,SAAUhD,EACV,SAAUwC,EACV,OAAQxC,EACR,OAAQwC,CAAA,EAEV,YAAK,OAAO,KAAKQ,CAAQ,EACzB,KAAK,YAAcA,EAEnB,KAAK,KAA4B,mBAAoB,KAAKH,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,EACT,CAES,gBAAgBN,EAAuC,CAI9D,GAHI,KAAK,OAAO,OAAS,SACrB,CAAC,KAAK,YAAc,CAAC,KAAK,YAC1BA,EAAM,WAAa,QAAaA,EAAM,WAAa,QACnDA,EAAM,SAAW,EAAG,OAGxB,IAAIc,EAAYd,EAAM,SACtB,MAAMG,EAAS,KAAK,QAAQW,CAAS,EACrC,GAAIX,GAAUE,kBAAgBF,CAAM,EAAG,CAErC,MAAMY,EAAe,KAAK,QAAQ,UAAW7B,GAAQ,CAACmB,kBAAgBnB,CAAG,CAAC,EACtE6B,GAAgB,IAClBD,EAAYC,EAEhB,CAEA,MAAMN,EAAWhB,EAAsB,KAAK,WAAY,CAAE,IAAKO,EAAM,SAAU,IAAKc,EAAW,EAE/F,OAAI,KAAK,OAAO,OAAS,EACvB,KAAK,OAAO,KAAK,OAAO,OAAS,CAAC,EAAIL,EAEtC,KAAK,OAAO,KAAKA,CAAQ,EAE3B,KAAK,YAAcA,EAEnB,KAAK,KAA4B,mBAAoB,KAAKH,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,EACT,CAES,cAAcU,EAAwC,CAC7D,GAAI,KAAK,OAAO,OAAS,SACrB,KAAK,WACP,YAAK,WAAa,GACX,EAEX,CAMAC,IAA+B,CAC7B,MAAMC,EAAS,KAAK,YACpB,GAAI,CAACA,EAAQ,OAEb,KAAM,CAAE,KAAAxD,GAAS,KAAK,OAGLwD,EAAO,iBAAiB,OAAO,EACvC,QAAS1B,GAAS,CACzBA,EAAK,UAAU,OAAO,WAAY,MAAO,SAAU,QAAS,MAAM,CACpE,CAAC,EAED,MAAM2B,EAAUD,EAAO,iBAAiB,gBAAgB,EAoBxD,GAnBAC,EAAQ,QAASlC,GAAQ,CACvBA,EAAI,UAAU,OAAO,WAAY,WAAW,CAC9C,CAAC,EAGGvB,IAAS,QAEX0D,EAAAA,eAAeF,CAAM,EAErBC,EAAQ,QAASlC,GAAQ,CACvB,MAAMoC,EAAYpC,EAAI,cAAc,iBAAiB,EAC/CxB,EAAW6D,EAAAA,oBAAoBD,CAAS,EAC1C5D,GAAY,GAAK,KAAK,SAAS,IAAIA,CAAQ,GAC7CwB,EAAI,UAAU,IAAI,WAAY,WAAW,CAE7C,CAAC,GAICvB,IAAS,SAAW,KAAK,OAAO,OAAS,EAAG,CAE9C0D,EAAAA,eAAeF,CAAM,EAErB,MAAMrC,EAAa,KAAK,YAAcH,EAAe,KAAK,WAAW,EAAI,KAGnE6C,EAAoB,KAAK,QAAQ,UAAWrC,GAAQ,CAACmB,kBAAgBnB,CAAG,CAAC,EACtD,KAAK,QAAQ,OAAS,EAEjCgC,EAAO,iBAAiB,2BAA2B,EAC3D,QAAS1B,GAAS,CACtB,MAAM/B,EAAW,SAAS+B,EAAK,aAAa,UAAU,GAAK,KAAM,EAAE,EAC7DS,EAAW,SAAST,EAAK,aAAa,UAAU,GAAK,KAAM,EAAE,EACnE,GAAI/B,GAAY,GAAKwC,GAAY,EAAG,CAElC,MAAME,EAAS,KAAK,QAAQF,CAAQ,EACpC,GAAIE,GAAUE,kBAAgBF,CAAM,EAClC,OAKF,GAFgBhB,EAAiB1B,EAAUwC,EAAU,KAAK,MAAM,IAG9DT,EAAK,UAAU,IAAI,UAAU,EAEzBX,GAAY,CACVpB,IAAaoB,EAAW,UAAUW,EAAK,UAAU,IAAI,KAAK,EAC1D/B,IAAaoB,EAAW,QAAQW,EAAK,UAAU,IAAI,QAAQ,EAE/D,MAAMgC,EAAoB,KAAK,IAAI3C,EAAW,SAAU0C,CAAiB,EACrEtB,IAAauB,GAAmBhC,EAAK,UAAU,IAAI,OAAO,EAC1DS,IAAapB,EAAW,QAAQW,EAAK,UAAU,IAAI,MAAM,CAC/D,CAEJ,CACF,CAAC,CACH,CAIF,CAES,aAAoB,CAC3B,MAAM0B,EAAS,KAAK,YACpB,GAAI,CAACA,EAAQ,OAEb,MAAMO,EAAYP,EAAO,SAAS,CAAC,EAC7B,CAAE,KAAAxD,GAAS,KAAK,OAItB,GAAI,KAAK,uBAAyBA,IAAS,QAAS,CAClD,KAAM,CAAE,SAAA6C,GAAa,KAAK,sBAC1B,KAAK,sBAAwB,KAE7B,MAAMmB,EAAa,KAAK,KAAK,UACvBC,EAAa,KAAK,KAAK,UAE7B,GAAIpB,GAAY,KAAK,WAAY,CAE/B,MAAME,EAAWhB,EAAsB,KAAK,WAAY,CAAE,IAAKiC,EAAY,IAAKC,EAAY,EAC5F,KAAK,OAAS,CAAClB,CAAQ,EACvB,KAAK,YAAcA,CACrB,MAAYF,IAEV,KAAK,OAAS,CAAA,EACd,KAAK,YAAc,KACnB,KAAK,WAAa,CAAE,IAAKmB,EAAY,IAAKC,CAAA,GAG5C,KAAK,KAA4B,mBAAoB,KAAKrB,GAAA,CAAa,CACzE,CAGC,KAAK,KAA4B,aAAa,sBAAuB5C,CAAI,EAGtE+D,GACFA,EAAU,UAAU,OAAO,YAAa,KAAK,UAAU,EAGzD,KAAKR,GAAA,CACP,CAMS,gBAAuB,CAC9B,KAAKA,GAAA,CACP,CAqBA,cAAgC,CAC9B,MAAO,CACL,KAAM,KAAK,OAAO,KAClB,OAAQ,KAAKX,GAAA,EAAc,OAC3B,OAAQ,KAAK,UAAA,CAEjB,CAMA,iBAAuD,CACrD,KAAM,CAAE,KAAA5C,EAAM,OAAAqB,GAAW,KAAK,aAAA,EAC9B,OAAIrB,IAAS,QAAUqB,EAAO,OAAS,EAC9BA,EAAO,CAAC,EAAE,KAEZ,IACT,CAMA,iBAA4B,CAC1B,KAAM,CAAE,KAAArB,EAAM,OAAAqB,GAAW,KAAK,aAAA,EAC9B,OAAIrB,IAAS,MACJqB,EAAO,IAAK6C,GAAMA,EAAE,KAAK,GAAG,EAE9B,CAAA,CACT,CAMA,WAAyB,CACvB,OAAO,KAAK,eAAe,MAC7B,CAKA,kBAAwD,CACtD,OAAOtC,EAAoB,KAAK,MAAM,CACxC,CAKA,eAAeL,EAAaC,EAAsB,CAChD,OAAOC,EAAiBF,EAAKC,EAAK,KAAK,MAAM,CAC/C,CAKA,gBAAuB,CACrB,KAAK,aAAe,KACpB,KAAK,SAAS,MAAA,EACd,KAAK,OAAS,KACd,KAAK,OAAS,CAAA,EACd,KAAK,YAAc,KACnB,KAAK,WAAa,KAClB,KAAK,KAA4B,mBAAoB,CAAE,KAAM,KAAK,OAAO,KAAM,OAAQ,CAAA,EAAI,EAC3F,KAAK,mBAAA,CACP,CAKA,UAAUH,EAA2B,CACnC,KAAK,OAASA,EAAO,IAAK6C,IAAO,CAC/B,SAAUA,EAAE,KAAK,IACjB,SAAUA,EAAE,KAAK,IACjB,OAAQA,EAAE,GAAG,IACb,OAAQA,EAAE,GAAG,GAAA,EACb,EACF,KAAK,YAAc,KAAK,OAAO,OAAS,EAAI,KAAK,OAAO,KAAK,OAAO,OAAS,CAAC,EAAI,KAClF,KAAK,KAA4B,mBAAoB,CACnD,KAAM,KAAK,OAAO,KAClB,OAAQ9C,EAAe,KAAK,MAAM,CAAA,CACnC,EACD,KAAK,mBAAA,CACP,CAMAwB,IAAqC,CACnC,OAAOX,EACL,KAAK,OAAO,KACZ,CACE,aAAc,KAAK,aACnB,SAAU,KAAK,SACf,OAAQ,KAAK,MAAA,EAEf,KAAK,QAAQ,MAAA,CAEjB,CAGF"}
@@ -1,2 +1,2 @@
1
- (function(h,y){typeof exports=="object"&&typeof module<"u"?y(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],y):(h=typeof globalThis<"u"?globalThis:h||self,y(h.TbwGridPlugin_tree={},h.TbwGrid))})(this,(function(h,y){"use strict";function K(s,t,e){return s.id!==void 0?String(s.id):e?`${e}-${t}`:String(t)}function x(s,t){const e=new Set(s);return e.has(t)?e.delete(t):e.add(t),e}function m(s,t,e=null,n=0){const r=t.childrenField??"children",a=new Set;for(let i=0;i<s.length;i++){const l=s[i],o=K(l,i,e),d=l[r];if(Array.isArray(d)&&d.length>0){a.add(o);const f=m(d,t,o,n+1);for(const u of f)a.add(u)}}return a}function k(){return new Set}function A(s,t,e,n=null,r=0){const a=e.childrenField??"children";for(let i=0;i<s.length;i++){const l=s[i],o=K(l,i,n);if(o===t)return[o];const d=l[a];if(Array.isArray(d)&&d.length>0){const f=A(d,t,e,o,r+1);if(f)return[o,...f]}}return null}function v(s,t,e,n){const r=A(s,t,e);if(!r)return n;const a=new Set(n);for(let i=0;i<r.length-1;i++)a.add(r[i]);return a}function b(s,t="children"){if(!Array.isArray(s)||s.length===0)return!1;for(const e of s){if(!e)continue;const n=e[t];if(Array.isArray(n)&&n.length>0)return!0}return!1}function S(s){if(!Array.isArray(s)||s.length===0)return null;const t=["children","items","nodes","subRows","nested"];for(const e of s)if(!(!e||typeof e!="object"))for(const n of t){const r=e[n];if(Array.isArray(r)&&r.length>0)return n}return null}function R(s,t="children",e=0){if(!Array.isArray(s)||s.length===0)return e;let n=e;for(const r of s){if(!r)continue;const a=r[t];if(Array.isArray(a)&&a.length>0){const i=R(a,t,e+1);i>n&&(n=i)}}return n}function _(s,t="children"){if(!Array.isArray(s))return 0;let e=0;for(const n of s){if(!n)continue;e++;const r=n[t];Array.isArray(r)&&(e+=_(r,t))}return e}const E=".cell[data-field=__tbw_expander]{border-right:none!important;padding:0;display:flex;align-items:center;justify-content:flex-start}.header-row .cell[data-field=__tbw_expander]{visibility:hidden;border:none!important;padding:0;overflow:hidden}.header-row .cell[data-field=__tbw_expander]+.cell{margin-left:-32px;padding-left:calc(var(--tbw-cell-padding, 8px) + 32px)}.tree-expander{display:flex;align-items:center;justify-content:flex-start;width:100%;height:100%;box-sizing:border-box}.tree-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center}.tree-toggle:hover{color:var(--tbw-tree-accent, var(--tbw-color-accent))}.tree-spacer{width:14px;display:inline-block}.data-grid-row.tbw-tree-slide-in{animation:tbw-tree-slide-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-tree-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}.data-grid-row.tbw-tree-fade-in{animation:tbw-tree-fade-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-tree-fade-in{0%{opacity:0}to{opacity:1}}";class T extends y.BaseGridPlugin{name="tree";styles=E;get defaultConfig(){return{childrenField:"children",autoDetect:!0,defaultExpanded:!1,indentWidth:20,showExpandIcons:!0,animation:"slide"}}expandedKeys=new Set;initialExpansionDone=!1;flattenedRows=[];rowKeyMap=new Map;previousVisibleKeys=new Set;keysToAnimate=new Set;sortState=null;detach(){this.expandedKeys.clear(),this.initialExpansionDone=!1,this.flattenedRows=[],this.rowKeyMap.clear(),this.previousVisibleKeys.clear(),this.keysToAnimate.clear(),this.sortState=null}get animationStyle(){return this.isAnimationEnabled?this.config.animation??"slide":!1}detect(t){if(!this.config.autoDetect)return!1;const e=t,n=this.config.childrenField??S(e)??"children";return b(e,n)}processRows(t){const e=this.config.childrenField??"children",n=t;if(!b(n,e))return this.flattenedRows=[],this.rowKeyMap.clear(),this.previousVisibleKeys.clear(),[...t];let r=this.withStableKeys(n);this.sortState&&(r=this.sortTree(r,this.sortState.field,this.sortState.direction)),this.config.defaultExpanded&&!this.initialExpansionDone&&(this.expandedKeys=m(r,this.config),this.initialExpansionDone=!0),this.flattenedRows=this.flattenTree(r,this.expandedKeys),this.rowKeyMap.clear(),this.keysToAnimate.clear();const a=new Set;for(const i of this.flattenedRows)this.rowKeyMap.set(i.key,i),a.add(i.key),!this.previousVisibleKeys.has(i.key)&&i.depth>0&&this.keysToAnimate.add(i.key);return this.previousVisibleKeys=a,this.flattenedRows.map(i=>({...i.data,__treeKey:i.key,__treeDepth:i.depth,__treeHasChildren:i.hasChildren,__treeExpanded:i.isExpanded}))}withStableKeys(t,e=null){const n=this.config.childrenField??"children";return t.map((r,a)=>{const i=r.__stableKey,l=r.id!==void 0?String(r.id):i??(e?`${e}-${a}`:String(a)),o=r[n],d=Array.isArray(o)&&o.length>0;return{...r,__stableKey:l,...d?{[n]:this.withStableKeys(o,l)}:{}}})}flattenTree(t,e,n=0){const r=this.config.childrenField??"children",a=[];for(const i of t){const o=i.__stableKey??String(i.id??"?"),d=i[r],f=Array.isArray(d)&&d.length>0,u=e.has(o);a.push({key:o,data:i,depth:n,hasChildren:f,isExpanded:u,parentKey:n>0&&o.substring(0,o.lastIndexOf("-"))||null}),f&&u&&a.push(...this.flattenTree(d,e,n+1))}return a}sortTree(t,e,n){const r=this.config.childrenField??"children";return[...t].sort((i,l)=>{const o=i[e],d=l[e];return o==null&&d==null?0:o==null?-1:d==null?1:o>d?n:o<d?-n:0}).map(i=>{const l=i[r];return Array.isArray(l)&&l.length>0?{...i,[r]:this.sortTree(l,e,n)}:i})}processColumns(t){if(this.flattenedRows.length===0)return[...t];const e=[...t];if(e.length===0)return e;const n=e[0],r=n.viewRenderer,a=()=>this.config,i=this.setIcon.bind(this),l=this.resolveIcon.bind(this),o=d=>{const{row:f,value:u}=d,{indentWidth:C=20,showExpandIcons:F=!0}=a(),g=f,q=g.__treeDepth??0,p=document.createElement("span");if(p.className="tree-cell-wrapper",p.style.paddingLeft=`${Number(q)*C}px`,F)if(g.__treeHasChildren){const c=document.createElement("span");c.className=`tree-toggle${g.__treeExpanded?" expanded":""}`,i(c,l(g.__treeExpanded?"collapse":"expand")),c.setAttribute("data-tree-key",String(g.__treeKey??"")),p.appendChild(c)}else{const c=document.createElement("span");c.className="tree-spacer",p.appendChild(c)}const w=document.createElement("span");if(w.className="tree-content",r){const c=r(d);c instanceof Node?w.appendChild(c):typeof c=="string"&&(w.innerHTML=c)}else w.textContent=u!=null?String(u):"";return p.appendChild(w),p};return e[0]={...n,viewRenderer:o},e}onCellClick(t){const e=t.originalEvent?.target;if(!e?.classList.contains("tree-toggle"))return!1;const n=e.getAttribute("data-tree-key");if(!n)return!1;const r=this.rowKeyMap.get(n);return r?(this.expandedKeys=x(this.expandedKeys,n),this.emit("tree-expand",{key:n,row:r.data,expanded:this.expandedKeys.has(n),depth:r.depth}),this.requestRender(),!0):!1}onKeyDown(t){if(t.key!==" ")return;const e=this.grid._focusRow,n=this.flattenedRows[e];if(n?.hasChildren)return t.preventDefault(),this.expandedKeys=x(this.expandedKeys,n.key),this.emit("tree-expand",{key:n.key,row:n.data,expanded:this.expandedKeys.has(n.key),depth:n.depth}),this.requestRenderWithFocus(),!0}onHeaderClick(t){if(this.flattenedRows.length===0||!t.column.sortable)return!1;const{field:e}=t.column;!this.sortState||this.sortState.field!==e?this.sortState={field:e,direction:1}:this.sortState.direction===1?this.sortState={field:e,direction:-1}:this.sortState=null;const n=this.grid;return n._sortState!==void 0&&(n._sortState=this.sortState?{...this.sortState}:null),this.emit("sort-change",{field:e,direction:this.sortState?.direction??0}),this.requestRender(),!0}afterRender(){const t=this.animationStyle;if(t===!1||this.keysToAnimate.size===0)return;const e=this.shadowRoot?.querySelector(".rows");if(!e)return;const n=t==="fade"?"tbw-tree-fade-in":"tbw-tree-slide-in";for(const r of e.querySelectorAll(".data-grid-row")){const a=r.querySelector(".cell[data-row]"),i=a?parseInt(a.getAttribute("data-row")??"-1",10):-1,l=this.flattenedRows[i]?.key;l&&this.keysToAnimate.has(l)&&(r.classList.add(n),r.addEventListener("animationend",()=>r.classList.remove(n),{once:!0}))}this.keysToAnimate.clear()}expand(t){this.expandedKeys.add(t),this.requestRender()}collapse(t){this.expandedKeys.delete(t),this.requestRender()}toggle(t){this.expandedKeys=x(this.expandedKeys,t),this.requestRender()}expandAll(){this.expandedKeys=m(this.rows,this.config),this.requestRender()}collapseAll(){this.expandedKeys=k(),this.requestRender()}isExpanded(t){return this.expandedKeys.has(t)}getExpandedKeys(){return[...this.expandedKeys]}getFlattenedRows(){return[...this.flattenedRows]}getRowByKey(t){return this.rowKeyMap.get(t)?.data}expandToKey(t){this.expandedKeys=v(this.rows,t,this.config,this.expandedKeys),this.requestRender()}}h.TreePlugin=T,h.countNodes=_,h.detectTreeStructure=b,h.getMaxDepth=R,h.inferChildrenField=S,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(h,p){typeof exports=="object"&&typeof module<"u"?p(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],p):(h=typeof globalThis<"u"?globalThis:h||self,p(h.TbwGridPlugin_tree={},h.TbwGrid))})(this,(function(h,p){"use strict";function K(s,t,e){return s.id!==void 0?String(s.id):e?`${e}-${t}`:String(t)}function b(s,t){const e=new Set(s);return e.has(t)?e.delete(t):e.add(t),e}function x(s,t,e=null,n=0){const r=t.childrenField??"children",a=new Set;for(let i=0;i<s.length;i++){const d=s[i],o=K(d,i,e),l=d[r];if(Array.isArray(l)&&l.length>0){a.add(o);const f=x(l,t,o,n+1);for(const u of f)a.add(u)}}return a}function v(){return new Set}function S(s,t,e,n=null,r=0){const a=e.childrenField??"children";for(let i=0;i<s.length;i++){const d=s[i],o=K(d,i,n);if(o===t)return[o];const l=d[a];if(Array.isArray(l)&&l.length>0){const f=S(l,t,e,o,r+1);if(f)return[o,...f]}}return null}function E(s,t,e,n){const r=S(s,t,e);if(!r)return n;const a=new Set(n);for(let i=0;i<r.length-1;i++)a.add(r[i]);return a}function m(s,t="children"){if(!Array.isArray(s)||s.length===0)return!1;for(const e of s){if(!e)continue;const n=e[t];if(Array.isArray(n)&&n.length>0)return!0}return!1}function A(s){if(!Array.isArray(s)||s.length===0)return null;const t=["children","items","nodes","subRows","nested"];for(const e of s)if(!(!e||typeof e!="object"))for(const n of t){const r=e[n];if(Array.isArray(r)&&r.length>0)return n}return null}function R(s,t="children",e=0){if(!Array.isArray(s)||s.length===0)return e;let n=e;for(const r of s){if(!r)continue;const a=r[t];if(Array.isArray(a)&&a.length>0){const i=R(a,t,e+1);i>n&&(n=i)}}return n}function _(s,t="children"){if(!Array.isArray(s))return 0;let e=0;for(const n of s){if(!n)continue;e++;const r=n[t];Array.isArray(r)&&(e+=_(r,t))}return e}const T="@layer tbw-plugins{tbw-grid .cell[data-field=__tbw_expander]{border-right:none!important;padding:0;display:flex;align-items:center;justify-content:flex-start}tbw-grid .header-row .cell[data-field=__tbw_expander]{display:none}tbw-grid .header-row .cell[data-field=__tbw_expander]+.cell{grid-column:1 / 3}tbw-grid .tree-cell-wrapper{display:inline-flex;align-items:center;padding-left:calc(var(--tbw-tree-depth, 0) * var(--tbw-tree-indent-width, var(--tbw-tree-toggle-size, 1.25em)))}tbw-grid .tree-expander{display:flex;align-items:center;justify-content:flex-start;width:100%;height:100%;box-sizing:border-box}tbw-grid .tree-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center;width:var(--tbw-tree-toggle-size, 1.25em);height:var(--tbw-tree-toggle-size, 1.25em);flex-shrink:0}tbw-grid .tree-toggle:hover{color:var(--tbw-tree-accent, var(--tbw-color-accent))}tbw-grid .tree-spacer{width:var(--tbw-tree-toggle-size, 1.25em);display:inline-block;flex-shrink:0}tbw-grid .data-grid-row.tbw-tree-slide-in{animation:tbw-tree-slide-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}tbw-grid .data-grid-row.tbw-tree-fade-in{animation:tbw-tree-fade-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-tree-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}@keyframes tbw-tree-fade-in{0%{opacity:0}to{opacity:1}}}";class C extends p.BaseGridPlugin{name="tree";styles=T;get defaultConfig(){return{childrenField:"children",autoDetect:!0,defaultExpanded:!1,indentWidth:20,showExpandIcons:!0,animation:"slide"}}expandedKeys=new Set;initialExpansionDone=!1;flattenedRows=[];rowKeyMap=new Map;previousVisibleKeys=new Set;keysToAnimate=new Set;sortState=null;detach(){this.expandedKeys.clear(),this.initialExpansionDone=!1,this.flattenedRows=[],this.rowKeyMap.clear(),this.previousVisibleKeys.clear(),this.keysToAnimate.clear(),this.sortState=null}get animationStyle(){return this.isAnimationEnabled?this.config.animation??"slide":!1}detect(t){if(!this.config.autoDetect)return!1;const e=t,n=this.config.childrenField??A(e)??"children";return m(e,n)}processRows(t){const e=this.config.childrenField??"children",n=t;if(!m(n,e))return this.flattenedRows=[],this.rowKeyMap.clear(),this.previousVisibleKeys.clear(),[...t];let r=this.withStableKeys(n);this.sortState&&(r=this.sortTree(r,this.sortState.field,this.sortState.direction)),this.config.defaultExpanded&&!this.initialExpansionDone&&(this.expandedKeys=x(r,this.config),this.initialExpansionDone=!0),this.flattenedRows=this.flattenTree(r,this.expandedKeys),this.rowKeyMap.clear(),this.keysToAnimate.clear();const a=new Set;for(const i of this.flattenedRows)this.rowKeyMap.set(i.key,i),a.add(i.key),!this.previousVisibleKeys.has(i.key)&&i.depth>0&&this.keysToAnimate.add(i.key);return this.previousVisibleKeys=a,this.flattenedRows.map(i=>({...i.data,__treeKey:i.key,__treeDepth:i.depth,__treeHasChildren:i.hasChildren,__treeExpanded:i.isExpanded}))}withStableKeys(t,e=null){const n=this.config.childrenField??"children";return t.map((r,a)=>{const i=r.__stableKey,d=r.id!==void 0?String(r.id):i??(e?`${e}-${a}`:String(a)),o=r[n],l=Array.isArray(o)&&o.length>0;return{...r,__stableKey:d,...l?{[n]:this.withStableKeys(o,d)}:{}}})}flattenTree(t,e,n=0){const r=this.config.childrenField??"children",a=[];for(const i of t){const o=i.__stableKey??String(i.id??"?"),l=i[r],f=Array.isArray(l)&&l.length>0,u=e.has(o);a.push({key:o,data:i,depth:n,hasChildren:f,isExpanded:u,parentKey:n>0&&o.substring(0,o.lastIndexOf("-"))||null}),f&&u&&a.push(...this.flattenTree(l,e,n+1))}return a}sortTree(t,e,n){const r=this.config.childrenField??"children";return[...t].sort((i,d)=>{const o=i[e],l=d[e];return o==null&&l==null?0:o==null?-1:l==null?1:o>l?n:o<l?-n:0}).map(i=>{const d=i[r];return Array.isArray(d)&&d.length>0?{...i,[r]:this.sortTree(d,e,n)}:i})}processColumns(t){if(this.flattenedRows.length===0)return[...t];const e=[...t];if(e.length===0)return e;const n=e[0],r=n.viewRenderer,a=()=>this.config,i=this.setIcon.bind(this),d=this.resolveIcon.bind(this),o=l=>{const{row:f,value:u}=l,{showExpandIcons:F=!0,indentWidth:k}=a(),g=f,q=g.__treeDepth??0,y=document.createElement("span");if(y.className="tree-cell-wrapper",y.style.setProperty("--tbw-tree-depth",String(q)),k!==void 0&&y.style.setProperty("--tbw-tree-indent-width",`${k}px`),F)if(g.__treeHasChildren){const c=document.createElement("span");c.className=`tree-toggle${g.__treeExpanded?" expanded":""}`,i(c,d(g.__treeExpanded?"collapse":"expand")),c.setAttribute("data-tree-key",String(g.__treeKey??"")),y.appendChild(c)}else{const c=document.createElement("span");c.className="tree-spacer",y.appendChild(c)}const w=document.createElement("span");if(w.className="tree-content",r){const c=r(l);c instanceof Node?w.appendChild(c):typeof c=="string"&&(w.innerHTML=c)}else w.textContent=u!=null?String(u):"";return y.appendChild(w),y};return e[0]={...n,viewRenderer:o},e}onCellClick(t){const e=t.originalEvent?.target;if(!e?.classList.contains("tree-toggle"))return!1;const n=e.getAttribute("data-tree-key");if(!n)return!1;const r=this.rowKeyMap.get(n);return r?(this.expandedKeys=b(this.expandedKeys,n),this.emit("tree-expand",{key:n,row:r.data,expanded:this.expandedKeys.has(n),depth:r.depth}),this.requestRender(),!0):!1}onKeyDown(t){if(t.key!==" ")return;const e=this.grid._focusRow,n=this.flattenedRows[e];if(n?.hasChildren)return t.preventDefault(),this.expandedKeys=b(this.expandedKeys,n.key),this.emit("tree-expand",{key:n.key,row:n.data,expanded:this.expandedKeys.has(n.key),depth:n.depth}),this.requestRenderWithFocus(),!0}onHeaderClick(t){if(this.flattenedRows.length===0||!t.column.sortable)return!1;const{field:e}=t.column;!this.sortState||this.sortState.field!==e?this.sortState={field:e,direction:1}:this.sortState.direction===1?this.sortState={field:e,direction:-1}:this.sortState=null;const n=this.grid;return n._sortState!==void 0&&(n._sortState=this.sortState?{...this.sortState}:null),this.emit("sort-change",{field:e,direction:this.sortState?.direction??0}),this.requestRender(),!0}afterRender(){const t=this.animationStyle;if(t===!1||this.keysToAnimate.size===0)return;const e=this.gridElement?.querySelector(".rows");if(!e)return;const n=t==="fade"?"tbw-tree-fade-in":"tbw-tree-slide-in";for(const r of e.querySelectorAll(".data-grid-row")){const a=r.querySelector(".cell[data-row]"),i=a?parseInt(a.getAttribute("data-row")??"-1",10):-1,d=this.flattenedRows[i]?.key;d&&this.keysToAnimate.has(d)&&(r.classList.add(n),r.addEventListener("animationend",()=>r.classList.remove(n),{once:!0}))}this.keysToAnimate.clear()}expand(t){this.expandedKeys.add(t),this.requestRender()}collapse(t){this.expandedKeys.delete(t),this.requestRender()}toggle(t){this.expandedKeys=b(this.expandedKeys,t),this.requestRender()}expandAll(){this.expandedKeys=x(this.rows,this.config),this.requestRender()}collapseAll(){this.expandedKeys=v(),this.requestRender()}isExpanded(t){return this.expandedKeys.has(t)}getExpandedKeys(){return[...this.expandedKeys]}getFlattenedRows(){return[...this.flattenedRows]}getRowByKey(t){return this.rowKeyMap.get(t)?.data}expandToKey(t){this.expandedKeys=E(this.rows,t,this.config,this.expandedKeys),this.requestRender()}}h.TreePlugin=C,h.countNodes=_,h.detectTreeStructure=m,h.getMaxDepth=R,h.inferChildrenField=A,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
2
2
  //# sourceMappingURL=tree.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tree.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/tree/tree-data.ts","../../../../../libs/grid/src/lib/plugins/tree/tree-detect.ts","../../../../../libs/grid/src/lib/plugins/tree/TreePlugin.ts"],"sourcesContent":["/**\n * Core Tree Data Logic\n *\n * Pure functions for tree flattening, expansion, and traversal.\n */\n\nimport type { FlattenedTreeRow, TreeConfig, TreeRow } from './types';\n\n/**\n * Generates a unique key for a row.\n * Uses row.id if available, otherwise generates from path.\n */\nexport function generateRowKey(row: TreeRow, index: number, parentKey: string | null): string {\n if (row.id !== undefined) return String(row.id);\n return parentKey ? `${parentKey}-${index}` : String(index);\n}\n\n/**\n * Flattens a hierarchical tree into a flat array of rows with metadata.\n * Only includes children of expanded nodes.\n */\nexport function flattenTree(\n rows: readonly TreeRow[],\n config: TreeConfig,\n expandedKeys: Set<string>,\n parentKey: string | null = null,\n depth = 0,\n): FlattenedTreeRow[] {\n const childrenField = config.childrenField ?? 'children';\n const result: FlattenedTreeRow[] = [];\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n const children = row[childrenField];\n const hasChildren = Array.isArray(children) && children.length > 0;\n const isExpanded = expandedKeys.has(key);\n\n result.push({\n key,\n data: row,\n depth,\n hasChildren,\n isExpanded,\n parentKey,\n });\n\n // Recursively add children if expanded\n if (hasChildren && isExpanded) {\n const childRows = flattenTree(children as TreeRow[], config, expandedKeys, key, depth + 1);\n result.push(...childRows);\n }\n }\n\n return result;\n}\n\n/**\n * Toggles the expansion state of a row.\n * Returns a new Set with the toggled state.\n */\nexport function toggleExpand(expandedKeys: Set<string>, key: string): Set<string> {\n const newExpanded = new Set(expandedKeys);\n if (newExpanded.has(key)) {\n newExpanded.delete(key);\n } else {\n newExpanded.add(key);\n }\n return newExpanded;\n}\n\n/**\n * Expands all nodes in the tree.\n * Returns a Set of all parent row keys.\n */\nexport function expandAll(\n rows: readonly TreeRow[],\n config: TreeConfig,\n parentKey: string | null = null,\n depth = 0,\n): Set<string> {\n const childrenField = config.childrenField ?? 'children';\n const keys = new Set<string>();\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n const children = row[childrenField];\n\n if (Array.isArray(children) && children.length > 0) {\n keys.add(key);\n const childKeys = expandAll(children as TreeRow[], config, key, depth + 1);\n for (const k of childKeys) keys.add(k);\n }\n }\n\n return keys;\n}\n\n/**\n * Collapses all nodes.\n * Returns an empty Set.\n */\nexport function collapseAll(): Set<string> {\n return new Set();\n}\n\n/**\n * Gets all descendants of a node from the flattened row list.\n * Useful for operations that need to affect an entire subtree.\n */\nexport function getDescendants(flattenedRows: FlattenedTreeRow[], parentKey: string): FlattenedTreeRow[] {\n const descendants: FlattenedTreeRow[] = [];\n let collecting = false;\n let parentDepth = -1;\n\n for (const row of flattenedRows) {\n if (row.key === parentKey) {\n collecting = true;\n parentDepth = row.depth;\n continue;\n }\n\n if (collecting) {\n if (row.depth > parentDepth) {\n descendants.push(row);\n } else {\n break; // No longer a descendant\n }\n }\n }\n\n return descendants;\n}\n\n/**\n * Finds the path from root to a specific row key.\n * Returns an array of keys from root to the target (inclusive).\n */\nexport function getPathToKey(\n rows: readonly TreeRow[],\n targetKey: string,\n config: TreeConfig,\n parentKey: string | null = null,\n depth = 0,\n): string[] | null {\n const childrenField = config.childrenField ?? 'children';\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n\n if (key === targetKey) {\n return [key];\n }\n\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n const childPath = getPathToKey(children as TreeRow[], targetKey, config, key, depth + 1);\n if (childPath) {\n return [key, ...childPath];\n }\n }\n }\n\n return null;\n}\n\n/**\n * Expands all ancestors of a specific row to make it visible.\n * Returns a new Set with the required keys added.\n */\nexport function expandToKey(\n rows: readonly TreeRow[],\n targetKey: string,\n config: TreeConfig,\n existingExpanded: Set<string>,\n): Set<string> {\n const path = getPathToKey(rows, targetKey, config);\n if (!path) return existingExpanded;\n\n const newExpanded = new Set(existingExpanded);\n // Add all keys except the last one (the target itself)\n for (let i = 0; i < path.length - 1; i++) {\n newExpanded.add(path[i]);\n }\n return newExpanded;\n}\n","/**\n * Tree Structure Auto-Detection\n *\n * Utilities for detecting hierarchical tree data structures.\n */\n\nimport type { TreeRow } from './types';\n\n/**\n * Detects if the data has a tree structure by checking for children arrays.\n */\nexport function detectTreeStructure(rows: readonly TreeRow[], childrenField = 'children'): boolean {\n if (!Array.isArray(rows) || rows.length === 0) return false;\n\n // Check if any row has a non-empty children array\n for (const row of rows) {\n if (!row) continue;\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Attempts to infer the children field name from common patterns.\n * Returns the first field that contains an array with items.\n */\nexport function inferChildrenField(rows: readonly TreeRow[]): string | null {\n if (!Array.isArray(rows) || rows.length === 0) return null;\n\n const commonArrayFields = ['children', 'items', 'nodes', 'subRows', 'nested'];\n\n for (const row of rows) {\n if (!row || typeof row !== 'object') continue;\n\n for (const field of commonArrayFields) {\n const value = row[field];\n if (Array.isArray(value) && value.length > 0) {\n return field;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Calculates the maximum depth of the tree.\n * Useful for layout calculations and virtualization.\n */\nexport function getMaxDepth(rows: readonly TreeRow[], childrenField = 'children', currentDepth = 0): number {\n if (!Array.isArray(rows) || rows.length === 0) return currentDepth;\n\n let maxDepth = currentDepth;\n\n for (const row of rows) {\n if (!row) continue;\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n const childDepth = getMaxDepth(children as TreeRow[], childrenField, currentDepth + 1);\n if (childDepth > maxDepth) {\n maxDepth = childDepth;\n }\n }\n }\n\n return maxDepth;\n}\n\n/**\n * Counts total nodes in the tree (including all descendants).\n */\nexport function countNodes(rows: readonly TreeRow[], childrenField = 'children'): number {\n if (!Array.isArray(rows)) return 0;\n\n let count = 0;\n for (const row of rows) {\n if (!row) continue;\n count++;\n const children = row[childrenField];\n if (Array.isArray(children)) {\n count += countNodes(children as TreeRow[], childrenField);\n }\n }\n\n return count;\n}\n","/**\n * Tree Data Plugin\n *\n * Enables hierarchical tree data with expand/collapse, sorting, and auto-detection.\n */\n\nimport { BaseGridPlugin, CellClickEvent, HeaderClickEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ColumnViewRenderer } from '../../core/types';\nimport { collapseAll, expandAll, expandToKey, toggleExpand } from './tree-data';\nimport { detectTreeStructure, inferChildrenField } from './tree-detect';\nimport styles from './tree.css?inline';\nimport type { ExpandCollapseAnimation, FlattenedTreeRow, TreeConfig, TreeExpandDetail, TreeRow } from './types';\n\ninterface GridWithSortState {\n _sortState?: { field: string; direction: 1 | -1 } | null;\n}\n\n/**\n * Tree Data Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new TreePlugin({ defaultExpanded: true, indentWidth: 24 })\n * ```\n */\nexport class TreePlugin extends BaseGridPlugin<TreeConfig> {\n readonly name = 'tree';\n override readonly styles = styles;\n\n protected override get defaultConfig(): Partial<TreeConfig> {\n return {\n childrenField: 'children',\n autoDetect: true,\n defaultExpanded: false,\n indentWidth: 20,\n showExpandIcons: true,\n animation: 'slide',\n };\n }\n\n // #region State\n\n private expandedKeys = new Set<string>();\n private initialExpansionDone = false;\n private flattenedRows: FlattenedTreeRow[] = [];\n private rowKeyMap = new Map<string, FlattenedTreeRow>();\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n private sortState: { field: string; direction: 1 | -1 } | null = null;\n\n override detach(): void {\n this.expandedKeys.clear();\n this.initialExpansionDone = false;\n this.flattenedRows = [];\n this.rowKeyMap.clear();\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.sortState = null;\n }\n\n // #endregion\n\n // #region Animation\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Auto-Detection\n\n detect(rows: readonly unknown[]): boolean {\n if (!this.config.autoDetect) return false;\n const treeRows = rows as readonly TreeRow[];\n const field = this.config.childrenField ?? inferChildrenField(treeRows) ?? 'children';\n return detectTreeStructure(treeRows, field);\n }\n\n // #endregion\n\n // #region Data Processing\n\n override processRows(rows: readonly unknown[]): TreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n const treeRows = rows as readonly TreeRow[];\n\n if (!detectTreeStructure(treeRows, childrenField)) {\n this.flattenedRows = [];\n this.rowKeyMap.clear();\n this.previousVisibleKeys.clear();\n return [...rows] as TreeRow[];\n }\n\n // Assign stable keys, then optionally sort\n let data = this.withStableKeys(treeRows);\n if (this.sortState) {\n data = this.sortTree(data, this.sortState.field, this.sortState.direction);\n }\n\n // Initialize expansion if needed\n if (this.config.defaultExpanded && !this.initialExpansionDone) {\n this.expandedKeys = expandAll(data, this.config);\n this.initialExpansionDone = true;\n }\n\n // Flatten and track animations\n this.flattenedRows = this.flattenTree(data, this.expandedKeys);\n this.rowKeyMap.clear();\n this.keysToAnimate.clear();\n const currentKeys = new Set<string>();\n\n for (const row of this.flattenedRows) {\n this.rowKeyMap.set(row.key, row);\n currentKeys.add(row.key);\n if (!this.previousVisibleKeys.has(row.key) && row.depth > 0) {\n this.keysToAnimate.add(row.key);\n }\n }\n this.previousVisibleKeys = currentKeys;\n\n return this.flattenedRows.map((r) => ({\n ...r.data,\n __treeKey: r.key,\n __treeDepth: r.depth,\n __treeHasChildren: r.hasChildren,\n __treeExpanded: r.isExpanded,\n }));\n }\n\n /** Assign stable keys to rows (preserves key across sort operations) */\n private withStableKeys(rows: readonly TreeRow[], parentKey: string | null = null): TreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n return rows.map((row, i) => {\n const stableKey = row.__stableKey as string | undefined;\n const key = row.id !== undefined ? String(row.id) : (stableKey ?? (parentKey ? `${parentKey}-${i}` : String(i)));\n const children = row[childrenField];\n const hasChildren = Array.isArray(children) && children.length > 0;\n return {\n ...row,\n __stableKey: key,\n ...(hasChildren ? { [childrenField]: this.withStableKeys(children as TreeRow[], key) } : {}),\n };\n });\n }\n\n /** Flatten tree using stable keys */\n private flattenTree(rows: readonly TreeRow[], expanded: Set<string>, depth = 0): FlattenedTreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n const result: FlattenedTreeRow[] = [];\n\n for (const row of rows) {\n const stableKey = row.__stableKey as string | undefined;\n const key = stableKey ?? String(row.id ?? '?');\n const children = row[childrenField];\n const hasChildren = Array.isArray(children) && children.length > 0;\n const isExpanded = expanded.has(key);\n\n result.push({\n key,\n data: row,\n depth,\n hasChildren,\n isExpanded,\n parentKey: depth > 0 ? key.substring(0, key.lastIndexOf('-')) || null : null,\n });\n\n if (hasChildren && isExpanded) {\n result.push(...this.flattenTree(children as TreeRow[], expanded, depth + 1));\n }\n }\n return result;\n }\n\n /** Sort tree recursively, keeping children with parents */\n private sortTree(rows: readonly TreeRow[], field: string, dir: 1 | -1): TreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n const sorted = [...rows].sort((a, b) => {\n const aVal = a[field],\n bVal = b[field];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n return aVal > bVal ? dir : aVal < bVal ? -dir : 0;\n });\n return sorted.map((row) => {\n const children = row[childrenField];\n return Array.isArray(children) && children.length > 0\n ? { ...row, [childrenField]: this.sortTree(children as TreeRow[], field, dir) }\n : row;\n });\n }\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (this.flattenedRows.length === 0) return [...columns];\n\n const cols = [...columns] as ColumnConfig[];\n if (cols.length === 0) return cols;\n\n // Wrap the first column's renderer to add tree indentation and expand icons\n // This is the correct approach for trees because:\n // 1. Indentation can grow naturally with depth\n // 2. Expand icons appear inline with content\n // 3. Works with column reordering (icons stay with first visible column)\n const firstCol = cols[0];\n const originalRenderer = firstCol.viewRenderer;\n const getConfig = () => this.config;\n const setIcon = this.setIcon.bind(this);\n const resolveIcon = this.resolveIcon.bind(this);\n\n const wrappedRenderer: ColumnViewRenderer = (ctx) => {\n const { row, value } = ctx;\n const { indentWidth = 20, showExpandIcons = true } = getConfig();\n const treeRow = row as TreeRow;\n const depth = treeRow.__treeDepth ?? 0;\n\n const container = document.createElement('span');\n container.className = 'tree-cell-wrapper';\n container.style.paddingLeft = `${Number(depth) * indentWidth}px`;\n\n // Add expand/collapse icon or spacer\n if (showExpandIcons) {\n if (treeRow.__treeHasChildren) {\n const icon = document.createElement('span');\n icon.className = `tree-toggle${treeRow.__treeExpanded ? ' expanded' : ''}`;\n setIcon(icon, resolveIcon(treeRow.__treeExpanded ? 'collapse' : 'expand'));\n icon.setAttribute('data-tree-key', String(treeRow.__treeKey ?? ''));\n container.appendChild(icon);\n } else {\n const spacer = document.createElement('span');\n spacer.className = 'tree-spacer';\n container.appendChild(spacer);\n }\n }\n\n // Add the original content\n const content = document.createElement('span');\n content.className = 'tree-content';\n if (originalRenderer) {\n const result = originalRenderer(ctx);\n if (result instanceof Node) {\n content.appendChild(result);\n } else if (typeof result === 'string') {\n content.innerHTML = result;\n }\n } else {\n content.textContent = value != null ? String(value) : '';\n }\n container.appendChild(content);\n\n return container;\n };\n\n cols[0] = { ...firstCol, viewRenderer: wrappedRenderer };\n return cols;\n }\n\n // #endregion\n\n // #region Event Handlers\n\n override onCellClick(event: CellClickEvent): boolean {\n const target = event.originalEvent?.target as HTMLElement;\n if (!target?.classList.contains('tree-toggle')) return false;\n\n const key = target.getAttribute('data-tree-key');\n if (!key) return false;\n\n const flatRow = this.rowKeyMap.get(key);\n if (!flatRow) return false;\n\n this.expandedKeys = toggleExpand(this.expandedKeys, key);\n this.emit<TreeExpandDetail>('tree-expand', {\n key,\n row: flatRow.data,\n expanded: this.expandedKeys.has(key),\n depth: flatRow.depth,\n });\n this.requestRender();\n return true;\n }\n\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion when on a row with children\n if (event.key !== ' ') return;\n\n const focusRow = this.grid._focusRow;\n const flatRow = this.flattenedRows[focusRow];\n if (!flatRow?.hasChildren) return;\n\n event.preventDefault();\n this.expandedKeys = toggleExpand(this.expandedKeys, flatRow.key);\n this.emit<TreeExpandDetail>('tree-expand', {\n key: flatRow.key,\n row: flatRow.data,\n expanded: this.expandedKeys.has(flatRow.key),\n depth: flatRow.depth,\n });\n this.requestRenderWithFocus();\n return true;\n }\n\n override onHeaderClick(event: HeaderClickEvent): boolean {\n if (this.flattenedRows.length === 0 || !event.column.sortable) return false;\n\n const { field } = event.column;\n if (!this.sortState || this.sortState.field !== field) {\n this.sortState = { field, direction: 1 };\n } else if (this.sortState.direction === 1) {\n this.sortState = { field, direction: -1 };\n } else {\n this.sortState = null;\n }\n\n // Sync grid sort indicator\n const gridEl = this.grid as unknown as GridWithSortState;\n if (gridEl._sortState !== undefined) {\n gridEl._sortState = this.sortState ? { ...this.sortState } : null;\n }\n\n this.emit('sort-change', { field, direction: this.sortState?.direction ?? 0 });\n this.requestRender();\n return true;\n }\n\n override afterRender(): void {\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.shadowRoot?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-tree-fade-in' : 'tbw-tree-slide-in';\n for (const rowEl of body.querySelectorAll('.data-grid-row')) {\n const cell = rowEl.querySelector('.cell[data-row]');\n const idx = cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n const key = this.flattenedRows[idx]?.key;\n\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n\n // #endregion\n\n // #region Public API\n\n expand(key: string): void {\n this.expandedKeys.add(key);\n this.requestRender();\n }\n\n collapse(key: string): void {\n this.expandedKeys.delete(key);\n this.requestRender();\n }\n\n toggle(key: string): void {\n this.expandedKeys = toggleExpand(this.expandedKeys, key);\n this.requestRender();\n }\n\n expandAll(): void {\n this.expandedKeys = expandAll(this.rows as TreeRow[], this.config);\n this.requestRender();\n }\n\n collapseAll(): void {\n this.expandedKeys = collapseAll();\n this.requestRender();\n }\n\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n getExpandedKeys(): string[] {\n return [...this.expandedKeys];\n }\n\n getFlattenedRows(): FlattenedTreeRow[] {\n return [...this.flattenedRows];\n }\n\n getRowByKey(key: string): TreeRow | undefined {\n return this.rowKeyMap.get(key)?.data;\n }\n\n expandToKey(key: string): void {\n this.expandedKeys = expandToKey(this.rows as TreeRow[], key, this.config, this.expandedKeys);\n this.requestRender();\n }\n\n // #endregion\n}\n"],"names":["generateRowKey","row","index","parentKey","toggleExpand","expandedKeys","key","newExpanded","expandAll","rows","config","depth","childrenField","keys","children","childKeys","k","collapseAll","getPathToKey","targetKey","childPath","expandToKey","existingExpanded","path","detectTreeStructure","inferChildrenField","commonArrayFields","field","value","getMaxDepth","currentDepth","maxDepth","childDepth","countNodes","count","TreePlugin","BaseGridPlugin","styles","treeRows","data","currentKeys","r","i","stableKey","hasChildren","expanded","result","isExpanded","dir","a","b","aVal","bVal","columns","cols","firstCol","originalRenderer","getConfig","setIcon","resolveIcon","wrappedRenderer","ctx","indentWidth","showExpandIcons","treeRow","container","icon","spacer","content","event","target","flatRow","focusRow","gridEl","style","body","animClass","rowEl","cell","idx"],"mappings":"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"}
1
+ {"version":3,"file":"tree.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/tree/tree-data.ts","../../../../../libs/grid/src/lib/plugins/tree/tree-detect.ts","../../../../../libs/grid/src/lib/plugins/tree/TreePlugin.ts"],"sourcesContent":["/**\n * Core Tree Data Logic\n *\n * Pure functions for tree flattening, expansion, and traversal.\n */\n\nimport type { FlattenedTreeRow, TreeConfig, TreeRow } from './types';\n\n/**\n * Generates a unique key for a row.\n * Uses row.id if available, otherwise generates from path.\n */\nexport function generateRowKey(row: TreeRow, index: number, parentKey: string | null): string {\n if (row.id !== undefined) return String(row.id);\n return parentKey ? `${parentKey}-${index}` : String(index);\n}\n\n/**\n * Flattens a hierarchical tree into a flat array of rows with metadata.\n * Only includes children of expanded nodes.\n */\nexport function flattenTree(\n rows: readonly TreeRow[],\n config: TreeConfig,\n expandedKeys: Set<string>,\n parentKey: string | null = null,\n depth = 0,\n): FlattenedTreeRow[] {\n const childrenField = config.childrenField ?? 'children';\n const result: FlattenedTreeRow[] = [];\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n const children = row[childrenField];\n const hasChildren = Array.isArray(children) && children.length > 0;\n const isExpanded = expandedKeys.has(key);\n\n result.push({\n key,\n data: row,\n depth,\n hasChildren,\n isExpanded,\n parentKey,\n });\n\n // Recursively add children if expanded\n if (hasChildren && isExpanded) {\n const childRows = flattenTree(children as TreeRow[], config, expandedKeys, key, depth + 1);\n result.push(...childRows);\n }\n }\n\n return result;\n}\n\n/**\n * Toggles the expansion state of a row.\n * Returns a new Set with the toggled state.\n */\nexport function toggleExpand(expandedKeys: Set<string>, key: string): Set<string> {\n const newExpanded = new Set(expandedKeys);\n if (newExpanded.has(key)) {\n newExpanded.delete(key);\n } else {\n newExpanded.add(key);\n }\n return newExpanded;\n}\n\n/**\n * Expands all nodes in the tree.\n * Returns a Set of all parent row keys.\n */\nexport function expandAll(\n rows: readonly TreeRow[],\n config: TreeConfig,\n parentKey: string | null = null,\n depth = 0,\n): Set<string> {\n const childrenField = config.childrenField ?? 'children';\n const keys = new Set<string>();\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n const children = row[childrenField];\n\n if (Array.isArray(children) && children.length > 0) {\n keys.add(key);\n const childKeys = expandAll(children as TreeRow[], config, key, depth + 1);\n for (const k of childKeys) keys.add(k);\n }\n }\n\n return keys;\n}\n\n/**\n * Collapses all nodes.\n * Returns an empty Set.\n */\nexport function collapseAll(): Set<string> {\n return new Set();\n}\n\n/**\n * Gets all descendants of a node from the flattened row list.\n * Useful for operations that need to affect an entire subtree.\n */\nexport function getDescendants(flattenedRows: FlattenedTreeRow[], parentKey: string): FlattenedTreeRow[] {\n const descendants: FlattenedTreeRow[] = [];\n let collecting = false;\n let parentDepth = -1;\n\n for (const row of flattenedRows) {\n if (row.key === parentKey) {\n collecting = true;\n parentDepth = row.depth;\n continue;\n }\n\n if (collecting) {\n if (row.depth > parentDepth) {\n descendants.push(row);\n } else {\n break; // No longer a descendant\n }\n }\n }\n\n return descendants;\n}\n\n/**\n * Finds the path from root to a specific row key.\n * Returns an array of keys from root to the target (inclusive).\n */\nexport function getPathToKey(\n rows: readonly TreeRow[],\n targetKey: string,\n config: TreeConfig,\n parentKey: string | null = null,\n depth = 0,\n): string[] | null {\n const childrenField = config.childrenField ?? 'children';\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n\n if (key === targetKey) {\n return [key];\n }\n\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n const childPath = getPathToKey(children as TreeRow[], targetKey, config, key, depth + 1);\n if (childPath) {\n return [key, ...childPath];\n }\n }\n }\n\n return null;\n}\n\n/**\n * Expands all ancestors of a specific row to make it visible.\n * Returns a new Set with the required keys added.\n */\nexport function expandToKey(\n rows: readonly TreeRow[],\n targetKey: string,\n config: TreeConfig,\n existingExpanded: Set<string>,\n): Set<string> {\n const path = getPathToKey(rows, targetKey, config);\n if (!path) return existingExpanded;\n\n const newExpanded = new Set(existingExpanded);\n // Add all keys except the last one (the target itself)\n for (let i = 0; i < path.length - 1; i++) {\n newExpanded.add(path[i]);\n }\n return newExpanded;\n}\n","/**\n * Tree Structure Auto-Detection\n *\n * Utilities for detecting hierarchical tree data structures.\n */\n\nimport type { TreeRow } from './types';\n\n/**\n * Detects if the data has a tree structure by checking for children arrays.\n */\nexport function detectTreeStructure(rows: readonly TreeRow[], childrenField = 'children'): boolean {\n if (!Array.isArray(rows) || rows.length === 0) return false;\n\n // Check if any row has a non-empty children array\n for (const row of rows) {\n if (!row) continue;\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Attempts to infer the children field name from common patterns.\n * Returns the first field that contains an array with items.\n */\nexport function inferChildrenField(rows: readonly TreeRow[]): string | null {\n if (!Array.isArray(rows) || rows.length === 0) return null;\n\n const commonArrayFields = ['children', 'items', 'nodes', 'subRows', 'nested'];\n\n for (const row of rows) {\n if (!row || typeof row !== 'object') continue;\n\n for (const field of commonArrayFields) {\n const value = row[field];\n if (Array.isArray(value) && value.length > 0) {\n return field;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Calculates the maximum depth of the tree.\n * Useful for layout calculations and virtualization.\n */\nexport function getMaxDepth(rows: readonly TreeRow[], childrenField = 'children', currentDepth = 0): number {\n if (!Array.isArray(rows) || rows.length === 0) return currentDepth;\n\n let maxDepth = currentDepth;\n\n for (const row of rows) {\n if (!row) continue;\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n const childDepth = getMaxDepth(children as TreeRow[], childrenField, currentDepth + 1);\n if (childDepth > maxDepth) {\n maxDepth = childDepth;\n }\n }\n }\n\n return maxDepth;\n}\n\n/**\n * Counts total nodes in the tree (including all descendants).\n */\nexport function countNodes(rows: readonly TreeRow[], childrenField = 'children'): number {\n if (!Array.isArray(rows)) return 0;\n\n let count = 0;\n for (const row of rows) {\n if (!row) continue;\n count++;\n const children = row[childrenField];\n if (Array.isArray(children)) {\n count += countNodes(children as TreeRow[], childrenField);\n }\n }\n\n return count;\n}\n","/**\n * Tree Data Plugin\n *\n * Enables hierarchical tree data with expand/collapse, sorting, and auto-detection.\n */\n\nimport { BaseGridPlugin, CellClickEvent, HeaderClickEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ColumnViewRenderer } from '../../core/types';\nimport { collapseAll, expandAll, expandToKey, toggleExpand } from './tree-data';\nimport { detectTreeStructure, inferChildrenField } from './tree-detect';\nimport styles from './tree.css?inline';\nimport type { ExpandCollapseAnimation, FlattenedTreeRow, TreeConfig, TreeExpandDetail, TreeRow } from './types';\n\ninterface GridWithSortState {\n _sortState?: { field: string; direction: 1 | -1 } | null;\n}\n\n/**\n * Tree Data Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new TreePlugin({ defaultExpanded: true, indentWidth: 24 })\n * ```\n */\nexport class TreePlugin extends BaseGridPlugin<TreeConfig> {\n readonly name = 'tree';\n override readonly styles = styles;\n\n protected override get defaultConfig(): Partial<TreeConfig> {\n return {\n childrenField: 'children',\n autoDetect: true,\n defaultExpanded: false,\n indentWidth: 20,\n showExpandIcons: true,\n animation: 'slide',\n };\n }\n\n // #region State\n\n private expandedKeys = new Set<string>();\n private initialExpansionDone = false;\n private flattenedRows: FlattenedTreeRow[] = [];\n private rowKeyMap = new Map<string, FlattenedTreeRow>();\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n private sortState: { field: string; direction: 1 | -1 } | null = null;\n\n override detach(): void {\n this.expandedKeys.clear();\n this.initialExpansionDone = false;\n this.flattenedRows = [];\n this.rowKeyMap.clear();\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.sortState = null;\n }\n\n // #endregion\n\n // #region Animation\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Auto-Detection\n\n detect(rows: readonly unknown[]): boolean {\n if (!this.config.autoDetect) return false;\n const treeRows = rows as readonly TreeRow[];\n const field = this.config.childrenField ?? inferChildrenField(treeRows) ?? 'children';\n return detectTreeStructure(treeRows, field);\n }\n\n // #endregion\n\n // #region Data Processing\n\n override processRows(rows: readonly unknown[]): TreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n const treeRows = rows as readonly TreeRow[];\n\n if (!detectTreeStructure(treeRows, childrenField)) {\n this.flattenedRows = [];\n this.rowKeyMap.clear();\n this.previousVisibleKeys.clear();\n return [...rows] as TreeRow[];\n }\n\n // Assign stable keys, then optionally sort\n let data = this.withStableKeys(treeRows);\n if (this.sortState) {\n data = this.sortTree(data, this.sortState.field, this.sortState.direction);\n }\n\n // Initialize expansion if needed\n if (this.config.defaultExpanded && !this.initialExpansionDone) {\n this.expandedKeys = expandAll(data, this.config);\n this.initialExpansionDone = true;\n }\n\n // Flatten and track animations\n this.flattenedRows = this.flattenTree(data, this.expandedKeys);\n this.rowKeyMap.clear();\n this.keysToAnimate.clear();\n const currentKeys = new Set<string>();\n\n for (const row of this.flattenedRows) {\n this.rowKeyMap.set(row.key, row);\n currentKeys.add(row.key);\n if (!this.previousVisibleKeys.has(row.key) && row.depth > 0) {\n this.keysToAnimate.add(row.key);\n }\n }\n this.previousVisibleKeys = currentKeys;\n\n return this.flattenedRows.map((r) => ({\n ...r.data,\n __treeKey: r.key,\n __treeDepth: r.depth,\n __treeHasChildren: r.hasChildren,\n __treeExpanded: r.isExpanded,\n }));\n }\n\n /** Assign stable keys to rows (preserves key across sort operations) */\n private withStableKeys(rows: readonly TreeRow[], parentKey: string | null = null): TreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n return rows.map((row, i) => {\n const stableKey = row.__stableKey as string | undefined;\n const key = row.id !== undefined ? String(row.id) : (stableKey ?? (parentKey ? `${parentKey}-${i}` : String(i)));\n const children = row[childrenField];\n const hasChildren = Array.isArray(children) && children.length > 0;\n return {\n ...row,\n __stableKey: key,\n ...(hasChildren ? { [childrenField]: this.withStableKeys(children as TreeRow[], key) } : {}),\n };\n });\n }\n\n /** Flatten tree using stable keys */\n private flattenTree(rows: readonly TreeRow[], expanded: Set<string>, depth = 0): FlattenedTreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n const result: FlattenedTreeRow[] = [];\n\n for (const row of rows) {\n const stableKey = row.__stableKey as string | undefined;\n const key = stableKey ?? String(row.id ?? '?');\n const children = row[childrenField];\n const hasChildren = Array.isArray(children) && children.length > 0;\n const isExpanded = expanded.has(key);\n\n result.push({\n key,\n data: row,\n depth,\n hasChildren,\n isExpanded,\n parentKey: depth > 0 ? key.substring(0, key.lastIndexOf('-')) || null : null,\n });\n\n if (hasChildren && isExpanded) {\n result.push(...this.flattenTree(children as TreeRow[], expanded, depth + 1));\n }\n }\n return result;\n }\n\n /** Sort tree recursively, keeping children with parents */\n private sortTree(rows: readonly TreeRow[], field: string, dir: 1 | -1): TreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n const sorted = [...rows].sort((a, b) => {\n const aVal = a[field],\n bVal = b[field];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n return aVal > bVal ? dir : aVal < bVal ? -dir : 0;\n });\n return sorted.map((row) => {\n const children = row[childrenField];\n return Array.isArray(children) && children.length > 0\n ? { ...row, [childrenField]: this.sortTree(children as TreeRow[], field, dir) }\n : row;\n });\n }\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (this.flattenedRows.length === 0) return [...columns];\n\n const cols = [...columns] as ColumnConfig[];\n if (cols.length === 0) return cols;\n\n // Wrap the first column's renderer to add tree indentation and expand icons\n // This is the correct approach for trees because:\n // 1. Indentation can grow naturally with depth\n // 2. Expand icons appear inline with content\n // 3. Works with column reordering (icons stay with first visible column)\n const firstCol = cols[0];\n const originalRenderer = firstCol.viewRenderer;\n const getConfig = () => this.config;\n const setIcon = this.setIcon.bind(this);\n const resolveIcon = this.resolveIcon.bind(this);\n\n const wrappedRenderer: ColumnViewRenderer = (ctx) => {\n const { row, value } = ctx;\n const { showExpandIcons = true, indentWidth } = getConfig();\n const treeRow = row as TreeRow;\n const depth = treeRow.__treeDepth ?? 0;\n\n const container = document.createElement('span');\n container.className = 'tree-cell-wrapper';\n container.style.setProperty('--tbw-tree-depth', String(depth));\n // Allow config-based indentWidth to override CSS default\n if (indentWidth !== undefined) {\n container.style.setProperty('--tbw-tree-indent-width', `${indentWidth}px`);\n }\n\n // Add expand/collapse icon or spacer\n if (showExpandIcons) {\n if (treeRow.__treeHasChildren) {\n const icon = document.createElement('span');\n icon.className = `tree-toggle${treeRow.__treeExpanded ? ' expanded' : ''}`;\n setIcon(icon, resolveIcon(treeRow.__treeExpanded ? 'collapse' : 'expand'));\n icon.setAttribute('data-tree-key', String(treeRow.__treeKey ?? ''));\n container.appendChild(icon);\n } else {\n const spacer = document.createElement('span');\n spacer.className = 'tree-spacer';\n container.appendChild(spacer);\n }\n }\n\n // Add the original content\n const content = document.createElement('span');\n content.className = 'tree-content';\n if (originalRenderer) {\n const result = originalRenderer(ctx);\n if (result instanceof Node) {\n content.appendChild(result);\n } else if (typeof result === 'string') {\n content.innerHTML = result;\n }\n } else {\n content.textContent = value != null ? String(value) : '';\n }\n container.appendChild(content);\n\n return container;\n };\n\n cols[0] = { ...firstCol, viewRenderer: wrappedRenderer };\n return cols;\n }\n\n // #endregion\n\n // #region Event Handlers\n\n override onCellClick(event: CellClickEvent): boolean {\n const target = event.originalEvent?.target as HTMLElement;\n if (!target?.classList.contains('tree-toggle')) return false;\n\n const key = target.getAttribute('data-tree-key');\n if (!key) return false;\n\n const flatRow = this.rowKeyMap.get(key);\n if (!flatRow) return false;\n\n this.expandedKeys = toggleExpand(this.expandedKeys, key);\n this.emit<TreeExpandDetail>('tree-expand', {\n key,\n row: flatRow.data,\n expanded: this.expandedKeys.has(key),\n depth: flatRow.depth,\n });\n this.requestRender();\n return true;\n }\n\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion when on a row with children\n if (event.key !== ' ') return;\n\n const focusRow = this.grid._focusRow;\n const flatRow = this.flattenedRows[focusRow];\n if (!flatRow?.hasChildren) return;\n\n event.preventDefault();\n this.expandedKeys = toggleExpand(this.expandedKeys, flatRow.key);\n this.emit<TreeExpandDetail>('tree-expand', {\n key: flatRow.key,\n row: flatRow.data,\n expanded: this.expandedKeys.has(flatRow.key),\n depth: flatRow.depth,\n });\n this.requestRenderWithFocus();\n return true;\n }\n\n override onHeaderClick(event: HeaderClickEvent): boolean {\n if (this.flattenedRows.length === 0 || !event.column.sortable) return false;\n\n const { field } = event.column;\n if (!this.sortState || this.sortState.field !== field) {\n this.sortState = { field, direction: 1 };\n } else if (this.sortState.direction === 1) {\n this.sortState = { field, direction: -1 };\n } else {\n this.sortState = null;\n }\n\n // Sync grid sort indicator\n const gridEl = this.grid as unknown as GridWithSortState;\n if (gridEl._sortState !== undefined) {\n gridEl._sortState = this.sortState ? { ...this.sortState } : null;\n }\n\n this.emit('sort-change', { field, direction: this.sortState?.direction ?? 0 });\n this.requestRender();\n return true;\n }\n\n override afterRender(): void {\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-tree-fade-in' : 'tbw-tree-slide-in';\n for (const rowEl of body.querySelectorAll('.data-grid-row')) {\n const cell = rowEl.querySelector('.cell[data-row]');\n const idx = cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n const key = this.flattenedRows[idx]?.key;\n\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n\n // #endregion\n\n // #region Public API\n\n expand(key: string): void {\n this.expandedKeys.add(key);\n this.requestRender();\n }\n\n collapse(key: string): void {\n this.expandedKeys.delete(key);\n this.requestRender();\n }\n\n toggle(key: string): void {\n this.expandedKeys = toggleExpand(this.expandedKeys, key);\n this.requestRender();\n }\n\n expandAll(): void {\n this.expandedKeys = expandAll(this.rows as TreeRow[], this.config);\n this.requestRender();\n }\n\n collapseAll(): void {\n this.expandedKeys = collapseAll();\n this.requestRender();\n }\n\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n getExpandedKeys(): string[] {\n return [...this.expandedKeys];\n }\n\n getFlattenedRows(): FlattenedTreeRow[] {\n return [...this.flattenedRows];\n }\n\n getRowByKey(key: string): TreeRow | undefined {\n return this.rowKeyMap.get(key)?.data;\n }\n\n expandToKey(key: string): void {\n this.expandedKeys = expandToKey(this.rows as TreeRow[], key, this.config, this.expandedKeys);\n this.requestRender();\n }\n\n // #endregion\n}\n"],"names":["generateRowKey","row","index","parentKey","toggleExpand","expandedKeys","key","newExpanded","expandAll","rows","config","depth","childrenField","keys","children","childKeys","k","collapseAll","getPathToKey","targetKey","childPath","expandToKey","existingExpanded","path","detectTreeStructure","inferChildrenField","commonArrayFields","field","value","getMaxDepth","currentDepth","maxDepth","childDepth","countNodes","count","TreePlugin","BaseGridPlugin","styles","treeRows","data","currentKeys","r","i","stableKey","hasChildren","expanded","result","isExpanded","dir","a","b","aVal","bVal","columns","cols","firstCol","originalRenderer","getConfig","setIcon","resolveIcon","wrappedRenderer","ctx","showExpandIcons","indentWidth","treeRow","container","icon","spacer","content","event","target","flatRow","focusRow","gridEl","style","body","animClass","rowEl","cell","idx"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- (function(d,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],c):(d=typeof globalThis<"u"?globalThis:d||self,c(d.TbwGridPlugin_visibility={},d.TbwGrid))})(this,(function(d,c){"use strict";const h='.tbw-visibility-content{display:flex;flex-direction:column;height:100%}.tbw-visibility-list{flex:1;overflow-y:auto;padding:8px}.tbw-visibility-row{display:flex;align-items:center;gap:8px;padding:6px 4px;cursor:pointer;font-size:13px;border-radius:var(--tbw-border-radius, 4px);position:relative}.tbw-visibility-row:hover{background:var(--tbw-visibility-hover, var(--tbw-color-row-hover, #f3f4f6))}.tbw-visibility-row input[type=checkbox]{cursor:pointer}.tbw-visibility-row.locked span{color:var(--tbw-color-fg-muted, #888)}.tbw-visibility-handle{cursor:grab;color:var(--tbw-color-fg-muted, #888);font-size:10px;letter-spacing:-2px;-webkit-user-select:none;user-select:none;flex-shrink:0}.tbw-visibility-row.reorderable:hover .tbw-visibility-handle{color:var(--tbw-color-fg, #1f2937)}.tbw-visibility-label{display:flex;align-items:center;gap:8px;flex:1;cursor:pointer}.tbw-visibility-row.dragging{opacity:.5;cursor:grabbing}.tbw-visibility-row.drop-before:before{content:"";position:absolute;left:0;right:0;top:0;height:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent, #3b82f6))}.tbw-visibility-row.drop-after:after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent, #3b82f6))}.tbw-visibility-show-all{margin:8px;padding:8px 12px;border:1px solid var(--tbw-visibility-border, var(--tbw-color-border, #e5e7eb));border-radius:var(--tbw-border-radius, 4px);background:var(--tbw-visibility-btn-bg, var(--tbw-color-header-bg, #f9fafb));color:var(--tbw-color-fg, #1f2937);cursor:pointer;font-size:13px}.tbw-visibility-show-all:hover{background:var(--tbw-visibility-hover, var(--tbw-color-row-hover, #f3f4f6))}';function p(f){const e=f.meta??{};return e.lockPosition!==!0&&e.suppressMovable!==!0}class a extends c.BaseGridPlugin{static dependencies=[{name:"reorder",required:!1,reason:"Enables drag-to-reorder columns in visibility panel"}];name="visibility";static PANEL_ID="columns";styles=h;get defaultConfig(){return{allowHideAll:!1}}columnListElement=null;isDragging=!1;draggedField=null;draggedIndex=null;dropIndex=null;clearDragClasses(e){e.querySelectorAll(".tbw-visibility-row").forEach(l=>{l.classList.remove("dragging","drop-target","drop-before","drop-after")})}detach(){this.columnListElement=null,this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null}getToolPanel(){return{id:a.PANEL_ID,title:"Columns",icon:"☰",tooltip:"Column visibility",order:100,render:e=>this.renderPanelContent(e)}}show(){this.grid.openToolPanel(),this.grid.expandedToolPanelSections.includes(a.PANEL_ID)||this.grid.toggleToolPanelSection(a.PANEL_ID)}hide(){this.grid.closeToolPanel()}toggle(){this.grid.isToolPanelOpen||this.grid.openToolPanel(),this.grid.toggleToolPanelSection(a.PANEL_ID)}isColumnVisible(e){return this.grid.isColumnVisible(e)}setColumnVisible(e,l){this.grid.setColumnVisible(e,l)}getVisibleColumns(){return this.grid.getAllColumns().filter(e=>e.visible).map(e=>e.field)}getHiddenColumns(){return this.grid.getAllColumns().filter(e=>!e.visible).map(e=>e.field)}showAll(){this.grid.showAllColumns()}toggleColumn(e){this.grid.toggleColumnVisibility(e)}showColumn(e){this.setColumnVisible(e,!0)}hideColumn(e){this.setColumnVisible(e,!1)}getAllColumns(){return this.grid.getAllColumns()}isPanelVisible(){return this.grid.isToolPanelOpen&&this.grid.expandedToolPanelSections.includes(a.PANEL_ID)}renderPanelContent(e){const l=document.createElement("div");l.className="tbw-visibility-content";const s=document.createElement("div");s.className="tbw-visibility-list",l.appendChild(s);const r=document.createElement("button");return r.className="tbw-visibility-show-all",r.textContent="Show All",r.addEventListener("click",()=>{this.grid.showAllColumns(),this.rebuildToggles(s)}),l.appendChild(r),this.columnListElement=s,this.rebuildToggles(s),e.appendChild(l),()=>{this.columnListElement=null,l.remove()}}hasReorderPlugin(){const e=this.grid?.getPluginByName?.("reorder");return!!(e&&typeof e.moveColumn=="function")}rebuildToggles(e){const l=this.hasReorderPlugin();e.innerHTML="";const s=this.grid.getAllColumns().filter(r=>!r.utility);for(let r=0;r<s.length;r++){const t=s[r],g=t.header||t.field,i=document.createElement("div");i.className=t.lockVisible?"tbw-visibility-row locked":"tbw-visibility-row",i.setAttribute("data-field",t.field),i.setAttribute("data-index",String(r)),l&&p(t)&&(i.draggable=!0,i.classList.add("reorderable"),this.setupDragListeners(i,t.field,r,e));const o=document.createElement("label");o.className="tbw-visibility-label";const n=document.createElement("input");n.type="checkbox",n.checked=t.visible,n.disabled=t.lockVisible??!1,n.addEventListener("change",()=>{this.grid.toggleColumnVisibility(t.field),setTimeout(()=>this.rebuildToggles(e),0)});const b=document.createElement("span");if(b.textContent=g,o.appendChild(n),o.appendChild(b),l&&p(t)){const u=document.createElement("span");u.className="tbw-visibility-handle",this.setIcon(u,this.resolveIcon("dragHandle")),u.title="Drag to reorder",i.appendChild(u)}i.appendChild(o),e.appendChild(i)}}setupDragListeners(e,l,s,r){e.addEventListener("dragstart",t=>{this.isDragging=!0,this.draggedField=l,this.draggedIndex=s,t.dataTransfer&&(t.dataTransfer.effectAllowed="move",t.dataTransfer.setData("text/plain",l)),e.classList.add("dragging")}),e.addEventListener("dragend",()=>{this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null,this.clearDragClasses(r)}),e.addEventListener("dragover",t=>{if(t.preventDefault(),!this.isDragging||this.draggedField===l)return;const g=e.getBoundingClientRect(),i=g.top+g.height/2;this.dropIndex=t.clientY<i?s:s+1,r.querySelectorAll(".tbw-visibility-row").forEach(o=>{o!==e&&o.classList.remove("drop-target","drop-before","drop-after")}),e.classList.add("drop-target"),e.classList.toggle("drop-before",t.clientY<i),e.classList.toggle("drop-after",t.clientY>=i)}),e.addEventListener("dragleave",()=>{e.classList.remove("drop-target","drop-before","drop-after")}),e.addEventListener("drop",t=>{t.preventDefault();const g=this.draggedField,i=this.draggedIndex,o=this.dropIndex;if(!this.isDragging||g===null||i===null||o===null)return;const n=o>i?o-1:o;if(n!==i){const b={field:g,fromIndex:i,toIndex:n};this.emit("column-reorder-request",b),setTimeout(()=>{this.rebuildToggles(r)},0)}})}}d.VisibilityPlugin=a,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(d,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],c):(d=typeof globalThis<"u"?globalThis:d||self,c(d.TbwGridPlugin_visibility={},d.TbwGrid))})(this,(function(d,c){"use strict";const f='@layer tbw-plugins{.tbw-visibility-content{display:flex;flex-direction:column;height:100%}.tbw-visibility-list{flex:1;overflow-y:auto;padding:var(--tbw-panel-padding, var(--tbw-spacing-md, .5rem))}.tbw-visibility-row{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));padding:var(--tbw-menu-item-padding, .375rem .25rem);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem);border-radius:var(--tbw-border-radius, .25rem);position:relative}.tbw-visibility-row:hover{background:var(--tbw-visibility-hover, var(--tbw-color-row-hover, #f3f4f6))}.tbw-visibility-row input[type=checkbox]{cursor:pointer}.tbw-visibility-row.locked span{color:var(--tbw-color-fg-muted, #888)}.tbw-visibility-handle{cursor:grab;color:var(--tbw-color-fg-muted, #888);font-size:var(--tbw-font-size-2xs, .625rem);letter-spacing:-2px;-webkit-user-select:none;user-select:none;flex-shrink:0}.tbw-visibility-row.reorderable:hover .tbw-visibility-handle{color:var(--tbw-color-fg, #1f2937)}.tbw-visibility-label{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));flex:1;cursor:pointer}.tbw-visibility-row.dragging{opacity:.5;cursor:grabbing}.tbw-visibility-row.drop-before:before{content:"";position:absolute;left:0;right:0;top:0;height:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent, #3b82f6))}.tbw-visibility-row.drop-after:after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent, #3b82f6))}.tbw-visibility-show-all{margin:var(--tbw-panel-padding, var(--tbw-spacing-md, .5rem));padding:var(--tbw-button-padding, .5rem .75rem);border:1px solid var(--tbw-visibility-border, var(--tbw-color-border, #e5e7eb));border-radius:var(--tbw-border-radius, .25rem);background:var(--tbw-visibility-btn-bg, var(--tbw-color-header-bg, #f9fafb));color:var(--tbw-color-fg, #1f2937);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem)}.tbw-visibility-show-all:hover{background:var(--tbw-visibility-hover, var(--tbw-color-row-hover, #f3f4f6))}}';function h(v){const e=v.meta??{};return e.lockPosition!==!0&&e.suppressMovable!==!0}class a extends c.BaseGridPlugin{static dependencies=[{name:"reorder",required:!1,reason:"Enables drag-to-reorder columns in visibility panel"}];name="visibility";static PANEL_ID="columns";styles=f;get defaultConfig(){return{allowHideAll:!1}}columnListElement=null;isDragging=!1;draggedField=null;draggedIndex=null;dropIndex=null;clearDragClasses(e){e.querySelectorAll(".tbw-visibility-row").forEach(l=>{l.classList.remove("dragging","drop-target","drop-before","drop-after")})}detach(){this.columnListElement=null,this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null}getToolPanel(){return{id:a.PANEL_ID,title:"Columns",icon:"☰",tooltip:"Column visibility",order:100,render:e=>this.renderPanelContent(e)}}show(){this.grid.openToolPanel(),this.grid.expandedToolPanelSections.includes(a.PANEL_ID)||this.grid.toggleToolPanelSection(a.PANEL_ID)}hide(){this.grid.closeToolPanel()}toggle(){this.grid.isToolPanelOpen||this.grid.openToolPanel(),this.grid.toggleToolPanelSection(a.PANEL_ID)}isColumnVisible(e){return this.grid.isColumnVisible(e)}setColumnVisible(e,l){this.grid.setColumnVisible(e,l)}getVisibleColumns(){return this.grid.getAllColumns().filter(e=>e.visible).map(e=>e.field)}getHiddenColumns(){return this.grid.getAllColumns().filter(e=>!e.visible).map(e=>e.field)}showAll(){this.grid.showAllColumns()}toggleColumn(e){this.grid.toggleColumnVisibility(e)}showColumn(e){this.setColumnVisible(e,!0)}hideColumn(e){this.setColumnVisible(e,!1)}getAllColumns(){return this.grid.getAllColumns()}isPanelVisible(){return this.grid.isToolPanelOpen&&this.grid.expandedToolPanelSections.includes(a.PANEL_ID)}renderPanelContent(e){const l=document.createElement("div");l.className="tbw-visibility-content";const n=document.createElement("div");n.className="tbw-visibility-list",l.appendChild(n);const r=document.createElement("button");return r.className="tbw-visibility-show-all",r.textContent="Show All",r.addEventListener("click",()=>{this.grid.showAllColumns(),this.rebuildToggles(n)}),l.appendChild(r),this.columnListElement=n,this.rebuildToggles(n),e.appendChild(l),()=>{this.columnListElement=null,l.remove()}}hasReorderPlugin(){const e=this.grid?.getPluginByName?.("reorder");return!!(e&&typeof e.moveColumn=="function")}rebuildToggles(e){const l=this.hasReorderPlugin();e.innerHTML="";const n=this.grid.getAllColumns().filter(r=>!r.utility);for(let r=0;r<n.length;r++){const t=n[r],g=t.header||t.field,i=document.createElement("div");i.className=t.lockVisible?"tbw-visibility-row locked":"tbw-visibility-row",i.setAttribute("data-field",t.field),i.setAttribute("data-index",String(r)),l&&h(t)&&(i.draggable=!0,i.classList.add("reorderable"),this.setupDragListeners(i,t.field,r,e));const s=document.createElement("label");s.className="tbw-visibility-label";const o=document.createElement("input");o.type="checkbox",o.checked=t.visible,o.disabled=t.lockVisible??!1,o.addEventListener("change",()=>{this.grid.toggleColumnVisibility(t.field),setTimeout(()=>this.rebuildToggles(e),0)});const b=document.createElement("span");if(b.textContent=g,s.appendChild(o),s.appendChild(b),l&&h(t)){const u=document.createElement("span");u.className="tbw-visibility-handle",this.setIcon(u,this.resolveIcon("dragHandle")),u.title="Drag to reorder",i.appendChild(u)}i.appendChild(s),e.appendChild(i)}}setupDragListeners(e,l,n,r){e.addEventListener("dragstart",t=>{this.isDragging=!0,this.draggedField=l,this.draggedIndex=n,t.dataTransfer&&(t.dataTransfer.effectAllowed="move",t.dataTransfer.setData("text/plain",l)),e.classList.add("dragging")}),e.addEventListener("dragend",()=>{this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null,this.clearDragClasses(r)}),e.addEventListener("dragover",t=>{if(t.preventDefault(),!this.isDragging||this.draggedField===l)return;const g=e.getBoundingClientRect(),i=g.top+g.height/2;this.dropIndex=t.clientY<i?n:n+1,r.querySelectorAll(".tbw-visibility-row").forEach(s=>{s!==e&&s.classList.remove("drop-target","drop-before","drop-after")}),e.classList.add("drop-target"),e.classList.toggle("drop-before",t.clientY<i),e.classList.toggle("drop-after",t.clientY>=i)}),e.addEventListener("dragleave",()=>{e.classList.remove("drop-target","drop-before","drop-after")}),e.addEventListener("drop",t=>{t.preventDefault();const g=this.draggedField,i=this.draggedIndex,s=this.dropIndex;if(!this.isDragging||g===null||i===null||s===null)return;const o=s>i?s-1:s;if(o!==i){const b=this.grid.getAllColumns(),m=b.filter(p=>!p.utility)[o]?.field,w=m?b.findIndex(p=>p.field===m):b.length,y={field:g,fromIndex:i,toIndex:w};this.emit("column-reorder-request",y),setTimeout(()=>{this.rebuildToggles(r)},0)}})}}d.VisibilityPlugin=a,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
2
2
  //# sourceMappingURL=visibility.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"visibility.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/visibility/VisibilityPlugin.ts"],"sourcesContent":["/**\n * Column Visibility Plugin (Class-based)\n *\n * Provides a UI for column visibility control via the shell's tool panel system.\n * Column visibility is a core grid feature - this plugin provides:\n * - A tool panel for column visibility management (registered with the shell)\n * - Backward-compatible API methods that delegate to grid.setColumnVisible(), etc.\n *\n * The grid emits 'column-visibility' events when columns are shown/hidden,\n * allowing consumers to save user preferences.\n *\n * When a reorder plugin is present, column rows become draggable for reordering.\n * Drag-drop emits 'column-reorder-request' events that the ReorderPlugin can listen for.\n */\n\nimport { BaseGridPlugin, type PluginDependency } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ToolPanelDefinition } from '../../core/types';\nimport type { VisibilityConfig } from './types';\nimport styles from './visibility.css?inline';\n\n/**\n * Detail for column-reorder-request events emitted when users drag-drop in the visibility panel.\n */\nexport interface ColumnReorderRequestDetail {\n /** The field name of the column to move */\n field: string;\n /** The source index (before move) */\n fromIndex: number;\n /** The target index (after move) */\n toIndex: number;\n}\n\n/**\n * Check if a column can be moved (respects lockPosition/suppressMovable).\n * Inlined to avoid importing from reorder plugin.\n */\nfunction canMoveColumn(column: ColumnConfig): boolean {\n const meta = column.meta ?? {};\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Column Visibility Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new VisibilityPlugin({ enabled: true, allowHideAll: false })\n * ```\n */\nexport class VisibilityPlugin extends BaseGridPlugin<VisibilityConfig> {\n /**\n * Plugin dependencies - VisibilityPlugin optionally uses ReorderPlugin for drag-drop reordering.\n *\n * When ReorderPlugin is present, columns in the visibility panel become draggable.\n */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'reorder', required: false, reason: 'Enables drag-to-reorder columns in visibility panel' },\n ];\n\n readonly name = 'visibility';\n\n /** Tool panel ID for shell integration */\n static readonly PANEL_ID = 'columns';\n override readonly styles = styles;\n\n protected override get defaultConfig(): Partial<VisibilityConfig> {\n return {\n allowHideAll: false,\n };\n }\n\n // #region Internal State\n private columnListElement: HTMLElement | null = null;\n\n // Drag state for reorder integration\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n\n /** Clear drag-related classes from all rows in a list. */\n private clearDragClasses(container: HTMLElement): void {\n container.querySelectorAll('.tbw-visibility-row').forEach((r) => {\n r.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\n });\n }\n // #endregion\n\n // #region Lifecycle\n\n override detach(): void {\n this.columnListElement = null;\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n }\n // #endregion\n\n // #region Shell Integration\n\n /**\n * Register the column visibility tool panel with the shell.\n */\n override getToolPanel(): ToolPanelDefinition | undefined {\n return {\n id: VisibilityPlugin.PANEL_ID,\n title: 'Columns',\n icon: '☰',\n tooltip: 'Column visibility',\n order: 100, // High order so it appears last\n render: (container) => this.renderPanelContent(container),\n };\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Show the visibility sidebar panel.\n * Opens the tool panel and ensures this section is expanded.\n */\n show(): void {\n this.grid.openToolPanel();\n // Ensure our section is expanded\n if (!this.grid.expandedToolPanelSections.includes(VisibilityPlugin.PANEL_ID)) {\n this.grid.toggleToolPanelSection(VisibilityPlugin.PANEL_ID);\n }\n }\n\n /**\n * Hide the visibility sidebar panel.\n */\n hide(): void {\n this.grid.closeToolPanel();\n }\n\n /**\n * Toggle the visibility sidebar panel section.\n */\n toggle(): void {\n // If tool panel is closed, open it first\n if (!this.grid.isToolPanelOpen) {\n this.grid.openToolPanel();\n }\n this.grid.toggleToolPanelSection(VisibilityPlugin.PANEL_ID);\n }\n\n /**\n * Check if a specific column is visible.\n * Delegates to grid.isColumnVisible().\n * @param field - The field name to check\n * @returns True if the column is visible\n */\n isColumnVisible(field: string): boolean {\n return this.grid.isColumnVisible(field);\n }\n\n /**\n * Set visibility for a specific column.\n * Delegates to grid.setColumnVisible().\n * @param field - The field name of the column\n * @param visible - Whether the column should be visible\n */\n setColumnVisible(field: string, visible: boolean): void {\n this.grid.setColumnVisible(field, visible);\n }\n\n /**\n * Get list of all visible column fields.\n * @returns Array of visible field names\n */\n getVisibleColumns(): string[] {\n return this.grid\n .getAllColumns()\n .filter((c) => c.visible)\n .map((c) => c.field);\n }\n\n /**\n * Get list of all hidden column fields.\n * @returns Array of hidden field names\n */\n getHiddenColumns(): string[] {\n return this.grid\n .getAllColumns()\n .filter((c) => !c.visible)\n .map((c) => c.field);\n }\n\n /**\n * Show all columns.\n * Delegates to grid.showAllColumns().\n */\n showAll(): void {\n this.grid.showAllColumns();\n }\n\n /**\n * Toggle visibility for a specific column.\n * Delegates to grid.toggleColumnVisibility().\n * @param field - The field name of the column\n */\n toggleColumn(field: string): void {\n this.grid.toggleColumnVisibility(field);\n }\n\n /**\n * Show a specific column.\n * Delegates to grid.setColumnVisible().\n * @param field - The field name of the column to show\n */\n showColumn(field: string): void {\n this.setColumnVisible(field, true);\n }\n\n /**\n * Hide a specific column.\n * Delegates to grid.setColumnVisible().\n * @param field - The field name of the column to hide\n */\n hideColumn(field: string): void {\n this.setColumnVisible(field, false);\n }\n\n /**\n * Get all columns with their visibility status.\n * Useful for building visibility UI.\n * @returns Array of column info with visibility status\n */\n getAllColumns(): Array<{\n field: string;\n header: string;\n visible: boolean;\n lockVisible?: boolean;\n utility?: boolean;\n }> {\n return this.grid.getAllColumns();\n }\n\n /**\n * Check if the sidebar panel is currently open.\n * @returns True if the panel section is expanded\n */\n isPanelVisible(): boolean {\n return this.grid.isToolPanelOpen && this.grid.expandedToolPanelSections.includes(VisibilityPlugin.PANEL_ID);\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Render the panel content into the shell's tool panel container.\n * Returns a cleanup function.\n */\n private renderPanelContent(container: HTMLElement): (() => void) | void {\n // Create content wrapper\n const wrapper = document.createElement('div');\n wrapper.className = 'tbw-visibility-content';\n\n // Column list container\n const columnList = document.createElement('div');\n columnList.className = 'tbw-visibility-list';\n wrapper.appendChild(columnList);\n\n // Show all button\n const showAllBtn = document.createElement('button');\n showAllBtn.className = 'tbw-visibility-show-all';\n showAllBtn.textContent = 'Show All';\n showAllBtn.addEventListener('click', () => {\n this.grid.showAllColumns();\n this.rebuildToggles(columnList);\n });\n wrapper.appendChild(showAllBtn);\n\n // Store reference\n this.columnListElement = columnList;\n\n // Build initial toggles\n this.rebuildToggles(columnList);\n\n // Append to container\n container.appendChild(wrapper);\n\n // Return cleanup function\n return () => {\n this.columnListElement = null;\n wrapper.remove();\n };\n }\n\n /**\n * Check if a reorder plugin is present (by name to avoid static import).\n */\n private hasReorderPlugin(): boolean {\n const plugin = this.grid?.getPluginByName?.('reorder');\n // Duck-type check - just verify the plugin exists and has a moveColumn method\n return !!(plugin && typeof (plugin as { moveColumn?: unknown }).moveColumn === 'function');\n }\n\n /**\n * Build the column toggle checkboxes.\n * When a reorder plugin is present, adds drag handles for reordering.\n */\n private rebuildToggles(columnList: HTMLElement): void {\n const reorderEnabled = this.hasReorderPlugin();\n\n columnList.innerHTML = '';\n\n // getAllColumns() now returns columns in their effective display order\n // Filter out utility columns (e.g., expander column) as they're internal\n const allColumns = this.grid.getAllColumns().filter((c) => !c.utility);\n\n for (let i = 0; i < allColumns.length; i++) {\n const col = allColumns[i];\n const label = col.header || col.field;\n\n const row = document.createElement('div');\n row.className = col.lockVisible ? 'tbw-visibility-row locked' : 'tbw-visibility-row';\n row.setAttribute('data-field', col.field);\n row.setAttribute('data-index', String(i));\n\n // Add drag handle if reorder is enabled\n if (reorderEnabled && canMoveColumn(col as unknown as ColumnConfig)) {\n row.draggable = true;\n row.classList.add('reorderable');\n\n this.setupDragListeners(row, col.field, i, columnList);\n }\n\n const labelWrapper = document.createElement('label');\n labelWrapper.className = 'tbw-visibility-label';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.checked = col.visible;\n checkbox.disabled = col.lockVisible ?? false;\n checkbox.addEventListener('change', () => {\n this.grid.toggleColumnVisibility(col.field);\n // Refresh after toggle (grid may re-render)\n setTimeout(() => this.rebuildToggles(columnList), 0);\n });\n\n const text = document.createElement('span');\n text.textContent = label;\n\n labelWrapper.appendChild(checkbox);\n labelWrapper.appendChild(text);\n\n // Add drag handle icon if reorderable\n if (reorderEnabled && canMoveColumn(col as unknown as ColumnConfig)) {\n const handle = document.createElement('span');\n handle.className = 'tbw-visibility-handle';\n // Use grid-level icons (fall back to defaults)\n this.setIcon(handle, this.resolveIcon('dragHandle'));\n handle.title = 'Drag to reorder';\n row.appendChild(handle);\n }\n\n row.appendChild(labelWrapper);\n columnList.appendChild(row);\n }\n }\n\n /**\n * Set up drag-and-drop event listeners for a row.\n * On drop, emits a 'column-reorder-request' event for other plugins to handle.\n */\n private setupDragListeners(row: HTMLElement, field: string, index: number, columnList: HTMLElement): void {\n row.addEventListener('dragstart', (e: DragEvent) => {\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = index;\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n row.classList.add('dragging');\n });\n\n row.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.clearDragClasses(columnList);\n });\n\n row.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging || this.draggedField === field) return;\n\n const rect = row.getBoundingClientRect();\n const midY = rect.top + rect.height / 2;\n\n this.dropIndex = e.clientY < midY ? index : index + 1;\n\n // Clear other row highlights\n columnList.querySelectorAll('.tbw-visibility-row').forEach((r) => {\n if (r !== row) r.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n row.classList.add('drop-target');\n row.classList.toggle('drop-before', e.clientY < midY);\n row.classList.toggle('drop-after', e.clientY >= midY);\n });\n\n row.addEventListener('dragleave', () => {\n row.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n row.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\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 // Calculate the effective target index\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n\n if (effectiveToIndex !== draggedIndex) {\n // Emit a request event - other plugins (like ReorderPlugin) can listen and handle\n const detail: ColumnReorderRequestDetail = {\n field: draggedField,\n fromIndex: draggedIndex,\n toIndex: effectiveToIndex,\n };\n this.emit<ColumnReorderRequestDetail>('column-reorder-request', detail);\n\n // Rebuild the panel after reorder (deferred to allow re-render)\n setTimeout(() => {\n this.rebuildToggles(columnList);\n }, 0);\n }\n });\n }\n // #endregion\n}\n"],"names":["canMoveColumn","column","meta","VisibilityPlugin","BaseGridPlugin","styles","container","r","field","visible","c","wrapper","columnList","showAllBtn","plugin","reorderEnabled","allColumns","i","col","label","row","labelWrapper","checkbox","text","handle","index","e","rect","midY","draggedField","draggedIndex","dropIndex","effectiveToIndex","detail"],"mappings":"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"}
1
+ {"version":3,"file":"visibility.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/visibility/VisibilityPlugin.ts"],"sourcesContent":["/**\n * Column Visibility Plugin (Class-based)\n *\n * Provides a UI for column visibility control via the shell's tool panel system.\n * Column visibility is a core grid feature - this plugin provides:\n * - A tool panel for column visibility management (registered with the shell)\n * - Backward-compatible API methods that delegate to grid.setColumnVisible(), etc.\n *\n * The grid emits 'column-visibility' events when columns are shown/hidden,\n * allowing consumers to save user preferences.\n *\n * When a reorder plugin is present, column rows become draggable for reordering.\n * Drag-drop emits 'column-reorder-request' events that the ReorderPlugin can listen for.\n */\n\nimport { BaseGridPlugin, type PluginDependency } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ToolPanelDefinition } from '../../core/types';\nimport type { VisibilityConfig } from './types';\nimport styles from './visibility.css?inline';\n\n/**\n * Detail for column-reorder-request events emitted when users drag-drop in the visibility panel.\n */\nexport interface ColumnReorderRequestDetail {\n /** The field name of the column to move */\n field: string;\n /** The source index (before move) */\n fromIndex: number;\n /** The target index (after move) */\n toIndex: number;\n}\n\n/**\n * Check if a column can be moved (respects lockPosition/suppressMovable).\n * Inlined to avoid importing from reorder plugin.\n */\nfunction canMoveColumn(column: ColumnConfig): boolean {\n const meta = column.meta ?? {};\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Column Visibility Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new VisibilityPlugin({ enabled: true, allowHideAll: false })\n * ```\n */\nexport class VisibilityPlugin extends BaseGridPlugin<VisibilityConfig> {\n /**\n * Plugin dependencies - VisibilityPlugin optionally uses ReorderPlugin for drag-drop reordering.\n *\n * When ReorderPlugin is present, columns in the visibility panel become draggable.\n */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'reorder', required: false, reason: 'Enables drag-to-reorder columns in visibility panel' },\n ];\n\n readonly name = 'visibility';\n\n /** Tool panel ID for shell integration */\n static readonly PANEL_ID = 'columns';\n override readonly styles = styles;\n\n protected override get defaultConfig(): Partial<VisibilityConfig> {\n return {\n allowHideAll: false,\n };\n }\n\n // #region Internal State\n private columnListElement: HTMLElement | null = null;\n\n // Drag state for reorder integration\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n\n /** Clear drag-related classes from all rows in a list. */\n private clearDragClasses(container: HTMLElement): void {\n container.querySelectorAll('.tbw-visibility-row').forEach((r) => {\n r.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\n });\n }\n // #endregion\n\n // #region Lifecycle\n\n override detach(): void {\n this.columnListElement = null;\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n }\n // #endregion\n\n // #region Shell Integration\n\n /**\n * Register the column visibility tool panel with the shell.\n */\n override getToolPanel(): ToolPanelDefinition | undefined {\n return {\n id: VisibilityPlugin.PANEL_ID,\n title: 'Columns',\n icon: '☰',\n tooltip: 'Column visibility',\n order: 100, // High order so it appears last\n render: (container) => this.renderPanelContent(container),\n };\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Show the visibility sidebar panel.\n * Opens the tool panel and ensures this section is expanded.\n */\n show(): void {\n this.grid.openToolPanel();\n // Ensure our section is expanded\n if (!this.grid.expandedToolPanelSections.includes(VisibilityPlugin.PANEL_ID)) {\n this.grid.toggleToolPanelSection(VisibilityPlugin.PANEL_ID);\n }\n }\n\n /**\n * Hide the visibility sidebar panel.\n */\n hide(): void {\n this.grid.closeToolPanel();\n }\n\n /**\n * Toggle the visibility sidebar panel section.\n */\n toggle(): void {\n // If tool panel is closed, open it first\n if (!this.grid.isToolPanelOpen) {\n this.grid.openToolPanel();\n }\n this.grid.toggleToolPanelSection(VisibilityPlugin.PANEL_ID);\n }\n\n /**\n * Check if a specific column is visible.\n * Delegates to grid.isColumnVisible().\n * @param field - The field name to check\n * @returns True if the column is visible\n */\n isColumnVisible(field: string): boolean {\n return this.grid.isColumnVisible(field);\n }\n\n /**\n * Set visibility for a specific column.\n * Delegates to grid.setColumnVisible().\n * @param field - The field name of the column\n * @param visible - Whether the column should be visible\n */\n setColumnVisible(field: string, visible: boolean): void {\n this.grid.setColumnVisible(field, visible);\n }\n\n /**\n * Get list of all visible column fields.\n * @returns Array of visible field names\n */\n getVisibleColumns(): string[] {\n return this.grid\n .getAllColumns()\n .filter((c) => c.visible)\n .map((c) => c.field);\n }\n\n /**\n * Get list of all hidden column fields.\n * @returns Array of hidden field names\n */\n getHiddenColumns(): string[] {\n return this.grid\n .getAllColumns()\n .filter((c) => !c.visible)\n .map((c) => c.field);\n }\n\n /**\n * Show all columns.\n * Delegates to grid.showAllColumns().\n */\n showAll(): void {\n this.grid.showAllColumns();\n }\n\n /**\n * Toggle visibility for a specific column.\n * Delegates to grid.toggleColumnVisibility().\n * @param field - The field name of the column\n */\n toggleColumn(field: string): void {\n this.grid.toggleColumnVisibility(field);\n }\n\n /**\n * Show a specific column.\n * Delegates to grid.setColumnVisible().\n * @param field - The field name of the column to show\n */\n showColumn(field: string): void {\n this.setColumnVisible(field, true);\n }\n\n /**\n * Hide a specific column.\n * Delegates to grid.setColumnVisible().\n * @param field - The field name of the column to hide\n */\n hideColumn(field: string): void {\n this.setColumnVisible(field, false);\n }\n\n /**\n * Get all columns with their visibility status.\n * Useful for building visibility UI.\n * @returns Array of column info with visibility status\n */\n getAllColumns(): Array<{\n field: string;\n header: string;\n visible: boolean;\n lockVisible?: boolean;\n utility?: boolean;\n }> {\n return this.grid.getAllColumns();\n }\n\n /**\n * Check if the sidebar panel is currently open.\n * @returns True if the panel section is expanded\n */\n isPanelVisible(): boolean {\n return this.grid.isToolPanelOpen && this.grid.expandedToolPanelSections.includes(VisibilityPlugin.PANEL_ID);\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Render the panel content into the shell's tool panel container.\n * Returns a cleanup function.\n */\n private renderPanelContent(container: HTMLElement): (() => void) | void {\n // Create content wrapper\n const wrapper = document.createElement('div');\n wrapper.className = 'tbw-visibility-content';\n\n // Column list container\n const columnList = document.createElement('div');\n columnList.className = 'tbw-visibility-list';\n wrapper.appendChild(columnList);\n\n // Show all button\n const showAllBtn = document.createElement('button');\n showAllBtn.className = 'tbw-visibility-show-all';\n showAllBtn.textContent = 'Show All';\n showAllBtn.addEventListener('click', () => {\n this.grid.showAllColumns();\n this.rebuildToggles(columnList);\n });\n wrapper.appendChild(showAllBtn);\n\n // Store reference\n this.columnListElement = columnList;\n\n // Build initial toggles\n this.rebuildToggles(columnList);\n\n // Append to container\n container.appendChild(wrapper);\n\n // Return cleanup function\n return () => {\n this.columnListElement = null;\n wrapper.remove();\n };\n }\n\n /**\n * Check if a reorder plugin is present (by name to avoid static import).\n */\n private hasReorderPlugin(): boolean {\n const plugin = this.grid?.getPluginByName?.('reorder');\n // Duck-type check - just verify the plugin exists and has a moveColumn method\n return !!(plugin && typeof (plugin as { moveColumn?: unknown }).moveColumn === 'function');\n }\n\n /**\n * Build the column toggle checkboxes.\n * When a reorder plugin is present, adds drag handles for reordering.\n */\n private rebuildToggles(columnList: HTMLElement): void {\n const reorderEnabled = this.hasReorderPlugin();\n\n columnList.innerHTML = '';\n\n // getAllColumns() now returns columns in their effective display order\n // Filter out utility columns (e.g., expander column) as they're internal\n const allColumns = this.grid.getAllColumns().filter((c) => !c.utility);\n\n for (let i = 0; i < allColumns.length; i++) {\n const col = allColumns[i];\n const label = col.header || col.field;\n\n const row = document.createElement('div');\n row.className = col.lockVisible ? 'tbw-visibility-row locked' : 'tbw-visibility-row';\n row.setAttribute('data-field', col.field);\n row.setAttribute('data-index', String(i));\n\n // Add drag handle if reorder is enabled\n if (reorderEnabled && canMoveColumn(col as unknown as ColumnConfig)) {\n row.draggable = true;\n row.classList.add('reorderable');\n\n this.setupDragListeners(row, col.field, i, columnList);\n }\n\n const labelWrapper = document.createElement('label');\n labelWrapper.className = 'tbw-visibility-label';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.checked = col.visible;\n checkbox.disabled = col.lockVisible ?? false;\n checkbox.addEventListener('change', () => {\n this.grid.toggleColumnVisibility(col.field);\n // Refresh after toggle (grid may re-render)\n setTimeout(() => this.rebuildToggles(columnList), 0);\n });\n\n const text = document.createElement('span');\n text.textContent = label;\n\n labelWrapper.appendChild(checkbox);\n labelWrapper.appendChild(text);\n\n // Add drag handle icon if reorderable\n if (reorderEnabled && canMoveColumn(col as unknown as ColumnConfig)) {\n const handle = document.createElement('span');\n handle.className = 'tbw-visibility-handle';\n // Use grid-level icons (fall back to defaults)\n this.setIcon(handle, this.resolveIcon('dragHandle'));\n handle.title = 'Drag to reorder';\n row.appendChild(handle);\n }\n\n row.appendChild(labelWrapper);\n columnList.appendChild(row);\n }\n }\n\n /**\n * Set up drag-and-drop event listeners for a row.\n * On drop, emits a 'column-reorder-request' event for other plugins to handle.\n */\n private setupDragListeners(row: HTMLElement, field: string, index: number, columnList: HTMLElement): void {\n row.addEventListener('dragstart', (e: DragEvent) => {\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = index;\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n row.classList.add('dragging');\n });\n\n row.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.clearDragClasses(columnList);\n });\n\n row.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging || this.draggedField === field) return;\n\n const rect = row.getBoundingClientRect();\n const midY = rect.top + rect.height / 2;\n\n this.dropIndex = e.clientY < midY ? index : index + 1;\n\n // Clear other row highlights\n columnList.querySelectorAll('.tbw-visibility-row').forEach((r) => {\n if (r !== row) r.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n row.classList.add('drop-target');\n row.classList.toggle('drop-before', e.clientY < midY);\n row.classList.toggle('drop-after', e.clientY >= midY);\n });\n\n row.addEventListener('dragleave', () => {\n row.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n row.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\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 // Calculate the effective target index (in the filtered non-utility list)\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n\n if (effectiveToIndex !== draggedIndex) {\n // Convert from non-utility index to full column order index\n // by counting how many utility columns come before the target position\n const allColumns = this.grid.getAllColumns();\n const nonUtilityColumns = allColumns.filter((c) => !c.utility);\n\n // Find the target field at effectiveToIndex in non-utility list\n const targetField = nonUtilityColumns[effectiveToIndex]?.field;\n // Find its actual index in the full column order\n const fullOrderToIndex = targetField ? allColumns.findIndex((c) => c.field === targetField) : allColumns.length;\n\n // Emit a request event - other plugins (like ReorderPlugin) can listen and handle\n const detail: ColumnReorderRequestDetail = {\n field: draggedField,\n fromIndex: draggedIndex, // Not used by ReorderPlugin, just for info\n toIndex: fullOrderToIndex,\n };\n this.emit<ColumnReorderRequestDetail>('column-reorder-request', detail);\n\n // Rebuild the panel after reorder (deferred to allow re-render)\n setTimeout(() => {\n this.rebuildToggles(columnList);\n }, 0);\n }\n });\n }\n // #endregion\n}\n"],"names":["canMoveColumn","column","meta","VisibilityPlugin","BaseGridPlugin","styles","container","r","field","visible","c","wrapper","columnList","showAllBtn","plugin","reorderEnabled","allColumns","i","col","label","row","labelWrapper","checkbox","text","handle","index","e","rect","midY","draggedField","draggedIndex","dropIndex","effectiveToIndex","targetField","fullOrderToIndex","detail"],"mappings":"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"}