@toolbox-web/grid 1.1.2 → 1.3.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 (84) hide show
  1. package/README.md +80 -22
  2. package/all.d.ts +1 -0
  3. package/all.d.ts.map +1 -1
  4. package/all.js +557 -365
  5. package/all.js.map +1 -1
  6. package/index.d.ts +1 -1
  7. package/index.d.ts.map +1 -1
  8. package/index.js +903 -769
  9. package/index.js.map +1 -1
  10. package/lib/core/grid.d.ts +102 -3
  11. package/lib/core/grid.d.ts.map +1 -1
  12. package/lib/core/internal/row-animation.d.ts +37 -0
  13. package/lib/core/internal/row-animation.d.ts.map +1 -0
  14. package/lib/core/internal/rows.d.ts.map +1 -1
  15. package/lib/core/internal/shell.d.ts.map +1 -1
  16. package/lib/core/plugin/base-plugin.d.ts +65 -3
  17. package/lib/core/plugin/base-plugin.d.ts.map +1 -1
  18. package/lib/core/plugin/index.d.ts +1 -1
  19. package/lib/core/plugin/index.d.ts.map +1 -1
  20. package/lib/core/plugin/plugin-manager.d.ts +25 -1
  21. package/lib/core/plugin/plugin-manager.d.ts.map +1 -1
  22. package/lib/core/plugin/types.d.ts +62 -0
  23. package/lib/core/plugin/types.d.ts.map +1 -1
  24. package/lib/core/types.d.ts +64 -1
  25. package/lib/core/types.d.ts.map +1 -1
  26. package/lib/plugins/clipboard/index.js +73 -69
  27. package/lib/plugins/clipboard/index.js.map +1 -1
  28. package/lib/plugins/clipboard/types.d.ts +1 -0
  29. package/lib/plugins/clipboard/types.d.ts.map +1 -1
  30. package/lib/plugins/column-virtualization/index.js.map +1 -1
  31. package/lib/plugins/context-menu/index.js.map +1 -1
  32. package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
  33. package/lib/plugins/editing/index.js +69 -40
  34. package/lib/plugins/editing/index.js.map +1 -1
  35. package/lib/plugins/export/index.js.map +1 -1
  36. package/lib/plugins/filtering/index.js.map +1 -1
  37. package/lib/plugins/grouping-columns/index.js.map +1 -1
  38. package/lib/plugins/grouping-rows/index.js.map +1 -1
  39. package/lib/plugins/master-detail/MasterDetailPlugin.d.ts.map +1 -1
  40. package/lib/plugins/master-detail/index.js +14 -12
  41. package/lib/plugins/master-detail/index.js.map +1 -1
  42. package/lib/plugins/multi-sort/index.js.map +1 -1
  43. package/lib/plugins/pinned-columns/index.js.map +1 -1
  44. package/lib/plugins/pinned-rows/index.js.map +1 -1
  45. package/lib/plugins/pivot/index.js.map +1 -1
  46. package/lib/plugins/reorder/index.js.map +1 -1
  47. package/lib/plugins/responsive/index.js.map +1 -1
  48. package/lib/plugins/row-reorder/RowReorderPlugin.d.ts +155 -0
  49. package/lib/plugins/row-reorder/RowReorderPlugin.d.ts.map +1 -0
  50. package/lib/plugins/row-reorder/index.d.ts +9 -0
  51. package/lib/plugins/row-reorder/index.d.ts.map +1 -0
  52. package/lib/plugins/row-reorder/index.js +597 -0
  53. package/lib/plugins/row-reorder/index.js.map +1 -0
  54. package/lib/plugins/row-reorder/types.d.ts +80 -0
  55. package/lib/plugins/row-reorder/types.d.ts.map +1 -0
  56. package/lib/plugins/selection/SelectionPlugin.d.ts +13 -0
  57. package/lib/plugins/selection/SelectionPlugin.d.ts.map +1 -1
  58. package/lib/plugins/selection/index.d.ts +1 -1
  59. package/lib/plugins/selection/index.d.ts.map +1 -1
  60. package/lib/plugins/selection/index.js +95 -64
  61. package/lib/plugins/selection/index.js.map +1 -1
  62. package/lib/plugins/selection/types.d.ts +50 -6
  63. package/lib/plugins/selection/types.d.ts.map +1 -1
  64. package/lib/plugins/server-side/index.js.map +1 -1
  65. package/lib/plugins/tree/index.js.map +1 -1
  66. package/lib/plugins/undo-redo/index.js.map +1 -1
  67. package/lib/plugins/visibility/index.js.map +1 -1
  68. package/package.json +21 -4
  69. package/public.d.ts +15 -2
  70. package/public.d.ts.map +1 -1
  71. package/umd/grid.all.umd.js +23 -23
  72. package/umd/grid.all.umd.js.map +1 -1
  73. package/umd/grid.umd.js +15 -15
  74. package/umd/grid.umd.js.map +1 -1
  75. package/umd/plugins/clipboard.umd.js +5 -5
  76. package/umd/plugins/clipboard.umd.js.map +1 -1
  77. package/umd/plugins/editing.umd.js +1 -1
  78. package/umd/plugins/editing.umd.js.map +1 -1
  79. package/umd/plugins/master-detail.umd.js +1 -1
  80. package/umd/plugins/master-detail.umd.js.map +1 -1
  81. package/umd/plugins/row-reorder.umd.js +2 -0
  82. package/umd/plugins/row-reorder.umd.js.map +1 -0
  83. package/umd/plugins/selection.umd.js +2 -2
  84. package/umd/plugins/selection.umd.js.map +1 -1
