@toolbox-web/grid 1.2.0 → 1.3.1

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 (79) hide show
  1. package/README.md +80 -22
  2. package/all.js +619 -571
  3. package/all.js.map +1 -1
  4. package/index.js +362 -302
  5. package/index.js.map +1 -1
  6. package/lib/core/grid.d.ts +64 -1
  7. package/lib/core/grid.d.ts.map +1 -1
  8. package/lib/core/internal/row-animation.d.ts +37 -0
  9. package/lib/core/internal/row-animation.d.ts.map +1 -0
  10. package/lib/core/types.d.ts +17 -0
  11. package/lib/core/types.d.ts.map +1 -1
  12. package/lib/plugins/clipboard/index.js +82 -76
  13. package/lib/plugins/clipboard/index.js.map +1 -1
  14. package/lib/plugins/clipboard/types.d.ts +1 -0
  15. package/lib/plugins/clipboard/types.d.ts.map +1 -1
  16. package/lib/plugins/column-virtualization/index.js +43 -41
  17. package/lib/plugins/column-virtualization/index.js.map +1 -1
  18. package/lib/plugins/context-menu/index.js +24 -22
  19. package/lib/plugins/context-menu/index.js.map +1 -1
  20. package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
  21. package/lib/plugins/editing/index.js +83 -52
  22. package/lib/plugins/editing/index.js.map +1 -1
  23. package/lib/plugins/export/index.js +22 -20
  24. package/lib/plugins/export/index.js.map +1 -1
  25. package/lib/plugins/filtering/FilteringPlugin.d.ts +11 -1
  26. package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
  27. package/lib/plugins/filtering/index.js +160 -125
  28. package/lib/plugins/filtering/index.js.map +1 -1
  29. package/lib/plugins/grouping-columns/index.js +20 -18
  30. package/lib/plugins/grouping-columns/index.js.map +1 -1
  31. package/lib/plugins/grouping-rows/index.js +66 -64
  32. package/lib/plugins/grouping-rows/index.js.map +1 -1
  33. package/lib/plugins/master-detail/index.js +51 -49
  34. package/lib/plugins/master-detail/index.js.map +1 -1
  35. package/lib/plugins/multi-sort/index.js +17 -15
  36. package/lib/plugins/multi-sort/index.js.map +1 -1
  37. package/lib/plugins/pinned-columns/index.js +24 -22
  38. package/lib/plugins/pinned-columns/index.js.map +1 -1
  39. package/lib/plugins/pinned-rows/index.js +25 -23
  40. package/lib/plugins/pinned-rows/index.js.map +1 -1
  41. package/lib/plugins/pivot/index.js +49 -47
  42. package/lib/plugins/pivot/index.js.map +1 -1
  43. package/lib/plugins/reorder/index.js +24 -22
  44. package/lib/plugins/reorder/index.js.map +1 -1
  45. package/lib/plugins/responsive/index.js +19 -17
  46. package/lib/plugins/responsive/index.js.map +1 -1
  47. package/lib/plugins/row-reorder/index.js +38 -36
  48. package/lib/plugins/row-reorder/index.js.map +1 -1
  49. package/lib/plugins/selection/SelectionPlugin.d.ts +13 -0
  50. package/lib/plugins/selection/SelectionPlugin.d.ts.map +1 -1
  51. package/lib/plugins/selection/index.d.ts +1 -1
  52. package/lib/plugins/selection/index.d.ts.map +1 -1
  53. package/lib/plugins/selection/index.js +118 -85
  54. package/lib/plugins/selection/index.js.map +1 -1
  55. package/lib/plugins/selection/types.d.ts +50 -6
  56. package/lib/plugins/selection/types.d.ts.map +1 -1
  57. package/lib/plugins/server-side/index.js +34 -32
  58. package/lib/plugins/server-side/index.js.map +1 -1
  59. package/lib/plugins/tree/index.js +25 -23
  60. package/lib/plugins/tree/index.js.map +1 -1
  61. package/lib/plugins/undo-redo/index.js +22 -20
  62. package/lib/plugins/undo-redo/index.js.map +1 -1
  63. package/lib/plugins/visibility/index.js +21 -19
  64. package/lib/plugins/visibility/index.js.map +1 -1
  65. package/package.json +21 -4
  66. package/public.d.ts +1 -1
  67. package/public.d.ts.map +1 -1
  68. package/umd/grid.all.umd.js +19 -19
  69. package/umd/grid.all.umd.js.map +1 -1
  70. package/umd/grid.umd.js +9 -9
  71. package/umd/grid.umd.js.map +1 -1
  72. package/umd/plugins/clipboard.umd.js +5 -5
  73. package/umd/plugins/clipboard.umd.js.map +1 -1
  74. package/umd/plugins/editing.umd.js +1 -1
  75. package/umd/plugins/editing.umd.js.map +1 -1
  76. package/umd/plugins/filtering.umd.js +1 -1
  77. package/umd/plugins/filtering.umd.js.map +1 -1
  78. package/umd/plugins/selection.umd.js +2 -2
  79. package/umd/plugins/selection.umd.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"filtering.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/filtering/filter-model.ts","../../../../../libs/grid/src/lib/plugins/filtering/FilteringPlugin.ts"],"sourcesContent":["/**\n * Filter Model Core Logic\n *\n * Pure functions for filtering operations.\n */\n\nimport type { FilterModel } from './types';\n\n/**\n * Check if a single row matches a filter condition.\n *\n * @param row - The row data object\n * @param filter - The filter to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns True if the row matches the filter\n */\nexport function matchesFilter(row: Record<string, unknown>, filter: FilterModel, caseSensitive = false): boolean {\n const rawValue = row[filter.field];\n\n // Handle blank/notBlank first - these work on null/undefined/empty\n if (filter.operator === 'blank') {\n return rawValue == null || rawValue === '';\n }\n if (filter.operator === 'notBlank') {\n return rawValue != null && rawValue !== '';\n }\n\n // Null/undefined values don't match other filters\n if (rawValue == null) return false;\n\n // Prepare values for comparison\n const stringValue = String(rawValue);\n const compareValue = caseSensitive ? stringValue : stringValue.toLowerCase();\n const filterValue = caseSensitive ? String(filter.value) : String(filter.value).toLowerCase();\n\n switch (filter.operator) {\n // Text operators\n case 'contains':\n return compareValue.includes(filterValue);\n\n case 'notContains':\n return !compareValue.includes(filterValue);\n\n case 'equals':\n return compareValue === filterValue;\n\n case 'notEquals':\n return compareValue !== filterValue;\n\n case 'startsWith':\n return compareValue.startsWith(filterValue);\n\n case 'endsWith':\n return compareValue.endsWith(filterValue);\n\n // Number/Date operators (use raw numeric values)\n case 'lessThan':\n return Number(rawValue) < Number(filter.value);\n\n case 'lessThanOrEqual':\n return Number(rawValue) <= Number(filter.value);\n\n case 'greaterThan':\n return Number(rawValue) > Number(filter.value);\n\n case 'greaterThanOrEqual':\n return Number(rawValue) >= Number(filter.value);\n\n case 'between':\n return Number(rawValue) >= Number(filter.value) && Number(rawValue) <= Number(filter.valueTo);\n\n // Set operators\n case 'in':\n return Array.isArray(filter.value) && filter.value.includes(rawValue);\n\n case 'notIn':\n return Array.isArray(filter.value) && !filter.value.includes(rawValue);\n\n default:\n return true;\n }\n}\n\n/**\n * Filter rows based on multiple filter conditions (AND logic).\n * All filters must match for a row to be included.\n *\n * @param rows - The rows to filter\n * @param filters - Array of filters to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns Filtered rows\n */\nexport function filterRows<T extends Record<string, unknown>>(\n rows: T[],\n filters: FilterModel[],\n caseSensitive = false\n): T[] {\n if (!filters.length) return rows;\n return rows.filter((row) => filters.every((f) => matchesFilter(row, f, caseSensitive)));\n}\n\n/**\n * Compute a cache key for a set of filters.\n * Used for memoization of filter results.\n *\n * @param filters - Array of filters\n * @returns Stable string key for the filter set\n */\nexport function computeFilterCacheKey(filters: FilterModel[]): string {\n return JSON.stringify(\n filters.map((f) => ({\n field: f.field,\n operator: f.operator,\n value: f.value,\n valueTo: f.valueTo,\n }))\n );\n}\n\n/**\n * Extract unique values from a field across all rows.\n * Useful for populating \"set\" filter dropdowns.\n *\n * @param rows - The rows to extract values from\n * @param field - The field name\n * @returns Sorted array of unique non-null values\n */\nexport function getUniqueValues<T extends Record<string, unknown>>(rows: T[], field: string): unknown[] {\n const values = new Set<unknown>();\n for (const row of rows) {\n const value = row[field];\n if (value != null) {\n values.add(value);\n }\n }\n return [...values].sort((a, b) => {\n // Handle mixed types gracefully\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b;\n }\n return String(a).localeCompare(String(b));\n });\n}\n","/**\n * Filtering Plugin (Class-based)\n *\n * Provides comprehensive filtering functionality for tbw-grid.\n * Supports text, number, date, set, and boolean filters with caching.\n * Includes UI with filter buttons in headers and dropdown filter panels.\n */\n\nimport { computeVirtualWindow, shouldBypassVirtualization } from '../../core/internal/virtualization';\nimport { BaseGridPlugin, type GridElement } from '../../core/plugin/base-plugin';\nimport { isUtilityColumn } from '../../core/plugin/expander-column';\nimport type { ColumnConfig, ColumnState } from '../../core/types';\nimport { computeFilterCacheKey, filterRows, getUniqueValues } from './filter-model';\nimport styles from './filtering.css?inline';\nimport filterPanelStyles from './FilteringPlugin.css?inline';\nimport type { FilterChangeDetail, FilterConfig, FilterModel, FilterPanelParams } from './types';\n\n/**\n * Filtering Plugin for tbw-grid\n *\n * Adds column header filters with text search, dropdown options, and custom filter panels.\n * Supports both **local filtering** for small datasets and **async handlers** for server-side\n * filtering on large datasets.\n *\n * ## Installation\n *\n * ```ts\n * import { FilteringPlugin } from '@toolbox-web/grid/plugins/filtering';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `debounceMs` | `number` | `300` | Debounce delay for filter input |\n * | `caseSensitive` | `boolean` | `false` | Case-sensitive string matching |\n * | `trimInput` | `boolean` | `true` | Trim whitespace from filter input |\n * | `useWorker` | `boolean` | `true` | Use Web Worker for datasets >1000 rows |\n * | `filterPanelRenderer` | `FilterPanelRenderer` | - | Custom filter panel renderer |\n * | `valuesHandler` | `FilterValuesHandler` | - | Async handler to fetch unique filter values |\n * | `filterHandler` | `FilterHandler<TRow>` | - | Async handler to apply filters remotely |\n *\n * ## Column Configuration\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `filterable` | `boolean` | Enable filtering for this column |\n * | `filterType` | `'text' \\| 'select' \\| 'number' \\| 'date'` | Filter UI type |\n * | `filterOptions` | `unknown[]` | Predefined options for select filters |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `setFilter` | `(field, value) => void` | Set filter value for a column |\n * | `getFilters` | `() => FilterModel[]` | Get all current filters |\n * | `clearFilters` | `() => void` | Clear all filters |\n * | `clearFilter` | `(field) => void` | Clear filter for a specific column |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-filter-panel-bg` | `var(--tbw-color-panel-bg)` | Panel background |\n * | `--tbw-filter-panel-fg` | `var(--tbw-color-fg)` | Panel text color |\n * | `--tbw-filter-panel-border` | `var(--tbw-color-border)` | Panel border |\n * | `--tbw-filter-active-color` | `var(--tbw-color-accent)` | Active filter indicator |\n * | `--tbw-filter-input-bg` | `var(--tbw-color-bg)` | Input background |\n * | `--tbw-filter-input-focus` | `var(--tbw-color-accent)` | Input focus border |\n *\n * @example Basic Usage with Filterable Columns\n * ```ts\n * import '@toolbox-web/grid';\n * import { FilteringPlugin } from '@toolbox-web/grid/plugins/filtering';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name', filterable: true },\n * { field: 'status', header: 'Status', filterable: true, filterType: 'select' },\n * { field: 'email', header: 'Email', filterable: true },\n * ],\n * plugins: [new FilteringPlugin({ debounceMs: 300 })],\n * };\n * grid.rows = data;\n * ```\n *\n * @example Server-Side Filtering with Async Handlers\n * ```ts\n * new FilteringPlugin({\n * // Fetch unique values from server for filter dropdown\n * valuesHandler: async (field, column) => {\n * const response = await fetch(`/api/distinct-values?field=${field}`);\n * return response.json();\n * },\n * // Apply filters on the server\n * filterHandler: async (filters, currentRows) => {\n * const response = await fetch('/api/data', {\n * method: 'POST',\n * body: JSON.stringify({ filters }),\n * });\n * return response.json();\n * },\n * });\n * ```\n *\n * @see {@link FilterConfig} for all configuration options\n * @see {@link FilterModel} for filter data structure\n * @see {@link FilterPanelParams} for custom panel renderer parameters\n *\n * @internal Extends BaseGridPlugin\n */\nexport class FilteringPlugin extends BaseGridPlugin<FilterConfig> {\n /** @internal */\n readonly name = 'filtering';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<FilterConfig> {\n return {\n debounceMs: 300,\n caseSensitive: false,\n trimInput: true,\n useWorker: true,\n };\n }\n\n // #region Internal State\n private filters: Map<string, FilterModel> = new Map();\n private cachedResult: unknown[] | null = null;\n private cacheKey: string | null = null;\n private openPanelField: string | null = null;\n private panelElement: HTMLElement | null = null;\n private panelAnchorElement: HTMLElement | null = null; // For CSS anchor positioning cleanup\n private searchText: Map<string, string> = new Map();\n private excludedValues: Map<string, Set<unknown>> = new Map();\n private panelAbortController: AbortController | null = null; // For panel-scoped listeners\n private globalStylesInjected = false;\n\n // Virtualization constants for filter value list\n private static readonly LIST_ITEM_HEIGHT = 28;\n private static readonly LIST_OVERSCAN = 3;\n private static readonly LIST_BYPASS_THRESHOLD = 50; // Don't virtualize if < 50 items\n\n /**\n * Sync excludedValues map from a filter model (for set filters).\n */\n private syncExcludedValues(field: string, filter: FilterModel | null): void {\n if (!filter) {\n this.excludedValues.delete(field);\n } else if (filter.type === 'set' && filter.operator === 'notIn' && Array.isArray(filter.value)) {\n this.excludedValues.set(field, new Set(filter.value));\n } else if (filter.type === 'set') {\n // Other set operators may have different semantics; clear for safety\n this.excludedValues.delete(field);\n }\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.injectGlobalStyles();\n }\n\n /** @internal */\n override detach(): void {\n this.filters.clear();\n this.cachedResult = null;\n this.cacheKey = null;\n this.openPanelField = null;\n if (this.panelElement) {\n this.panelElement.remove();\n this.panelElement = null;\n }\n this.searchText.clear();\n this.excludedValues.clear();\n // Abort panel-scoped listeners (document click handler, etc.)\n this.panelAbortController?.abort();\n this.panelAbortController = null;\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processRows(rows: readonly unknown[]): unknown[] {\n const filterList = [...this.filters.values()];\n if (!filterList.length) return [...rows];\n\n // If using async filterHandler, processRows becomes a passthrough\n // Actual filtering happens in applyFiltersAsync and rows are set directly on grid\n if (this.config.filterHandler) {\n // Return cached result if available (set by async handler)\n if (this.cachedResult) return this.cachedResult;\n // Otherwise return rows as-is (filtering happens async)\n return [...rows];\n }\n\n // Check cache\n const newCacheKey = computeFilterCacheKey(filterList);\n if (this.cacheKey === newCacheKey && this.cachedResult) {\n return this.cachedResult;\n }\n\n // Filter rows synchronously (worker support can be added later)\n const result = filterRows([...rows] as Record<string, unknown>[], filterList, this.config.caseSensitive);\n\n // Update cache\n this.cachedResult = result;\n this.cacheKey = newCacheKey;\n\n return result;\n }\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Find all header cells (using part attribute, not class)\n const headerCells = gridEl.querySelectorAll('[part~=\"header-cell\"]');\n headerCells.forEach((cell) => {\n const colIndex = cell.getAttribute('data-col');\n if (colIndex === null) return;\n\n // Use visibleColumns since data-col is the index within _visibleColumns\n const col = this.visibleColumns[parseInt(colIndex, 10)] as ColumnConfig;\n if (!col || col.filterable === false) return;\n\n // Skip utility columns (expander, selection checkbox, etc.)\n if (isUtilityColumn(col)) return;\n\n const field = col.field;\n if (!field) return;\n\n const hasFilter = this.filters.has(field);\n\n // Check if button already exists\n let filterBtn = cell.querySelector('.tbw-filter-btn') as HTMLElement | null;\n\n if (filterBtn) {\n // Update active state of existing button\n filterBtn.classList.toggle('active', hasFilter);\n (cell as HTMLElement).classList.toggle('filtered', hasFilter);\n return;\n }\n\n // Create filter button\n filterBtn = document.createElement('button');\n filterBtn.className = 'tbw-filter-btn';\n filterBtn.setAttribute('aria-label', `Filter ${col.header ?? field}`);\n filterBtn.innerHTML = `<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\"><path fill=\"currentColor\" d=\"M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z\"/></svg>`;\n\n // Mark button as active if filter exists\n if (hasFilter) {\n filterBtn.classList.add('active');\n (cell as HTMLElement).classList.add('filtered');\n }\n\n filterBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.toggleFilterPanel(field, col, filterBtn!);\n });\n\n // Insert before resize handle to maintain order: [label, sort-indicator, filter-btn, resize-handle]\n const resizeHandle = cell.querySelector('.resize-handle');\n if (resizeHandle) {\n cell.insertBefore(filterBtn, resizeHandle);\n } else {\n cell.appendChild(filterBtn);\n }\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Set a filter on a specific field.\n * Pass null to remove the filter.\n */\n setFilter(field: string, filter: Omit<FilterModel, 'field'> | null): void {\n if (filter === null) {\n this.filters.delete(field);\n this.syncExcludedValues(field, null);\n } else {\n const fullFilter = { ...filter, field };\n this.filters.set(field, fullFilter);\n this.syncExcludedValues(field, fullFilter);\n }\n // Invalidate cache\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0, // Will be accurate after processRows\n });\n this.requestRender();\n }\n\n /**\n * Get the current filter for a field.\n */\n getFilter(field: string): FilterModel | undefined {\n return this.filters.get(field);\n }\n\n /**\n * Get all active filters.\n */\n getFilters(): FilterModel[] {\n return [...this.filters.values()];\n }\n\n /**\n * Alias for getFilters() to match functional API naming.\n */\n getFilterModel(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Set filters from an array (replaces all existing filters).\n */\n setFilterModel(filters: FilterModel[]): void {\n this.filters.clear();\n this.excludedValues.clear();\n for (const filter of filters) {\n this.filters.set(filter.field, filter);\n this.syncExcludedValues(filter.field, filter);\n }\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n /**\n * Clear all filters.\n */\n clearAllFilters(): void {\n this.filters.clear();\n this.excludedValues.clear();\n this.searchText.clear();\n\n this.applyFiltersInternal();\n }\n\n /**\n * Clear filter for a specific field.\n */\n clearFieldFilter(field: string): void {\n this.filters.delete(field);\n this.excludedValues.delete(field);\n this.searchText.delete(field);\n\n this.applyFiltersInternal();\n }\n\n /**\n * Check if a field has an active filter.\n */\n isFieldFiltered(field: string): boolean {\n return this.filters.has(field);\n }\n\n /**\n * Get the count of filtered rows (from cache).\n */\n getFilteredRowCount(): number {\n return this.cachedResult?.length ?? this.rows.length;\n }\n\n /**\n * Get all active filters (alias for getFilters).\n */\n getActiveFilters(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Get unique values for a field (for set filter dropdowns).\n * Uses sourceRows to include all values regardless of current filter.\n */\n getUniqueValues(field: string): unknown[] {\n return getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Inject global styles for filter panel (rendered in document.body)\n */\n private injectGlobalStyles(): void {\n if (this.globalStylesInjected) return;\n if (document.getElementById('tbw-filter-panel-styles')) {\n this.globalStylesInjected = true;\n return;\n }\n // Only inject if we have valid CSS text (Vite's ?inline import)\n // When importing from source without Vite, the import is a module object, not a string\n if (typeof filterPanelStyles !== 'string' || !filterPanelStyles) {\n this.globalStylesInjected = true;\n return;\n }\n const style = document.createElement('style');\n style.id = 'tbw-filter-panel-styles';\n style.textContent = filterPanelStyles;\n document.head.appendChild(style);\n this.globalStylesInjected = true;\n }\n\n /**\n * Toggle the filter panel for a field\n */\n private toggleFilterPanel(field: string, column: ColumnConfig, buttonEl: HTMLElement): void {\n // Close if already open\n if (this.openPanelField === field) {\n this.closeFilterPanel();\n return;\n }\n\n // Close any existing panel\n this.closeFilterPanel();\n\n // Create panel\n const panel = document.createElement('div');\n panel.className = 'tbw-filter-panel';\n // Add animation class if animations are enabled\n if (this.isAnimationEnabled) {\n panel.classList.add('tbw-filter-panel-animated');\n }\n this.panelElement = panel;\n this.openPanelField = field;\n\n // If using async valuesHandler, show loading state and fetch values\n if (this.config.valuesHandler) {\n panel.innerHTML = '<div class=\"tbw-filter-loading\">Loading...</div>';\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n this.setupPanelCloseHandler(panel, buttonEl);\n\n this.config.valuesHandler(field, column).then((values) => {\n // Check if panel is still open for this field\n if (this.openPanelField !== field || !this.panelElement) return;\n panel.innerHTML = '';\n this.renderPanelContent(field, column, panel, values);\n });\n return;\n }\n\n // Sync path: get unique values from local rows\n const uniqueValues = getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n this.renderPanelContent(field, column, panel, uniqueValues);\n\n // Position and append to body\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n this.setupPanelCloseHandler(panel, buttonEl);\n }\n\n /**\n * Render filter panel content with given values\n */\n private renderPanelContent(field: string, column: ColumnConfig, panel: HTMLElement, uniqueValues: unknown[]): void {\n // Get current excluded values or initialize empty\n let excludedSet = this.excludedValues.get(field);\n if (!excludedSet) {\n excludedSet = new Set();\n this.excludedValues.set(field, excludedSet);\n }\n\n // Get current search text\n const currentSearchText = this.searchText.get(field) ?? '';\n\n // Create panel params for custom renderer\n const params: FilterPanelParams = {\n field,\n column,\n uniqueValues,\n excludedValues: excludedSet,\n searchText: currentSearchText,\n applySetFilter: (excluded: unknown[]) => {\n this.applySetFilter(field, excluded);\n this.closeFilterPanel();\n },\n applyTextFilter: (operator, value, valueTo) => {\n this.applyTextFilter(field, operator, value, valueTo);\n this.closeFilterPanel();\n },\n clearFilter: () => {\n this.clearFieldFilter(field);\n this.closeFilterPanel();\n },\n closePanel: () => this.closeFilterPanel(),\n };\n\n // Use custom renderer or default\n // Custom renderer can return undefined to fall back to default panel for specific columns\n let usedCustomRenderer = false;\n if (this.config.filterPanelRenderer) {\n this.config.filterPanelRenderer(panel, params);\n // If renderer added content to panel, it handled rendering\n usedCustomRenderer = panel.children.length > 0;\n }\n if (!usedCustomRenderer) {\n this.renderDefaultFilterPanel(panel, params, uniqueValues, excludedSet);\n }\n }\n\n /**\n * Setup click-outside handler to close the panel\n */\n private setupPanelCloseHandler(panel: HTMLElement, buttonEl: HTMLElement): void {\n // Create abort controller for panel-scoped listeners\n // This allows cleanup when panel closes OR when grid disconnects\n this.panelAbortController = new AbortController();\n\n // Add global click handler to close on outside click\n // Defer to next tick to avoid immediate close from the click that opened the panel\n setTimeout(() => {\n document.addEventListener(\n 'click',\n (e: MouseEvent) => {\n if (!panel.contains(e.target as Node) && e.target !== buttonEl) {\n this.closeFilterPanel();\n }\n },\n { signal: this.panelAbortController?.signal },\n );\n }, 0);\n }\n\n /**\n * Close the filter panel\n */\n private closeFilterPanel(): void {\n const panel = this.panelElement;\n if (panel) {\n panel.remove();\n this.panelElement = null;\n }\n // Clean up anchor name from header cell\n if (this.panelAnchorElement) {\n (this.panelAnchorElement.style as any).anchorName = '';\n this.panelAnchorElement = null;\n }\n this.openPanelField = null;\n // Abort panel-scoped listeners (document click handler)\n this.panelAbortController?.abort();\n this.panelAbortController = null;\n }\n\n /** Cache for CSS anchor positioning support check */\n private static supportsAnchorPositioning: boolean | null = null;\n\n /**\n * Check if browser supports CSS Anchor Positioning\n */\n private static checkAnchorPositioningSupport(): boolean {\n if (FilteringPlugin.supportsAnchorPositioning === null) {\n FilteringPlugin.supportsAnchorPositioning = CSS.supports('anchor-name', '--test');\n }\n return FilteringPlugin.supportsAnchorPositioning;\n }\n\n /**\n * Position the panel below the header cell\n * Uses CSS Anchor Positioning if supported, falls back to JS positioning\n */\n private positionPanel(panel: HTMLElement, buttonEl: HTMLElement): void {\n // Find the parent header cell\n const headerCell = buttonEl.closest('.cell') as HTMLElement | null;\n const anchorEl = headerCell ?? buttonEl;\n\n // Set anchor name on the header cell for CSS anchor positioning\n (anchorEl.style as any).anchorName = '--tbw-filter-anchor';\n this.panelAnchorElement = anchorEl; // Store for cleanup\n\n // If CSS Anchor Positioning is supported, CSS handles positioning\n // but we need to detect if it flipped above to adjust animation\n if (FilteringPlugin.checkAnchorPositioningSupport()) {\n // Check position after CSS anchor positioning takes effect\n requestAnimationFrame(() => {\n const panelRect = panel.getBoundingClientRect();\n const anchorRect = anchorEl.getBoundingClientRect();\n // If panel top is above anchor top, it flipped to above\n if (panelRect.top < anchorRect.top) {\n panel.classList.add('tbw-filter-panel-above');\n }\n });\n return;\n }\n\n // Fallback: JS-based positioning for older browsers\n const rect = anchorEl.getBoundingClientRect();\n\n panel.style.position = 'fixed';\n panel.style.top = `${rect.bottom + 4}px`;\n panel.style.left = `${rect.left}px`;\n\n // Adjust if overflows viewport edges\n requestAnimationFrame(() => {\n const panelRect = panel.getBoundingClientRect();\n\n // Check horizontal overflow - align right edge to header cell right edge\n if (panelRect.right > window.innerWidth - 8) {\n panel.style.left = `${rect.right - panelRect.width}px`;\n }\n\n // Check vertical overflow - flip to above header cell\n if (panelRect.bottom > window.innerHeight - 8) {\n panel.style.top = `${rect.top - panelRect.height - 4}px`;\n panel.classList.add('tbw-filter-panel-above');\n }\n });\n }\n\n /**\n * Render the default filter panel content\n */\n private renderDefaultFilterPanel(\n panel: HTMLElement,\n params: FilterPanelParams,\n uniqueValues: unknown[],\n excludedValues: Set<unknown>,\n ): void {\n const { field } = params;\n\n // Search input\n const searchContainer = document.createElement('div');\n searchContainer.className = 'tbw-filter-search';\n\n const searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.placeholder = 'Search...';\n searchInput.className = 'tbw-filter-search-input';\n searchInput.value = this.searchText.get(field) ?? '';\n searchContainer.appendChild(searchInput);\n panel.appendChild(searchContainer);\n\n // Select All tristate checkbox\n const actionsRow = document.createElement('div');\n actionsRow.className = 'tbw-filter-actions';\n\n const selectAllLabel = document.createElement('label');\n selectAllLabel.className = 'tbw-filter-value-item';\n selectAllLabel.style.padding = '0';\n selectAllLabel.style.margin = '0';\n\n const selectAllCheckbox = document.createElement('input');\n selectAllCheckbox.type = 'checkbox';\n selectAllCheckbox.className = 'tbw-filter-checkbox';\n\n const selectAllText = document.createElement('span');\n selectAllText.textContent = 'Select All';\n\n selectAllLabel.appendChild(selectAllCheckbox);\n selectAllLabel.appendChild(selectAllText);\n actionsRow.appendChild(selectAllLabel);\n\n // Update tristate checkbox based on checkState\n const updateSelectAllState = () => {\n const values = [...checkState.values()];\n const allChecked = values.every((v) => v);\n const noneChecked = values.every((v) => !v);\n\n selectAllCheckbox.checked = allChecked;\n selectAllCheckbox.indeterminate = !allChecked && !noneChecked;\n };\n\n // Toggle all on click\n selectAllCheckbox.addEventListener('change', () => {\n const newState = selectAllCheckbox.checked;\n for (const key of checkState.keys()) {\n checkState.set(key, newState);\n }\n updateSelectAllState();\n renderVisibleItems();\n });\n\n panel.appendChild(actionsRow);\n\n // Values container with virtualization support\n const valuesContainer = document.createElement('div');\n valuesContainer.className = 'tbw-filter-values';\n\n // Spacer for virtual height\n const spacer = document.createElement('div');\n spacer.className = 'tbw-filter-values-spacer';\n valuesContainer.appendChild(spacer);\n\n // Content container positioned absolutely\n const contentContainer = document.createElement('div');\n contentContainer.className = 'tbw-filter-values-content';\n valuesContainer.appendChild(contentContainer);\n\n // Track current check state for values (persists across virtualizations)\n const checkState = new Map<string, boolean>();\n uniqueValues.forEach((value) => {\n const key = value == null ? '__null__' : String(value);\n checkState.set(key, !excludedValues.has(value));\n });\n\n // Initialize select all state\n updateSelectAllState();\n\n // Filtered values cache\n let filteredValues: unknown[] = [];\n\n // Create a single checkbox item element\n const createItem = (value: unknown, index: number): HTMLElement => {\n const strValue = value == null ? '(Blank)' : String(value);\n const key = value == null ? '__null__' : String(value);\n\n const item = document.createElement('label');\n item.className = 'tbw-filter-value-item';\n item.style.position = 'absolute';\n item.style.top = `${index * FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n item.style.left = '0';\n item.style.right = '0';\n item.style.height = `${FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n item.style.boxSizing = 'border-box';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'tbw-filter-checkbox';\n checkbox.checked = checkState.get(key) ?? true;\n checkbox.dataset.value = key;\n\n // Sync check state on change and update tristate checkbox\n checkbox.addEventListener('change', () => {\n checkState.set(key, checkbox.checked);\n updateSelectAllState();\n });\n\n const label = document.createElement('span');\n label.textContent = strValue;\n\n item.appendChild(checkbox);\n item.appendChild(label);\n return item;\n };\n\n // Render visible items using virtualization\n const renderVisibleItems = () => {\n const totalItems = filteredValues.length;\n const viewportHeight = valuesContainer.clientHeight;\n const scrollTop = valuesContainer.scrollTop;\n\n // Set total height for scrollbar\n spacer.style.height = `${totalItems * FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n\n // Bypass virtualization for small lists\n if (shouldBypassVirtualization(totalItems, FilteringPlugin.LIST_BYPASS_THRESHOLD / 3)) {\n contentContainer.innerHTML = '';\n contentContainer.style.transform = 'translateY(0px)';\n filteredValues.forEach((value, idx) => {\n contentContainer.appendChild(createItem(value, idx));\n });\n return;\n }\n\n // Use computeVirtualWindow for real-scroll virtualization\n const window = computeVirtualWindow({\n totalRows: totalItems,\n viewportHeight,\n scrollTop,\n rowHeight: FilteringPlugin.LIST_ITEM_HEIGHT,\n overscan: FilteringPlugin.LIST_OVERSCAN,\n });\n\n // Position content container\n contentContainer.style.transform = `translateY(${window.offsetY}px)`;\n\n // Clear and render visible items\n contentContainer.innerHTML = '';\n for (let i = window.start; i < window.end; i++) {\n contentContainer.appendChild(createItem(filteredValues[i], i - window.start));\n }\n };\n\n // Filter and re-render values\n const renderValues = (filterText: string) => {\n const caseSensitive = this.config.caseSensitive ?? false;\n const compareFilter = caseSensitive ? filterText : filterText.toLowerCase();\n\n // Filter the unique values\n filteredValues = uniqueValues.filter((value) => {\n const strValue = value == null ? '(Blank)' : String(value);\n const compareValue = caseSensitive ? strValue : strValue.toLowerCase();\n return !filterText || compareValue.includes(compareFilter);\n });\n\n if (filteredValues.length === 0) {\n spacer.style.height = '0px';\n contentContainer.innerHTML = '';\n const noMatch = document.createElement('div');\n noMatch.className = 'tbw-filter-no-match';\n noMatch.textContent = 'No matching values';\n contentContainer.appendChild(noMatch);\n return;\n }\n\n renderVisibleItems();\n };\n\n // Scroll handler for virtualization\n valuesContainer.addEventListener(\n 'scroll',\n () => {\n if (filteredValues.length > 0) {\n renderVisibleItems();\n }\n },\n { passive: true },\n );\n\n renderValues(searchInput.value);\n panel.appendChild(valuesContainer);\n\n // Debounced search\n let debounceTimer: ReturnType<typeof setTimeout>;\n searchInput.addEventListener('input', () => {\n clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n this.searchText.set(field, searchInput.value);\n renderValues(searchInput.value);\n }, this.config.debounceMs ?? 150);\n });\n\n // Apply/Clear buttons\n const buttonRow = document.createElement('div');\n buttonRow.className = 'tbw-filter-buttons';\n\n const applyBtn = document.createElement('button');\n applyBtn.className = 'tbw-filter-apply-btn';\n applyBtn.textContent = 'Apply';\n applyBtn.addEventListener('click', () => {\n // Read from checkState map (works with virtualization)\n const excluded: unknown[] = [];\n for (const [key, isChecked] of checkState) {\n if (!isChecked) {\n if (key === '__null__') {\n excluded.push(null);\n } else {\n // Try to match original value type\n const original = uniqueValues.find((v) => String(v) === key);\n excluded.push(original !== undefined ? original : key);\n }\n }\n }\n params.applySetFilter(excluded);\n });\n buttonRow.appendChild(applyBtn);\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'tbw-filter-clear-btn';\n clearBtn.textContent = 'Clear Filter';\n clearBtn.addEventListener('click', () => {\n params.clearFilter();\n });\n buttonRow.appendChild(clearBtn);\n\n panel.appendChild(buttonRow);\n }\n\n /**\n * Apply a set filter (exclude values)\n */\n private applySetFilter(field: string, excluded: unknown[]): void {\n // Store excluded values\n this.excludedValues.set(field, new Set(excluded));\n\n if (excluded.length === 0) {\n // No exclusions = no filter\n this.filters.delete(field);\n } else {\n // Create \"notIn\" filter\n this.filters.set(field, {\n field,\n type: 'set',\n operator: 'notIn',\n value: excluded,\n });\n }\n\n this.applyFiltersInternal();\n }\n\n /**\n * Apply a text filter\n */\n private applyTextFilter(field: string, operator: FilterModel['operator'], value: string, valueTo?: string): void {\n this.filters.set(field, {\n field,\n type: 'text',\n operator,\n value,\n valueTo,\n });\n\n this.applyFiltersInternal();\n }\n\n /**\n * Internal method to apply filters (sync or async based on config)\n */\n private applyFiltersInternal(): void {\n this.cachedResult = null;\n this.cacheKey = null;\n\n const filterList = [...this.filters.values()];\n\n // If using async filterHandler, delegate to server\n if (this.config.filterHandler) {\n const gridEl = this.grid as unknown as Element;\n gridEl.setAttribute('aria-busy', 'true');\n\n const result = this.config.filterHandler(filterList, this.sourceRows as unknown[]);\n\n // Handle async or sync result\n const handleResult = (rows: unknown[]) => {\n gridEl.removeAttribute('aria-busy');\n this.cachedResult = rows;\n\n // Update grid rows directly for async filtering\n (this.grid as unknown as { rows: unknown[] }).rows = rows;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: filterList,\n filteredRowCount: rows.length,\n });\n\n // Trigger afterRender to update filter button active state\n this.requestRender();\n };\n\n if (result && typeof (result as Promise<unknown[]>).then === 'function') {\n (result as Promise<unknown[]>).then(handleResult);\n } else {\n handleResult(result as unknown[]);\n }\n return;\n }\n\n // Sync path: emit event and re-render (processRows will handle filtering)\n this.emit<FilterChangeDetail>('filter-change', {\n filters: filterList,\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n // #endregion\n\n // #region Column State Hooks\n\n /**\n * Return filter state for a column if it has an active filter.\n * @internal\n */\n override getColumnState(field: string): Partial<ColumnState> | undefined {\n const filterModel = this.filters.get(field);\n if (!filterModel) return undefined;\n\n return {\n filter: {\n type: filterModel.type,\n operator: filterModel.operator,\n value: filterModel.value,\n valueTo: filterModel.valueTo,\n },\n };\n }\n\n /**\n * Apply filter state from column state.\n * @internal\n */\n override applyColumnState(field: string, state: ColumnState): void {\n // Only process if the column has filter state\n if (!state.filter) {\n this.filters.delete(field);\n return;\n }\n\n // Reconstruct the FilterModel from the stored state\n const filterModel: FilterModel = {\n field,\n type: state.filter.type,\n operator: state.filter.operator as FilterModel['operator'],\n value: state.filter.value,\n valueTo: state.filter.valueTo,\n };\n\n this.filters.set(field, filterModel);\n // Invalidate cache so filter is reapplied\n this.cachedResult = null;\n this.cacheKey = null;\n }\n // #endregion\n}\n"],"names":["matchesFilter","row","filter","caseSensitive","rawValue","stringValue","compareValue","filterValue","filterRows","rows","filters","f","computeFilterCacheKey","getUniqueValues","field","values","value","a","b","FilteringPlugin","BaseGridPlugin","styles","grid","filterList","newCacheKey","result","gridEl","cell","colIndex","col","isUtilityColumn","hasFilter","filterBtn","e","resizeHandle","fullFilter","style","filterPanelStyles","column","buttonEl","panel","uniqueValues","excludedSet","currentSearchText","params","excluded","operator","valueTo","usedCustomRenderer","anchorEl","panelRect","anchorRect","rect","excludedValues","searchContainer","searchInput","actionsRow","selectAllLabel","selectAllCheckbox","selectAllText","updateSelectAllState","checkState","allChecked","v","noneChecked","newState","key","renderVisibleItems","valuesContainer","spacer","contentContainer","filteredValues","createItem","index","strValue","item","checkbox","label","totalItems","viewportHeight","scrollTop","shouldBypassVirtualization","idx","window","computeVirtualWindow","i","renderValues","filterText","compareFilter","noMatch","debounceTimer","buttonRow","applyBtn","isChecked","original","clearBtn","handleResult","filterModel","state"],"mappings":"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"}
