@toolbox-web/grid 1.12.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all.js +1171 -943
- package/all.js.map +1 -1
- package/index.js +735 -737
- package/index.js.map +1 -1
- package/lib/core/grid.d.ts.map +1 -1
- package/lib/core/internal/row-animation.d.ts.map +1 -1
- package/lib/core/internal/sanitize.d.ts.map +1 -1
- package/lib/core/internal/validate-config.d.ts.map +1 -1
- package/lib/core/plugin/types.d.ts +1 -1
- package/lib/core/plugin/types.d.ts.map +1 -1
- package/lib/core/types.d.ts +48 -1
- package/lib/core/types.d.ts.map +1 -1
- package/lib/plugins/clipboard/ClipboardPlugin.d.ts +69 -8
- package/lib/plugins/clipboard/ClipboardPlugin.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.d.ts +1 -1
- package/lib/plugins/clipboard/index.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.js +257 -192
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/clipboard/types.d.ts +31 -0
- package/lib/plugins/clipboard/types.d.ts.map +1 -1
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/ContextMenuPlugin.d.ts +8 -0
- package/lib/plugins/context-menu/ContextMenuPlugin.d.ts.map +1 -1
- package/lib/plugins/context-menu/index.js +75 -60
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/context-menu/types.d.ts +7 -0
- package/lib/plugins/context-menu/types.d.ts.map +1 -1
- package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
- package/lib/plugins/editing/editors.d.ts +2 -2
- package/lib/plugins/editing/editors.d.ts.map +1 -1
- package/lib/plugins/editing/index.d.ts +1 -1
- package/lib/plugins/editing/index.d.ts.map +1 -1
- package/lib/plugins/editing/index.js +393 -337
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/editing/types.d.ts +50 -23
- package/lib/plugins/editing/types.d.ts.map +1 -1
- package/lib/plugins/export/ExportPlugin.d.ts.map +1 -1
- package/lib/plugins/export/index.js +75 -66
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/index.d.ts +1 -1
- package/lib/plugins/filtering/index.d.ts.map +1 -1
- package/lib/plugins/filtering/index.js +9 -9
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js +57 -56
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts +1 -0
- package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts.map +1 -1
- package/lib/plugins/pinned-rows/index.js +118 -87
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pinned-rows/pinned-rows.d.ts +2 -1
- package/lib/plugins/pinned-rows/pinned-rows.d.ts.map +1 -1
- package/lib/plugins/pinned-rows/types.d.ts +23 -2
- package/lib/plugins/pinned-rows/types.d.ts.map +1 -1
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/print/index.js.map +1 -1
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/responsive/index.js +40 -39
- package/lib/plugins/responsive/index.js.map +1 -1
- package/lib/plugins/row-reorder/index.js.map +1 -1
- package/lib/plugins/selection/SelectionPlugin.d.ts +51 -0
- package/lib/plugins/selection/SelectionPlugin.d.ts.map +1 -1
- package/lib/plugins/selection/index.js +347 -145
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/selection/types.d.ts +18 -0
- package/lib/plugins/selection/types.d.ts.map +1 -1
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/shared/data-collection.d.ts +33 -0
- package/lib/plugins/shared/data-collection.d.ts.map +1 -0
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +1 -1
- package/public.d.ts +2 -0
- package/public.d.ts.map +1 -1
- package/themes/dg-theme-bootstrap.css +192 -8
- package/themes/dg-theme-material.css +243 -0
- package/umd/grid.all.umd.js +42 -42
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +19 -19
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/clipboard.umd.js +5 -5
- package/umd/plugins/clipboard.umd.js.map +1 -1
- package/umd/plugins/context-menu.umd.js +1 -1
- package/umd/plugins/context-menu.umd.js.map +1 -1
- package/umd/plugins/editing.umd.js +1 -1
- package/umd/plugins/editing.umd.js.map +1 -1
- package/umd/plugins/export.umd.js +7 -7
- package/umd/plugins/export.umd.js.map +1 -1
- package/umd/plugins/filtering.umd.js +1 -1
- package/umd/plugins/filtering.umd.js.map +1 -1
- package/umd/plugins/pinned-rows.umd.js +1 -1
- package/umd/plugins/pinned-rows.umd.js.map +1 -1
- package/umd/plugins/selection.umd.js +2 -2
- 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, type PluginManifest } 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 /**\n * Plugin manifest - declares events emitted by this plugin.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n events: [\n {\n type: 'filter-applied',\n description: 'Emitted when filter criteria change. Subscribers can react to row visibility changes.',\n },\n ],\n };\n\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 Helpers\n\n /**\n * Check if filtering is enabled at the grid level.\n * Grid-wide `filterable: false` disables filtering for all columns.\n */\n private isFilteringEnabled(): boolean {\n return this.grid.effectiveConfig?.filterable !== false;\n }\n\n /**\n * Check if a specific column is filterable, respecting both grid-level and column-level settings.\n */\n private isColumnFilterable(col: { filterable?: boolean; field?: string }): boolean {\n if (!this.isFilteringEnabled()) return false;\n return col.filterable !== false;\n }\n\n // #endregion\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 || !this.isColumnFilterable(col)) 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 // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: [...this.filters.values()] });\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 // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: [...this.filters.values()] });\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\n // Position and append to body BEFORE rendering content\n // so getListItemHeight() can read CSS variables from computed styles\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n\n this.renderPanelContent(field, column, panel, uniqueValues);\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 // Resolution order: plugin config → typeDefaults → built-in\n let usedCustomRenderer = false;\n\n // 1. Check plugin-level filterPanelRenderer\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\n // 2. Check typeDefaults for this column's type\n if (!usedCustomRenderer && column.type) {\n const typeDefault = this.grid.effectiveConfig.typeDefaults?.[column.type];\n if (typeDefault?.filterPanelRenderer) {\n typeDefault.filterPanelRenderer(panel, params);\n usedCustomRenderer = panel.children.length > 0;\n }\n }\n\n // 3. Fall back to built-in type-specific panel renderers\n if (!usedCustomRenderer) {\n const columnType = column.type;\n if (columnType === 'number') {\n this.renderNumberFilterPanel(panel, params, uniqueValues);\n } else if (columnType === 'date') {\n this.renderDateFilterPanel(panel, params, uniqueValues);\n } else {\n this.renderDefaultFilterPanel(panel, params, uniqueValues, excludedSet);\n }\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 = `calc(var(--tbw-filter-item-height, 28px) * ${index})`;\n item.style.left = '0';\n item.style.right = '0';\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 * Render a number range filter panel with min/max inputs and slider\n */\n private renderNumberFilterPanel(panel: HTMLElement, params: FilterPanelParams, uniqueValues: unknown[]): void {\n const { field, column } = params;\n\n // Get range configuration from filterParams, editorParams, or compute from data\n const filterParams = column.filterParams;\n const editorParams = column.editorParams as { min?: number; max?: number; step?: number } | undefined;\n\n // Helper to convert to number\n const toNumber = (val: unknown, fallback: number): number => {\n if (typeof val === 'number') return val;\n if (typeof val === 'string') {\n const num = parseFloat(val);\n return isNaN(num) ? fallback : num;\n }\n return fallback;\n };\n\n // Compute min/max from data if not specified\n const numericValues = uniqueValues.filter((v) => typeof v === 'number' && !isNaN(v)) as number[];\n const dataMin = numericValues.length > 0 ? Math.min(...numericValues) : 0;\n const dataMax = numericValues.length > 0 ? Math.max(...numericValues) : 100;\n\n const min = toNumber(filterParams?.min ?? editorParams?.min, dataMin);\n const max = toNumber(filterParams?.max ?? editorParams?.max, dataMax);\n const step = filterParams?.step ?? editorParams?.step ?? 1;\n\n // Get current filter values if any\n const currentFilter = this.filters.get(field);\n let currentMin = min;\n let currentMax = max;\n if (currentFilter?.operator === 'between') {\n currentMin = toNumber(currentFilter.value, min);\n currentMax = toNumber(currentFilter.valueTo, max);\n } else if (currentFilter?.operator === 'greaterThanOrEqual') {\n currentMin = toNumber(currentFilter.value, min);\n } else if (currentFilter?.operator === 'lessThanOrEqual') {\n currentMax = toNumber(currentFilter.value, max);\n }\n\n // Range inputs container\n const rangeContainer = document.createElement('div');\n rangeContainer.className = 'tbw-filter-range-inputs';\n\n // Min input\n const minGroup = document.createElement('div');\n minGroup.className = 'tbw-filter-range-group';\n\n const minLabel = document.createElement('label');\n minLabel.textContent = 'Min';\n minLabel.className = 'tbw-filter-range-label';\n\n const minInput = document.createElement('input');\n minInput.type = 'number';\n minInput.className = 'tbw-filter-range-input';\n minInput.min = String(min);\n minInput.max = String(max);\n minInput.step = String(step);\n minInput.value = String(currentMin);\n\n minGroup.appendChild(minLabel);\n minGroup.appendChild(minInput);\n rangeContainer.appendChild(minGroup);\n\n // Separator\n const separator = document.createElement('span');\n separator.className = 'tbw-filter-range-separator';\n separator.textContent = '–';\n rangeContainer.appendChild(separator);\n\n // Max input\n const maxGroup = document.createElement('div');\n maxGroup.className = 'tbw-filter-range-group';\n\n const maxLabel = document.createElement('label');\n maxLabel.textContent = 'Max';\n maxLabel.className = 'tbw-filter-range-label';\n\n const maxInput = document.createElement('input');\n maxInput.type = 'number';\n maxInput.className = 'tbw-filter-range-input';\n maxInput.min = String(min);\n maxInput.max = String(max);\n maxInput.step = String(step);\n maxInput.value = String(currentMax);\n\n maxGroup.appendChild(maxLabel);\n maxGroup.appendChild(maxInput);\n rangeContainer.appendChild(maxGroup);\n\n panel.appendChild(rangeContainer);\n\n // Range slider (dual thumb using two range inputs)\n const sliderContainer = document.createElement('div');\n sliderContainer.className = 'tbw-filter-range-slider';\n\n const sliderTrack = document.createElement('div');\n sliderTrack.className = 'tbw-filter-range-track';\n\n const sliderFill = document.createElement('div');\n sliderFill.className = 'tbw-filter-range-fill';\n\n const minSlider = document.createElement('input');\n minSlider.type = 'range';\n minSlider.className = 'tbw-filter-range-thumb tbw-filter-range-thumb-min';\n minSlider.min = String(min);\n minSlider.max = String(max);\n minSlider.step = String(step);\n minSlider.value = String(currentMin);\n\n const maxSlider = document.createElement('input');\n maxSlider.type = 'range';\n maxSlider.className = 'tbw-filter-range-thumb tbw-filter-range-thumb-max';\n maxSlider.min = String(min);\n maxSlider.max = String(max);\n maxSlider.step = String(step);\n maxSlider.value = String(currentMax);\n\n sliderContainer.appendChild(sliderTrack);\n sliderContainer.appendChild(sliderFill);\n sliderContainer.appendChild(minSlider);\n sliderContainer.appendChild(maxSlider);\n panel.appendChild(sliderContainer);\n\n // Update fill position\n const updateFill = () => {\n const minVal = parseFloat(minSlider.value);\n const maxVal = parseFloat(maxSlider.value);\n const range = max - min;\n const leftPercent = ((minVal - min) / range) * 100;\n const rightPercent = ((maxVal - min) / range) * 100;\n sliderFill.style.left = `${leftPercent}%`;\n sliderFill.style.width = `${rightPercent - leftPercent}%`;\n };\n\n // Sync inputs with sliders\n minSlider.addEventListener('input', () => {\n const val = Math.min(parseFloat(minSlider.value), parseFloat(maxSlider.value));\n minSlider.value = String(val);\n minInput.value = String(val);\n updateFill();\n });\n\n maxSlider.addEventListener('input', () => {\n const val = Math.max(parseFloat(maxSlider.value), parseFloat(minSlider.value));\n maxSlider.value = String(val);\n maxInput.value = String(val);\n updateFill();\n });\n\n // Sync sliders with inputs\n minInput.addEventListener('input', () => {\n let val = parseFloat(minInput.value) || min;\n val = Math.max(min, Math.min(val, parseFloat(maxInput.value)));\n minSlider.value = String(val);\n updateFill();\n });\n\n maxInput.addEventListener('input', () => {\n let val = parseFloat(maxInput.value) || max;\n val = Math.min(max, Math.max(val, parseFloat(minInput.value)));\n maxSlider.value = String(val);\n updateFill();\n });\n\n // Initialize fill\n updateFill();\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 const minVal = parseFloat(minInput.value);\n const maxVal = parseFloat(maxInput.value);\n params.applyTextFilter('between', minVal, maxVal);\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 * Render a date range filter panel with from/to date inputs\n */\n private renderDateFilterPanel(panel: HTMLElement, params: FilterPanelParams, uniqueValues: unknown[]): void {\n const { field, column } = params;\n\n // Get range configuration from filterParams, editorParams, or compute from data\n const filterParams = column.filterParams;\n const editorParams = column.editorParams as { min?: string; max?: string } | undefined;\n\n // Compute min/max from data if not specified\n const dateValues = uniqueValues\n .filter((v) => v instanceof Date || (typeof v === 'string' && !isNaN(Date.parse(v))))\n .map((v) => (v instanceof Date ? v : new Date(v as string)))\n .filter((d) => !isNaN(d.getTime()));\n\n const dataMin = dateValues.length > 0 ? new Date(Math.min(...dateValues.map((d) => d.getTime()))) : null;\n const dataMax = dateValues.length > 0 ? new Date(Math.max(...dateValues.map((d) => d.getTime()))) : null;\n\n // Format date for input[type=\"date\"] (YYYY-MM-DD)\n const formatDateForInput = (date: Date | null): string => {\n if (!date) return '';\n return date.toISOString().split('T')[0];\n };\n\n const parseFilterParam = (value: unknown): string => {\n if (!value) return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'number') return formatDateForInput(new Date(value));\n return '';\n };\n\n const minDate =\n parseFilterParam(filterParams?.min) || parseFilterParam(editorParams?.min) || formatDateForInput(dataMin);\n const maxDate =\n parseFilterParam(filterParams?.max) || parseFilterParam(editorParams?.max) || formatDateForInput(dataMax);\n\n // Get current filter values if any\n const currentFilter = this.filters.get(field);\n let currentFrom = '';\n let currentTo = '';\n if (currentFilter?.operator === 'between') {\n currentFrom = parseFilterParam(currentFilter.value) || '';\n currentTo = parseFilterParam(currentFilter.valueTo) || '';\n } else if (currentFilter?.operator === 'greaterThanOrEqual') {\n currentFrom = parseFilterParam(currentFilter.value) || '';\n } else if (currentFilter?.operator === 'lessThanOrEqual') {\n currentTo = parseFilterParam(currentFilter.value) || '';\n }\n\n // Date range inputs container\n const rangeContainer = document.createElement('div');\n rangeContainer.className = 'tbw-filter-date-range';\n\n // From input\n const fromGroup = document.createElement('div');\n fromGroup.className = 'tbw-filter-date-group';\n\n const fromLabel = document.createElement('label');\n fromLabel.textContent = 'From';\n fromLabel.className = 'tbw-filter-range-label';\n\n const fromInput = document.createElement('input');\n fromInput.type = 'date';\n fromInput.className = 'tbw-filter-date-input';\n if (minDate) fromInput.min = minDate;\n if (maxDate) fromInput.max = maxDate;\n fromInput.value = currentFrom;\n\n fromGroup.appendChild(fromLabel);\n fromGroup.appendChild(fromInput);\n rangeContainer.appendChild(fromGroup);\n\n // Separator\n const separator = document.createElement('span');\n separator.className = 'tbw-filter-range-separator';\n separator.textContent = '–';\n rangeContainer.appendChild(separator);\n\n // To input\n const toGroup = document.createElement('div');\n toGroup.className = 'tbw-filter-date-group';\n\n const toLabel = document.createElement('label');\n toLabel.textContent = 'To';\n toLabel.className = 'tbw-filter-range-label';\n\n const toInput = document.createElement('input');\n toInput.type = 'date';\n toInput.className = 'tbw-filter-date-input';\n if (minDate) toInput.min = minDate;\n if (maxDate) toInput.max = maxDate;\n toInput.value = currentTo;\n\n toGroup.appendChild(toLabel);\n toGroup.appendChild(toInput);\n rangeContainer.appendChild(toGroup);\n\n panel.appendChild(rangeContainer);\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 const from = fromInput.value;\n const to = toInput.value;\n\n if (from && to) {\n params.applyTextFilter('between', from, to);\n } else if (from) {\n params.applyTextFilter('greaterThanOrEqual', from);\n } else if (to) {\n params.applyTextFilter('lessThanOrEqual', to);\n } else {\n params.clearFilter();\n }\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/number/date filter\n */\n private applyTextFilter(\n field: string,\n operator: FilterModel['operator'],\n value: string | number,\n valueTo?: string | number,\n ): 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 // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: filterList });\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 // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: filterList });\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","col","cssValue","parsed","grid","filterList","newCacheKey","result","gridEl","cell","colIndex","isUtilityColumn","hasFilter","filterBtn","wasActive","iconName","e","resizeHandle","fullFilter","panel","className","theme","style","filterPanelStyles","column","buttonEl","uniqueValues","excludedSet","currentSearchText","params","excluded","operator","valueTo","usedCustomRenderer","typeDefault","columnType","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","filterParams","editorParams","toNumber","val","fallback","num","numericValues","dataMin","dataMax","min","max","step","currentFilter","currentMin","currentMax","rangeContainer","minGroup","minLabel","minInput","separator","maxGroup","maxLabel","maxInput","sliderContainer","sliderTrack","sliderFill","minSlider","maxSlider","updateFill","minVal","maxVal","range","leftPercent","rightPercent","dateValues","d","formatDateForInput","date","parseFilterParam","minDate","maxDate","currentFrom","currentTo","fromGroup","fromLabel","fromInput","toGroup","toLabel","toInput","from","to","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, type PluginManifest } 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 /**\n * Plugin manifest - declares events emitted by this plugin.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n events: [\n {\n type: 'filter-applied',\n description: 'Emitted when filter criteria change. Subscribers can react to row visibility changes.',\n },\n ],\n };\n\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 Helpers\n\n /**\n * Check if filtering is enabled at the grid level.\n * Grid-wide `filterable: false` disables filtering for all columns.\n */\n private isFilteringEnabled(): boolean {\n return this.grid.effectiveConfig?.filterable !== false;\n }\n\n /**\n * Check if a specific column is filterable, respecting both grid-level and column-level settings.\n */\n private isColumnFilterable(col: { filterable?: boolean; field?: string }): boolean {\n if (!this.isFilteringEnabled()) return false;\n return col.filterable !== false;\n }\n\n // #endregion\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 || !this.isColumnFilterable(col)) 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 // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: [...this.filters.values()] });\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 // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: [...this.filters.values()] });\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\n // Position and append to body BEFORE rendering content\n // so getListItemHeight() can read CSS variables from computed styles\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n\n this.renderPanelContent(field, column, panel, uniqueValues);\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 // Resolution order: plugin config → typeDefaults → built-in\n let usedCustomRenderer = false;\n\n // 1. Check plugin-level filterPanelRenderer\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\n // 2. Check typeDefaults for this column's type\n if (!usedCustomRenderer && column.type) {\n const typeDefault = this.grid.effectiveConfig.typeDefaults?.[column.type];\n if (typeDefault?.filterPanelRenderer) {\n typeDefault.filterPanelRenderer(panel, params);\n usedCustomRenderer = panel.children.length > 0;\n }\n }\n\n // 3. Fall back to built-in type-specific panel renderers\n if (!usedCustomRenderer) {\n const columnType = column.type;\n if (columnType === 'number') {\n this.renderNumberFilterPanel(panel, params, uniqueValues);\n } else if (columnType === 'date') {\n this.renderDateFilterPanel(panel, params, uniqueValues);\n } else {\n this.renderDefaultFilterPanel(panel, params, uniqueValues, excludedSet);\n }\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 = `calc(var(--tbw-filter-item-height, 28px) * ${index})`;\n item.style.left = '0';\n item.style.right = '0';\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 * Render a number range filter panel with min/max inputs and slider\n */\n private renderNumberFilterPanel(panel: HTMLElement, params: FilterPanelParams, uniqueValues: unknown[]): void {\n const { field, column } = params;\n\n // Get range configuration from filterParams, editorParams, or compute from data\n const filterParams = column.filterParams;\n const editorParams = column.editorParams as { min?: number; max?: number; step?: number } | undefined;\n\n // Helper to convert to number\n const toNumber = (val: unknown, fallback: number): number => {\n if (typeof val === 'number') return val;\n if (typeof val === 'string') {\n const num = parseFloat(val);\n return isNaN(num) ? fallback : num;\n }\n return fallback;\n };\n\n // Compute min/max from data if not specified\n const numericValues = uniqueValues.filter((v) => typeof v === 'number' && !isNaN(v)) as number[];\n const dataMin = numericValues.length > 0 ? Math.min(...numericValues) : 0;\n const dataMax = numericValues.length > 0 ? Math.max(...numericValues) : 100;\n\n const min = toNumber(filterParams?.min ?? editorParams?.min, dataMin);\n const max = toNumber(filterParams?.max ?? editorParams?.max, dataMax);\n const step = filterParams?.step ?? editorParams?.step ?? 1;\n\n // Get current filter values if any\n const currentFilter = this.filters.get(field);\n let currentMin = min;\n let currentMax = max;\n if (currentFilter?.operator === 'between') {\n currentMin = toNumber(currentFilter.value, min);\n currentMax = toNumber(currentFilter.valueTo, max);\n } else if (currentFilter?.operator === 'greaterThanOrEqual') {\n currentMin = toNumber(currentFilter.value, min);\n } else if (currentFilter?.operator === 'lessThanOrEqual') {\n currentMax = toNumber(currentFilter.value, max);\n }\n\n // Range inputs container\n const rangeContainer = document.createElement('div');\n rangeContainer.className = 'tbw-filter-range-inputs';\n\n // Min input\n const minGroup = document.createElement('div');\n minGroup.className = 'tbw-filter-range-group';\n\n const minLabel = document.createElement('label');\n minLabel.textContent = 'Min';\n minLabel.className = 'tbw-filter-range-label';\n\n const minInput = document.createElement('input');\n minInput.type = 'number';\n minInput.className = 'tbw-filter-range-input';\n minInput.min = String(min);\n minInput.max = String(max);\n minInput.step = String(step);\n minInput.value = String(currentMin);\n\n minGroup.appendChild(minLabel);\n minGroup.appendChild(minInput);\n rangeContainer.appendChild(minGroup);\n\n // Separator\n const separator = document.createElement('span');\n separator.className = 'tbw-filter-range-separator';\n separator.textContent = '–';\n rangeContainer.appendChild(separator);\n\n // Max input\n const maxGroup = document.createElement('div');\n maxGroup.className = 'tbw-filter-range-group';\n\n const maxLabel = document.createElement('label');\n maxLabel.textContent = 'Max';\n maxLabel.className = 'tbw-filter-range-label';\n\n const maxInput = document.createElement('input');\n maxInput.type = 'number';\n maxInput.className = 'tbw-filter-range-input';\n maxInput.min = String(min);\n maxInput.max = String(max);\n maxInput.step = String(step);\n maxInput.value = String(currentMax);\n\n maxGroup.appendChild(maxLabel);\n maxGroup.appendChild(maxInput);\n rangeContainer.appendChild(maxGroup);\n\n panel.appendChild(rangeContainer);\n\n // Range slider (dual thumb using two range inputs)\n const sliderContainer = document.createElement('div');\n sliderContainer.className = 'tbw-filter-range-slider';\n\n const sliderTrack = document.createElement('div');\n sliderTrack.className = 'tbw-filter-range-track';\n\n const sliderFill = document.createElement('div');\n sliderFill.className = 'tbw-filter-range-fill';\n\n const minSlider = document.createElement('input');\n minSlider.type = 'range';\n minSlider.className = 'tbw-filter-range-thumb tbw-filter-range-thumb-min';\n minSlider.min = String(min);\n minSlider.max = String(max);\n minSlider.step = String(step);\n minSlider.value = String(currentMin);\n\n const maxSlider = document.createElement('input');\n maxSlider.type = 'range';\n maxSlider.className = 'tbw-filter-range-thumb tbw-filter-range-thumb-max';\n maxSlider.min = String(min);\n maxSlider.max = String(max);\n maxSlider.step = String(step);\n maxSlider.value = String(currentMax);\n\n sliderContainer.appendChild(sliderTrack);\n sliderContainer.appendChild(sliderFill);\n sliderContainer.appendChild(minSlider);\n sliderContainer.appendChild(maxSlider);\n panel.appendChild(sliderContainer);\n\n // Update fill position\n const updateFill = () => {\n const minVal = parseFloat(minSlider.value);\n const maxVal = parseFloat(maxSlider.value);\n const range = max - min;\n const leftPercent = ((minVal - min) / range) * 100;\n const rightPercent = ((maxVal - min) / range) * 100;\n sliderFill.style.left = `${leftPercent}%`;\n sliderFill.style.width = `${rightPercent - leftPercent}%`;\n };\n\n // Sync inputs with sliders\n minSlider.addEventListener('input', () => {\n const val = Math.min(parseFloat(minSlider.value), parseFloat(maxSlider.value));\n minSlider.value = String(val);\n minInput.value = String(val);\n updateFill();\n });\n\n maxSlider.addEventListener('input', () => {\n const val = Math.max(parseFloat(maxSlider.value), parseFloat(minSlider.value));\n maxSlider.value = String(val);\n maxInput.value = String(val);\n updateFill();\n });\n\n // Sync sliders with inputs\n minInput.addEventListener('input', () => {\n let val = parseFloat(minInput.value) || min;\n val = Math.max(min, Math.min(val, parseFloat(maxInput.value)));\n minSlider.value = String(val);\n updateFill();\n });\n\n maxInput.addEventListener('input', () => {\n let val = parseFloat(maxInput.value) || max;\n val = Math.min(max, Math.max(val, parseFloat(minInput.value)));\n maxSlider.value = String(val);\n updateFill();\n });\n\n // Initialize fill\n updateFill();\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 const minVal = parseFloat(minInput.value);\n const maxVal = parseFloat(maxInput.value);\n params.applyTextFilter('between', minVal, maxVal);\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 * Render a date range filter panel with from/to date inputs\n */\n private renderDateFilterPanel(panel: HTMLElement, params: FilterPanelParams, uniqueValues: unknown[]): void {\n const { field, column } = params;\n\n // Get range configuration from filterParams, editorParams, or compute from data\n const filterParams = column.filterParams;\n const editorParams = column.editorParams as { min?: string; max?: string } | undefined;\n\n // Compute min/max from data if not specified\n const dateValues = uniqueValues\n .filter((v) => v instanceof Date || (typeof v === 'string' && !isNaN(Date.parse(v))))\n .map((v) => (v instanceof Date ? v : new Date(v as string)))\n .filter((d) => !isNaN(d.getTime()));\n\n const dataMin = dateValues.length > 0 ? new Date(Math.min(...dateValues.map((d) => d.getTime()))) : null;\n const dataMax = dateValues.length > 0 ? new Date(Math.max(...dateValues.map((d) => d.getTime()))) : null;\n\n // Format date for input[type=\"date\"] (YYYY-MM-DD)\n const formatDateForInput = (date: Date | null): string => {\n if (!date) return '';\n return date.toISOString().split('T')[0];\n };\n\n const parseFilterParam = (value: unknown): string => {\n if (!value) return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'number') return formatDateForInput(new Date(value));\n return '';\n };\n\n const minDate =\n parseFilterParam(filterParams?.min) || parseFilterParam(editorParams?.min) || formatDateForInput(dataMin);\n const maxDate =\n parseFilterParam(filterParams?.max) || parseFilterParam(editorParams?.max) || formatDateForInput(dataMax);\n\n // Get current filter values if any\n const currentFilter = this.filters.get(field);\n let currentFrom = '';\n let currentTo = '';\n if (currentFilter?.operator === 'between') {\n currentFrom = parseFilterParam(currentFilter.value) || '';\n currentTo = parseFilterParam(currentFilter.valueTo) || '';\n } else if (currentFilter?.operator === 'greaterThanOrEqual') {\n currentFrom = parseFilterParam(currentFilter.value) || '';\n } else if (currentFilter?.operator === 'lessThanOrEqual') {\n currentTo = parseFilterParam(currentFilter.value) || '';\n }\n\n // Date range inputs container\n const rangeContainer = document.createElement('div');\n rangeContainer.className = 'tbw-filter-date-range';\n\n // From input\n const fromGroup = document.createElement('div');\n fromGroup.className = 'tbw-filter-date-group';\n\n const fromLabel = document.createElement('label');\n fromLabel.textContent = 'From';\n fromLabel.className = 'tbw-filter-range-label';\n\n const fromInput = document.createElement('input');\n fromInput.type = 'date';\n fromInput.className = 'tbw-filter-date-input';\n if (minDate) fromInput.min = minDate;\n if (maxDate) fromInput.max = maxDate;\n fromInput.value = currentFrom;\n\n fromGroup.appendChild(fromLabel);\n fromGroup.appendChild(fromInput);\n rangeContainer.appendChild(fromGroup);\n\n // Separator\n const separator = document.createElement('span');\n separator.className = 'tbw-filter-range-separator';\n separator.textContent = '–';\n rangeContainer.appendChild(separator);\n\n // To input\n const toGroup = document.createElement('div');\n toGroup.className = 'tbw-filter-date-group';\n\n const toLabel = document.createElement('label');\n toLabel.textContent = 'To';\n toLabel.className = 'tbw-filter-range-label';\n\n const toInput = document.createElement('input');\n toInput.type = 'date';\n toInput.className = 'tbw-filter-date-input';\n if (minDate) toInput.min = minDate;\n if (maxDate) toInput.max = maxDate;\n toInput.value = currentTo;\n\n toGroup.appendChild(toLabel);\n toGroup.appendChild(toInput);\n rangeContainer.appendChild(toGroup);\n\n panel.appendChild(rangeContainer);\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 const from = fromInput.value;\n const to = toInput.value;\n\n if (from && to) {\n params.applyTextFilter('between', from, to);\n } else if (from) {\n params.applyTextFilter('greaterThanOrEqual', from);\n } else if (to) {\n params.applyTextFilter('lessThanOrEqual', to);\n } else {\n params.clearFilter();\n }\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/number/date filter\n */\n private applyTextFilter(\n field: string,\n operator: FilterModel['operator'],\n value: string | number,\n valueTo?: string | number,\n ): 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 // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: filterList });\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 // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: filterList });\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","col","cssValue","parsed","grid","filterList","newCacheKey","result","gridEl","cell","colIndex","isUtilityColumn","hasFilter","filterBtn","wasActive","iconName","e","resizeHandle","fullFilter","panel","className","theme","style","filterPanelStyles","column","buttonEl","uniqueValues","excludedSet","currentSearchText","params","excluded","operator","valueTo","usedCustomRenderer","typeDefault","columnType","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","filterParams","editorParams","toNumber","val","fallback","num","numericValues","dataMin","dataMax","min","max","step","currentFilter","currentMin","currentMax","rangeContainer","minGroup","minLabel","minInput","separator","maxGroup","maxLabel","maxInput","sliderContainer","sliderTrack","sliderFill","minSlider","maxSlider","updateFill","minVal","maxVal","range","leftPercent","rightPercent","dateValues","d","formatDateForInput","date","parseFilterParam","minDate","maxDate","currentFrom","currentTo","fromGroup","fromLabel","fromInput","toGroup","toLabel","toInput","from","to","handleResult","filterModel","state"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(f,h){typeof exports=="object"&&typeof module<"u"?h(exports,require("../../core/plugin/base-plugin"),require("../../core/internal/aggregators")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin","../../core/internal/aggregators"],h):(f=typeof globalThis<"u"?globalThis:f||self,h(f.TbwGridPlugin_pinnedRows={},f.TbwGrid,f.TbwGrid))})(this,(function(f,h,w){"use strict";function v(o){return typeof o=="object"&&o!==null&&"aggFunc"in o}function u(o,e){const n=document.createElement("div");n.className="tbw-pinned-rows",n.setAttribute("role","presentation"),n.setAttribute("aria-live","polite");const r=document.createElement("div");r.className="tbw-pinned-rows-left";const s=document.createElement("div");s.className="tbw-pinned-rows-center";const i=document.createElement("div");if(i.className="tbw-pinned-rows-right",o.showRowCount!==!1){const t=document.createElement("span");t.className="tbw-status-panel tbw-status-panel-row-count",t.textContent=`Total: ${e.totalRows} rows`,r.appendChild(t)}if(o.showFilteredCount&&e.filteredRows!==e.totalRows){const t=document.createElement("span");t.className="tbw-status-panel tbw-status-panel-filtered-count",t.textContent=`Filtered: ${e.filteredRows}`,r.appendChild(t)}if(o.showSelectedCount&&e.selectedRows>0){const t=document.createElement("span");t.className="tbw-status-panel tbw-status-panel-selected-count",t.textContent=`Selected: ${e.selectedRows}`,i.appendChild(t)}if(o.customPanels)for(const t of o.customPanels){const a=A(t,e);switch(t.position){case"left":r.appendChild(a);break;case"center":s.appendChild(a);break;case"right":i.appendChild(a);break}}return n.appendChild(r),n.appendChild(s),n.appendChild(i),n}function m(o){const e=document.createElement("div");return e.className=`tbw-aggregation-rows tbw-aggregation-rows-${o}`,e.setAttribute("role","presentation"),e}function b(o,e,n,r){o.innerHTML="";for(const s of e){const i=document.createElement("div");if(i.className="tbw-aggregation-row",i.setAttribute("role","presentation"),s.id&&i.setAttribute("data-aggregation-id",s.id),s.fullWidth){const t=document.createElement("div");t.className="tbw-aggregation-cell tbw-aggregation-cell-full",t.style.gridColumn="1 / -1",t.textContent=s.label||"",i.appendChild(t)}else for(const t of n){const a=document.createElement("div");a.className="tbw-aggregation-cell",a.setAttribute("data-field",t.field);let l,p;const d=s.aggregators?.[t.field];if(d)if(v(d)){const g=w.getAggregator(d.aggFunc);g&&(l=g(r,t.field,t)),p=d.formatter}else{const g=w.getAggregator(d);g&&(l=g(r,t.field,t))}else if(s.cells&&Object.prototype.hasOwnProperty.call(s.cells,t.field)){const g=s.cells[t.field];typeof g=="function"?l=g(r,t.field,t):l=g}l!=null?a.textContent=p?p(l,t.field,t):String(l):a.textContent="",i.appendChild(a)}o.appendChild(i)}}function A(o,e){const n=document.createElement("div");n.className="tbw-status-panel tbw-status-panel-custom",n.id=`status-panel-${o.id}`;const r=o.render(e);return typeof r=="string"?n.innerHTML=r:n.appendChild(r),n}function C(o,e,n,r,s){return{totalRows:o.length,filteredRows:s?.cachedResult?.length??o.length,selectedRows:r?.selected?.size??0,columns:e,rows:o,grid:n}}const E="@layer tbw-plugins{.tbw-footer{flex-shrink:0;z-index:var(--tbw-z-layer-pinned-rows, 20);background:var(--tbw-color-panel-bg)}.tbw-pinned-rows{display:flex;align-items:center;justify-content:space-between;padding:var(--tbw-button-padding, var(--tbw-spacing-md, .5rem) var(--tbw-spacing-lg, .75rem));background:var(--tbw-pinned-rows-bg, var(--tbw-color-panel-bg));border-top:1px solid var(--tbw-pinned-rows-border, var(--tbw-color-border));font-size:var(--tbw-font-size-xs, .75rem);color:var(--tbw-pinned-rows-color, var(--tbw-color-fg-muted));min-height:32px;box-sizing:border-box;min-width:fit-content}.tbw-pinned-rows-left,.tbw-pinned-rows-center,.tbw-pinned-rows-right{display:flex;align-items:center;gap:var(--tbw-spacing-xl, 1rem)}.tbw-pinned-rows-left{justify-content:flex-start}.tbw-pinned-rows-center{justify-content:center;flex:1}.tbw-pinned-rows-right{justify-content:flex-end}.tbw-status-panel{white-space:nowrap}.tbw-aggregation-rows{min-width:fit-content;background:var(--tbw-aggregation-bg, var(--tbw-color-header-bg))}.tbw-aggregation-rows-top{border-bottom:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-rows-bottom{border-top:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-row{display:grid;grid-template-columns:var(--tbw-column-template);font-size:var(--tbw-aggregation-font-size, .8em);font-weight:var(--tbw-aggregation-font-weight, 600)}.tbw-aggregation-cell{padding:var(--tbw-cell-padding, .125rem .5rem);min-height:var(--tbw-row-height, 1.75rem);display:block;align-items:center;border-right:1px solid var(--tbw-color-border-cell);overflow:hidden;text-overflow:ellipsis;white-space:var(--tbw-cell-white-space, nowrap)}.tbw-aggregation-cell:last-child{border-right:0}.tbw-aggregation-cell-full{grid-column:1 / -1;border-right:0}}";class R extends h.BaseGridPlugin{name="pinnedRows";styles=E;get defaultConfig(){return{position:"bottom",showRowCount:!0,showSelectedCount:!0,showFilteredCount:!0}}infoBarElement=null;topAggregationContainer=null;bottomAggregationContainer=null;footerWrapper=null;detach(){this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null),this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null)}afterRender(){const e=this.gridElement;if(!e)return;const n=e.querySelector(".tbw-scroll-area")??e.querySelector(".tbw-grid-content")??e.children[0];if(!n)return;this.footerWrapper&&!n.contains(this.footerWrapper)&&(this.footerWrapper=null,this.bottomAggregationContainer=null,this.infoBarElement=null),this.topAggregationContainer&&!n.contains(this.topAggregationContainer)&&(this.topAggregationContainer=null),this.infoBarElement&&!n.contains(this.infoBarElement)&&(this.infoBarElement=null);const r=this.getSelectionState(),s=this.getFilterState(),i=C(this.sourceRows,this.columns,this.grid,r,s),t=this.config.aggregationRows||[],a=t.filter(c=>c.position==="top"),l=t.filter(c=>c.position!=="top");if(a.length>0){if(!this.topAggregationContainer){this.topAggregationContainer=m("top");const c=e.querySelector(".header");c&&c.nextSibling?n.insertBefore(this.topAggregationContainer,c.nextSibling):n.appendChild(this.topAggregationContainer)}b(this.topAggregationContainer,a,this.visibleColumns,this.sourceRows)}else this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null);const p=this.config.showRowCount!==!1||this.config.showSelectedCount&&i.selectedRows>0||this.config.showFilteredCount&&i.filteredRows!==i.totalRows||this.config.customPanels&&this.config.customPanels.length>0,d=p&&this.config.position!=="top",g=l.length>0||d;if(p&&this.config.position==="top")if(!this.infoBarElement)this.infoBarElement=u(this.config,i),n.insertBefore(this.infoBarElement,n.firstChild);else{const c=u(this.config,i);this.infoBarElement.replaceWith(c),this.infoBarElement=c}else this.config.position==="top"&&this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null);g?(this.footerWrapper||(this.footerWrapper=document.createElement("div"),this.footerWrapper.className="tbw-footer",n.appendChild(this.footerWrapper)),this.footerWrapper.innerHTML="",l.length>0&&(this.bottomAggregationContainer||(this.bottomAggregationContainer=m("bottom")),this.footerWrapper.appendChild(this.bottomAggregationContainer),b(this.bottomAggregationContainer,l,this.visibleColumns,this.sourceRows)),d&&(this.infoBarElement=u(this.config,i),this.footerWrapper.appendChild(this.infoBarElement))):this.cleanupFooter()}cleanup(){this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null),this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null)}cleanupFooter(){this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.infoBarElement&&this.config.position!=="top"&&(this.infoBarElement.remove(),this.infoBarElement=null)}getSelectionState(){try{return this.grid?.getPluginState?.("selection")??null}catch{return null}}getFilterState(){try{return this.grid?.getPluginState?.("filtering")??null}catch{return null}}refresh(){this.requestRender()}getContext(){const e=this.getSelectionState(),n=this.getFilterState();return C(this.rows,this.columns,this.grid,e,n)}addPanel(e){this.config.customPanels||(this.config.customPanels=[]),this.config.customPanels.push(e),this.requestRender()}removePanel(e){this.config.customPanels&&(this.config.customPanels=this.config.customPanels.filter(n=>n.id!==e),this.requestRender())}addAggregationRow(e){this.config.aggregationRows||(this.config.aggregationRows=[]),this.config.aggregationRows.push(e),this.requestRender()}removeAggregationRow(e){this.config.aggregationRows&&(this.config.aggregationRows=this.config.aggregationRows.filter(n=>n.id!==e),this.requestRender())}}f.PinnedRowsPlugin=R,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(g,f){typeof exports=="object"&&typeof module<"u"?f(exports,require("../../core/plugin/base-plugin"),require("../../core/internal/aggregators")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin","../../core/internal/aggregators"],f):(g=typeof globalThis<"u"?globalThis:g||self,f(g.TbwGridPlugin_pinnedRows={},g.TbwGrid,g.TbwGrid))})(this,(function(g,f,m){"use strict";function A(o){return typeof o=="object"&&o!==null&&"aggFunc"in o}function h(o,t){const n=document.createElement("div");n.className="tbw-pinned-rows",n.setAttribute("role","presentation"),n.setAttribute("aria-live","polite");const r=document.createElement("div");r.className="tbw-pinned-rows-left";const a=document.createElement("div");a.className="tbw-pinned-rows-center";const i=document.createElement("div");if(i.className="tbw-pinned-rows-right",o.showRowCount!==!1){const e=document.createElement("span");e.className="tbw-status-panel tbw-status-panel-row-count",e.textContent=`Total: ${t.totalRows} rows`,r.appendChild(e)}if(o.showFilteredCount&&t.filteredRows!==t.totalRows){const e=document.createElement("span");e.className="tbw-status-panel tbw-status-panel-filtered-count",e.textContent=`Filtered: ${t.filteredRows}`,r.appendChild(e)}if(o.showSelectedCount&&t.selectedRows>0){const e=document.createElement("span");e.className="tbw-status-panel tbw-status-panel-selected-count",e.textContent=`Selected: ${t.selectedRows}`,i.appendChild(e)}if(o.customPanels)for(const e of o.customPanels){const s=B(e,t);switch(e.position){case"left":r.appendChild(s);break;case"center":a.appendChild(s);break;case"right":i.appendChild(s);break}}return n.appendChild(r),n.appendChild(a),n.appendChild(i),n}function w(o){const t=document.createElement("div");return t.className=`tbw-aggregation-rows tbw-aggregation-rows-${o}`,t.setAttribute("role","presentation"),t}function b(o,t,n,r,a=!1){o.innerHTML="";for(const i of t){const e=document.createElement("div");e.className="tbw-aggregation-row",e.setAttribute("role","presentation"),i.id&&e.setAttribute("data-aggregation-id",i.id),i.fullWidth??a?E(e,i,n,r):R(e,i,n,r),o.appendChild(e)}}function E(o,t,n,r){const a=document.createElement("div");a.className="tbw-aggregation-cell tbw-aggregation-cell-full",a.style.gridColumn="1 / -1";const i=typeof t.label=="function"?t.label(r,n):t.label;if(i){const s=document.createElement("span");s.className="tbw-aggregation-label",s.textContent=i,a.appendChild(s)}const e=y(t,n,r);e&&a.appendChild(e),o.appendChild(a)}function R(o,t,n,r){for(const a of n){const i=document.createElement("div");i.className="tbw-aggregation-cell",i.setAttribute("data-field",a.field);const{value:e,formatter:s}=C(t,a,r);e!=null?i.textContent=s?s(e,a.field,a):String(e):i.textContent="",o.appendChild(i)}}function C(o,t,n){let r,a;const i=o.aggregators?.[t.field];if(i)if(A(i)){const e=m.getAggregator(i.aggFunc);e&&(r=e(n,t.field,t)),a=i.formatter}else{const e=m.getAggregator(i);e&&(r=e(n,t.field,t))}else if(o.cells&&Object.prototype.hasOwnProperty.call(o.cells,t.field)){const e=o.cells[t.field];typeof e=="function"?r=e(n,t.field,t):r=e}return{value:r,formatter:a}}function y(o,t,n){const r=o.aggregators&&Object.keys(o.aggregators).length>0,a=o.cells&&Object.keys(o.cells).length>0;if(!r&&!a)return null;const i=document.createElement("span");i.className="tbw-aggregation-aggregates";for(const e of t){const{value:s,formatter:d}=C(o,e,n);if(s!=null){const c=document.createElement("span");c.className="tbw-aggregation-aggregate",c.setAttribute("data-field",e.field);const p=e.header??e.field,u=d?d(s,e.field,e):String(s);c.textContent=`${p}: ${u}`,i.appendChild(c)}}return i.children.length>0?i:null}function B(o,t){const n=document.createElement("div");n.className="tbw-status-panel tbw-status-panel-custom",n.id=`status-panel-${o.id}`;const r=o.render(t);return typeof r=="string"?n.innerHTML=r:n.appendChild(r),n}function v(o,t,n,r,a){return{totalRows:o.length,filteredRows:a?.cachedResult?.length??o.length,selectedRows:r?.selected?.size??0,columns:t,rows:o,grid:n}}const S="@layer tbw-plugins{.tbw-footer{flex-shrink:0;z-index:var(--tbw-z-layer-pinned-rows, 20);background:var(--tbw-color-panel-bg)}.tbw-pinned-rows{display:flex;align-items:center;justify-content:space-between;padding:var(--tbw-button-padding, var(--tbw-spacing-md, .5rem) var(--tbw-spacing-lg, .75rem));background:var(--tbw-pinned-rows-bg, var(--tbw-color-panel-bg));border-top:1px solid var(--tbw-pinned-rows-border, var(--tbw-color-border));font-size:var(--tbw-font-size-xs, .75rem);color:var(--tbw-pinned-rows-color, var(--tbw-color-fg-muted));min-height:32px;box-sizing:border-box;min-width:fit-content}.tbw-pinned-rows-left,.tbw-pinned-rows-center,.tbw-pinned-rows-right{display:flex;align-items:center;gap:var(--tbw-spacing-xl, 1rem)}.tbw-pinned-rows-left{justify-content:flex-start}.tbw-pinned-rows-center{justify-content:center;flex:1}.tbw-pinned-rows-right{justify-content:flex-end}.tbw-status-panel{white-space:nowrap}.tbw-aggregation-rows{min-width:fit-content;background:var(--tbw-aggregation-bg, var(--tbw-color-header-bg))}.tbw-aggregation-rows-top{border-bottom:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-rows-bottom{border-top:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-row{display:grid;grid-template-columns:var(--tbw-column-template);font-size:var(--tbw-aggregation-font-size, .8em);font-weight:var(--tbw-aggregation-font-weight, 600)}.tbw-aggregation-cell{padding:var(--tbw-cell-padding, .125rem .5rem);min-height:var(--tbw-row-height, 1.75rem);display:block;align-items:center;align-content:center;border-right:1px solid var(--tbw-color-border-cell);overflow:hidden;text-overflow:ellipsis;white-space:var(--tbw-cell-white-space, nowrap)}.tbw-aggregation-cell:last-child{border-right:0}.tbw-aggregation-cell-full{grid-column:1 / -1;border-right:0;display:flex;align-items:center;gap:var(--tbw-spacing-lg, .75rem)}.tbw-aggregation-label{white-space:nowrap}.tbw-aggregation-aggregates{display:flex;align-items:center;gap:var(--tbw-spacing-lg, .75rem);font-weight:400;opacity:.85}.tbw-aggregation-aggregate{white-space:nowrap}}";class x extends f.BaseGridPlugin{name="pinnedRows";styles=S;get defaultConfig(){return{position:"bottom",showRowCount:!0,showSelectedCount:!0,showFilteredCount:!0}}infoBarElement=null;topAggregationContainer=null;bottomAggregationContainer=null;footerWrapper=null;detach(){this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null),this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null)}afterRender(){const t=this.gridElement;if(!t)return;const n=t.querySelector(".tbw-scroll-area")??t.querySelector(".tbw-grid-content")??t.children[0];if(!n)return;this.footerWrapper&&!n.contains(this.footerWrapper)&&(this.footerWrapper=null,this.bottomAggregationContainer=null,this.infoBarElement=null),this.topAggregationContainer&&!n.contains(this.topAggregationContainer)&&(this.topAggregationContainer=null),this.infoBarElement&&!n.contains(this.infoBarElement)&&(this.infoBarElement=null);const r=this.getSelectionState(),a=this.getFilterState(),i=v(this.sourceRows,this.columns,this.grid,r,a),e=this.config.aggregationRows||[],s=e.filter(l=>l.position==="top"),d=e.filter(l=>l.position!=="top");if(s.length>0){if(!this.topAggregationContainer){this.topAggregationContainer=w("top");const l=t.querySelector(".header");l&&l.nextSibling?n.insertBefore(this.topAggregationContainer,l.nextSibling):n.appendChild(this.topAggregationContainer)}b(this.topAggregationContainer,s,this.visibleColumns,this.sourceRows,this.config.fullWidth)}else this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null);const c=this.config.showRowCount!==!1||this.config.showSelectedCount&&i.selectedRows>0||this.config.showFilteredCount&&i.filteredRows!==i.totalRows||this.config.customPanels&&this.config.customPanels.length>0,p=c&&this.config.position!=="top",u=d.length>0||p;if(c&&this.config.position==="top")if(!this.infoBarElement)this.infoBarElement=h(this.config,i),n.insertBefore(this.infoBarElement,n.firstChild);else{const l=h(this.config,i);this.infoBarElement.replaceWith(l),this.infoBarElement=l}else this.config.position==="top"&&this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null);u?(this.footerWrapper||(this.footerWrapper=document.createElement("div"),this.footerWrapper.className="tbw-footer",n.appendChild(this.footerWrapper)),this.footerWrapper.innerHTML="",d.length>0&&(this.bottomAggregationContainer||(this.bottomAggregationContainer=w("bottom")),this.footerWrapper.appendChild(this.bottomAggregationContainer),b(this.bottomAggregationContainer,d,this.visibleColumns,this.sourceRows,this.config.fullWidth)),p&&(this.infoBarElement=h(this.config,i),this.footerWrapper.appendChild(this.infoBarElement))):this.cleanupFooter()}cleanup(){this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null),this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null)}cleanupFooter(){this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.infoBarElement&&this.config.position!=="top"&&(this.infoBarElement.remove(),this.infoBarElement=null)}getSelectionState(){try{return this.grid?.getPluginState?.("selection")??null}catch{return null}}getFilterState(){try{return this.grid?.getPluginState?.("filtering")??null}catch{return null}}refresh(){this.requestRender()}getContext(){const t=this.getSelectionState(),n=this.getFilterState();return v(this.rows,this.columns,this.grid,t,n)}addPanel(t){this.config.customPanels||(this.config.customPanels=[]),this.config.customPanels.push(t),this.requestRender()}removePanel(t){this.config.customPanels&&(this.config.customPanels=this.config.customPanels.filter(n=>n.id!==t),this.requestRender())}addAggregationRow(t){this.config.aggregationRows||(this.config.aggregationRows=[]),this.config.aggregationRows.push(t),this.requestRender()}removeAggregationRow(t){this.config.aggregationRows&&(this.config.aggregationRows=this.config.aggregationRows.filter(n=>n.id!==t),this.requestRender())}}g.PinnedRowsPlugin=x,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=pinned-rows.umd.js.map
|