@@ -0,0 +1,2 @@
1
+ (function(a,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("../../core/internal/keyboard"),require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/keyboard","../../core/plugin/base-plugin"],l):(a=typeof globalThis<"u"?globalThis:a||self,l(a.TbwGridPlugin_rowReorder={},a.TbwGrid,a.TbwGrid))})(this,(function(a,l,g){"use strict";const u='.dg-row-drag-handle{display:flex;align-items:center;justify-content:center;cursor:grab;-webkit-user-select:none;user-select:none;color:var(--tbw-color-fg-muted, #999);transition:color .15s ease;font-size:14px;letter-spacing:-2px}.dg-row-drag-handle:hover{color:var(--tbw-color-fg, #333)}.dg-row-drag-handle:active{cursor:grabbing}.data-grid-row.dragging{opacity:.6}.data-grid-row.drop-target{position:relative}.data-grid-row.drop-target.drop-before:before{content:"";position:absolute;top:0;left:0;right:0;height:2px;background-color:var(--tbw-color-accent, #1976d2);z-index:10}.data-grid-row.drop-target.drop-after:after{content:"";position:absolute;bottom:0;left:0;right:0;height:2px;background-color:var(--tbw-color-accent, #1976d2);z-index:10}.data-grid-row.keyboard-moving{background-color:var(--tbw-color-bg-selected, #e3f2fd);box-shadow:0 0 0 1px var(--tbw-color-accent, #1976d2) inset}.data-grid-row.animate-flip{transition:transform var(--tbw-animation-duration, .2s) ease-out}',c="__tbw_row_drag";class h extends g.BaseGridPlugin{name="rowReorder";styles=u;get defaultConfig(){return{enableKeyboard:!0,showDragHandle:!0,dragHandlePosition:"left",dragHandleWidth:40,debounceMs:150,animation:"flip"}}isDragging=!1;draggedRowIndex=null;dropRowIndex=null;pendingMove=null;debounceTimer=null;lastFocusCol=0;detach(){this.clearDebounceTimer(),this.isDragging=!1,this.draggedRowIndex=null,this.dropRowIndex=null,this.pendingMove=null}processColumns(e){if(!this.config.showDragHandle)return[...e];const r={field:c,header:"",width:this.config.dragHandleWidth??40,resizable:!1,sortable:!1,filterable:!1,meta:{lockPosition:!0,suppressMovable:!0,utility:!0},viewRenderer:()=>{const t=document.createElement("div");return t.className="dg-row-drag-handle",t.setAttribute("aria-label","Drag to reorder"),t.setAttribute("role","button"),t.setAttribute("tabindex","-1"),t.draggable=!0,this.setIcon(t,this.resolveIcon("dragHandle")),t}};return this.config.dragHandlePosition==="right"?[...e,r]:[r,...e]}afterRender(){if(!this.config.showDragHandle)return;const e=this.gridElement;if(!e)return;e.querySelectorAll(".dg-row-drag-handle").forEach(o=>{const i=o;if(i.getAttribute("data-drag-bound"))return;i.setAttribute("data-drag-bound","true");const n=i.closest(".data-grid-row");n&&this.setupHandleDragListeners(i,n)}),e.querySelectorAll(".data-grid-row").forEach(o=>{const i=o;i.getAttribute("data-drop-bound")||(i.setAttribute("data-drop-bound","true"),this.setupRowDropListeners(i))})}onKeyDown(e){if(!this.config.enableKeyboard||!e.ctrlKey||e.key!=="ArrowUp"&&e.key!=="ArrowDown")return;const r=this.grid,t=r._focusRow,o=r._rows??this.sourceRows;if(t<0||t>=o.length)return;const i=e.key==="ArrowUp"?"up":"down",n=i==="up"?t-1:t+1;if(n<0||n>=o.length)return;const s=o[t];if(!(this.config.canMove&&!this.config.canMove(s,t,n,i)))return this.handleKeyboardMove(s,t,n,i,r._focusCol),e.preventDefault(),e.stopPropagation(),!0}onCellClick(){this.flushPendingMove()}moveRow(e,r){const t=[...this.sourceRows];if(e<0||e>=t.length||r<0||r>=t.length||e===r)return;const o=r<e?"up":"down",i=t[e];this.config.canMove&&!this.config.canMove(i,e,r,o)||this.executeMove(i,e,r,"keyboard")}canMoveRow(e,r){const t=this.sourceRows;if(e<0||e>=t.length||r<0||r>=t.length||e===r)return!1;if(!this.config.canMove)return!0;const o=r<e?"up":"down";return this.config.canMove(t[e],e,r,o)}setupHandleDragListeners(e,r){e.addEventListener("dragstart",t=>{const o=this.getRowIndex(r);o<0||(this.isDragging=!0,this.draggedRowIndex=o,t.dataTransfer&&(t.dataTransfer.effectAllowed="move",t.dataTransfer.setData("text/plain",String(o))),r.classList.add("dragging"))}),e.addEventListener("dragend",()=>{this.isDragging=!1,this.draggedRowIndex=null,this.dropRowIndex=null,this.clearDragClasses()})}setupRowDropListeners(e){e.addEventListener("dragover",r=>{if(r.preventDefault(),!this.isDragging||this.draggedRowIndex===null)return;const t=this.getRowIndex(e);if(t<0||t===this.draggedRowIndex)return;const o=e.getBoundingClientRect(),i=o.top+o.height/2,n=r.clientY<i;this.dropRowIndex=n?t:t+1,e.classList.add("drop-target"),e.classList.toggle("drop-before",n),e.classList.toggle("drop-after",!n)}),e.addEventListener("dragleave",()=>{e.classList.remove("drop-target","drop-before","drop-after")}),e.addEventListener("drop",r=>{r.preventDefault();const t=this.draggedRowIndex;let o=this.dropRowIndex;if(!(!this.isDragging||t===null||o===null)&&(o>t&&o--,t!==o)){const n=this.sourceRows[t],s=o<t?"up":"down";(!this.config.canMove||this.config.canMove(n,t,o,s))&&this.executeMove(n,t,o,"drag")}})}handleKeyboardMove(e,r,t,o,i){this.pendingMove?this.pendingMove.currentIndex=t:this.pendingMove={originalIndex:r,currentIndex:t,row:e},this.lastFocusCol=i;const n=this.grid,s=[...n._rows??this.sourceRows],[d]=s.splice(r,1);s.splice(t,0,d),n._rows=s,n._focusRow=t,n._focusCol=i,n.refreshVirtualWindow(!0),l.ensureCellVisible(n),this.clearDebounceTimer(),this.debounceTimer=setTimeout(()=>{this.flushPendingMove()},this.config.debounceMs??300)}flushPendingMove(){if(this.clearDebounceTimer(),!this.pendingMove)return;const{originalIndex:e,currentIndex:r,row:t}=this.pendingMove;if(this.pendingMove=null,e===r)return;const o={row:t,fromIndex:e,toIndex:r,rows:[...this.sourceRows],source:"keyboard"};if(this.emitCancelable("row-move",o)){const n=[...this.sourceRows],[s]=n.splice(r,1);n.splice(e,0,s);const d=this.grid;d._rows=n,d._focusRow=e,d._focusCol=this.lastFocusCol,d.refreshVirtualWindow(!0),l.ensureCellVisible(d)}}executeMove(e,r,t,o){const i=[...this.sourceRows],[n]=i.splice(r,1);i.splice(t,0,n);const s={row:e,fromIndex:r,toIndex:t,rows:i,source:o};this.emitCancelable("row-move",s)||(this.grid.rows=i)}getRowIndex(e){const r=e.querySelector(".cell[data-row]");return r?parseInt(r.getAttribute("data-row")??"-1",10):-1}clearDragClasses(){this.gridElement?.querySelectorAll(".data-grid-row").forEach(e=>{e.classList.remove("dragging","drop-target","drop-before","drop-after")})}clearDebounceTimer(){this.debounceTimer&&(clearTimeout(this.debounceTimer),this.debounceTimer=null)}}a.ROW_DRAG_HANDLE_FIELD=c,a.RowReorderPlugin=h,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}));
2
+ //# sourceMappingURL=row-reorder.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row-reorder.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/row-reorder/RowReorderPlugin.ts"],"sourcesContent":["/**\n * Row Reordering Plugin\n *\n * Provides keyboard and drag-drop row reordering functionality for tbw-grid.\n * Supports Ctrl+Up/Down keyboard shortcuts and optional drag handle column.\n */\n\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, InternalGrid } from '../../core/types';\nimport styles from './row-reorder.css?inline';\nimport type { PendingMove, RowMoveDetail, RowReorderConfig } from './types';\n\n/** Field name for the drag handle column */\nexport const ROW_DRAG_HANDLE_FIELD = '__tbw_row_drag';\n\n/**\n * Row Reorder Plugin for tbw-grid\n *\n * Enables row reordering via keyboard shortcuts (Ctrl+Up/Down) and drag-drop.\n * Supports validation callbacks and debounced keyboard moves.\n *\n * ## Installation\n *\n * ```ts\n * import { RowReorderPlugin } from '@toolbox-web/grid/plugins/row-reorder';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `enableKeyboard` | `boolean` | `true` | Enable Ctrl+Up/Down shortcuts |\n * | `showDragHandle` | `boolean` | `true` | Show drag handle column |\n * | `dragHandlePosition` | `'left' \\| 'right'` | `'left'` | Drag handle column position |\n * | `dragHandleWidth` | `number` | `40` | Drag handle column width |\n * | `canMove` | `function` | - | Validation callback |\n * | `debounceMs` | `number` | `300` | Debounce time for keyboard moves |\n * | `animation` | `false \\| 'flip'` | `'flip'` | Animation for row moves |\n *\n * ## Keyboard Shortcuts\n *\n * | Key | Action |\n * |-----|--------|\n * | `Ctrl + ↑` | Move focused row up |\n * | `Ctrl + ↓` | Move focused row down |\n *\n * ## Events\n *\n * | Event | Detail | Cancelable | Description |\n * |-------|--------|------------|-------------|\n * | `row-move` | `RowMoveDetail` | Yes | Fired when a row move is attempted |\n *\n * @example Basic Row Reordering\n * ```ts\n * import '@toolbox-web/grid';\n * import { RowReorderPlugin } from '@toolbox-web/grid/plugins/row-reorder';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID' },\n * { field: 'name', header: 'Name' },\n * ],\n * plugins: [new RowReorderPlugin()],\n * };\n *\n * grid.addEventListener('row-move', (e) => {\n * console.log('Row moved from', e.detail.fromIndex, 'to', e.detail.toIndex);\n * });\n * ```\n *\n * @example With Validation\n * ```ts\n * new RowReorderPlugin({\n * canMove: (row, fromIndex, toIndex, direction) => {\n * // Prevent moving locked rows\n * return !row.locked;\n * },\n * })\n * ```\n *\n * @see {@link RowReorderConfig} for all configuration options\n * @see {@link RowMoveDetail} for the event detail structure\n */\nexport class RowReorderPlugin extends BaseGridPlugin<RowReorderConfig> {\n /** @internal */\n readonly name = 'rowReorder';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<RowReorderConfig> {\n return {\n enableKeyboard: true,\n showDragHandle: true,\n dragHandlePosition: 'left',\n dragHandleWidth: 40,\n debounceMs: 150,\n animation: 'flip',\n };\n }\n\n // #region Internal State\n private isDragging = false;\n private draggedRowIndex: number | null = null;\n private dropRowIndex: number | null = null;\n private pendingMove: PendingMove | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n /** Column index to use when flushing pending move */\n private lastFocusCol = 0;\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.clearDebounceTimer();\n this.isDragging = false;\n this.draggedRowIndex = null;\n this.dropRowIndex = null;\n this.pendingMove = null;\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.config.showDragHandle) {\n return [...columns];\n }\n\n const dragHandleColumn: ColumnConfig = {\n field: ROW_DRAG_HANDLE_FIELD,\n header: '',\n width: this.config.dragHandleWidth ?? 40,\n resizable: false,\n sortable: false,\n filterable: false,\n meta: {\n lockPosition: true,\n suppressMovable: true,\n utility: true,\n },\n viewRenderer: () => {\n const container = document.createElement('div');\n container.className = 'dg-row-drag-handle';\n container.setAttribute('aria-label', 'Drag to reorder');\n container.setAttribute('role', 'button');\n container.setAttribute('tabindex', '-1');\n // Set draggable as property (not just attribute) for proper HTML5 drag-drop\n container.draggable = true;\n\n // Use the grid's configured dragHandle icon\n this.setIcon(container, this.resolveIcon('dragHandle'));\n\n return container;\n },\n };\n\n // Position the drag handle column\n if (this.config.dragHandlePosition === 'right') {\n return [...columns, dragHandleColumn];\n }\n return [dragHandleColumn, ...columns];\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.config.showDragHandle) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Set up drag start/end listeners on drag handles\n const handles = gridEl.querySelectorAll('.dg-row-drag-handle');\n handles.forEach((handle) => {\n const handleEl = handle as HTMLElement;\n if (handleEl.getAttribute('data-drag-bound')) return;\n handleEl.setAttribute('data-drag-bound', 'true');\n\n const rowEl = handleEl.closest('.data-grid-row') as HTMLElement;\n if (!rowEl) return;\n\n // Set up dragstart/dragend on the handle\n this.setupHandleDragListeners(handleEl, rowEl);\n });\n\n // Set up drop target listeners on ALL rows (not just the handle's row)\n const rows = gridEl.querySelectorAll('.data-grid-row');\n rows.forEach((row) => {\n const rowEl = row as HTMLElement;\n if (rowEl.getAttribute('data-drop-bound')) return;\n rowEl.setAttribute('data-drop-bound', 'true');\n\n this.setupRowDropListeners(rowEl);\n });\n }\n\n /**\n * Handle Ctrl+Arrow keyboard shortcuts for row reordering.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n if (!this.config.enableKeyboard) return;\n if (!event.ctrlKey || (event.key !== 'ArrowUp' && event.key !== 'ArrowDown')) {\n return;\n }\n\n const grid = this.grid as unknown as InternalGrid;\n const focusRow = grid._focusRow;\n // Use _rows (current visual state) for keyboard moves, not sourceRows\n // This ensures rapid moves work correctly since we update _rows directly\n // Fallback to sourceRows for compatibility with tests\n const rows = grid._rows ?? this.sourceRows;\n\n if (focusRow < 0 || focusRow >= rows.length) return;\n\n const direction = event.key === 'ArrowUp' ? 'up' : 'down';\n const toIndex = direction === 'up' ? focusRow - 1 : focusRow + 1;\n\n // Check bounds\n if (toIndex < 0 || toIndex >= rows.length) return;\n\n const row = rows[focusRow];\n\n // Validate move\n if (this.config.canMove && !this.config.canMove(row, focusRow, toIndex, direction)) {\n return;\n }\n\n // Debounce keyboard moves\n this.handleKeyboardMove(row, focusRow, toIndex, direction, grid._focusCol);\n\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n\n /**\n * Flush pending keyboard moves when user clicks a cell.\n * This commits the move immediately so focus works correctly.\n * @internal\n */\n override onCellClick(): void {\n // If there's a pending keyboard move, flush it immediately\n // so the user's click focus isn't overridden\n this.flushPendingMove();\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Move a row to a new position programmatically.\n * @param fromIndex - Current index of the row\n * @param toIndex - Target index\n */\n moveRow(fromIndex: number, toIndex: number): void {\n const rows = [...this.sourceRows];\n if (fromIndex < 0 || fromIndex >= rows.length) return;\n if (toIndex < 0 || toIndex >= rows.length) return;\n if (fromIndex === toIndex) return;\n\n const direction = toIndex < fromIndex ? 'up' : 'down';\n const row = rows[fromIndex];\n\n // Validate move\n if (this.config.canMove && !this.config.canMove(row, fromIndex, toIndex, direction)) {\n return;\n }\n\n this.executeMove(row, fromIndex, toIndex, 'keyboard');\n }\n\n /**\n * Check if a row can be moved to a position.\n * @param fromIndex - Current index of the row\n * @param toIndex - Target index\n */\n canMoveRow(fromIndex: number, toIndex: number): boolean {\n const rows = this.sourceRows;\n if (fromIndex < 0 || fromIndex >= rows.length) return false;\n if (toIndex < 0 || toIndex >= rows.length) return false;\n if (fromIndex === toIndex) return false;\n\n if (!this.config.canMove) return true;\n\n const direction = toIndex < fromIndex ? 'up' : 'down';\n return this.config.canMove(rows[fromIndex], fromIndex, toIndex, direction);\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Set up drag start/end listeners on the drag handle element.\n */\n private setupHandleDragListeners(handleEl: HTMLElement, rowEl: HTMLElement): void {\n handleEl.addEventListener('dragstart', (e: DragEvent) => {\n const rowIndex = this.getRowIndex(rowEl);\n if (rowIndex < 0) return;\n\n this.isDragging = true;\n this.draggedRowIndex = rowIndex;\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', String(rowIndex));\n }\n\n rowEl.classList.add('dragging');\n });\n\n handleEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedRowIndex = null;\n this.dropRowIndex = null;\n this.clearDragClasses();\n });\n }\n\n /**\n * Set up drop target listeners on a row element.\n * All rows are valid drop targets during drag operations.\n */\n private setupRowDropListeners(rowEl: HTMLElement): void {\n rowEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging || this.draggedRowIndex === null) return;\n\n const targetIndex = this.getRowIndex(rowEl);\n if (targetIndex < 0 || targetIndex === this.draggedRowIndex) return;\n\n const rect = rowEl.getBoundingClientRect();\n const midY = rect.top + rect.height / 2;\n const isBefore = e.clientY < midY;\n\n this.dropRowIndex = isBefore ? targetIndex : targetIndex + 1;\n\n rowEl.classList.add('drop-target');\n rowEl.classList.toggle('drop-before', isBefore);\n rowEl.classList.toggle('drop-after', !isBefore);\n });\n\n rowEl.addEventListener('dragleave', () => {\n rowEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n rowEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n const fromIndex = this.draggedRowIndex;\n let toIndex = this.dropRowIndex;\n\n if (!this.isDragging || fromIndex === null || toIndex === null) {\n return;\n }\n\n // Adjust toIndex if dropping after the dragged row\n if (toIndex > fromIndex) {\n toIndex--;\n }\n\n if (fromIndex !== toIndex) {\n const rows = this.sourceRows;\n const row = rows[fromIndex];\n const direction = toIndex < fromIndex ? 'up' : 'down';\n\n // Validate move\n if (!this.config.canMove || this.config.canMove(row, fromIndex, toIndex, direction)) {\n this.executeMove(row, fromIndex, toIndex, 'drag');\n }\n }\n });\n }\n\n /**\n * Handle debounced keyboard moves.\n * Rows move immediately for visual feedback, but the event emission is debounced.\n */\n private handleKeyboardMove(\n row: unknown,\n fromIndex: number,\n toIndex: number,\n direction: 'up' | 'down',\n focusCol: number,\n ): void {\n // Track move for debounced event emission\n if (!this.pendingMove) {\n this.pendingMove = {\n originalIndex: fromIndex,\n currentIndex: toIndex,\n row,\n };\n } else {\n // Update the current index for rapid moves\n this.pendingMove.currentIndex = toIndex;\n }\n\n // Store focus column for flush\n this.lastFocusCol = focusCol;\n\n // Move rows immediately for visual feedback\n // Use _rows (current visual state) for rapid moves, not sourceRows\n // Fallback to sourceRows for compatibility with tests\n const grid = this.grid as unknown as InternalGrid;\n const rows = [...(grid._rows ?? this.sourceRows)];\n const [movedRow] = rows.splice(fromIndex, 1);\n rows.splice(toIndex, 0, movedRow);\n\n // Update grid rows immediately (without triggering change events)\n grid._rows = rows;\n\n // Update focus to follow the row\n grid._focusRow = toIndex;\n grid._focusCol = focusCol;\n\n // Refresh virtual window directly - this re-renders from _rows\n // without overwriting _rows from #rows (which requestRender does)\n grid.refreshVirtualWindow(true);\n\n // Ensure focus styling is applied after the row rebuild\n ensureCellVisible(grid);\n\n // Debounce the event emission only\n this.clearDebounceTimer();\n this.debounceTimer = setTimeout(() => {\n this.flushPendingMove();\n }, this.config.debounceMs ?? 300);\n }\n\n /**\n * Flush the pending move by emitting the event.\n * Called when debounce timer fires or user clicks elsewhere.\n */\n private flushPendingMove(): void {\n this.clearDebounceTimer();\n\n if (!this.pendingMove) return;\n\n const { originalIndex, currentIndex, row: movedRow } = this.pendingMove;\n this.pendingMove = null;\n\n if (originalIndex === currentIndex) return;\n\n // Emit cancelable event\n const detail: RowMoveDetail = {\n row: movedRow,\n fromIndex: originalIndex,\n toIndex: currentIndex,\n rows: [...this.sourceRows],\n source: 'keyboard',\n };\n\n const cancelled = this.emitCancelable('row-move', detail);\n if (cancelled) {\n // Revert to original position\n const rows = [...this.sourceRows];\n const [row] = rows.splice(currentIndex, 1);\n rows.splice(originalIndex, 0, row);\n\n const grid = this.grid as unknown as InternalGrid;\n grid._rows = rows;\n grid._focusRow = originalIndex;\n grid._focusCol = this.lastFocusCol;\n grid.refreshVirtualWindow(true);\n ensureCellVisible(grid);\n }\n }\n\n /**\n * Execute a row move and emit the event.\n */\n private executeMove(row: unknown, fromIndex: number, toIndex: number, source: 'keyboard' | 'drag'): void {\n const rows = [...this.sourceRows];\n const [movedRow] = rows.splice(fromIndex, 1);\n rows.splice(toIndex, 0, movedRow);\n\n const detail: RowMoveDetail = {\n row,\n fromIndex,\n toIndex,\n rows,\n source,\n };\n\n // Emit cancelable event\n const cancelled = this.emitCancelable('row-move', detail);\n if (!cancelled) {\n // Update the grid's rows\n this.grid.rows = rows;\n }\n }\n\n /**\n * Get the row index from a row element by checking data-row attribute on cells.\n * This is consistent with how other plugins retrieve row indices.\n */\n private getRowIndex(rowEl: HTMLElement): number {\n const cell = rowEl.querySelector('.cell[data-row]');\n return cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n }\n\n /**\n * Clear all drag-related classes from rows.\n */\n private clearDragClasses(): void {\n this.gridElement?.querySelectorAll('.data-grid-row').forEach((row) => {\n row.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\n });\n }\n\n /**\n * Clear the debounce timer.\n */\n private clearDebounceTimer(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n }\n // #endregion\n}\n"],"names":["ROW_DRAG_HANDLE_FIELD","RowReorderPlugin","BaseGridPlugin","styles","columns","dragHandleColumn","container","gridEl","handle","handleEl","rowEl","row","event","grid","focusRow","rows","direction","toIndex","fromIndex","e","rowIndex","targetIndex","rect","midY","isBefore","focusCol","movedRow","ensureCellVisible","originalIndex","currentIndex","detail","source","cell"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- (function(d,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("../../core/internal/utils"),require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/utils","../../core/plugin/base-plugin","../../core/plugin/expander-column"],g):(d=typeof globalThis<"u"?globalThis:d||self,g(d.TbwGridPlugin_selection={},d.TbwGrid,d.TbwGrid,d.TbwGrid))})(this,(function(d,g,b,u){"use strict";function w(n){return{startRow:Math.min(n.startRow,n.endRow),startCol:Math.min(n.startCol,n.endCol),endRow:Math.max(n.startRow,n.endRow),endCol:Math.max(n.startCol,n.endCol)}}function y(n){const e=w(n);return{from:{row:e.startRow,col:e.startCol},to:{row:e.endRow,col:e.endCol}}}function R(n){return n.map(y)}function p(n,e,t){const s=w(t);return n>=s.startRow&&n<=s.endRow&&e>=s.startCol&&e<=s.endCol}function C(n,e,t){return t.some(s=>p(n,e,s))}function A(n){const e=[],t=w(n);for(let s=t.startRow;s<=t.endRow;s++)for(let r=t.startCol;r<=t.endCol;r++)e.push({row:s,col:r});return e}function x(n){const e=new Map;for(const t of n)for(const s of A(t))e.set(`${s.row},${s.col}`,s);return[...e.values()]}function m(n,e){return{startRow:n.row,startCol:n.col,endRow:e.row,endCol:e.col}}const v="@layer tbw-plugins{tbw-grid.selecting .data-grid-row>.cell{-webkit-user-select:none;user-select:none}tbw-grid[data-has-focus] .data-grid-row.row-focus{background-color:var(--tbw-focus-background, rgba(from var(--tbw-color-accent) r g b / 12%))}tbw-grid[data-selection-mode=row] .cell-focus{outline:none}tbw-grid .data-grid-row>.cell.selected{background-color:var(--tbw-range-selection-bg)}tbw-grid .data-grid-row>.cell.selected.top{border-top:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.bottom{border-bottom:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.first{border-left:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.last{border-right:2px solid var(--tbw-range-border-color)}tbw-grid .tbw-selection-summary{font-size:var(--tbw-font-size-sm, .8125rem);color:var(--tbw-color-fg-muted);white-space:nowrap}}";function I(n,e,t){if(n==="cell"&&e.selectedCell)return{mode:n,ranges:[{from:{row:e.selectedCell.row,col:e.selectedCell.col},to:{row:e.selectedCell.row,col:e.selectedCell.col}}]};if(n==="row"&&e.selected.size>0){const s=[...e.selected].map(r=>({from:{row:r,col:0},to:{row:r,col:t-1}}));return{mode:n,ranges:s}}return n==="range"&&e.ranges.length>0?{mode:n,ranges:R(e.ranges)}:{mode:n,ranges:[]}}class K extends b.BaseGridPlugin{static manifest={configRules:[{id:"selection/range-dblclick",severity:"warn",message:`"triggerOn: 'dblclick'" has no effect when mode is "range".
1
+ (function(h,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("../../core/internal/utils"),require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/utils","../../core/plugin/base-plugin","../../core/plugin/expander-column"],g):(h=typeof globalThis<"u"?globalThis:h||self,g(h.TbwGridPlugin_selection={},h.TbwGrid,h.TbwGrid,h.TbwGrid))})(this,(function(h,g,y,f){"use strict";function w(r){return{startRow:Math.min(r.startRow,r.endRow),startCol:Math.min(r.startCol,r.endCol),endRow:Math.max(r.startRow,r.endRow),endCol:Math.max(r.startCol,r.endCol)}}function p(r){const e=w(r);return{from:{row:e.startRow,col:e.startCol},to:{row:e.endRow,col:e.endCol}}}function m(r){return r.map(p)}function A(r,e,t){const s=w(t);return r>=s.startRow&&r<=s.endRow&&e>=s.startCol&&e<=s.endCol}function R(r,e,t){return t.some(s=>A(r,e,s))}function S(r){const e=[],t=w(r);for(let s=t.startRow;s<=t.endRow;s++)for(let i=t.startCol;i<=t.endCol;i++)e.push({row:s,col:i});return e}function v(r){const e=new Map;for(const t of r)for(const s of S(t))e.set(`${s.row},${s.col}`,s);return[...e.values()]}function b(r,e){return{startRow:r.row,startCol:r.col,endRow:e.row,endCol:e.col}}const x="@layer tbw-plugins{tbw-grid.selecting .data-grid-row>.cell{-webkit-user-select:none;user-select:none}tbw-grid[data-has-focus] .data-grid-row.row-focus{background-color:var(--tbw-focus-background, rgba(from var(--tbw-color-accent) r g b / 12%))}tbw-grid[data-selection-mode=row] .cell-focus{outline:none}tbw-grid .data-grid-row>.cell.selected{background-color:var(--tbw-range-selection-bg)}tbw-grid .data-grid-row>.cell.selected.top{border-top:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.bottom{border-bottom:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.first{border-left:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.last{border-right:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row[data-selectable=false]{cursor:not-allowed;opacity:.6}tbw-grid .data-grid-row[data-selectable=false].row-focus{background-color:var(--tbw-color-row-alt)}tbw-grid .data-grid-row>.cell[data-selectable=false]{cursor:not-allowed;opacity:.6}tbw-grid .data-grid-row>.cell[data-selectable=false].selected{background-color:var(--tbw-color-warning-bg, rgba(255, 243, 205, .5))}tbw-grid .tbw-selection-summary{font-size:var(--tbw-font-size-sm, .8125rem);color:var(--tbw-color-fg-muted);white-space:nowrap}}";function I(r,e,t){if(r==="cell"&&e.selectedCell)return{mode:r,ranges:[{from:{row:e.selectedCell.row,col:e.selectedCell.col},to:{row:e.selectedCell.row,col:e.selectedCell.col}}]};if(r==="row"&&e.selected.size>0){const s=[...e.selected].map(i=>({from:{row:i,col:0},to:{row:i,col:t-1}}));return{mode:r,ranges:s}}return r==="range"&&e.ranges.length>0?{mode:r,ranges:m(e.ranges)}:{mode:r,ranges:[]}}class k extends y.BaseGridPlugin{static manifest={configRules:[{id:"selection/range-dblclick",severity:"warn",message:`"triggerOn: 'dblclick'" has no effect when mode is "range".
2
2
  → Range selection uses drag interaction (mousedown → mousemove), not click events.
3
- → The "triggerOn" option only affects "cell" and "row" selection modes.`,check:e=>e.mode==="range"&&e.triggerOn==="dblclick"}]};name="selection";styles=v;get defaultConfig(){return{mode:"cell",triggerOn:"click"}}selected=new Set;lastSelected=null;anchor=null;ranges=[];activeRange=null;cellAnchor=null;isDragging=!1;pendingKeyboardUpdate=null;selectedCell=null;detach(){this.selected.clear(),this.ranges=[],this.activeRange=null,this.cellAnchor=null,this.isDragging=!1,this.selectedCell=null,this.pendingKeyboardUpdate=null}onCellClick(e){const{rowIndex:t,colIndex:s,originalEvent:r}=e,{mode:i,triggerOn:o="click"}=this.config;if(r.type!==o)return!1;const c=this.columns[s],a=c&&u.isUtilityColumn(c);if(i==="cell")return a||(this.selectedCell={row:t,col:s},this.emit("selection-change",this.#e()),this.requestAfterRender()),!1;if(i==="row")return this.selected.clear(),this.selected.add(t),this.lastSelected=t,this.emit("selection-change",this.#e()),this.requestAfterRender(),!1;if(i==="range"){if(a)return!1;const f=r.shiftKey,h=r.ctrlKey||r.metaKey;if(f&&this.cellAnchor){const l=m(this.cellAnchor,{row:t,col:s});h?this.ranges.length>0?this.ranges[this.ranges.length-1]=l:this.ranges.push(l):this.ranges=[l],this.activeRange=l}else if(h){const l={startRow:t,startCol:s,endRow:t,endCol:s};this.ranges.push(l),this.activeRange=l,this.cellAnchor={row:t,col:s}}else{const l={startRow:t,startCol:s,endRow:t,endCol:s};this.ranges=[l],this.activeRange=l,this.cellAnchor={row:t,col:s}}return this.emit("selection-change",this.#e()),this.requestAfterRender(),!1}return!1}onKeyDown(e){const{mode:t}=this.config,r=["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Tab","Home","End","PageUp","PageDown"].includes(e.key);if(e.key==="Escape")return t==="cell"?this.selectedCell=null:t==="row"?(this.selected.clear(),this.anchor=null):t==="range"&&(this.ranges=[],this.activeRange=null,this.cellAnchor=null),this.emit("selection-change",this.#e()),this.requestAfterRender(),!0;if(t==="cell"&&r)return queueMicrotask(()=>{this.selectedCell={row:this.grid._focusRow,col:this.grid._focusCol},this.emit("selection-change",this.#e()),this.requestAfterRender()}),!1;if(t==="row"&&(e.key==="ArrowUp"||e.key==="ArrowDown"))return queueMicrotask(()=>{this.selected.clear(),this.selected.add(this.grid._focusRow),this.lastSelected=this.grid._focusRow,this.emit("selection-change",this.#e()),this.requestAfterRender()}),!1;if(t==="range"&&r){const i=e.key==="Tab",o=e.shiftKey&&!i;return o&&!this.cellAnchor&&(this.cellAnchor={row:this.grid._focusRow,col:this.grid._focusCol}),this.pendingKeyboardUpdate={shiftKey:o},queueMicrotask(()=>this.requestAfterRender()),!1}if(t==="range"&&e.key==="a"&&(e.ctrlKey||e.metaKey)){const i=this.rows.length,o=this.columns.length;if(i>0&&o>0){const c={startRow:0,startCol:0,endRow:i-1,endCol:o-1};return this.ranges=[c],this.activeRange=c,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0}}return!1}onCellMouseDown(e){if(this.config.mode!=="range"||e.rowIndex===void 0||e.colIndex===void 0||e.rowIndex<0)return;const t=this.columns[e.colIndex];if(t&&u.isUtilityColumn(t)||e.originalEvent.shiftKey&&this.cellAnchor)return;this.isDragging=!0;const s=e.rowIndex,r=e.colIndex;this.cellAnchor={row:s,col:r},e.originalEvent.ctrlKey||e.originalEvent.metaKey||(this.ranges=[]);const o={startRow:s,startCol:r,endRow:s,endCol:r};return this.ranges.push(o),this.activeRange=o,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0}onCellMouseMove(e){if(this.config.mode!=="range"||!this.isDragging||!this.cellAnchor||e.rowIndex===void 0||e.colIndex===void 0||e.rowIndex<0)return;let t=e.colIndex;const s=this.columns[t];if(s&&u.isUtilityColumn(s)){const i=this.columns.findIndex(o=>!u.isUtilityColumn(o));i>=0&&(t=i)}const r=m(this.cellAnchor,{row:e.rowIndex,col:t});return this.ranges.length>0?this.ranges[this.ranges.length-1]=r:this.ranges.push(r),this.activeRange=r,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0}onCellMouseUp(e){if(this.config.mode==="range"&&this.isDragging)return this.isDragging=!1,!0}#t(){const e=this.gridElement;if(!e)return;const{mode:t}=this.config;e.querySelectorAll(".cell").forEach(i=>{i.classList.remove("selected","top","bottom","first","last")});const r=e.querySelectorAll(".data-grid-row");if(r.forEach(i=>{i.classList.remove("selected","row-focus")}),t==="row"&&(g.clearCellFocus(e),r.forEach(i=>{const o=i.querySelector(".cell[data-row]"),c=g.getRowIndexFromCell(o);c>=0&&this.selected.has(c)&&i.classList.add("selected","row-focus")})),t==="range"&&this.ranges.length>0){g.clearCellFocus(e);const i=this.activeRange?w(this.activeRange):null,o=this.columns.findIndex(a=>!u.isUtilityColumn(a));this.columns.length-1,e.querySelectorAll(".cell[data-row][data-col]").forEach(a=>{const f=parseInt(a.getAttribute("data-row")??"-1",10),h=parseInt(a.getAttribute("data-col")??"-1",10);if(f>=0&&h>=0){const l=this.columns[h];if(l&&u.isUtilityColumn(l))return;if(C(f,h,this.ranges)&&(a.classList.add("selected"),i)){f===i.startRow&&a.classList.add("top"),f===i.endRow&&a.classList.add("bottom");const q=Math.max(i.startCol,o);h===q&&a.classList.add("first"),h===i.endCol&&a.classList.add("last")}}})}}afterRender(){const e=this.gridElement;if(!e)return;const t=e.children[0],{mode:s}=this.config;if(this.pendingKeyboardUpdate&&s==="range"){const{shiftKey:r}=this.pendingKeyboardUpdate;this.pendingKeyboardUpdate=null;const i=this.grid._focusRow,o=this.grid._focusCol;if(r&&this.cellAnchor){const c=m(this.cellAnchor,{row:i,col:o});this.ranges=[c],this.activeRange=c}else r||(this.ranges=[],this.activeRange=null,this.cellAnchor={row:i,col:o});this.emit("selection-change",this.#e())}this.grid.setAttribute("data-selection-mode",s),t&&t.classList.toggle("selecting",this.isDragging),this.#t()}onScrollRender(){this.#t()}getSelection(){return{mode:this.config.mode,ranges:this.#e().ranges,anchor:this.cellAnchor}}getSelectedCells(){return x(this.ranges)}isCellSelected(e,t){return C(e,t,this.ranges)}clearSelection(){this.selectedCell=null,this.selected.clear(),this.anchor=null,this.ranges=[],this.activeRange=null,this.cellAnchor=null,this.emit("selection-change",{mode:this.config.mode,ranges:[]}),this.requestAfterRender()}setRanges(e){this.ranges=e.map(t=>({startRow:t.from.row,startCol:t.from.col,endRow:t.to.row,endCol:t.to.col})),this.activeRange=this.ranges.length>0?this.ranges[this.ranges.length-1]:null,this.emit("selection-change",{mode:this.config.mode,ranges:R(this.ranges)}),this.requestAfterRender()}#e(){return I(this.config.mode,{selectedCell:this.selectedCell,selected:this.selected,ranges:this.ranges},this.columns.length)}}d.SelectionPlugin=K,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
3
+ → The "triggerOn" option only affects "cell" and "row" selection modes.`,check:e=>e.mode==="range"&&e.triggerOn==="dblclick"}]};name="selection";styles=x;get defaultConfig(){return{mode:"cell",triggerOn:"click"}}selected=new Set;lastSelected=null;anchor=null;ranges=[];activeRange=null;cellAnchor=null;isDragging=!1;pendingKeyboardUpdate=null;selectedCell=null;checkSelectable(e,t){const{isSelectable:s}=this.config;if(!s)return!0;const i=this.rows[e];if(!i)return!1;const o=t!==void 0?this.columns[t]:void 0;return s(i,e,o,t)}isRowSelectable(e){return this.checkSelectable(e)}isCellSelectable(e,t){return this.checkSelectable(e,t)}detach(){this.selected.clear(),this.ranges=[],this.activeRange=null,this.cellAnchor=null,this.isDragging=!1,this.selectedCell=null,this.pendingKeyboardUpdate=null}onCellClick(e){const{rowIndex:t,colIndex:s,originalEvent:i}=e,{mode:o,triggerOn:l="click"}=this.config;if(i.type!==l)return!1;const n=this.columns[s],d=n&&f.isUtilityColumn(n);if(o==="cell")return d||!this.isCellSelectable(t,s)||(this.selectedCell={row:t,col:s},this.emit("selection-change",this.#e()),this.requestAfterRender()),!1;if(o==="row")return this.isRowSelectable(t)&&(this.selected.clear(),this.selected.add(t),this.lastSelected=t,this.emit("selection-change",this.#e()),this.requestAfterRender()),!1;if(o==="range"){if(d||!this.isCellSelectable(t,s))return!1;const a=i.shiftKey,u=i.ctrlKey||i.metaKey;if(a&&this.cellAnchor){const c=b(this.cellAnchor,{row:t,col:s});u?this.ranges.length>0?this.ranges[this.ranges.length-1]=c:this.ranges.push(c):this.ranges=[c],this.activeRange=c}else if(u){const c={startRow:t,startCol:s,endRow:t,endCol:s};this.ranges.push(c),this.activeRange=c,this.cellAnchor={row:t,col:s}}else{const c={startRow:t,startCol:s,endRow:t,endCol:s};this.ranges=[c],this.activeRange=c,this.cellAnchor={row:t,col:s}}return this.emit("selection-change",this.#e()),this.requestAfterRender(),!1}return!1}onKeyDown(e){const{mode:t}=this.config,i=["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Tab","Home","End","PageUp","PageDown"].includes(e.key);if(e.key==="Escape")return t==="cell"?this.selectedCell=null:t==="row"?(this.selected.clear(),this.anchor=null):t==="range"&&(this.ranges=[],this.activeRange=null,this.cellAnchor=null),this.emit("selection-change",this.#e()),this.requestAfterRender(),!0;if(t==="cell"&&i)return queueMicrotask(()=>{const o=this.grid._focusRow,l=this.grid._focusCol;this.isCellSelectable(o,l)?this.selectedCell={row:o,col:l}:this.selectedCell=null,this.emit("selection-change",this.#e()),this.requestAfterRender()}),!1;if(t==="row"&&(e.key==="ArrowUp"||e.key==="ArrowDown"))return queueMicrotask(()=>{const o=this.grid._focusRow;this.isRowSelectable(o)?(this.selected.clear(),this.selected.add(o),this.lastSelected=o):this.selected.clear(),this.emit("selection-change",this.#e()),this.requestAfterRender()}),!1;if(t==="range"&&i){const o=e.key==="Tab",l=e.shiftKey&&!o;return l&&!this.cellAnchor&&(this.cellAnchor={row:this.grid._focusRow,col:this.grid._focusCol}),this.pendingKeyboardUpdate={shiftKey:l},queueMicrotask(()=>this.requestAfterRender()),!1}if(t==="range"&&e.key==="a"&&(e.ctrlKey||e.metaKey)){const o=this.rows.length,l=this.columns.length;if(o>0&&l>0){const n={startRow:0,startCol:0,endRow:o-1,endCol:l-1};return this.ranges=[n],this.activeRange=n,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0}}return!1}onCellMouseDown(e){if(this.config.mode!=="range"||e.rowIndex===void 0||e.colIndex===void 0||e.rowIndex<0)return;const t=this.columns[e.colIndex];if(t&&f.isUtilityColumn(t)||!this.isCellSelectable(e.rowIndex,e.colIndex)||e.originalEvent.shiftKey&&this.cellAnchor)return;this.isDragging=!0;const s=e.rowIndex,i=e.colIndex;this.cellAnchor={row:s,col:i},e.originalEvent.ctrlKey||e.originalEvent.metaKey||(this.ranges=[]);const l={startRow:s,startCol:i,endRow:s,endCol:i};return this.ranges.push(l),this.activeRange=l,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0}onCellMouseMove(e){if(this.config.mode!=="range"||!this.isDragging||!this.cellAnchor||e.rowIndex===void 0||e.colIndex===void 0||e.rowIndex<0)return;let t=e.colIndex;const s=this.columns[t];if(s&&f.isUtilityColumn(s)){const o=this.columns.findIndex(l=>!f.isUtilityColumn(l));o>=0&&(t=o)}const i=b(this.cellAnchor,{row:e.rowIndex,col:t});return this.ranges.length>0?this.ranges[this.ranges.length-1]=i:this.ranges.push(i),this.activeRange=i,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0}onCellMouseUp(e){if(this.config.mode==="range"&&this.isDragging)return this.isDragging=!1,!0}#t(){const e=this.gridElement;if(!e)return;const{mode:t}=this.config,s=!!this.config.isSelectable;e.querySelectorAll(".cell").forEach(l=>{l.classList.remove("selected","top","bottom","first","last"),s&&l.removeAttribute("data-selectable")});const o=e.querySelectorAll(".data-grid-row");if(o.forEach(l=>{l.classList.remove("selected","row-focus"),s&&l.removeAttribute("data-selectable")}),t==="row"&&(g.clearCellFocus(e),o.forEach(l=>{const n=l.querySelector(".cell[data-row]"),d=g.getRowIndexFromCell(n);d>=0&&(s&&!this.isRowSelectable(d)&&l.setAttribute("data-selectable","false"),this.selected.has(d)&&l.classList.add("selected","row-focus"))})),(t==="cell"||t==="range")&&s&&e.querySelectorAll(".cell[data-row][data-col]").forEach(n=>{const d=parseInt(n.getAttribute("data-row")??"-1",10),a=parseInt(n.getAttribute("data-col")??"-1",10);d>=0&&a>=0&&(this.isCellSelectable(d,a)||n.setAttribute("data-selectable","false"))}),t==="range"&&this.ranges.length>0){g.clearCellFocus(e);const l=this.activeRange?w(this.activeRange):null,n=this.columns.findIndex(a=>!f.isUtilityColumn(a));this.columns.length-1,e.querySelectorAll(".cell[data-row][data-col]").forEach(a=>{const u=parseInt(a.getAttribute("data-row")??"-1",10),c=parseInt(a.getAttribute("data-col")??"-1",10);if(u>=0&&c>=0){const C=this.columns[c];if(C&&f.isUtilityColumn(C))return;if(R(u,c,this.ranges)&&(a.classList.add("selected"),l)){u===l.startRow&&a.classList.add("top"),u===l.endRow&&a.classList.add("bottom");const K=Math.max(l.startCol,n);c===K&&a.classList.add("first"),c===l.endCol&&a.classList.add("last")}}})}}afterRender(){const e=this.gridElement;if(!e)return;const t=e.children[0],{mode:s}=this.config;if(this.pendingKeyboardUpdate&&s==="range"){const{shiftKey:i}=this.pendingKeyboardUpdate;this.pendingKeyboardUpdate=null;const o=this.grid._focusRow,l=this.grid._focusCol;if(i&&this.cellAnchor){const n=b(this.cellAnchor,{row:o,col:l});this.ranges=[n],this.activeRange=n}else i||(this.ranges=[],this.activeRange=null,this.cellAnchor={row:o,col:l});this.emit("selection-change",this.#e())}this.grid.setAttribute("data-selection-mode",s),t&&t.classList.toggle("selecting",this.isDragging),this.#t()}onScrollRender(){this.#t()}getSelection(){return{mode:this.config.mode,ranges:this.#e().ranges,anchor:this.cellAnchor}}getSelectedCells(){return v(this.ranges)}isCellSelected(e,t){return R(e,t,this.ranges)}clearSelection(){this.selectedCell=null,this.selected.clear(),this.anchor=null,this.ranges=[],this.activeRange=null,this.cellAnchor=null,this.emit("selection-change",{mode:this.config.mode,ranges:[]}),this.requestAfterRender()}setRanges(e){this.ranges=e.map(t=>({startRow:t.from.row,startCol:t.from.col,endRow:t.to.row,endCol:t.to.col})),this.activeRange=this.ranges.length>0?this.ranges[this.ranges.length-1]:null,this.emit("selection-change",{mode:this.config.mode,ranges:m(this.ranges)}),this.requestAfterRender()}#e(){return I(this.config.mode,{selectedCell:this.selectedCell,selected:this.selected,ranges:this.ranges},this.columns.length)}}h.SelectionPlugin=k,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
4
4
  //# sourceMappingURL=selection.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"selection.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/selection/range-selection.ts","../../../../../libs/grid/src/lib/plugins/selection/SelectionPlugin.ts"],"sourcesContent":["/**\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 type { PluginManifest } from '../../core/plugin/base-plugin';\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 * Adds cell, row, and range selection capabilities to the grid with full keyboard support.\n * Whether you need simple cell highlighting or complex multi-range selections, this plugin has you covered.\n *\n * ## Installation\n *\n * ```ts\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n * ```\n *\n * ## Selection Modes\n *\n * Configure the plugin with one of three modes via {@link SelectionConfig}:\n *\n * - **`'cell'`** - Single cell selection (default). Click cells to select individually.\n * - **`'row'`** - Full row selection. Click anywhere in a row to select the entire row.\n * - **`'range'`** - Rectangular selection. Click and drag or Shift+Click to select ranges.\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Arrow Keys` | Move selection |\n * | `Shift + Arrow` | Extend selection (range mode) |\n * | `Ctrl/Cmd + Click` | Toggle selection (multi-select) |\n * | `Shift + Click` | Extend to clicked cell/row |\n * | `Ctrl/Cmd + A` | Select all (range mode) |\n * | `Escape` | Clear selection |\n *\n * ## CSS Custom Properties\n *\n * | Property | Description |\n * |----------|-------------|\n * | `--tbw-focus-background` | Focused row background |\n * | `--tbw-range-selection-bg` | Range selection fill |\n * | `--tbw-range-border-color` | Range selection border |\n *\n * @example Basic row selection\n * ```ts\n * grid.gridConfig = {\n * columns: [...],\n * plugins: [new SelectionPlugin({ mode: 'row' })],\n * };\n * ```\n *\n * @example Range selection with event handling\n * ```ts\n * grid.gridConfig = {\n * plugins: [new SelectionPlugin({ mode: 'range' })],\n * };\n *\n * grid.addEventListener('selection-change', (e) => {\n * const { mode, ranges } = e.detail;\n * console.log(`Selected ${ranges.length} ranges in ${mode} mode`);\n * });\n * ```\n *\n * @example Programmatic selection control\n * ```ts\n * const plugin = grid.getPlugin(SelectionPlugin);\n *\n * // Get current selection\n * const selection = plugin.getSelection();\n * console.log(selection.ranges);\n *\n * // Set selection programmatically\n * plugin.setRanges([{ from: { row: 0, col: 0 }, to: { row: 5, col: 3 } }]);\n *\n * // Clear all selection\n * plugin.clearSelection();\n * ```\n *\n * @see {@link SelectionMode} for detailed mode descriptions\n * @see {@link SelectionConfig} for configuration options\n * @see {@link SelectionResult} for the selection result structure\n * @see [Live Demos](?path=/docs/grid-plugins-selection--docs) for interactive examples\n */\nexport class SelectionPlugin extends BaseGridPlugin<SelectionConfig> {\n /**\n * Plugin manifest - declares configuration validation rules.\n * @internal\n */\n static override readonly manifest: PluginManifest<SelectionConfig> = {\n configRules: [\n {\n id: 'selection/range-dblclick',\n severity: 'warn',\n message:\n `\"triggerOn: 'dblclick'\" has no effect when mode is \"range\".\\n` +\n ` → Range selection uses drag interaction (mousedown → mousemove), not click events.\\n` +\n ` → The \"triggerOn\" option only affects \"cell\" and \"row\" selection modes.`,\n check: (config) => config.mode === 'range' && config.triggerOn === 'dblclick',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'selection';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<SelectionConfig> {\n return {\n mode: 'cell',\n triggerOn: 'click',\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 /** @internal */\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 /** @internal */\n override onCellClick(event: CellClickEvent): boolean {\n const { rowIndex, colIndex, originalEvent } = event;\n const { mode, triggerOn = 'click' } = this.config;\n\n // Skip if event type doesn't match configured trigger\n // This allows dblclick mode to only select on double-click\n if (originalEvent.type !== triggerOn) {\n return false;\n }\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 /** @internal */\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 /** @internal */\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 /** @internal */\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 /** @internal */\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 /** @internal */\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 * @internal\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 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":["normalizeRange","range","toPublicRange","normalized","toPublicRanges","ranges","isCellInRange","row","col","isCellInAnyRange","getCellsInRange","cells","getAllCellsInRanges","cellMap","cell","createRangeFromAnchor","anchor","current","buildSelectionEvent","mode","state","colCount","rowIndex","SelectionPlugin","BaseGridPlugin","config","styles","event","colIndex","originalEvent","triggerOn","column","isUtility","isUtilityColumn","#buildEvent","shiftKey","ctrlKey","newRange","isNavKey","isTabKey","shouldExtend","rowCount","allRange","targetCol","firstDataCol","_event","#applySelectionClasses","gridEl","allRows","clearCellFocus","firstCell","getRowIndexFromCell","firstDataColIndex","effectiveStartCol","container","currentRow","currentCol","r"],"mappings":"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"}
1
+ {"version":3,"file":"selection.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/selection/range-selection.ts","../../../../../libs/grid/src/lib/plugins/selection/SelectionPlugin.ts"],"sourcesContent":["/**\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 type { PluginManifest } from '../../core/plugin/base-plugin';\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 SelectableCallback,\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 * Adds cell, row, and range selection capabilities to the grid with full keyboard support.\n * Whether you need simple cell highlighting or complex multi-range selections, this plugin has you covered.\n *\n * ## Installation\n *\n * ```ts\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n * ```\n *\n * ## Selection Modes\n *\n * Configure the plugin with one of three modes via {@link SelectionConfig}:\n *\n * - **`'cell'`** - Single cell selection (default). Click cells to select individually.\n * - **`'row'`** - Full row selection. Click anywhere in a row to select the entire row.\n * - **`'range'`** - Rectangular selection. Click and drag or Shift+Click to select ranges.\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Arrow Keys` | Move selection |\n * | `Shift + Arrow` | Extend selection (range mode) |\n * | `Ctrl/Cmd + Click` | Toggle selection (multi-select) |\n * | `Shift + Click` | Extend to clicked cell/row |\n * | `Ctrl/Cmd + A` | Select all (range mode) |\n * | `Escape` | Clear selection |\n *\n * ## CSS Custom Properties\n *\n * | Property | Description |\n * |----------|-------------|\n * | `--tbw-focus-background` | Focused row background |\n * | `--tbw-range-selection-bg` | Range selection fill |\n * | `--tbw-range-border-color` | Range selection border |\n *\n * @example Basic row selection\n * ```ts\n * grid.gridConfig = {\n * columns: [...],\n * plugins: [new SelectionPlugin({ mode: 'row' })],\n * };\n * ```\n *\n * @example Range selection with event handling\n * ```ts\n * grid.gridConfig = {\n * plugins: [new SelectionPlugin({ mode: 'range' })],\n * };\n *\n * grid.addEventListener('selection-change', (e) => {\n * const { mode, ranges } = e.detail;\n * console.log(`Selected ${ranges.length} ranges in ${mode} mode`);\n * });\n * ```\n *\n * @example Programmatic selection control\n * ```ts\n * const plugin = grid.getPlugin(SelectionPlugin);\n *\n * // Get current selection\n * const selection = plugin.getSelection();\n * console.log(selection.ranges);\n *\n * // Set selection programmatically\n * plugin.setRanges([{ from: { row: 0, col: 0 }, to: { row: 5, col: 3 } }]);\n *\n * // Clear all selection\n * plugin.clearSelection();\n * ```\n *\n * @see {@link SelectionMode} for detailed mode descriptions\n * @see {@link SelectionConfig} for configuration options\n * @see {@link SelectionResult} for the selection result structure\n * @see [Live Demos](?path=/docs/grid-plugins-selection--docs) for interactive examples\n */\nexport class SelectionPlugin extends BaseGridPlugin<SelectionConfig> {\n /**\n * Plugin manifest - declares configuration validation rules.\n * @internal\n */\n static override readonly manifest: PluginManifest<SelectionConfig> = {\n configRules: [\n {\n id: 'selection/range-dblclick',\n severity: 'warn',\n message:\n `\"triggerOn: 'dblclick'\" has no effect when mode is \"range\".\\n` +\n ` → Range selection uses drag interaction (mousedown → mousemove), not click events.\\n` +\n ` → The \"triggerOn\" option only affects \"cell\" and \"row\" selection modes.`,\n check: (config) => config.mode === 'range' && config.triggerOn === 'dblclick',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'selection';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<SelectionConfig> {\n return {\n mode: 'cell',\n triggerOn: 'click',\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 Private Helpers - Selectability\n\n /**\n * Check if a row/cell is selectable.\n * Returns true if selectable, false if not.\n */\n private checkSelectable(rowIndex: number, colIndex?: number): boolean {\n const { isSelectable } = this.config;\n if (!isSelectable) return true; // No callback = all selectable\n\n const row = this.rows[rowIndex];\n if (!row) return false;\n\n const column = colIndex !== undefined ? this.columns[colIndex] : undefined;\n return isSelectable(row, rowIndex, column, colIndex);\n }\n\n /**\n * Check if an entire row is selectable (for row mode).\n */\n private isRowSelectable(rowIndex: number): boolean {\n return this.checkSelectable(rowIndex);\n }\n\n /**\n * Check if a cell is selectable (for cell/range modes).\n */\n private isCellSelectable(rowIndex: number, colIndex: number): boolean {\n return this.checkSelectable(rowIndex, colIndex);\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\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 /** @internal */\n override onCellClick(event: CellClickEvent): boolean {\n const { rowIndex, colIndex, originalEvent } = event;\n const { mode, triggerOn = 'click' } = this.config;\n\n // Skip if event type doesn't match configured trigger\n // This allows dblclick mode to only select on double-click\n if (originalEvent.type !== triggerOn) {\n return false;\n }\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 and non-selectable cells\n if (mode === 'cell') {\n if (isUtility) {\n return false; // Allow event to propagate, but don't select utility cells\n }\n if (!this.isCellSelectable(rowIndex, colIndex)) {\n return false; // Cell is not selectable\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 if (!this.isRowSelectable(rowIndex)) {\n return false; // Row is not selectable\n }\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 // Skip non-selectable cells in range mode\n if (!this.isCellSelectable(rowIndex, colIndex)) {\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 /** @internal */\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 (but respects selectability)\n if (mode === 'cell' && isNavKey) {\n // Use queueMicrotask so grid's handler runs first and updates focusRow/focusCol\n queueMicrotask(() => {\n const focusRow = this.grid._focusRow;\n const focusCol = this.grid._focusCol;\n // Only select if the cell is selectable\n if (this.isCellSelectable(focusRow, focusCol)) {\n this.selectedCell = { row: focusRow, col: focusCol };\n } else {\n // Clear selection when navigating to non-selectable cell\n this.selectedCell = null;\n }\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 (but respects selectability)\n if (mode === 'row' && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {\n // Let grid move focus first, then sync row selection\n queueMicrotask(() => {\n const focusRow = this.grid._focusRow;\n // Only select if the row is selectable\n if (this.isRowSelectable(focusRow)) {\n this.selected.clear();\n this.selected.add(focusRow);\n this.lastSelected = focusRow;\n } else {\n // Clear selection when navigating to non-selectable row\n this.selected.clear();\n }\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 /** @internal */\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 // Skip non-selectable cells - don't start drag from them\n if (!this.isCellSelectable(event.rowIndex, event.colIndex)) {\n return;\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 /** @internal */\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 /** @internal */\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 const hasSelectableCallback = !!this.config.isSelectable;\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 // Clear selectable attribute - will be re-applied below\n if (hasSelectableCallback) {\n cell.removeAttribute('data-selectable');\n }\n });\n\n const allRows = gridEl.querySelectorAll('.data-grid-row');\n allRows.forEach((row) => {\n row.classList.remove('selected', 'row-focus');\n // Clear selectable attribute - will be re-applied below\n if (hasSelectableCallback) {\n row.removeAttribute('data-selectable');\n }\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) {\n // Mark non-selectable rows\n if (hasSelectableCallback && !this.isRowSelectable(rowIndex)) {\n row.setAttribute('data-selectable', 'false');\n }\n if (this.selected.has(rowIndex)) {\n row.classList.add('selected', 'row-focus');\n }\n }\n });\n }\n\n // CELL/RANGE MODE: Mark non-selectable cells\n if ((mode === 'cell' || mode === 'range') && hasSelectableCallback) {\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 if (!this.isCellSelectable(rowIndex, colIndex)) {\n cell.setAttribute('data-selectable', 'false');\n }\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 /** @internal */\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 * @internal\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 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":["normalizeRange","range","toPublicRange","normalized","toPublicRanges","ranges","isCellInRange","row","col","isCellInAnyRange","getCellsInRange","cells","getAllCellsInRanges","cellMap","cell","createRangeFromAnchor","anchor","current","buildSelectionEvent","mode","state","colCount","rowIndex","SelectionPlugin","BaseGridPlugin","config","styles","colIndex","isSelectable","column","event","originalEvent","triggerOn","isUtility","isUtilityColumn","#buildEvent","shiftKey","ctrlKey","newRange","isNavKey","focusRow","focusCol","isTabKey","shouldExtend","rowCount","allRange","targetCol","firstDataCol","_event","#applySelectionClasses","gridEl","hasSelectableCallback","allRows","clearCellFocus","firstCell","getRowIndexFromCell","firstDataColIndex","effectiveStartCol","container","currentRow","currentCol","r"],"mappings":"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"}