1
+ {"version":3,"file":"filtering.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/filtering/filter-model.ts","../../../../../libs/grid/src/lib/plugins/filtering/FilteringPlugin.ts"],"sourcesContent":["/**\n * Filter Model Core Logic\n *\n * Pure functions for filtering operations.\n */\n\nimport type { FilterModel } from './types';\n\n/**\n * Check if a single row matches a filter condition.\n *\n * @param row - The row data object\n * @param filter - The filter to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns True if the row matches the filter\n */\nexport function matchesFilter(row: Record<string, unknown>, filter: FilterModel, caseSensitive = false): boolean {\n const rawValue = row[filter.field];\n\n // Handle blank/notBlank first - these work on null/undefined/empty\n if (filter.operator === 'blank') {\n return rawValue == null || rawValue === '';\n }\n if (filter.operator === 'notBlank') {\n return rawValue != null && rawValue !== '';\n }\n\n // Null/undefined values don't match other filters\n if (rawValue == null) return false;\n\n // Prepare values for comparison\n const stringValue = String(rawValue);\n const compareValue = caseSensitive ? stringValue : stringValue.toLowerCase();\n const filterValue = caseSensitive ? String(filter.value) : String(filter.value).toLowerCase();\n\n switch (filter.operator) {\n // Text operators\n case 'contains':\n return compareValue.includes(filterValue);\n\n case 'notContains':\n return !compareValue.includes(filterValue);\n\n case 'equals':\n return compareValue === filterValue;\n\n case 'notEquals':\n return compareValue !== filterValue;\n\n case 'startsWith':\n return compareValue.startsWith(filterValue);\n\n case 'endsWith':\n return compareValue.endsWith(filterValue);\n\n // Number/Date operators (use raw numeric values)\n case 'lessThan':\n return Number(rawValue) < Number(filter.value);\n\n case 'lessThanOrEqual':\n return Number(rawValue) <= Number(filter.value);\n\n case 'greaterThan':\n return Number(rawValue) > Number(filter.value);\n\n case 'greaterThanOrEqual':\n return Number(rawValue) >= Number(filter.value);\n\n case 'between':\n return Number(rawValue) >= Number(filter.value) && Number(rawValue) <= Number(filter.valueTo);\n\n // Set operators\n case 'in':\n return Array.isArray(filter.value) && filter.value.includes(rawValue);\n\n case 'notIn':\n return Array.isArray(filter.value) && !filter.value.includes(rawValue);\n\n default:\n return true;\n }\n}\n\n/**\n * Filter rows based on multiple filter conditions (AND logic).\n * All filters must match for a row to be included.\n *\n * @param rows - The rows to filter\n * @param filters - Array of filters to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns Filtered rows\n */\nexport function filterRows<T extends Record<string, unknown>>(\n rows: T[],\n filters: FilterModel[],\n caseSensitive = false\n): T[] {\n if (!filters.length) return rows;\n return rows.filter((row) => filters.every((f) => matchesFilter(row, f, caseSensitive)));\n}\n\n/**\n * Compute a cache key for a set of filters.\n * Used for memoization of filter results.\n *\n * @param filters - Array of filters\n * @returns Stable string key for the filter set\n */\nexport function computeFilterCacheKey(filters: FilterModel[]): string {\n return JSON.stringify(\n filters.map((f) => ({\n field: f.field,\n operator: f.operator,\n value: f.value,\n valueTo: f.valueTo,\n }))\n );\n}\n\n/**\n * Extract unique values from a field across all rows.\n * Useful for populating \"set\" filter dropdowns.\n *\n * @param rows - The rows to extract values from\n * @param field - The field name\n * @returns Sorted array of unique non-null values\n */\nexport function getUniqueValues<T extends Record<string, unknown>>(rows: T[], field: string): unknown[] {\n const values = new Set<unknown>();\n for (const row of rows) {\n const value = row[field];\n if (value != null) {\n values.add(value);\n }\n }\n return [...values].sort((a, b) => {\n // Handle mixed types gracefully\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b;\n }\n return String(a).localeCompare(String(b));\n });\n}\n","/**\n * Filtering Plugin (Class-based)\n *\n * Provides comprehensive filtering functionality for tbw-grid.\n * Supports text, number, date, set, and boolean filters with caching.\n * Includes UI with filter buttons in headers and dropdown filter panels.\n */\n\nimport { computeVirtualWindow, shouldBypassVirtualization } from '../../core/internal/virtualization';\nimport { BaseGridPlugin, type GridElement } from '../../core/plugin/base-plugin';\nimport { isUtilityColumn } from '../../core/plugin/expander-column';\nimport type { ColumnConfig, ColumnState } from '../../core/types';\nimport { computeFilterCacheKey, filterRows, getUniqueValues } from './filter-model';\nimport styles from './filtering.css?inline';\nimport filterPanelStyles from './FilteringPlugin.css?inline';\nimport type { FilterChangeDetail, FilterConfig, FilterModel, FilterPanelParams } from './types';\n\n/**\n * Filtering Plugin for tbw-grid\n *\n * Adds column header filters with text search, dropdown options, and custom filter panels.\n * Supports both **local filtering** for small datasets and **async handlers** for server-side\n * filtering on large datasets.\n *\n * ## Installation\n *\n * ```ts\n * import { FilteringPlugin } from '@toolbox-web/grid/plugins/filtering';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `debounceMs` | `number` | `300` | Debounce delay for filter input |\n * | `caseSensitive` | `boolean` | `false` | Case-sensitive string matching |\n * | `trimInput` | `boolean` | `true` | Trim whitespace from filter input |\n * | `useWorker` | `boolean` | `true` | Use Web Worker for datasets >1000 rows |\n * | `filterPanelRenderer` | `FilterPanelRenderer` | - | Custom filter panel renderer |\n * | `valuesHandler` | `FilterValuesHandler` | - | Async handler to fetch unique filter values |\n * | `filterHandler` | `FilterHandler<TRow>` | - | Async handler to apply filters remotely |\n *\n * ## Column Configuration\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `filterable` | `boolean` | Enable filtering for this column |\n * | `filterType` | `'text' \\| 'select' \\| 'number' \\| 'date'` | Filter UI type |\n * | `filterOptions` | `unknown[]` | Predefined options for select filters |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `setFilter` | `(field, value) => void` | Set filter value for a column |\n * | `getFilters` | `() => FilterModel[]` | Get all current filters |\n * | `clearFilters` | `() => void` | Clear all filters |\n * | `clearFilter` | `(field) => void` | Clear filter for a specific column |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-filter-panel-bg` | `var(--tbw-color-panel-bg)` | Panel background |\n * | `--tbw-filter-panel-fg` | `var(--tbw-color-fg)` | Panel text color |\n * | `--tbw-filter-panel-border` | `var(--tbw-color-border)` | Panel border |\n * | `--tbw-filter-active-color` | `var(--tbw-color-accent)` | Active filter indicator |\n * | `--tbw-filter-input-bg` | `var(--tbw-color-bg)` | Input background |\n * | `--tbw-filter-input-focus` | `var(--tbw-color-accent)` | Input focus border |\n *\n * @example Basic Usage with Filterable Columns\n * ```ts\n * import '@toolbox-web/grid';\n * import { FilteringPlugin } from '@toolbox-web/grid/plugins/filtering';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name', filterable: true },\n * { field: 'status', header: 'Status', filterable: true, filterType: 'select' },\n * { field: 'email', header: 'Email', filterable: true },\n * ],\n * plugins: [new FilteringPlugin({ debounceMs: 300 })],\n * };\n * grid.rows = data;\n * ```\n *\n * @example Server-Side Filtering with Async Handlers\n * ```ts\n * new FilteringPlugin({\n * // Fetch unique values from server for filter dropdown\n * valuesHandler: async (field, column) => {\n * const response = await fetch(`/api/distinct-values?field=${field}`);\n * return response.json();\n * },\n * // Apply filters on the server\n * filterHandler: async (filters, currentRows) => {\n * const response = await fetch('/api/data', {\n * method: 'POST',\n * body: JSON.stringify({ filters }),\n * });\n * return response.json();\n * },\n * });\n * ```\n *\n * @see {@link FilterConfig} for all configuration options\n * @see {@link FilterModel} for filter data structure\n * @see {@link FilterPanelParams} for custom panel renderer parameters\n *\n * @internal Extends BaseGridPlugin\n */\nexport class FilteringPlugin extends BaseGridPlugin<FilterConfig> {\n /** @internal */\n readonly name = 'filtering';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<FilterConfig> {\n return {\n debounceMs: 300,\n caseSensitive: false,\n trimInput: true,\n useWorker: true,\n };\n }\n\n // #region Internal State\n private filters: Map<string, FilterModel> = new Map();\n private cachedResult: unknown[] | null = null;\n private cacheKey: string | null = null;\n private openPanelField: string | null = null;\n private panelElement: HTMLElement | null = null;\n private panelAnchorElement: HTMLElement | null = null; // For CSS anchor positioning cleanup\n private searchText: Map<string, string> = new Map();\n private excludedValues: Map<string, Set<unknown>> = new Map();\n private panelAbortController: AbortController | null = null; // For panel-scoped listeners\n private globalStylesInjected = false;\n\n // Virtualization constants for filter value list\n private static readonly DEFAULT_LIST_ITEM_HEIGHT = 28;\n private static readonly LIST_OVERSCAN = 3;\n private static readonly LIST_BYPASS_THRESHOLD = 50; // Don't virtualize if < 50 items\n\n /**\n * Get the item height from CSS variable or fallback to default.\n * Reads --tbw-filter-item-height from the panel element.\n */\n private getListItemHeight(): number {\n if (this.panelElement) {\n const cssValue = getComputedStyle(this.panelElement).getPropertyValue('--tbw-filter-item-height');\n if (cssValue && cssValue.trim()) {\n const parsed = parseFloat(cssValue);\n if (!isNaN(parsed) && parsed > 0) {\n return parsed;\n }\n }\n }\n return FilteringPlugin.DEFAULT_LIST_ITEM_HEIGHT;\n }\n\n /**\n * Sync excludedValues map from a filter model (for set filters).\n */\n private syncExcludedValues(field: string, filter: FilterModel | null): void {\n if (!filter) {\n this.excludedValues.delete(field);\n } else if (filter.type === 'set' && filter.operator === 'notIn' && Array.isArray(filter.value)) {\n this.excludedValues.set(field, new Set(filter.value));\n } else if (filter.type === 'set') {\n // Other set operators may have different semantics; clear for safety\n this.excludedValues.delete(field);\n }\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.injectGlobalStyles();\n }\n\n /** @internal */\n override detach(): void {\n this.filters.clear();\n this.cachedResult = null;\n this.cacheKey = null;\n this.openPanelField = null;\n if (this.panelElement) {\n this.panelElement.remove();\n this.panelElement = null;\n }\n this.searchText.clear();\n this.excludedValues.clear();\n // Abort panel-scoped listeners (document click handler, etc.)\n this.panelAbortController?.abort();\n this.panelAbortController = null;\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processRows(rows: readonly unknown[]): unknown[] {\n const filterList = [...this.filters.values()];\n if (!filterList.length) return [...rows];\n\n // If using async filterHandler, processRows becomes a passthrough\n // Actual filtering happens in applyFiltersAsync and rows are set directly on grid\n if (this.config.filterHandler) {\n // Return cached result if available (set by async handler)\n if (this.cachedResult) return this.cachedResult;\n // Otherwise return rows as-is (filtering happens async)\n return [...rows];\n }\n\n // Check cache\n const newCacheKey = computeFilterCacheKey(filterList);\n if (this.cacheKey === newCacheKey && this.cachedResult) {\n return this.cachedResult;\n }\n\n // Filter rows synchronously (worker support can be added later)\n const result = filterRows([...rows] as Record<string, unknown>[], filterList, this.config.caseSensitive);\n\n // Update cache\n this.cachedResult = result;\n this.cacheKey = newCacheKey;\n\n return result;\n }\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Find all header cells (using part attribute, not class)\n const headerCells = gridEl.querySelectorAll('[part~=\"header-cell\"]');\n headerCells.forEach((cell) => {\n const colIndex = cell.getAttribute('data-col');\n if (colIndex === null) return;\n\n // Use visibleColumns since data-col is the index within _visibleColumns\n const col = this.visibleColumns[parseInt(colIndex, 10)] as ColumnConfig;\n if (!col || col.filterable === false) return;\n\n // Skip utility columns (expander, selection checkbox, etc.)\n if (isUtilityColumn(col)) return;\n\n const field = col.field;\n if (!field) return;\n\n const hasFilter = this.filters.has(field);\n\n // Check if button already exists\n let filterBtn = cell.querySelector('.tbw-filter-btn') as HTMLElement | null;\n\n if (filterBtn) {\n // Update active state and icon of existing button\n const wasActive = filterBtn.classList.contains('active');\n filterBtn.classList.toggle('active', hasFilter);\n (cell as HTMLElement).classList.toggle('filtered', hasFilter);\n // Update icon if active state changed\n if (wasActive !== hasFilter) {\n const iconName = hasFilter ? 'filterActive' : 'filter';\n this.setIcon(filterBtn, this.resolveIcon(iconName));\n }\n return;\n }\n\n // Create filter button\n filterBtn = document.createElement('button');\n filterBtn.className = 'tbw-filter-btn';\n filterBtn.setAttribute('aria-label', `Filter ${col.header ?? field}`);\n // Use grid icons configuration\n const iconName = hasFilter ? 'filterActive' : 'filter';\n this.setIcon(filterBtn, this.resolveIcon(iconName));\n\n // Mark button as active if filter exists\n if (hasFilter) {\n filterBtn.classList.add('active');\n (cell as HTMLElement).classList.add('filtered');\n }\n\n filterBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.toggleFilterPanel(field, col, filterBtn!);\n });\n\n // Insert before resize handle to maintain order: [label, sort-indicator, filter-btn, resize-handle]\n const resizeHandle = cell.querySelector('.resize-handle');\n if (resizeHandle) {\n cell.insertBefore(filterBtn, resizeHandle);\n } else {\n cell.appendChild(filterBtn);\n }\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Set a filter on a specific field.\n * Pass null to remove the filter.\n */\n setFilter(field: string, filter: Omit<FilterModel, 'field'> | null): void {\n if (filter === null) {\n this.filters.delete(field);\n this.syncExcludedValues(field, null);\n } else {\n const fullFilter = { ...filter, field };\n this.filters.set(field, fullFilter);\n this.syncExcludedValues(field, fullFilter);\n }\n // Invalidate cache\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0, // Will be accurate after processRows\n });\n this.requestRender();\n }\n\n /**\n * Get the current filter for a field.\n */\n getFilter(field: string): FilterModel | undefined {\n return this.filters.get(field);\n }\n\n /**\n * Get all active filters.\n */\n getFilters(): FilterModel[] {\n return [...this.filters.values()];\n }\n\n /**\n * Alias for getFilters() to match functional API naming.\n */\n getFilterModel(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Set filters from an array (replaces all existing filters).\n */\n setFilterModel(filters: FilterModel[]): void {\n this.filters.clear();\n this.excludedValues.clear();\n for (const filter of filters) {\n this.filters.set(filter.field, filter);\n this.syncExcludedValues(filter.field, filter);\n }\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n /**\n * Clear all filters.\n */\n clearAllFilters(): void {\n this.filters.clear();\n this.excludedValues.clear();\n this.searchText.clear();\n\n this.applyFiltersInternal();\n }\n\n /**\n * Clear filter for a specific field.\n */\n clearFieldFilter(field: string): void {\n this.filters.delete(field);\n this.excludedValues.delete(field);\n this.searchText.delete(field);\n\n this.applyFiltersInternal();\n }\n\n /**\n * Check if a field has an active filter.\n */\n isFieldFiltered(field: string): boolean {\n return this.filters.has(field);\n }\n\n /**\n * Get the count of filtered rows (from cache).\n */\n getFilteredRowCount(): number {\n return this.cachedResult?.length ?? this.rows.length;\n }\n\n /**\n * Get all active filters (alias for getFilters).\n */\n getActiveFilters(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Get unique values for a field (for set filter dropdowns).\n * Uses sourceRows to include all values regardless of current filter.\n */\n getUniqueValues(field: string): unknown[] {\n return getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Copy CSS classes and data attributes from grid to filter panel.\n * This ensures theme classes (e.g., .eds-theme) cascade to the panel.\n */\n private copyGridThemeContext(panel: HTMLElement): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Copy all CSS classes from grid to panel (except internal ones)\n for (const className of gridEl.classList) {\n // Skip internal classes that shouldn't be copied\n if (className.startsWith('tbw-') || className === 'selecting') continue;\n panel.classList.add(className);\n }\n\n // Copy data-theme attribute if present\n const theme = gridEl.dataset.theme;\n if (theme) {\n panel.dataset.theme = theme;\n }\n }\n\n /**\n * Inject global styles for filter panel (rendered in document.body)\n */\n private injectGlobalStyles(): void {\n if (this.globalStylesInjected) return;\n if (document.getElementById('tbw-filter-panel-styles')) {\n this.globalStylesInjected = true;\n return;\n }\n // Only inject if we have valid CSS text (Vite's ?inline import)\n // When importing from source without Vite, the import is a module object, not a string\n if (typeof filterPanelStyles !== 'string' || !filterPanelStyles) {\n this.globalStylesInjected = true;\n return;\n }\n const style = document.createElement('style');\n style.id = 'tbw-filter-panel-styles';\n style.textContent = filterPanelStyles;\n document.head.appendChild(style);\n this.globalStylesInjected = true;\n }\n\n /**\n * Toggle the filter panel for a field\n */\n private toggleFilterPanel(field: string, column: ColumnConfig, buttonEl: HTMLElement): void {\n // Close if already open\n if (this.openPanelField === field) {\n this.closeFilterPanel();\n return;\n }\n\n // Close any existing panel\n this.closeFilterPanel();\n\n // Create panel\n const panel = document.createElement('div');\n panel.className = 'tbw-filter-panel';\n // Copy theme classes from grid to panel for proper theming\n this.copyGridThemeContext(panel);\n // Add animation class if animations are enabled\n if (this.isAnimationEnabled) {\n panel.classList.add('tbw-filter-panel-animated');\n }\n this.panelElement = panel;\n this.openPanelField = field;\n\n // If using async valuesHandler, show loading state and fetch values\n if (this.config.valuesHandler) {\n panel.innerHTML = '<div class=\"tbw-filter-loading\">Loading...</div>';\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n this.setupPanelCloseHandler(panel, buttonEl);\n\n this.config.valuesHandler(field, column).then((values) => {\n // Check if panel is still open for this field\n if (this.openPanelField !== field || !this.panelElement) return;\n panel.innerHTML = '';\n this.renderPanelContent(field, column, panel, values);\n });\n return;\n }\n\n // Sync path: get unique values from local rows\n const uniqueValues = getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n this.renderPanelContent(field, column, panel, uniqueValues);\n\n // Position and append to body\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n this.setupPanelCloseHandler(panel, buttonEl);\n }\n\n /**\n * Render filter panel content with given values\n */\n private renderPanelContent(field: string, column: ColumnConfig, panel: HTMLElement, uniqueValues: unknown[]): void {\n // Get current excluded values or initialize empty\n let excludedSet = this.excludedValues.get(field);\n if (!excludedSet) {\n excludedSet = new Set();\n this.excludedValues.set(field, excludedSet);\n }\n\n // Get current search text\n const currentSearchText = this.searchText.get(field) ?? '';\n\n // Create panel params for custom renderer\n const params: FilterPanelParams = {\n field,\n column,\n uniqueValues,\n excludedValues: excludedSet,\n searchText: currentSearchText,\n applySetFilter: (excluded: unknown[]) => {\n this.applySetFilter(field, excluded);\n this.closeFilterPanel();\n },\n applyTextFilter: (operator, value, valueTo) => {\n this.applyTextFilter(field, operator, value, valueTo);\n this.closeFilterPanel();\n },\n clearFilter: () => {\n this.clearFieldFilter(field);\n this.closeFilterPanel();\n },\n closePanel: () => this.closeFilterPanel(),\n };\n\n // Use custom renderer or default\n // Custom renderer can return undefined to fall back to default panel for specific columns\n let usedCustomRenderer = false;\n if (this.config.filterPanelRenderer) {\n this.config.filterPanelRenderer(panel, params);\n // If renderer added content to panel, it handled rendering\n usedCustomRenderer = panel.children.length > 0;\n }\n if (!usedCustomRenderer) {\n this.renderDefaultFilterPanel(panel, params, uniqueValues, excludedSet);\n }\n }\n\n /**\n * Setup click-outside handler to close the panel\n */\n private setupPanelCloseHandler(panel: HTMLElement, buttonEl: HTMLElement): void {\n // Create abort controller for panel-scoped listeners\n // This allows cleanup when panel closes OR when grid disconnects\n this.panelAbortController = new AbortController();\n\n // Add global click handler to close on outside click\n // Defer to next tick to avoid immediate close from the click that opened the panel\n setTimeout(() => {\n document.addEventListener(\n 'click',\n (e: MouseEvent) => {\n if (!panel.contains(e.target as Node) && e.target !== buttonEl) {\n this.closeFilterPanel();\n }\n },\n { signal: this.panelAbortController?.signal },\n );\n }, 0);\n }\n\n /**\n * Close the filter panel\n */\n private closeFilterPanel(): void {\n const panel = this.panelElement;\n if (panel) {\n panel.remove();\n this.panelElement = null;\n }\n // Clean up anchor name from header cell\n if (this.panelAnchorElement) {\n (this.panelAnchorElement.style as any).anchorName = '';\n this.panelAnchorElement = null;\n }\n this.openPanelField = null;\n // Abort panel-scoped listeners (document click handler)\n this.panelAbortController?.abort();\n this.panelAbortController = null;\n }\n\n /** Cache for CSS anchor positioning support check */\n private static supportsAnchorPositioning: boolean | null = null;\n\n /**\n * Check if browser supports CSS Anchor Positioning\n */\n private static checkAnchorPositioningSupport(): boolean {\n if (FilteringPlugin.supportsAnchorPositioning === null) {\n FilteringPlugin.supportsAnchorPositioning = CSS.supports('anchor-name', '--test');\n }\n return FilteringPlugin.supportsAnchorPositioning;\n }\n\n /**\n * Position the panel below the header cell\n * Uses CSS Anchor Positioning if supported, falls back to JS positioning\n */\n private positionPanel(panel: HTMLElement, buttonEl: HTMLElement): void {\n // Find the parent header cell\n const headerCell = buttonEl.closest('.cell') as HTMLElement | null;\n const anchorEl = headerCell ?? buttonEl;\n\n // Set anchor name on the header cell for CSS anchor positioning\n (anchorEl.style as any).anchorName = '--tbw-filter-anchor';\n this.panelAnchorElement = anchorEl; // Store for cleanup\n\n // If CSS Anchor Positioning is supported, CSS handles positioning\n // but we need to detect if it flipped above to adjust animation\n if (FilteringPlugin.checkAnchorPositioningSupport()) {\n // Check position after CSS anchor positioning takes effect\n requestAnimationFrame(() => {\n const panelRect = panel.getBoundingClientRect();\n const anchorRect = anchorEl.getBoundingClientRect();\n // If panel top is above anchor top, it flipped to above\n if (panelRect.top < anchorRect.top) {\n panel.classList.add('tbw-filter-panel-above');\n }\n });\n return;\n }\n\n // Fallback: JS-based positioning for older browsers\n const rect = anchorEl.getBoundingClientRect();\n\n panel.style.position = 'fixed';\n panel.style.top = `${rect.bottom + 4}px`;\n panel.style.left = `${rect.left}px`;\n\n // Adjust if overflows viewport edges\n requestAnimationFrame(() => {\n const panelRect = panel.getBoundingClientRect();\n\n // Check horizontal overflow - align right edge to header cell right edge\n if (panelRect.right > window.innerWidth - 8) {\n panel.style.left = `${rect.right - panelRect.width}px`;\n }\n\n // Check vertical overflow - flip to above header cell\n if (panelRect.bottom > window.innerHeight - 8) {\n panel.style.top = `${rect.top - panelRect.height - 4}px`;\n panel.classList.add('tbw-filter-panel-above');\n }\n });\n }\n\n /**\n * Render the default filter panel content\n */\n private renderDefaultFilterPanel(\n panel: HTMLElement,\n params: FilterPanelParams,\n uniqueValues: unknown[],\n excludedValues: Set<unknown>,\n ): void {\n const { field } = params;\n // Get item height from CSS variable or use default\n const itemHeight = this.getListItemHeight();\n\n // Search input\n const searchContainer = document.createElement('div');\n searchContainer.className = 'tbw-filter-search';\n\n const searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.placeholder = 'Search...';\n searchInput.className = 'tbw-filter-search-input';\n searchInput.value = this.searchText.get(field) ?? '';\n searchContainer.appendChild(searchInput);\n panel.appendChild(searchContainer);\n\n // Select All tristate checkbox\n const actionsRow = document.createElement('div');\n actionsRow.className = 'tbw-filter-actions';\n\n const selectAllLabel = document.createElement('label');\n selectAllLabel.className = 'tbw-filter-value-item';\n selectAllLabel.style.padding = '0';\n selectAllLabel.style.margin = '0';\n\n const selectAllCheckbox = document.createElement('input');\n selectAllCheckbox.type = 'checkbox';\n selectAllCheckbox.className = 'tbw-filter-checkbox';\n\n const selectAllText = document.createElement('span');\n selectAllText.textContent = 'Select All';\n\n selectAllLabel.appendChild(selectAllCheckbox);\n selectAllLabel.appendChild(selectAllText);\n actionsRow.appendChild(selectAllLabel);\n\n // Update tristate checkbox based on checkState\n const updateSelectAllState = () => {\n const values = [...checkState.values()];\n const allChecked = values.every((v) => v);\n const noneChecked = values.every((v) => !v);\n\n selectAllCheckbox.checked = allChecked;\n selectAllCheckbox.indeterminate = !allChecked && !noneChecked;\n };\n\n // Toggle all on click\n selectAllCheckbox.addEventListener('change', () => {\n const newState = selectAllCheckbox.checked;\n for (const key of checkState.keys()) {\n checkState.set(key, newState);\n }\n updateSelectAllState();\n renderVisibleItems();\n });\n\n panel.appendChild(actionsRow);\n\n // Values container with virtualization support\n const valuesContainer = document.createElement('div');\n valuesContainer.className = 'tbw-filter-values';\n\n // Spacer for virtual height\n const spacer = document.createElement('div');\n spacer.className = 'tbw-filter-values-spacer';\n valuesContainer.appendChild(spacer);\n\n // Content container positioned absolutely\n const contentContainer = document.createElement('div');\n contentContainer.className = 'tbw-filter-values-content';\n valuesContainer.appendChild(contentContainer);\n\n // Track current check state for values (persists across virtualizations)\n const checkState = new Map<string, boolean>();\n uniqueValues.forEach((value) => {\n const key = value == null ? '__null__' : String(value);\n checkState.set(key, !excludedValues.has(value));\n });\n\n // Initialize select all state\n updateSelectAllState();\n\n // Filtered values cache\n let filteredValues: unknown[] = [];\n\n // Create a single checkbox item element\n const createItem = (value: unknown, index: number): HTMLElement => {\n const strValue = value == null ? '(Blank)' : String(value);\n const key = value == null ? '__null__' : String(value);\n\n const item = document.createElement('label');\n item.className = 'tbw-filter-value-item';\n item.style.position = 'absolute';\n item.style.top = `${index * itemHeight}px`;\n item.style.left = '0';\n item.style.right = '0';\n item.style.height = `${itemHeight}px`;\n item.style.boxSizing = 'border-box';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'tbw-filter-checkbox';\n checkbox.checked = checkState.get(key) ?? true;\n checkbox.dataset.value = key;\n\n // Sync check state on change and update tristate checkbox\n checkbox.addEventListener('change', () => {\n checkState.set(key, checkbox.checked);\n updateSelectAllState();\n });\n\n const label = document.createElement('span');\n label.textContent = strValue;\n\n item.appendChild(checkbox);\n item.appendChild(label);\n return item;\n };\n\n // Render visible items using virtualization\n const renderVisibleItems = () => {\n const totalItems = filteredValues.length;\n const viewportHeight = valuesContainer.clientHeight;\n const scrollTop = valuesContainer.scrollTop;\n\n // Set total height for scrollbar\n spacer.style.height = `${totalItems * itemHeight}px`;\n\n // Bypass virtualization for small lists\n if (shouldBypassVirtualization(totalItems, FilteringPlugin.LIST_BYPASS_THRESHOLD / 3)) {\n contentContainer.innerHTML = '';\n contentContainer.style.transform = 'translateY(0px)';\n filteredValues.forEach((value, idx) => {\n contentContainer.appendChild(createItem(value, idx));\n });\n return;\n }\n\n // Use computeVirtualWindow for real-scroll virtualization\n const window = computeVirtualWindow({\n totalRows: totalItems,\n viewportHeight,\n scrollTop,\n rowHeight: itemHeight,\n overscan: FilteringPlugin.LIST_OVERSCAN,\n });\n\n // Position content container\n contentContainer.style.transform = `translateY(${window.offsetY}px)`;\n\n // Clear and render visible items\n contentContainer.innerHTML = '';\n for (let i = window.start; i < window.end; i++) {\n contentContainer.appendChild(createItem(filteredValues[i], i - window.start));\n }\n };\n\n // Filter and re-render values\n const renderValues = (filterText: string) => {\n const caseSensitive = this.config.caseSensitive ?? false;\n const compareFilter = caseSensitive ? filterText : filterText.toLowerCase();\n\n // Filter the unique values\n filteredValues = uniqueValues.filter((value) => {\n const strValue = value == null ? '(Blank)' : String(value);\n const compareValue = caseSensitive ? strValue : strValue.toLowerCase();\n return !filterText || compareValue.includes(compareFilter);\n });\n\n if (filteredValues.length === 0) {\n spacer.style.height = '0px';\n contentContainer.innerHTML = '';\n const noMatch = document.createElement('div');\n noMatch.className = 'tbw-filter-no-match';\n noMatch.textContent = 'No matching values';\n contentContainer.appendChild(noMatch);\n return;\n }\n\n renderVisibleItems();\n };\n\n // Scroll handler for virtualization\n valuesContainer.addEventListener(\n 'scroll',\n () => {\n if (filteredValues.length > 0) {\n renderVisibleItems();\n }\n },\n { passive: true },\n );\n\n renderValues(searchInput.value);\n panel.appendChild(valuesContainer);\n\n // Debounced search\n let debounceTimer: ReturnType<typeof setTimeout>;\n searchInput.addEventListener('input', () => {\n clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n this.searchText.set(field, searchInput.value);\n renderValues(searchInput.value);\n }, this.config.debounceMs ?? 150);\n });\n\n // Apply/Clear buttons\n const buttonRow = document.createElement('div');\n buttonRow.className = 'tbw-filter-buttons';\n\n const applyBtn = document.createElement('button');\n applyBtn.className = 'tbw-filter-apply-btn';\n applyBtn.textContent = 'Apply';\n applyBtn.addEventListener('click', () => {\n // Read from checkState map (works with virtualization)\n const excluded: unknown[] = [];\n for (const [key, isChecked] of checkState) {\n if (!isChecked) {\n if (key === '__null__') {\n excluded.push(null);\n } else {\n // Try to match original value type\n const original = uniqueValues.find((v) => String(v) === key);\n excluded.push(original !== undefined ? original : key);\n }\n }\n }\n params.applySetFilter(excluded);\n });\n buttonRow.appendChild(applyBtn);\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'tbw-filter-clear-btn';\n clearBtn.textContent = 'Clear Filter';\n clearBtn.addEventListener('click', () => {\n params.clearFilter();\n });\n buttonRow.appendChild(clearBtn);\n\n panel.appendChild(buttonRow);\n }\n\n /**\n * Apply a set filter (exclude values)\n */\n private applySetFilter(field: string, excluded: unknown[]): void {\n // Store excluded values\n this.excludedValues.set(field, new Set(excluded));\n\n if (excluded.length === 0) {\n // No exclusions = no filter\n this.filters.delete(field);\n } else {\n // Create \"notIn\" filter\n this.filters.set(field, {\n field,\n type: 'set',\n operator: 'notIn',\n value: excluded,\n });\n }\n\n this.applyFiltersInternal();\n }\n\n /**\n * Apply a text filter\n */\n private applyTextFilter(field: string, operator: FilterModel['operator'], value: string, valueTo?: string): void {\n this.filters.set(field, {\n field,\n type: 'text',\n operator,\n value,\n valueTo,\n });\n\n this.applyFiltersInternal();\n }\n\n /**\n * Internal method to apply filters (sync or async based on config)\n */\n private applyFiltersInternal(): void {\n this.cachedResult = null;\n this.cacheKey = null;\n\n const filterList = [...this.filters.values()];\n\n // If using async filterHandler, delegate to server\n if (this.config.filterHandler) {\n const gridEl = this.grid as unknown as Element;\n gridEl.setAttribute('aria-busy', 'true');\n\n const result = this.config.filterHandler(filterList, this.sourceRows as unknown[]);\n\n // Handle async or sync result\n const handleResult = (rows: unknown[]) => {\n gridEl.removeAttribute('aria-busy');\n this.cachedResult = rows;\n\n // Update grid rows directly for async filtering\n (this.grid as unknown as { rows: unknown[] }).rows = rows;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: filterList,\n filteredRowCount: rows.length,\n });\n\n // Trigger afterRender to update filter button active state\n this.requestRender();\n };\n\n if (result && typeof (result as Promise<unknown[]>).then === 'function') {\n (result as Promise<unknown[]>).then(handleResult);\n } else {\n handleResult(result as unknown[]);\n }\n return;\n }\n\n // Sync path: emit event and re-render (processRows will handle filtering)\n this.emit<FilterChangeDetail>('filter-change', {\n filters: filterList,\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n // #endregion\n\n // #region Column State Hooks\n\n /**\n * Return filter state for a column if it has an active filter.\n * @internal\n */\n override getColumnState(field: string): Partial<ColumnState> | undefined {\n const filterModel = this.filters.get(field);\n if (!filterModel) return undefined;\n\n return {\n filter: {\n type: filterModel.type,\n operator: filterModel.operator,\n value: filterModel.value,\n valueTo: filterModel.valueTo,\n },\n };\n }\n\n /**\n * Apply filter state from column state.\n * @internal\n */\n override applyColumnState(field: string, state: ColumnState): void {\n // Only process if the column has filter state\n if (!state.filter) {\n this.filters.delete(field);\n return;\n }\n\n // Reconstruct the FilterModel from the stored state\n const filterModel: FilterModel = {\n field,\n type: state.filter.type,\n operator: state.filter.operator as FilterModel['operator'],\n value: state.filter.value,\n valueTo: state.filter.valueTo,\n };\n\n this.filters.set(field, filterModel);\n // Invalidate cache so filter is reapplied\n this.cachedResult = null;\n this.cacheKey = null;\n }\n // #endregion\n}\n"],"names":["matchesFilter","row","filter","caseSensitive","rawValue","stringValue","compareValue","filterValue","filterRows","rows","filters","f","computeFilterCacheKey","getUniqueValues","field","values","value","a","b","FilteringPlugin","BaseGridPlugin","styles","cssValue","parsed","grid","filterList","newCacheKey","result","gridEl","cell","colIndex","col","isUtilityColumn","hasFilter","filterBtn","wasActive","iconName","e","resizeHandle","fullFilter","panel","className","theme","style","filterPanelStyles","column","buttonEl","uniqueValues","excludedSet","currentSearchText","params","excluded","operator","valueTo","usedCustomRenderer","anchorEl","panelRect","anchorRect","rect","excludedValues","itemHeight","searchContainer","searchInput","actionsRow","selectAllLabel","selectAllCheckbox","selectAllText","updateSelectAllState","checkState","allChecked","v","noneChecked","newState","key","renderVisibleItems","valuesContainer","spacer","contentContainer","filteredValues","createItem","index","strValue","item","checkbox","label","totalItems","viewportHeight","scrollTop","shouldBypassVirtualization","idx","window","computeVirtualWindow","i","renderValues","filterText","compareFilter","noMatch","debounceTimer","buttonRow","applyBtn","isChecked","original","clearBtn","handleResult","filterModel","state"],"mappings":"igBAgBO,SAASA,EAAcC,EAA8BC,EAAqBC,EAAgB,GAAgB,CAC/G,MAAMC,EAAWH,EAAIC,EAAO,KAAK,EAGjC,GAAIA,EAAO,WAAa,QACtB,OAAOE,GAAY,MAAQA,IAAa,GAE1C,GAAIF,EAAO,WAAa,WACtB,OAAOE,GAAY,MAAQA,IAAa,GAI1C,GAAIA,GAAY,KAAM,MAAO,GAG7B,MAAMC,EAAc,OAAOD,CAAQ,EAC7BE,EAAeH,EAAgBE,EAAcA,EAAY,YAAA,EACzDE,EAAcJ,EAAgB,OAAOD,EAAO,KAAK,EAAI,OAAOA,EAAO,KAAK,EAAE,YAAA,EAEhF,OAAQA,EAAO,SAAA,CAEb,IAAK,WACH,OAAOI,EAAa,SAASC,CAAW,EAE1C,IAAK,cACH,MAAO,CAACD,EAAa,SAASC,CAAW,EAE3C,IAAK,SACH,OAAOD,IAAiBC,EAE1B,IAAK,YACH,OAAOD,IAAiBC,EAE1B,IAAK,aACH,OAAOD,EAAa,WAAWC,CAAW,EAE5C,IAAK,WACH,OAAOD,EAAa,SAASC,CAAW,EAG1C,IAAK,WACH,OAAO,OAAOH,CAAQ,EAAI,OAAOF,EAAO,KAAK,EAE/C,IAAK,kBACH,OAAO,OAAOE,CAAQ,GAAK,OAAOF,EAAO,KAAK,EAEhD,IAAK,cACH,OAAO,OAAOE,CAAQ,EAAI,OAAOF,EAAO,KAAK,EAE/C,IAAK,qBACH,OAAO,OAAOE,CAAQ,GAAK,OAAOF,EAAO,KAAK,EAEhD,IAAK,UACH,OAAO,OAAOE,CAAQ,GAAK,OAAOF,EAAO,KAAK,GAAK,OAAOE,CAAQ,GAAK,OAAOF,EAAO,OAAO,EAG9F,IAAK,KACH,OAAO,MAAM,QAAQA,EAAO,KAAK,GAAKA,EAAO,MAAM,SAASE,CAAQ,EAEtE,IAAK,QACH,OAAO,MAAM,QAAQF,EAAO,KAAK,GAAK,CAACA,EAAO,MAAM,SAASE,CAAQ,EAEvE,QACE,MAAO,EAAA,CAEb,CAWO,SAASI,EACdC,EACAC,EACAP,EAAgB,GACX,CACL,OAAKO,EAAQ,OACND,EAAK,OAAQR,GAAQS,EAAQ,MAAOC,GAAMX,EAAcC,EAAKU,EAAGR,CAAa,CAAC,CAAC,EAD1DM,CAE9B,CASO,SAASG,EAAsBF,EAAgC,CACpE,OAAO,KAAK,UACVA,EAAQ,IAAKC,IAAO,CAClB,MAAOA,EAAE,MACT,SAAUA,EAAE,SACZ,MAAOA,EAAE,MACT,QAASA,EAAE,OAAA,EACX,CAAA,CAEN,CAUO,SAASE,EAAmDJ,EAAWK,EAA0B,CACtG,MAAMC,MAAa,IACnB,UAAWd,KAAOQ,EAAM,CACtB,MAAMO,EAAQf,EAAIa,CAAK,EACnBE,GAAS,MACXD,EAAO,IAAIC,CAAK,CAEpB,CACA,MAAO,CAAC,GAAGD,CAAM,EAAE,KAAK,CAACE,EAAGC,IAEtB,OAAOD,GAAM,UAAY,OAAOC,GAAM,SACjCD,EAAIC,EAEN,OAAOD,CAAC,EAAE,cAAc,OAAOC,CAAC,CAAC,CACzC,CACH,u/MC9BO,MAAMC,UAAwBC,EAAAA,cAA6B,CAEvD,KAAO,YAEE,OAASC,EAG3B,IAAuB,eAAuC,CAC5D,MAAO,CACL,WAAY,IACZ,cAAe,GACf,UAAW,GACX,UAAW,EAAA,CAEf,CAGQ,YAAwC,IACxC,aAAiC,KACjC,SAA0B,KAC1B,eAAgC,KAChC,aAAmC,KACnC,mBAAyC,KACzC,eAAsC,IACtC,mBAAgD,IAChD,qBAA+C,KAC/C,qBAAuB,GAG/B,OAAwB,yBAA2B,GACnD,OAAwB,cAAgB,EACxC,OAAwB,sBAAwB,GAMxC,mBAA4B,CAClC,GAAI,KAAK,aAAc,CACrB,MAAMC,EAAW,iBAAiB,KAAK,YAAY,EAAE,iBAAiB,0BAA0B,EAChG,GAAIA,GAAYA,EAAS,OAAQ,CAC/B,MAAMC,EAAS,WAAWD,CAAQ,EAClC,GAAI,CAAC,MAAMC,CAAM,GAAKA,EAAS,EAC7B,OAAOA,CAEX,CACF,CACA,OAAOJ,EAAgB,wBACzB,CAKQ,mBAAmBL,EAAeZ,EAAkC,CACrEA,EAEMA,EAAO,OAAS,OAASA,EAAO,WAAa,SAAW,MAAM,QAAQA,EAAO,KAAK,EAC3F,KAAK,eAAe,IAAIY,EAAO,IAAI,IAAIZ,EAAO,KAAK,CAAC,EAC3CA,EAAO,OAAS,OAEzB,KAAK,eAAe,OAAOY,CAAK,EALhC,KAAK,eAAe,OAAOA,CAAK,CAOpC,CAMS,OAAOU,EAAyB,CACvC,MAAM,OAAOA,CAAI,EACjB,KAAK,mBAAA,CACP,CAGS,QAAe,CACtB,KAAK,QAAQ,MAAA,EACb,KAAK,aAAe,KACpB,KAAK,SAAW,KAChB,KAAK,eAAiB,KAClB,KAAK,eACP,KAAK,aAAa,OAAA,EAClB,KAAK,aAAe,MAEtB,KAAK,WAAW,MAAA,EAChB,KAAK,eAAe,MAAA,EAEpB,KAAK,sBAAsB,MAAA,EAC3B,KAAK,qBAAuB,IAC9B,CAMS,YAAYf,EAAqC,CACxD,MAAMgB,EAAa,CAAC,GAAG,KAAK,QAAQ,QAAQ,EAC5C,GAAI,CAACA,EAAW,OAAQ,MAAO,CAAC,GAAGhB,CAAI,EAIvC,GAAI,KAAK,OAAO,cAEd,OAAI,KAAK,aAAqB,KAAK,aAE5B,CAAC,GAAGA,CAAI,EAIjB,MAAMiB,EAAcd,EAAsBa,CAAU,EACpD,GAAI,KAAK,WAAaC,GAAe,KAAK,aACxC,OAAO,KAAK,aAId,MAAMC,EAASnB,EAAW,CAAC,GAAGC,CAAI,EAAgCgB,EAAY,KAAK,OAAO,aAAa,EAGvG,YAAK,aAAeE,EACpB,KAAK,SAAWD,EAETC,CACT,CAGS,aAAoB,CAC3B,MAAMC,EAAS,KAAK,YACpB,GAAI,CAACA,EAAQ,OAGOA,EAAO,iBAAiB,uBAAuB,EACvD,QAASC,GAAS,CAC5B,MAAMC,EAAWD,EAAK,aAAa,UAAU,EAC7C,GAAIC,IAAa,KAAM,OAGvB,MAAMC,EAAM,KAAK,eAAe,SAASD,EAAU,EAAE,CAAC,EAItD,GAHI,CAACC,GAAOA,EAAI,aAAe,IAG3BC,EAAAA,gBAAgBD,CAAG,EAAG,OAE1B,MAAMjB,EAAQiB,EAAI,MAClB,GAAI,CAACjB,EAAO,OAEZ,MAAMmB,EAAY,KAAK,QAAQ,IAAInB,CAAK,EAGxC,IAAIoB,EAAYL,EAAK,cAAc,iBAAiB,EAEpD,GAAIK,EAAW,CAEb,MAAMC,EAAYD,EAAU,UAAU,SAAS,QAAQ,EAIvD,GAHAA,EAAU,UAAU,OAAO,SAAUD,CAAS,EAC7CJ,EAAqB,UAAU,OAAO,WAAYI,CAAS,EAExDE,IAAcF,EAAW,CAC3B,MAAMG,EAAWH,EAAY,eAAiB,SAC9C,KAAK,QAAQC,EAAW,KAAK,YAAYE,CAAQ,CAAC,CACpD,CACA,MACF,CAGAF,EAAY,SAAS,cAAc,QAAQ,EAC3CA,EAAU,UAAY,iBACtBA,EAAU,aAAa,aAAc,UAAUH,EAAI,QAAUjB,CAAK,EAAE,EAEpE,MAAMsB,EAAWH,EAAY,eAAiB,SAC9C,KAAK,QAAQC,EAAW,KAAK,YAAYE,CAAQ,CAAC,EAG9CH,IACFC,EAAU,UAAU,IAAI,QAAQ,EAC/BL,EAAqB,UAAU,IAAI,UAAU,GAGhDK,EAAU,iBAAiB,QAAUG,GAAM,CACzCA,EAAE,gBAAA,EACF,KAAK,kBAAkBvB,EAAOiB,EAAKG,CAAU,CAC/C,CAAC,EAGD,MAAMI,EAAeT,EAAK,cAAc,gBAAgB,EACpDS,EACFT,EAAK,aAAaK,EAAWI,CAAY,EAEzCT,EAAK,YAAYK,CAAS,CAE9B,CAAC,CACH,CASA,UAAUpB,EAAeZ,EAAiD,CACxE,GAAIA,IAAW,KACb,KAAK,QAAQ,OAAOY,CAAK,EACzB,KAAK,mBAAmBA,EAAO,IAAI,MAC9B,CACL,MAAMyB,EAAa,CAAE,GAAGrC,EAAQ,MAAAY,CAAA,EAChC,KAAK,QAAQ,IAAIA,EAAOyB,CAAU,EAClC,KAAK,mBAAmBzB,EAAOyB,CAAU,CAC3C,CAEA,KAAK,aAAe,KACpB,KAAK,SAAW,KAEhB,KAAK,KAAyB,gBAAiB,CAC7C,QAAS,CAAC,GAAG,KAAK,QAAQ,QAAQ,EAClC,iBAAkB,CAAA,CACnB,EACD,KAAK,cAAA,CACP,CAKA,UAAUzB,EAAwC,CAChD,OAAO,KAAK,QAAQ,IAAIA,CAAK,CAC/B,CAKA,YAA4B,CAC1B,MAAO,CAAC,GAAG,KAAK,QAAQ,QAAQ,CAClC,CAKA,gBAAgC,CAC9B,OAAO,KAAK,WAAA,CACd,CAKA,eAAeJ,EAA8B,CAC3C,KAAK,QAAQ,MAAA,EACb,KAAK,eAAe,MAAA,EACpB,UAAWR,KAAUQ,EACnB,KAAK,QAAQ,IAAIR,EAAO,MAAOA,CAAM,EACrC,KAAK,mBAAmBA,EAAO,MAAOA,CAAM,EAE9C,KAAK,aAAe,KACpB,KAAK,SAAW,KAEhB,KAAK,KAAyB,gBAAiB,CAC7C,QAAS,CAAC,GAAG,KAAK,QAAQ,QAAQ,EAClC,iBAAkB,CAAA,CACnB,EACD,KAAK,cAAA,CACP,CAKA,iBAAwB,CACtB,KAAK,QAAQ,MAAA,EACb,KAAK,eAAe,MAAA,EACpB,KAAK,WAAW,MAAA,EAEhB,KAAK,qBAAA,CACP,CAKA,iBAAiBY,EAAqB,CACpC,KAAK,QAAQ,OAAOA,CAAK,EACzB,KAAK,eAAe,OAAOA,CAAK,EAChC,KAAK,WAAW,OAAOA,CAAK,EAE5B,KAAK,qBAAA,CACP,CAKA,gBAAgBA,EAAwB,CACtC,OAAO,KAAK,QAAQ,IAAIA,CAAK,CAC/B,CAKA,qBAA8B,CAC5B,OAAO,KAAK,cAAc,QAAU,KAAK,KAAK,MAChD,CAKA,kBAAkC,CAChC,OAAO,KAAK,WAAA,CACd,CAMA,gBAAgBA,EAA0B,CACxC,OAAOD,EAAgB,KAAK,WAAyCC,CAAK,CAC5E,CASQ,qBAAqB0B,EAA0B,CACrD,MAAMZ,EAAS,KAAK,YACpB,GAAI,CAACA,EAAQ,OAGb,UAAWa,KAAab,EAAO,UAEzBa,EAAU,WAAW,MAAM,GAAKA,IAAc,aAClDD,EAAM,UAAU,IAAIC,CAAS,EAI/B,MAAMC,EAAQd,EAAO,QAAQ,MACzBc,IACFF,EAAM,QAAQ,MAAQE,EAE1B,CAKQ,oBAA2B,CACjC,GAAI,KAAK,qBAAsB,OAC/B,GAAI,SAAS,eAAe,yBAAyB,EAAG,CACtD,KAAK,qBAAuB,GAC5B,MACF,CAOA,MAAMC,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,GAAK,0BACXA,EAAM,YAAcC,EACpB,SAAS,KAAK,YAAYD,CAAK,EAC/B,KAAK,qBAAuB,EAC9B,CAKQ,kBAAkB7B,EAAe+B,EAAsBC,EAA6B,CAE1F,GAAI,KAAK,iBAAmBhC,EAAO,CACjC,KAAK,iBAAA,EACL,MACF,CAGA,KAAK,iBAAA,EAGL,MAAM0B,EAAQ,SAAS,cAAc,KAAK,EAY1C,GAXAA,EAAM,UAAY,mBAElB,KAAK,qBAAqBA,CAAK,EAE3B,KAAK,oBACPA,EAAM,UAAU,IAAI,2BAA2B,EAEjD,KAAK,aAAeA,EACpB,KAAK,eAAiB1B,EAGlB,KAAK,OAAO,cAAe,CAC7B0B,EAAM,UAAY,mDAClB,SAAS,KAAK,YAAYA,CAAK,EAC/B,KAAK,cAAcA,EAAOM,CAAQ,EAClC,KAAK,uBAAuBN,EAAOM,CAAQ,EAE3C,KAAK,OAAO,cAAchC,EAAO+B,CAAM,EAAE,KAAM9B,GAAW,CAEpD,KAAK,iBAAmBD,GAAS,CAAC,KAAK,eAC3C0B,EAAM,UAAY,GAClB,KAAK,mBAAmB1B,EAAO+B,EAAQL,EAAOzB,CAAM,EACtD,CAAC,EACD,MACF,CAGA,MAAMgC,EAAelC,EAAgB,KAAK,WAAyCC,CAAK,EACxF,KAAK,mBAAmBA,EAAO+B,EAAQL,EAAOO,CAAY,EAG1D,SAAS,KAAK,YAAYP,CAAK,EAC/B,KAAK,cAAcA,EAAOM,CAAQ,EAClC,KAAK,uBAAuBN,EAAOM,CAAQ,CAC7C,CAKQ,mBAAmBhC,EAAe+B,EAAsBL,EAAoBO,EAA+B,CAEjH,IAAIC,EAAc,KAAK,eAAe,IAAIlC,CAAK,EAC1CkC,IACHA,MAAkB,IAClB,KAAK,eAAe,IAAIlC,EAAOkC,CAAW,GAI5C,MAAMC,EAAoB,KAAK,WAAW,IAAInC,CAAK,GAAK,GAGlDoC,EAA4B,CAChC,MAAApC,EACA,OAAA+B,EACA,aAAAE,EACA,eAAgBC,EAChB,WAAYC,EACZ,eAAiBE,GAAwB,CACvC,KAAK,eAAerC,EAAOqC,CAAQ,EACnC,KAAK,iBAAA,CACP,EACA,gBAAiB,CAACC,EAAUpC,EAAOqC,IAAY,CAC7C,KAAK,gBAAgBvC,EAAOsC,EAAUpC,EAAOqC,CAAO,EACpD,KAAK,iBAAA,CACP,EACA,YAAa,IAAM,CACjB,KAAK,iBAAiBvC,CAAK,EAC3B,KAAK,iBAAA,CACP,EACA,WAAY,IAAM,KAAK,iBAAA,CAAiB,EAK1C,IAAIwC,EAAqB,GACrB,KAAK,OAAO,sBACd,KAAK,OAAO,oBAAoBd,EAAOU,CAAM,EAE7CI,EAAqBd,EAAM,SAAS,OAAS,GAE1Cc,GACH,KAAK,yBAAyBd,EAAOU,EAAQH,EAAcC,CAAW,CAE1E,CAKQ,uBAAuBR,EAAoBM,EAA6B,CAG9E,KAAK,qBAAuB,IAAI,gBAIhC,WAAW,IAAM,CACf,SAAS,iBACP,QACCT,GAAkB,CACb,CAACG,EAAM,SAASH,EAAE,MAAc,GAAKA,EAAE,SAAWS,GACpD,KAAK,iBAAA,CAET,EACA,CAAE,OAAQ,KAAK,sBAAsB,MAAA,CAAO,CAEhD,EAAG,CAAC,CACN,CAKQ,kBAAyB,CAC/B,MAAMN,EAAQ,KAAK,aACfA,IACFA,EAAM,OAAA,EACN,KAAK,aAAe,MAGlB,KAAK,qBACN,KAAK,mBAAmB,MAAc,WAAa,GACpD,KAAK,mBAAqB,MAE5B,KAAK,eAAiB,KAEtB,KAAK,sBAAsB,MAAA,EAC3B,KAAK,qBAAuB,IAC9B,CAGA,OAAe,0BAA4C,KAK3D,OAAe,+BAAyC,CACtD,OAAIrB,EAAgB,4BAA8B,OAChDA,EAAgB,0BAA4B,IAAI,SAAS,cAAe,QAAQ,GAE3EA,EAAgB,yBACzB,CAMQ,cAAcqB,EAAoBM,EAA6B,CAGrE,MAAMS,EADaT,EAAS,QAAQ,OAAO,GACZA,EAQ/B,GALCS,EAAS,MAAc,WAAa,sBACrC,KAAK,mBAAqBA,EAItBpC,EAAgB,gCAAiC,CAEnD,sBAAsB,IAAM,CAC1B,MAAMqC,EAAYhB,EAAM,sBAAA,EAClBiB,EAAaF,EAAS,sBAAA,EAExBC,EAAU,IAAMC,EAAW,KAC7BjB,EAAM,UAAU,IAAI,wBAAwB,CAEhD,CAAC,EACD,MACF,CAGA,MAAMkB,EAAOH,EAAS,sBAAA,EAEtBf,EAAM,MAAM,SAAW,QACvBA,EAAM,MAAM,IAAM,GAAGkB,EAAK,OAAS,CAAC,KACpClB,EAAM,MAAM,KAAO,GAAGkB,EAAK,IAAI,KAG/B,sBAAsB,IAAM,CAC1B,MAAMF,EAAYhB,EAAM,sBAAA,EAGpBgB,EAAU,MAAQ,OAAO,WAAa,IACxChB,EAAM,MAAM,KAAO,GAAGkB,EAAK,MAAQF,EAAU,KAAK,MAIhDA,EAAU,OAAS,OAAO,YAAc,IAC1ChB,EAAM,MAAM,IAAM,GAAGkB,EAAK,IAAMF,EAAU,OAAS,CAAC,KACpDhB,EAAM,UAAU,IAAI,wBAAwB,EAEhD,CAAC,CACH,CAKQ,yBACNA,EACAU,EACAH,EACAY,EACM,CACN,KAAM,CAAE,MAAA7C,GAAUoC,EAEZU,EAAa,KAAK,kBAAA,EAGlBC,EAAkB,SAAS,cAAc,KAAK,EACpDA,EAAgB,UAAY,oBAE5B,MAAMC,EAAc,SAAS,cAAc,OAAO,EAClDA,EAAY,KAAO,OACnBA,EAAY,YAAc,YAC1BA,EAAY,UAAY,0BACxBA,EAAY,MAAQ,KAAK,WAAW,IAAIhD,CAAK,GAAK,GAClD+C,EAAgB,YAAYC,CAAW,EACvCtB,EAAM,YAAYqB,CAAe,EAGjC,MAAME,EAAa,SAAS,cAAc,KAAK,EAC/CA,EAAW,UAAY,qBAEvB,MAAMC,EAAiB,SAAS,cAAc,OAAO,EACrDA,EAAe,UAAY,wBAC3BA,EAAe,MAAM,QAAU,IAC/BA,EAAe,MAAM,OAAS,IAE9B,MAAMC,EAAoB,SAAS,cAAc,OAAO,EACxDA,EAAkB,KAAO,WACzBA,EAAkB,UAAY,sBAE9B,MAAMC,EAAgB,SAAS,cAAc,MAAM,EACnDA,EAAc,YAAc,aAE5BF,EAAe,YAAYC,CAAiB,EAC5CD,EAAe,YAAYE,CAAa,EACxCH,EAAW,YAAYC,CAAc,EAGrC,MAAMG,EAAuB,IAAM,CACjC,MAAMpD,EAAS,CAAC,GAAGqD,EAAW,QAAQ,EAChCC,EAAatD,EAAO,MAAOuD,GAAMA,CAAC,EAClCC,EAAcxD,EAAO,MAAOuD,GAAM,CAACA,CAAC,EAE1CL,EAAkB,QAAUI,EAC5BJ,EAAkB,cAAgB,CAACI,GAAc,CAACE,CACpD,EAGAN,EAAkB,iBAAiB,SAAU,IAAM,CACjD,MAAMO,EAAWP,EAAkB,QACnC,UAAWQ,KAAOL,EAAW,OAC3BA,EAAW,IAAIK,EAAKD,CAAQ,EAE9BL,EAAA,EACAO,EAAA,CACF,CAAC,EAEDlC,EAAM,YAAYuB,CAAU,EAG5B,MAAMY,EAAkB,SAAS,cAAc,KAAK,EACpDA,EAAgB,UAAY,oBAG5B,MAAMC,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAY,2BACnBD,EAAgB,YAAYC,CAAM,EAGlC,MAAMC,EAAmB,SAAS,cAAc,KAAK,EACrDA,EAAiB,UAAY,4BAC7BF,EAAgB,YAAYE,CAAgB,EAG5C,MAAMT,MAAiB,IACvBrB,EAAa,QAAS/B,GAAU,CAC9B,MAAMyD,EAAMzD,GAAS,KAAO,WAAa,OAAOA,CAAK,EACrDoD,EAAW,IAAIK,EAAK,CAACd,EAAe,IAAI3C,CAAK,CAAC,CAChD,CAAC,EAGDmD,EAAA,EAGA,IAAIW,EAA4B,CAAA,EAGhC,MAAMC,EAAa,CAAC/D,EAAgBgE,IAA+B,CACjE,MAAMC,EAAWjE,GAAS,KAAO,UAAY,OAAOA,CAAK,EACnDyD,EAAMzD,GAAS,KAAO,WAAa,OAAOA,CAAK,EAE/CkE,EAAO,SAAS,cAAc,OAAO,EAC3CA,EAAK,UAAY,wBACjBA,EAAK,MAAM,SAAW,WACtBA,EAAK,MAAM,IAAM,GAAGF,EAAQpB,CAAU,KACtCsB,EAAK,MAAM,KAAO,IAClBA,EAAK,MAAM,MAAQ,IACnBA,EAAK,MAAM,OAAS,GAAGtB,CAAU,KACjCsB,EAAK,MAAM,UAAY,aAEvB,MAAMC,EAAW,SAAS,cAAc,OAAO,EAC/CA,EAAS,KAAO,WAChBA,EAAS,UAAY,sBACrBA,EAAS,QAAUf,EAAW,IAAIK,CAAG,GAAK,GAC1CU,EAAS,QAAQ,MAAQV,EAGzBU,EAAS,iBAAiB,SAAU,IAAM,CACxCf,EAAW,IAAIK,EAAKU,EAAS,OAAO,EACpChB,EAAA,CACF,CAAC,EAED,MAAMiB,EAAQ,SAAS,cAAc,MAAM,EAC3C,OAAAA,EAAM,YAAcH,EAEpBC,EAAK,YAAYC,CAAQ,EACzBD,EAAK,YAAYE,CAAK,EACfF,CACT,EAGMR,EAAqB,IAAM,CAC/B,MAAMW,EAAaP,EAAe,OAC5BQ,EAAiBX,EAAgB,aACjCY,EAAYZ,EAAgB,UAMlC,GAHAC,EAAO,MAAM,OAAS,GAAGS,EAAazB,CAAU,KAG5C4B,EAAAA,2BAA2BH,EAAYlE,EAAgB,sBAAwB,CAAC,EAAG,CACrF0D,EAAiB,UAAY,GAC7BA,EAAiB,MAAM,UAAY,kBACnCC,EAAe,QAAQ,CAAC9D,EAAOyE,IAAQ,CACrCZ,EAAiB,YAAYE,EAAW/D,EAAOyE,CAAG,CAAC,CACrD,CAAC,EACD,MACF,CAGA,MAAMC,EAASC,EAAAA,qBAAqB,CAClC,UAAWN,EACX,eAAAC,EACA,UAAAC,EACA,UAAW3B,EACX,SAAUzC,EAAgB,aAAA,CAC3B,EAGD0D,EAAiB,MAAM,UAAY,cAAca,EAAO,OAAO,MAG/Db,EAAiB,UAAY,GAC7B,QAASe,EAAIF,EAAO,MAAOE,EAAIF,EAAO,IAAKE,IACzCf,EAAiB,YAAYE,EAAWD,EAAec,CAAC,EAAGA,EAAIF,EAAO,KAAK,CAAC,CAEhF,EAGMG,EAAgBC,GAAuB,CAC3C,MAAM3F,EAAgB,KAAK,OAAO,eAAiB,GAC7C4F,EAAgB5F,EAAgB2F,EAAaA,EAAW,YAAA,EAS9D,GANAhB,EAAiB/B,EAAa,OAAQ/B,GAAU,CAC9C,MAAMiE,EAAWjE,GAAS,KAAO,UAAY,OAAOA,CAAK,EACnDV,EAAeH,EAAgB8E,EAAWA,EAAS,YAAA,EACzD,MAAO,CAACa,GAAcxF,EAAa,SAASyF,CAAa,CAC3D,CAAC,EAEGjB,EAAe,SAAW,EAAG,CAC/BF,EAAO,MAAM,OAAS,MACtBC,EAAiB,UAAY,GAC7B,MAAMmB,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAY,sBACpBA,EAAQ,YAAc,qBACtBnB,EAAiB,YAAYmB,CAAO,EACpC,MACF,CAEAtB,EAAA,CACF,EAGAC,EAAgB,iBACd,SACA,IAAM,CACAG,EAAe,OAAS,GAC1BJ,EAAA,CAEJ,EACA,CAAE,QAAS,EAAA,CAAK,EAGlBmB,EAAa/B,EAAY,KAAK,EAC9BtB,EAAM,YAAYmC,CAAe,EAGjC,IAAIsB,EACJnC,EAAY,iBAAiB,QAAS,IAAM,CAC1C,aAAamC,CAAa,EAC1BA,EAAgB,WAAW,IAAM,CAC/B,KAAK,WAAW,IAAInF,EAAOgD,EAAY,KAAK,EAC5C+B,EAAa/B,EAAY,KAAK,CAChC,EAAG,KAAK,OAAO,YAAc,GAAG,CAClC,CAAC,EAGD,MAAMoC,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,UAAY,qBAEtB,MAAMC,EAAW,SAAS,cAAc,QAAQ,EAChDA,EAAS,UAAY,uBACrBA,EAAS,YAAc,QACvBA,EAAS,iBAAiB,QAAS,IAAM,CAEvC,MAAMhD,EAAsB,CAAA,EAC5B,SAAW,CAACsB,EAAK2B,CAAS,IAAKhC,EAC7B,GAAI,CAACgC,EACH,GAAI3B,IAAQ,WACVtB,EAAS,KAAK,IAAI,MACb,CAEL,MAAMkD,EAAWtD,EAAa,KAAMuB,GAAM,OAAOA,CAAC,IAAMG,CAAG,EAC3DtB,EAAS,KAAKkD,IAAa,OAAYA,EAAW5B,CAAG,CACvD,CAGJvB,EAAO,eAAeC,CAAQ,CAChC,CAAC,EACD+C,EAAU,YAAYC,CAAQ,EAE9B,MAAMG,EAAW,SAAS,cAAc,QAAQ,EAChDA,EAAS,UAAY,uBACrBA,EAAS,YAAc,eACvBA,EAAS,iBAAiB,QAAS,IAAM,CACvCpD,EAAO,YAAA,CACT,CAAC,EACDgD,EAAU,YAAYI,CAAQ,EAE9B9D,EAAM,YAAY0D,CAAS,CAC7B,CAKQ,eAAepF,EAAeqC,EAA2B,CAE/D,KAAK,eAAe,IAAIrC,EAAO,IAAI,IAAIqC,CAAQ,CAAC,EAE5CA,EAAS,SAAW,EAEtB,KAAK,QAAQ,OAAOrC,CAAK,EAGzB,KAAK,QAAQ,IAAIA,EAAO,CACtB,MAAAA,EACA,KAAM,MACN,SAAU,QACV,MAAOqC,CAAA,CACR,EAGH,KAAK,qBAAA,CACP,CAKQ,gBAAgBrC,EAAesC,EAAmCpC,EAAeqC,EAAwB,CAC/G,KAAK,QAAQ,IAAIvC,EAAO,CACtB,MAAAA,EACA,KAAM,OACN,SAAAsC,EACA,MAAApC,EACA,QAAAqC,CAAA,CACD,EAED,KAAK,qBAAA,CACP,CAKQ,sBAA6B,CACnC,KAAK,aAAe,KACpB,KAAK,SAAW,KAEhB,MAAM5B,EAAa,CAAC,GAAG,KAAK,QAAQ,QAAQ,EAG5C,GAAI,KAAK,OAAO,cAAe,CAC7B,MAAMG,EAAS,KAAK,KACpBA,EAAO,aAAa,YAAa,MAAM,EAEvC,MAAMD,EAAS,KAAK,OAAO,cAAcF,EAAY,KAAK,UAAuB,EAG3E8E,EAAgB9F,GAAoB,CACxCmB,EAAO,gBAAgB,WAAW,EAClC,KAAK,aAAenB,EAGnB,KAAK,KAAwC,KAAOA,EAErD,KAAK,KAAyB,gBAAiB,CAC7C,QAASgB,EACT,iBAAkBhB,EAAK,MAAA,CACxB,EAGD,KAAK,cAAA,CACP,EAEIkB,GAAU,OAAQA,EAA8B,MAAS,WAC1DA,EAA8B,KAAK4E,CAAY,EAEhDA,EAAa5E,CAAmB,EAElC,MACF,CAGA,KAAK,KAAyB,gBAAiB,CAC7C,QAASF,EACT,iBAAkB,CAAA,CACnB,EACD,KAAK,cAAA,CACP,CASS,eAAeX,EAAiD,CACvE,MAAM0F,EAAc,KAAK,QAAQ,IAAI1F,CAAK,EAC1C,GAAK0F,EAEL,MAAO,CACL,OAAQ,CACN,KAAMA,EAAY,KAClB,SAAUA,EAAY,SACtB,MAAOA,EAAY,MACnB,QAASA,EAAY,OAAA,CACvB,CAEJ,CAMS,iBAAiB1F,EAAe2F,EAA0B,CAEjE,GAAI,CAACA,EAAM,OAAQ,CACjB,KAAK,QAAQ,OAAO3F,CAAK,EACzB,MACF,CAGA,MAAM0F,EAA2B,CAC/B,MAAA1F,EACA,KAAM2F,EAAM,OAAO,KACnB,SAAUA,EAAM,OAAO,SACvB,MAAOA,EAAM,OAAO,MACpB,QAASA,EAAM,OAAO,OAAA,EAGxB,KAAK,QAAQ,IAAI3F,EAAO0F,CAAW,EAEnC,KAAK,aAAe,KACpB,KAAK,SAAW,IAClB,CAEF"}
@@ -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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"}