@toolbox-web/grid 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all.d.ts +3518 -0
- package/all.js +3762 -0
- package/all.js.map +1 -0
- package/index.d.ts +2367 -0
- package/index.js +3105 -0
- package/index.js.map +1 -0
- package/lib/plugins/clipboard/index.js +365 -0
- package/lib/plugins/clipboard/index.js.map +1 -0
- package/lib/plugins/column-virtualization/index.js +255 -0
- package/lib/plugins/column-virtualization/index.js.map +1 -0
- package/lib/plugins/context-menu/index.js +341 -0
- package/lib/plugins/context-menu/index.js.map +1 -0
- package/lib/plugins/export/index.js +305 -0
- package/lib/plugins/export/index.js.map +1 -0
- package/lib/plugins/filtering/index.js +759 -0
- package/lib/plugins/filtering/index.js.map +1 -0
- package/lib/plugins/grouping-columns/index.js +283 -0
- package/lib/plugins/grouping-columns/index.js.map +1 -0
- package/lib/plugins/grouping-rows/index.js +494 -0
- package/lib/plugins/grouping-rows/index.js.map +1 -0
- package/lib/plugins/master-detail/index.js +303 -0
- package/lib/plugins/master-detail/index.js.map +1 -0
- package/lib/plugins/multi-sort/index.js +270 -0
- package/lib/plugins/multi-sort/index.js.map +1 -0
- package/lib/plugins/pinned-columns/index.js +221 -0
- package/lib/plugins/pinned-columns/index.js.map +1 -0
- package/lib/plugins/pinned-rows/index.js +459 -0
- package/lib/plugins/pinned-rows/index.js.map +1 -0
- package/lib/plugins/pivot/index.js +326 -0
- package/lib/plugins/pivot/index.js.map +1 -0
- package/lib/plugins/reorder/index.js +260 -0
- package/lib/plugins/reorder/index.js.map +1 -0
- package/lib/plugins/selection/index.js +426 -0
- package/lib/plugins/selection/index.js.map +1 -0
- package/lib/plugins/server-side/index.js +241 -0
- package/lib/plugins/server-side/index.js.map +1 -0
- package/lib/plugins/tree/index.js +383 -0
- package/lib/plugins/tree/index.js.map +1 -0
- package/lib/plugins/undo-redo/index.js +289 -0
- package/lib/plugins/undo-redo/index.js.map +1 -0
- package/lib/plugins/visibility/index.js +430 -0
- package/lib/plugins/visibility/index.js.map +1 -0
- package/package.json +53 -0
- package/themes/dg-theme-contrast.css +43 -0
- package/themes/dg-theme-large.css +54 -0
- package/themes/dg-theme-standard.css +19 -0
- package/themes/dg-theme-vibrant.css +16 -0
- package/umd/grid.all.umd.js +660 -0
- package/umd/grid.all.umd.js.map +1 -0
- package/umd/grid.umd.js +105 -0
- package/umd/grid.umd.js.map +1 -0
- package/umd/plugins/clipboard.umd.js +9 -0
- package/umd/plugins/clipboard.umd.js.map +1 -0
- package/umd/plugins/column-virtualization.umd.js +2 -0
- package/umd/plugins/column-virtualization.umd.js.map +1 -0
- package/umd/plugins/context-menu.umd.js +53 -0
- package/umd/plugins/context-menu.umd.js.map +1 -0
- package/umd/plugins/export.umd.js +14 -0
- package/umd/plugins/export.umd.js.map +1 -0
- package/umd/plugins/filtering.umd.js +175 -0
- package/umd/plugins/filtering.umd.js.map +1 -0
- package/umd/plugins/grouping-columns.umd.js +29 -0
- package/umd/plugins/grouping-columns.umd.js.map +1 -0
- package/umd/plugins/grouping-rows.umd.js +40 -0
- package/umd/plugins/grouping-rows.umd.js.map +1 -0
- package/umd/plugins/master-detail.umd.js +27 -0
- package/umd/plugins/master-detail.umd.js.map +1 -0
- package/umd/plugins/multi-sort.umd.js +26 -0
- package/umd/plugins/multi-sort.umd.js.map +1 -0
- package/umd/plugins/pinned-columns.umd.js +2 -0
- package/umd/plugins/pinned-columns.umd.js.map +1 -0
- package/umd/plugins/pinned-rows.umd.js +73 -0
- package/umd/plugins/pinned-rows.umd.js.map +1 -0
- package/umd/plugins/pivot.umd.js +8 -0
- package/umd/plugins/pivot.umd.js.map +1 -0
- package/umd/plugins/reorder.umd.js +31 -0
- package/umd/plugins/reorder.umd.js.map +1 -0
- package/umd/plugins/selection.umd.js +34 -0
- package/umd/plugins/selection.umd.js.map +1 -0
- package/umd/plugins/server-side.umd.js +2 -0
- package/umd/plugins/server-side.umd.js.map +1 -0
- package/umd/plugins/tree.umd.js +11 -0
- package/umd/plugins/tree.umd.js.map +1 -0
- package/umd/plugins/undo-redo.umd.js +2 -0
- package/umd/plugins/undo-redo.umd.js.map +1 -0
- package/umd/plugins/visibility.umd.js +94 -0
- package/umd/plugins/visibility.umd.js.map +1 -0
|
@@ -0,0 +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 { BaseGridPlugin, type GridElement } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ColumnState } from '../../core/types';\nimport { computeVirtualWindow, shouldBypassVirtualization } from '../../core/internal/virtualization';\nimport { computeFilterCacheKey, filterRows, getUniqueValues } from './filter-model';\nimport type { FilterChangeDetail, FilterConfig, FilterModel, FilterPanelParams } from './types';\n\n/** Global styles for filter panel (rendered in document.body) */\nconst filterPanelStyles = `\n.tbw-filter-panel {\n position: fixed;\n background: var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));\n color: var(--tbw-filter-panel-fg, var(--tbw-color-fg, light-dark(#222222, #eeeeee)));\n border: 1px solid var(--tbw-filter-panel-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n border-radius: var(--tbw-filter-panel-radius, var(--tbw-border-radius, 4px));\n box-shadow: 0 4px 16px var(--tbw-filter-panel-shadow, var(--tbw-color-shadow, light-dark(rgba(0,0,0,0.1), rgba(0,0,0,0.3))));\n padding: 12px;\n z-index: 10000;\n min-width: 200px;\n max-width: 280px;\n max-height: 350px;\n display: flex;\n flex-direction: column;\n font-family: var(--tbw-font-family, system-ui, sans-serif);\n font-size: var(--tbw-font-size, 13px);\n}\n\n.tbw-filter-search {\n margin-bottom: 8px;\n}\n\n.tbw-filter-search-input {\n width: 100%;\n padding: 6px 10px;\n background: var(--tbw-filter-input-bg, var(--tbw-color-bg, transparent));\n color: inherit;\n border: 1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n border-radius: var(--tbw-filter-input-radius, 4px);\n font-size: inherit;\n box-sizing: border-box;\n}\n\n.tbw-filter-search-input:focus {\n outline: none;\n border-color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n box-shadow: 0 0 0 2px rgba(from var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6)) r g b / 15%);\n}\n\n.tbw-filter-actions {\n display: flex;\n padding: 4px 2px;\n margin-bottom: 8px;\n border-bottom: 1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n}\n\n.tbw-filter-action-btn {\n background: transparent;\n border: none;\n color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n cursor: pointer;\n font-size: 12px;\n padding: 2px 0;\n}\n\n.tbw-filter-action-btn:hover {\n text-decoration: underline;\n}\n\n.tbw-filter-values {\n flex: 1;\n overflow-y: auto;\n margin-bottom: 8px;\n max-height: 180px;\n position: relative;\n}\n\n.tbw-filter-values-spacer {\n width: 1px;\n}\n\n.tbw-filter-values-content {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n}\n\n.tbw-filter-value-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 2px;\n cursor: pointer;\n border-radius: 3px;\n}\n\n.tbw-filter-value-item:hover {\n background: var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)));\n}\n\n.tbw-filter-checkbox {\n margin: 0;\n cursor: pointer;\n accent-color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n}\n\n.tbw-filter-no-match {\n color: var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));\n padding: 8px 0;\n text-align: center;\n font-style: italic;\n}\n\n.tbw-filter-buttons {\n display: flex;\n gap: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n}\n\n.tbw-filter-apply-btn {\n flex: 1;\n padding: 6px 12px;\n background: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n color: var(--tbw-filter-accent-fg, var(--tbw-color-accent-fg, light-dark(#ffffff, #000000)));\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n}\n\n.tbw-filter-apply-btn:hover {\n filter: brightness(0.9);\n}\n\n.tbw-filter-clear-btn {\n flex: 1;\n padding: 6px 12px;\n background: transparent;\n color: var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));\n border: 1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n}\n\n.tbw-filter-clear-btn:hover {\n background: var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)));\n}\n`;\n\n/**\n * Filtering Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new FilteringPlugin({ enabled: true, debounceMs: 300 })\n * ```\n */\nexport class FilteringPlugin extends BaseGridPlugin<FilterConfig> {\n readonly name = 'filtering';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<FilterConfig> {\n return {\n enabled: true,\n debounceMs: 300,\n caseSensitive: false,\n trimInput: true,\n useWorker: true,\n };\n }\n\n // ===== 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 searchText: Map<string, string> = new Map();\n private excludedValues: Map<string, Set<unknown>> = new Map();\n private documentClickHandler: ((e: MouseEvent) => void) | null = null;\n private globalStylesInjected = false;\n\n // Virtualization constants for filter value list\n private static readonly LIST_ITEM_HEIGHT = 28;\n private static readonly LIST_OVERSCAN = 3;\n private static readonly LIST_BYPASS_THRESHOLD = 50; // Don't virtualize if < 50 items\n\n // ===== Lifecycle =====\n\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.injectGlobalStyles();\n }\n\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 this.removeDocumentClickHandler();\n }\n\n // ===== Hooks =====\n\n override processRows(rows: readonly unknown[]): unknown[] {\n const filterList = [...this.filters.values()];\n if (!filterList.length) return [...rows];\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 override afterRender(): void {\n if (!this.config.enabled) return;\n\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n // Find all header cells (using part attribute, not class)\n const headerCells = shadowRoot.querySelectorAll('[part~=\"header-cell\"]');\n headerCells.forEach((cell) => {\n const colIndex = cell.getAttribute('data-col');\n if (colIndex === null) return;\n\n const col = this.columns[parseInt(colIndex, 10)] as ColumnConfig;\n if (!col || col.filterable === false) return;\n\n // Skip if button already exists\n if (cell.querySelector('.tbw-filter-btn')) return;\n\n const field = col.field;\n if (!field) return;\n\n // Create filter button\n const filterBtn = document.createElement('button');\n filterBtn.className = 'tbw-filter-btn';\n filterBtn.setAttribute('aria-label', `Filter ${col.header ?? field}`);\n filterBtn.innerHTML = `<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\"><path fill=\"currentColor\" d=\"M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z\"/></svg>`;\n\n // Mark button as active if filter exists\n if (this.filters.has(field)) {\n filterBtn.classList.add('active');\n cell.classList.add('filtered');\n }\n\n filterBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.toggleFilterPanel(field, col, filterBtn);\n });\n\n // Append to header cell\n cell.appendChild(filterBtn);\n });\n }\n\n // ===== 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 } else {\n this.filters.set(field, { ...filter, field });\n }\n // Invalidate cache\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0, // Will be accurate after processRows\n });\n this.requestRender();\n }\n\n /**\n * Get the current filter for a field.\n */\n getFilter(field: string): FilterModel | undefined {\n return this.filters.get(field);\n }\n\n /**\n * Get all active filters.\n */\n getFilters(): FilterModel[] {\n return [...this.filters.values()];\n }\n\n /**\n * Alias for getFilters() to match functional API naming.\n */\n getFilterModel(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Set filters from an array (replaces all existing filters).\n */\n setFilterModel(filters: FilterModel[]): void {\n this.filters.clear();\n for (const filter of filters) {\n this.filters.set(filter.field, filter);\n }\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n /**\n * Clear all filters.\n */\n clearAllFilters(): void {\n this.filters.clear();\n this.excludedValues.clear();\n this.searchText.clear();\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [],\n filteredRowCount: this.rows.length,\n });\n this.requestRender();\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.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n /**\n * 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\n // ===== Private Methods =====\n\n /**\n * Inject global styles for filter panel (rendered in document.body)\n */\n private injectGlobalStyles(): void {\n if (this.globalStylesInjected) return;\n if (document.getElementById('tbw-filter-panel-styles')) {\n this.globalStylesInjected = true;\n return;\n }\n 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 this.panelElement = panel;\n this.openPanelField = field;\n\n // Get unique values for this field (from source rows, not filtered)\n const uniqueValues = getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n\n // Get current excluded values or initialize empty\n let excludedSet = this.excludedValues.get(field);\n if (!excludedSet) {\n excludedSet = new Set();\n this.excludedValues.set(field, excludedSet);\n }\n\n // Get current search text\n const currentSearchText = this.searchText.get(field) ?? '';\n\n // Create panel params for custom renderer\n const params: FilterPanelParams = {\n field,\n column,\n uniqueValues,\n excludedValues: excludedSet,\n searchText: currentSearchText,\n applySetFilter: (excluded: unknown[]) => {\n this.applySetFilter(field, excluded);\n this.closeFilterPanel();\n },\n applyTextFilter: (operator, value, valueTo) => {\n this.applyTextFilter(field, operator, value, valueTo);\n this.closeFilterPanel();\n },\n clearFilter: () => {\n this.clearFieldFilter(field);\n this.closeFilterPanel();\n },\n closePanel: () => this.closeFilterPanel(),\n };\n\n // Use custom renderer or default\n // Custom renderer can return undefined to fall back to default panel for specific columns\n let usedCustomRenderer = false;\n if (this.config.filterPanelRenderer) {\n const result = this.config.filterPanelRenderer(panel, params);\n // If renderer added content to panel, it handled rendering\n usedCustomRenderer = panel.children.length > 0;\n }\n if (!usedCustomRenderer) {\n this.renderDefaultFilterPanel(panel, params, uniqueValues, excludedSet);\n }\n\n // Position and append to body\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n\n // Add global click handler to close on outside click\n const handler = (e: MouseEvent) => {\n if (!panel.contains(e.target as Node) && e.target !== buttonEl) {\n this.closeFilterPanel();\n }\n };\n this.documentClickHandler = handler;\n // Defer to next tick to avoid immediate close\n setTimeout(() => {\n document.addEventListener('click', handler);\n }, 0);\n }\n\n /**\n * Close the filter panel\n */\n private closeFilterPanel(): void {\n if (this.panelElement) {\n this.panelElement.remove();\n this.panelElement = null;\n }\n this.openPanelField = null;\n this.removeDocumentClickHandler();\n }\n\n /**\n * Remove the document click handler\n */\n private removeDocumentClickHandler(): void {\n if (this.documentClickHandler) {\n document.removeEventListener('click', this.documentClickHandler);\n this.documentClickHandler = null;\n }\n }\n\n /**\n * Position the panel below the button\n */\n private positionPanel(panel: HTMLElement, buttonEl: HTMLElement): void {\n const rect = buttonEl.getBoundingClientRect();\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 right edge\n requestAnimationFrame(() => {\n const panelRect = panel.getBoundingClientRect();\n if (panelRect.right > window.innerWidth - 8) {\n panel.style.left = `${window.innerWidth - panelRect.width - 8}px`;\n }\n // Adjust if overflows bottom\n if (panelRect.bottom > window.innerHeight - 8) {\n panel.style.top = `${rect.top - panelRect.height - 4}px`;\n }\n });\n }\n\n /**\n * Render the default filter panel content\n */\n private renderDefaultFilterPanel(\n panel: HTMLElement,\n params: FilterPanelParams,\n uniqueValues: unknown[],\n excludedValues: Set<unknown>\n ): void {\n const { field } = params;\n\n // Search input\n const searchContainer = document.createElement('div');\n searchContainer.className = 'tbw-filter-search';\n\n const searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.placeholder = 'Search...';\n searchInput.className = 'tbw-filter-search-input';\n searchInput.value = this.searchText.get(field) ?? '';\n searchContainer.appendChild(searchInput);\n panel.appendChild(searchContainer);\n\n // Select All tristate checkbox\n const actionsRow = document.createElement('div');\n actionsRow.className = 'tbw-filter-actions';\n\n const selectAllLabel = document.createElement('label');\n selectAllLabel.className = 'tbw-filter-value-item';\n selectAllLabel.style.padding = '0';\n selectAllLabel.style.margin = '0';\n\n const selectAllCheckbox = document.createElement('input');\n selectAllCheckbox.type = 'checkbox';\n selectAllCheckbox.className = 'tbw-filter-checkbox';\n\n const selectAllText = document.createElement('span');\n selectAllText.textContent = 'Select All';\n\n selectAllLabel.appendChild(selectAllCheckbox);\n selectAllLabel.appendChild(selectAllText);\n actionsRow.appendChild(selectAllLabel);\n\n // Update tristate checkbox based on checkState\n const updateSelectAllState = () => {\n const values = [...checkState.values()];\n const allChecked = values.every((v) => v);\n const noneChecked = values.every((v) => !v);\n\n selectAllCheckbox.checked = allChecked;\n selectAllCheckbox.indeterminate = !allChecked && !noneChecked;\n };\n\n // Toggle all on click\n selectAllCheckbox.addEventListener('change', () => {\n const newState = selectAllCheckbox.checked;\n for (const key of checkState.keys()) {\n checkState.set(key, newState);\n }\n updateSelectAllState();\n renderVisibleItems();\n });\n\n panel.appendChild(actionsRow);\n\n // Values container with virtualization support\n const valuesContainer = document.createElement('div');\n valuesContainer.className = 'tbw-filter-values';\n\n // Spacer for virtual height\n const spacer = document.createElement('div');\n spacer.className = 'tbw-filter-values-spacer';\n valuesContainer.appendChild(spacer);\n\n // Content container positioned absolutely\n const contentContainer = document.createElement('div');\n contentContainer.className = 'tbw-filter-values-content';\n valuesContainer.appendChild(contentContainer);\n\n // Track current check state for values (persists across virtualizations)\n const checkState = new Map<string, boolean>();\n uniqueValues.forEach((value) => {\n const key = value == null ? '__null__' : String(value);\n checkState.set(key, !excludedValues.has(value));\n });\n\n // Initialize select all state\n updateSelectAllState();\n\n // Filtered values cache\n let filteredValues: unknown[] = [];\n\n // Create a single checkbox item element\n const createItem = (value: unknown, index: number): HTMLElement => {\n const strValue = value == null ? '(Blank)' : String(value);\n const key = value == null ? '__null__' : String(value);\n\n const item = document.createElement('label');\n item.className = 'tbw-filter-value-item';\n item.style.position = 'absolute';\n item.style.top = `${index * FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n item.style.left = '0';\n item.style.right = '0';\n item.style.height = `${FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n item.style.boxSizing = 'border-box';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'tbw-filter-checkbox';\n checkbox.checked = checkState.get(key) ?? true;\n checkbox.dataset.value = key;\n\n // Sync check state on change and update tristate checkbox\n checkbox.addEventListener('change', () => {\n checkState.set(key, checkbox.checked);\n updateSelectAllState();\n });\n\n const label = document.createElement('span');\n label.textContent = strValue;\n\n item.appendChild(checkbox);\n item.appendChild(label);\n return item;\n };\n\n // Render visible items using virtualization\n const renderVisibleItems = () => {\n const totalItems = filteredValues.length;\n const viewportHeight = valuesContainer.clientHeight;\n const scrollTop = valuesContainer.scrollTop;\n\n // Set total height for scrollbar\n spacer.style.height = `${totalItems * FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n\n // Bypass virtualization for small lists\n if (shouldBypassVirtualization(totalItems, FilteringPlugin.LIST_BYPASS_THRESHOLD / 3)) {\n contentContainer.innerHTML = '';\n contentContainer.style.transform = 'translateY(0px)';\n filteredValues.forEach((value, idx) => {\n contentContainer.appendChild(createItem(value, idx));\n });\n return;\n }\n\n // Use computeVirtualWindow for real-scroll virtualization\n const window = computeVirtualWindow({\n totalRows: totalItems,\n viewportHeight,\n scrollTop,\n rowHeight: FilteringPlugin.LIST_ITEM_HEIGHT,\n overscan: FilteringPlugin.LIST_OVERSCAN,\n });\n\n // Position content container\n contentContainer.style.transform = `translateY(${window.offsetY}px)`;\n\n // Clear and render visible items\n contentContainer.innerHTML = '';\n for (let i = window.start; i < window.end; i++) {\n contentContainer.appendChild(createItem(filteredValues[i], i - window.start));\n }\n };\n\n // Filter and re-render values\n const renderValues = (filterText: string) => {\n const lowerFilter = filterText.toLowerCase();\n\n // Filter the unique values\n filteredValues = uniqueValues.filter((value) => {\n const strValue = value == null ? '(Blank)' : String(value);\n return !filterText || strValue.toLowerCase().includes(lowerFilter);\n });\n\n if (filteredValues.length === 0) {\n spacer.style.height = '0px';\n contentContainer.innerHTML = '';\n const noMatch = document.createElement('div');\n noMatch.className = 'tbw-filter-no-match';\n noMatch.textContent = 'No matching values';\n contentContainer.appendChild(noMatch);\n return;\n }\n\n renderVisibleItems();\n };\n\n // Scroll handler for virtualization\n valuesContainer.addEventListener(\n 'scroll',\n () => {\n if (filteredValues.length > 0) {\n renderVisibleItems();\n }\n },\n { passive: true }\n );\n\n renderValues(searchInput.value);\n panel.appendChild(valuesContainer);\n\n // Debounced search\n let debounceTimer: ReturnType<typeof setTimeout>;\n searchInput.addEventListener('input', () => {\n clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n this.searchText.set(field, searchInput.value);\n renderValues(searchInput.value);\n }, this.config.debounceMs ?? 150);\n });\n\n // Apply/Clear buttons\n const buttonRow = document.createElement('div');\n buttonRow.className = 'tbw-filter-buttons';\n\n const applyBtn = document.createElement('button');\n applyBtn.className = 'tbw-filter-apply-btn';\n applyBtn.textContent = 'Apply';\n applyBtn.addEventListener('click', () => {\n // Read from checkState map (works with virtualization)\n const excluded: unknown[] = [];\n for (const [key, isChecked] of checkState) {\n if (!isChecked) {\n if (key === '__null__') {\n excluded.push(null);\n } else {\n // Try to match original value type\n const original = uniqueValues.find((v) => String(v) === key);\n excluded.push(original !== undefined ? original : key);\n }\n }\n }\n params.applySetFilter(excluded);\n });\n buttonRow.appendChild(applyBtn);\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'tbw-filter-clear-btn';\n clearBtn.textContent = 'Clear Filter';\n clearBtn.addEventListener('click', () => {\n params.clearFilter();\n });\n buttonRow.appendChild(clearBtn);\n\n panel.appendChild(buttonRow);\n }\n\n /**\n * Apply a set filter (exclude values)\n */\n private applySetFilter(field: string, excluded: unknown[]): void {\n // Store excluded values\n this.excludedValues.set(field, new Set(excluded));\n\n if (excluded.length === 0) {\n // No exclusions = no filter\n this.filters.delete(field);\n } else {\n // Create \"notIn\" filter\n this.filters.set(field, {\n field,\n type: 'set',\n operator: 'notIn',\n value: excluded,\n });\n }\n\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n /**\n * Apply a text filter\n */\n private applyTextFilter(field: string, operator: FilterModel['operator'], value: string, valueTo?: string): void {\n this.filters.set(field, {\n field,\n type: 'text',\n operator,\n value,\n valueTo,\n });\n\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n // ===== Column State Hooks =====\n\n /**\n * Return filter state for a column if it has an active filter.\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 */\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\n // ===== Styles =====\n\n override readonly styles = `\n .header-cell.filtered::before {\n content: '';\n position: absolute;\n top: 4px;\n right: 4px;\n width: 6px;\n height: 6px;\n background: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n border-radius: 50%;\n }\n .tbw-filter-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 2px;\n margin-left: 4px;\n opacity: 0.4;\n transition: opacity 0.15s;\n color: inherit;\n vertical-align: middle;\n }\n .tbw-filter-btn:hover,\n .tbw-filter-btn.active {\n opacity: 1;\n }\n .tbw-filter-btn.active {\n color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n }\n `;\n}\n"],"names":["matchesFilter","row","filter","caseSensitive","rawValue","stringValue","compareValue","filterValue","filterRows","rows","filters","f","computeFilterCacheKey","getUniqueValues","field","values","value","a","b","filterPanelStyles","FilteringPlugin","BaseGridPlugin","grid","filterList","newCacheKey","result","shadowRoot","cell","colIndex","col","filterBtn","e","style","column","buttonEl","panel","uniqueValues","excludedSet","currentSearchText","params","excluded","operator","valueTo","usedCustomRenderer","handler","rect","panelRect","excludedValues","searchContainer","searchInput","actionsRow","selectAllLabel","selectAllCheckbox","selectAllText","updateSelectAllState","checkState","allChecked","v","noneChecked","newState","key","renderVisibleItems","valuesContainer","spacer","contentContainer","filteredValues","createItem","index","strValue","item","checkbox","label","totalItems","viewportHeight","scrollTop","shouldBypassVirtualization","idx","window","computeVirtualWindow","i","renderValues","filterText","lowerFilter","noMatch","debounceTimer","buttonRow","applyBtn","isChecked","original","clearBtn","filterModel","state"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
(function(d,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],c):(d=typeof globalThis<"u"?globalThis:d||self,c(d.TbwGridPlugin_groupingColumns={},d.TbwGrid))})(this,(function(d,c){"use strict";function p(l){if(!l.length)return[];const e=new Map,t=[],r=(i,n)=>{if(!n.length)return;const s=t[t.length-1];if(s&&s.implicit&&s.firstIndex+s.columns.length===i){s.columns.push(...n);return}t.push({id:"__implicit__"+i,label:void 0,columns:n,firstIndex:i,implicit:!0})};let u=[],o=0;return l.forEach((i,n)=>{const s=i.group;if(!s){u.length===0&&(o=n),u.push(i);return}u.length&&(r(o,u.slice()),u=[]);const g=typeof s=="string"?s:s.id;let a=e.get(g);a||(a={id:g,label:typeof s=="string"?void 0:s.label,columns:[],firstIndex:n},e.set(g,a),t.push(a)),a.columns.push(i)}),u.length&&r(o,u),t.length===1&&t[0].implicit&&t[0].columns.length===l.length?[]:t}function f(l,e,t){if(!e.length||!l)return;const r=new Map;for(const o of e)for(const i of o.columns)i?.field&&r.set(i.field,o.id);const u=Array.from(l.querySelectorAll(".cell[data-field]"));u.forEach(o=>{const i=o.getAttribute("data-field")||"",n=r.get(i);n&&(o.classList.add("grouped"),o.getAttribute("data-group")||o.setAttribute("data-group",n))});for(const o of e){const i=o.columns[o.columns.length-1],n=u.find(s=>s.getAttribute("data-field")===i.field);n&&n.classList.add("group-end")}}function h(l,e){if(l.length===0)return null;const t=document.createElement("div");t.className="header-group-row",t.setAttribute("role","row");for(const r of l){const u=r.firstIndex!=null?r.firstIndex:e.findIndex(s=>r.columns.includes(s)),o=String(r.id).startsWith("__implicit__"),i=o?"":r.label||r.id,n=document.createElement("div");n.className="cell header-group-cell",o&&n.classList.add("implicit-group"),n.setAttribute("data-group",String(r.id)),n.style.gridColumn=`${u+1} / span ${r.columns.length}`,n.textContent=i,t.appendChild(n)}return t}function m(l){return l.some(e=>e.group!=null)}class b extends c.BaseGridPlugin{name="groupingColumns";version="1.0.0";get defaultConfig(){return{enabled:!0,showGroupBorders:!0}}groups=[];isActive=!1;detach(){this.groups=[],this.isActive=!1}static detect(e,t){const r=t?.columns;return Array.isArray(r)?m(r):!1}processColumns(e){if(!this.config.enabled)return this.isActive=!1,this.groups=[],[...e];const t=p(e);return t.length===0?(this.isActive=!1,this.groups=[],[...e]):(this.isActive=!0,this.groups=t,[...e])}afterRender(){if(!this.isActive||this.groups.length===0){const i=this.shadowRoot?.querySelector(".header")?.querySelector(".header-group-row");i&&i.remove();return}const e=this.shadowRoot?.querySelector(".header");if(!e)return;const t=e.querySelector(".header-group-row");t&&t.remove();const r=h(this.groups,this.columns);if(r){const o=e.querySelector(".header-row");o?e.insertBefore(r,o):e.appendChild(r)}const u=e.querySelector(".header-row");u&&f(u,this.groups,this.columns)}isGroupingActive(){return this.isActive}getGroups(){return this.groups}getGroupColumns(e){const t=this.groups.find(r=>r.id===e);return t?t.columns:[]}refresh(){this.requestRender()}styles=`
|
|
2
|
+
.header-group-row {
|
|
3
|
+
display: grid;
|
|
4
|
+
grid-auto-flow: column;
|
|
5
|
+
background: var(--tbw-grouping-columns-header-bg, var(--tbw-color-header-bg));
|
|
6
|
+
border-bottom: 1px solid var(--tbw-grouping-columns-border, var(--tbw-color-border));
|
|
7
|
+
}
|
|
8
|
+
.header-group-cell {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
padding: 4px 8px;
|
|
13
|
+
font-weight: 600;
|
|
14
|
+
font-size: 0.9em;
|
|
15
|
+
text-transform: uppercase;
|
|
16
|
+
letter-spacing: 0.5px;
|
|
17
|
+
border-right: 1px solid var(--tbw-grouping-columns-border, var(--tbw-color-border));
|
|
18
|
+
}
|
|
19
|
+
.header-group-cell:last-child {
|
|
20
|
+
border-right: none;
|
|
21
|
+
}
|
|
22
|
+
.header-row .cell.grouped {
|
|
23
|
+
border-top: none;
|
|
24
|
+
}
|
|
25
|
+
.header-row .cell.group-end {
|
|
26
|
+
border-right: 2px solid var(--tbw-grouping-columns-separator, var(--tbw-color-border-strong));
|
|
27
|
+
}
|
|
28
|
+
`}d.GroupingColumnsPlugin=b,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
29
|
+
//# sourceMappingURL=grouping-columns.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grouping-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/grouping-columns/grouping-columns.ts","../../../../../libs/grid/src/lib/plugins/grouping-columns/GroupingColumnsPlugin.ts"],"sourcesContent":["/**\n * Column Groups Core Logic\n *\n * Pure functions for computing and managing column header groups.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnGroup, ColumnGroupInternal } from './types';\n\n/**\n * Compute column groups from column configuration.\n * Handles explicit groups (via column.group) and creates implicit groups for ungrouped columns.\n *\n * @param columns - Array of column configurations\n * @returns Array of column groups, or empty if no meaningful groups\n */\nexport function computeColumnGroups<T>(columns: ColumnConfig<T>[]): ColumnGroup<T>[] {\n if (!columns.length) return [];\n\n const explicitMap = new Map<string, ColumnGroupInternal<T>>();\n const groupsOrdered: ColumnGroupInternal<T>[] = [];\n\n // Helper to push unnamed implicit group for a run of ungrouped columns\n const pushImplicit = (startIdx: number, cols: ColumnConfig<T>[]) => {\n if (!cols.length) return;\n // Merge with previous implicit group if adjacent to reduce noise\n const prev = groupsOrdered[groupsOrdered.length - 1];\n if (prev && prev.implicit && prev.firstIndex + prev.columns.length === startIdx) {\n prev.columns.push(...cols);\n return;\n }\n groupsOrdered.push({\n id: '__implicit__' + startIdx,\n label: undefined,\n columns: cols,\n firstIndex: startIdx,\n implicit: true,\n });\n };\n\n let run: ColumnConfig<T>[] = [];\n let runStart = 0;\n\n columns.forEach((col, idx) => {\n const g: any = (col as any).group;\n if (!g) {\n if (run.length === 0) runStart = idx;\n run.push(col);\n return;\n }\n // Close any pending implicit run\n if (run.length) {\n pushImplicit(runStart, run.slice());\n run = [];\n }\n const id = typeof g === 'string' ? g : g.id;\n let group = explicitMap.get(id);\n if (!group) {\n group = {\n id,\n label: typeof g === 'string' ? undefined : g.label,\n columns: [],\n firstIndex: idx,\n };\n explicitMap.set(id, group);\n groupsOrdered.push(group);\n }\n group.columns.push(col);\n });\n\n // Trailing implicit run\n if (run.length) pushImplicit(runStart, run);\n\n // If we only have a single implicit group covering all columns, treat as no groups\n if (\n groupsOrdered.length === 1 &&\n groupsOrdered[0].implicit &&\n groupsOrdered[0].columns.length === columns.length\n ) {\n return [];\n }\n\n return groupsOrdered as ColumnGroup<T>[];\n}\n\n/**\n * Apply CSS classes to header cells based on their group membership.\n *\n * @param headerRowEl - The header row element\n * @param groups - The computed column groups\n * @param columns - The column configurations\n */\nexport function applyGroupedHeaderCellClasses(\n headerRowEl: HTMLElement | null,\n groups: ColumnGroup[],\n columns: ColumnConfig<any>[]\n): void {\n if (!groups.length || !headerRowEl) return;\n\n const fieldToGroup = new Map<string, string>();\n for (const g of groups) {\n for (const c of g.columns) {\n if ((c as any)?.field) {\n fieldToGroup.set((c as any).field, g.id);\n }\n }\n }\n\n const headerCells = Array.from(headerRowEl.querySelectorAll('.cell[data-field]')) as HTMLElement[];\n headerCells.forEach((cell) => {\n const f = cell.getAttribute('data-field') || '';\n const gid = fieldToGroup.get(f);\n if (gid) {\n cell.classList.add('grouped');\n if (!cell.getAttribute('data-group')) {\n cell.setAttribute('data-group', gid);\n }\n }\n });\n\n // Mark group end cells for styling\n for (const g of groups) {\n const last = g.columns[g.columns.length - 1];\n const cell = headerCells.find((c) => c.getAttribute('data-field') === (last as any).field);\n if (cell) cell.classList.add('group-end');\n }\n}\n\n/**\n * Build the group header row element.\n *\n * @param groups - The computed column groups\n * @param columns - The column configurations\n * @returns The group header row element, or null if no groups\n */\nexport function buildGroupHeaderRow(\n groups: ColumnGroup[],\n columns: ColumnConfig<any>[]\n): HTMLElement | null {\n if (groups.length === 0) return null;\n\n const groupRow = document.createElement('div');\n groupRow.className = 'header-group-row';\n groupRow.setAttribute('role', 'row');\n\n for (const g of groups) {\n const startIndex =\n g.firstIndex != null\n ? g.firstIndex\n : columns.findIndex((c) => (g.columns as any[]).includes(c));\n\n const isImplicit = String(g.id).startsWith('__implicit__');\n const label = isImplicit ? '' : g.label || g.id;\n\n const cell = document.createElement('div');\n cell.className = 'cell header-group-cell';\n if (isImplicit) cell.classList.add('implicit-group');\n cell.setAttribute('data-group', String(g.id));\n cell.style.gridColumn = `${startIndex + 1} / span ${g.columns.length}`;\n cell.textContent = label;\n groupRow.appendChild(cell);\n }\n\n return groupRow;\n}\n\n/**\n * Check if any columns have group configuration.\n *\n * @param columns - The column configurations\n * @returns True if at least one column has a group\n */\nexport function hasColumnGroups(columns: ColumnConfig<any>[]): boolean {\n return columns.some((col) => (col as any).group != null);\n}\n\n/**\n * Get group ID for a specific column.\n *\n * @param column - The column configuration\n * @returns The group ID, or undefined if not grouped\n */\nexport function getColumnGroupId(column: ColumnConfig<any>): string | undefined {\n const g = (column as any).group;\n if (!g) return undefined;\n return typeof g === 'string' ? g : g.id;\n}\n","/**\n * Column Groups Plugin (Class-based)\n *\n * Enables multi-level column header grouping.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n applyGroupedHeaderCellClasses,\n buildGroupHeaderRow,\n computeColumnGroups,\n hasColumnGroups,\n} from './grouping-columns';\nimport type { ColumnGroup, GroupingColumnsConfig } from './types';\n\n/**\n * Column Groups Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new GroupingColumnsPlugin({\n * enabled: true,\n * showGroupBorders: true,\n * })\n * ```\n */\nexport class GroupingColumnsPlugin extends BaseGridPlugin<GroupingColumnsConfig> {\n readonly name = 'groupingColumns';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<GroupingColumnsConfig> {\n return {\n enabled: true,\n showGroupBorders: true,\n };\n }\n\n // ===== Internal State =====\n private groups: ColumnGroup[] = [];\n private isActive = false;\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.groups = [];\n this.isActive = false;\n }\n\n // ===== Static Detection =====\n\n /**\n * Auto-detect column groups from column configuration.\n */\n static detect(rows: readonly any[], config: any): boolean {\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasColumnGroups(columns);\n }\n\n // ===== Hooks =====\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.config.enabled) {\n this.isActive = false;\n this.groups = [];\n return [...columns];\n }\n\n // Compute groups from column definitions\n const groups = computeColumnGroups(columns as ColumnConfig[]);\n\n if (groups.length === 0) {\n this.isActive = false;\n this.groups = [];\n return [...columns];\n }\n\n this.isActive = true;\n this.groups = groups;\n\n // Return columns unchanged - the afterRender hook will add the group header\n return [...columns];\n }\n\n override afterRender(): void {\n if (!this.isActive || this.groups.length === 0) {\n // Remove any existing group header\n const header = this.shadowRoot?.querySelector('.header');\n const existingGroupRow = header?.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n return;\n }\n\n const header = this.shadowRoot?.querySelector('.header');\n if (!header) return;\n\n // Remove existing group row if present\n const existingGroupRow = header.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n\n // Build and insert group header row\n const groupRow = buildGroupHeaderRow(this.groups, this.columns as ColumnConfig[]);\n if (groupRow) {\n const headerRow = header.querySelector('.header-row');\n if (headerRow) {\n header.insertBefore(groupRow, headerRow);\n } else {\n header.appendChild(groupRow);\n }\n }\n\n // Apply classes to header cells\n const headerRow = header.querySelector('.header-row') as HTMLElement;\n if (headerRow) {\n applyGroupedHeaderCellClasses(headerRow, this.groups, this.columns as ColumnConfig[]);\n }\n }\n\n // ===== Public API =====\n\n /**\n * Check if column groups are active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Get the computed column groups.\n * @returns Array of column groups\n */\n getGroups(): ColumnGroup[] {\n return this.groups;\n }\n\n /**\n * Get columns in a specific group.\n * @param groupId - The group ID to find\n * @returns Array of columns in the group\n */\n getGroupColumns(groupId: string): ColumnConfig[] {\n const group = this.groups.find((g) => g.id === groupId);\n return group ? group.columns : [];\n }\n\n /**\n * Refresh column groups (recompute from current columns).\n */\n refresh(): void {\n this.requestRender();\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n .header-group-row {\n display: grid;\n grid-auto-flow: column;\n background: var(--tbw-grouping-columns-header-bg, var(--tbw-color-header-bg));\n border-bottom: 1px solid var(--tbw-grouping-columns-border, var(--tbw-color-border));\n }\n .header-group-cell {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 4px 8px;\n font-weight: 600;\n font-size: 0.9em;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n border-right: 1px solid var(--tbw-grouping-columns-border, var(--tbw-color-border));\n }\n .header-group-cell:last-child {\n border-right: none;\n }\n .header-row .cell.grouped {\n border-top: none;\n }\n .header-row .cell.group-end {\n border-right: 2px solid var(--tbw-grouping-columns-separator, var(--tbw-color-border-strong));\n }\n `;\n}\n"],"names":["computeColumnGroups","columns","explicitMap","groupsOrdered","pushImplicit","startIdx","cols","prev","run","runStart","col","idx","g","id","group","applyGroupedHeaderCellClasses","headerRowEl","groups","fieldToGroup","c","headerCells","cell","f","gid","last","buildGroupHeaderRow","groupRow","startIndex","isImplicit","label","hasColumnGroups","GroupingColumnsPlugin","BaseGridPlugin","rows","config","existingGroupRow","header","headerRow","groupId"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
(function(f,_){typeof exports=="object"&&typeof module<"u"?_(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=typeof globalThis<"u"?globalThis:f||self,_(f.TbwGridPlugin_groupingRows={},f.TbwGrid,f.TbwGrid))})(this,(function(f,_,y){"use strict";function R({rows:d,config:e,expanded:t}){const u=e.groupOn;if(!e.enabled||typeof u!="function")return[];const n={key:"__root__",value:null,depth:-1,rows:[],children:new Map};if(d.forEach(o=>{let s=u(o);s==null||s===!1?s=["__ungrouped__"]:Array.isArray(s)||(s=[s]);let r=n;s.forEach((g,x)=>{const p=g==null?"∅":String(g),c=r.key==="__root__"?p:r.key+"||"+p;let l=r.children.get(p);l||(l={key:c,value:g,depth:x,rows:[],children:new Map,parent:r},r.children.set(p,l)),r=l}),r.rows.push(o)}),n.children.size===1&&n.children.has("__ungrouped__")&&n.children.get("__ungrouped__").rows.length===d.length)return[];const i=[],a=o=>{if(o===n){o.children.forEach(r=>a(r));return}const s=t.has(o.key);i.push({kind:"group",key:o.key,value:o.value,depth:o.depth,rows:o.rows,expanded:s}),s&&(o.children.size?o.children.forEach(r=>a(r)):o.rows.forEach(r=>i.push({kind:"data",row:r,rowIndex:d.indexOf(r)})))};return a(n),i}function w(d,e){const t=new Set(d);return t.has(e)?t.delete(e):t.add(e),t}function m(d){const e=new Set;for(const t of d)t.kind==="group"&&e.add(t.key);return e}function v(){return new Set}function C(d){return d.kind!=="group"?0:d.rows.length}class K extends _.BaseGridPlugin{name="groupingRows";version="1.0.0";get defaultConfig(){return{enabled:!0,defaultExpanded:!1,showRowCount:!0,indentWidth:20,aggregators:{}}}expandedKeys=new Set;flattenedRows=[];isActive=!1;detach(){this.expandedKeys.clear(),this.flattenedRows=[],this.isActive=!1}static detect(e,t){return typeof t?.groupOn=="function"||typeof t?.enableRowGrouping=="boolean"}processRows(e){const t=this.config;if(!t.enabled||typeof t.groupOn!="function")return this.isActive=!1,this.flattenedRows=[],[...e];const u=R({rows:e,config:t,expanded:this.expandedKeys});return u.length===0?(this.isActive=!1,this.flattenedRows=[],[...e]):(this.isActive=!0,this.flattenedRows=u,u.map(n=>n.kind==="group"?{__isGroupRow:!0,__groupKey:n.key,__groupValue:n.value,__groupDepth:n.depth,__groupRows:n.rows,__groupExpanded:n.expanded,__groupRowCount:C(n)}:n.row))}onCellClick(e){const t=e.row;if(t?.__isGroupRow&&e.originalEvent.target?.closest(".group-toggle"))return this.toggle(t.__groupKey),!0}renderRow(e,t,u){if(!e?.__isGroupRow)return!1;const n=this.config;if(n.groupRowRenderer){const o=()=>{this.toggle(e.__groupKey)},s=n.groupRowRenderer({key:e.__groupKey,value:e.__groupValue,depth:e.__groupDepth,rows:e.__groupRows,expanded:e.__groupExpanded,toggleExpand:o});if(s)return t.className="group-row",t.__isCustomRow=!0,t.setAttribute("data-group-depth",String(e.__groupDepth)),typeof s=="string"?t.innerHTML=s:(t.innerHTML="",t.appendChild(s)),!0}const i=()=>{this.toggle(e.__groupKey)};return t.className="group-row",t.__isCustomRow=!0,t.setAttribute("data-group-depth",String(e.__groupDepth)),t.setAttribute("role","row"),t.setAttribute("aria-expanded",String(e.__groupExpanded)),t.style.paddingLeft=`${(e.__groupDepth||0)*(n.indentWidth??20)}px`,t.innerHTML="",n.fullWidth!==!1?this.renderFullWidthGroupRow(e,t,i):this.renderPerColumnGroupRow(e,t,i),!0}afterRender(){}renderFullWidthGroupRow(e,t,u){const n=this.config,i=document.createElement("div");i.className="cell group-full",i.style.gridColumn="1 / -1",i.setAttribute("role","gridcell");const a=document.createElement("button");a.type="button",a.className="group-toggle",a.setAttribute("aria-label",e.__groupExpanded?"Collapse group":"Expand group"),a.textContent=e.__groupExpanded?"▾":"▸",a.addEventListener("click",r=>{r.stopPropagation(),u()}),i.appendChild(a);const o=document.createElement("span");o.className="group-label";const s=n.formatLabel?n.formatLabel(e.__groupValue,e.__groupDepth||0,e.__groupKey):String(e.__groupValue);if(o.textContent=s,i.appendChild(o),n.showRowCount!==!1){const r=document.createElement("span");r.className="group-count",r.textContent=`(${e.__groupRowCount??e.__groupRows?.length??0})`,i.appendChild(r)}t.appendChild(i)}renderPerColumnGroupRow(e,t,u){const n=this.config,i=n.aggregators??{},a=this.columns,o=e.__groupRows??[];this.grid;const r=this.shadowRoot?.querySelector(".body")?.style.gridTemplateColumns||"";r&&(t.style.display="grid",t.style.gridTemplateColumns=r),a.forEach((g,x)=>{const p=document.createElement("div");if(p.className="cell group-cell",p.setAttribute("data-col",String(x)),p.setAttribute("role","gridcell"),x===0){const c=document.createElement("button");c.type="button",c.className="group-toggle",c.textContent=e.__groupExpanded?"▾":"▸",c.addEventListener("click",h=>{h.stopPropagation(),u()}),p.appendChild(c);const l=document.createElement("span"),b=i[g.field];if(b){const h=y.runAggregator(b,o,g.field,g);l.textContent=h!=null?String(h):String(e.__groupValue)}else{const h=n.formatLabel?n.formatLabel(e.__groupValue,e.__groupDepth||0,e.__groupKey):String(e.__groupValue);l.textContent=h}if(p.appendChild(l),n.showRowCount!==!1){const h=document.createElement("span");h.className="group-count",h.textContent=` (${o.length})`,p.appendChild(h)}}else{const c=i[g.field];if(c){const l=y.runAggregator(c,o,g.field,g);p.textContent=l!=null?String(l):""}else p.textContent=""}t.appendChild(p)})}expandAll(){this.expandedKeys=m(this.flattenedRows),this.requestRender()}collapseAll(){this.expandedKeys=v(),this.requestRender()}toggle(e){this.expandedKeys=w(this.expandedKeys,e);const t=this.flattenedRows.find(u=>u.kind==="group"&&u.key===e);this.emit("group-toggle",{key:e,expanded:this.expandedKeys.has(e),value:t?.value,depth:t?.depth??0}),this.requestRender()}isExpanded(e){return this.expandedKeys.has(e)}expand(e){this.expandedKeys.has(e)||(this.expandedKeys=new Set([...this.expandedKeys,e]),this.requestRender())}collapse(e){if(this.expandedKeys.has(e)){const t=new Set(this.expandedKeys);t.delete(e),this.expandedKeys=t,this.requestRender()}}getGroupState(){const e=this.flattenedRows.filter(t=>t.kind==="group");return{isActive:this.isActive,expandedCount:this.expandedKeys.size,totalGroups:e.length,expandedKeys:[...this.expandedKeys]}}getRowCount(){return this.flattenedRows.length}refreshGroups(){this.requestRender()}getExpandedGroups(){return[...this.expandedKeys]}getFlattenedRows(){return this.flattenedRows}isGroupingActive(){return this.isActive}setGroupOn(e){this.config.groupOn=e,this.requestRender()}styles=`
|
|
2
|
+
.group-row {
|
|
3
|
+
background: var(--tbw-grouping-rows-bg, var(--tbw-color-panel-bg));
|
|
4
|
+
font-weight: 500;
|
|
5
|
+
}
|
|
6
|
+
.group-row:hover {
|
|
7
|
+
background: var(--tbw-grouping-rows-bg-hover, var(--tbw-color-row-hover));
|
|
8
|
+
}
|
|
9
|
+
.group-toggle {
|
|
10
|
+
cursor: pointer;
|
|
11
|
+
user-select: none;
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
width: 20px;
|
|
16
|
+
height: 20px;
|
|
17
|
+
margin-right: 4px;
|
|
18
|
+
font-size: 10px;
|
|
19
|
+
}
|
|
20
|
+
.group-toggle:hover {
|
|
21
|
+
background: var(--tbw-grouping-rows-toggle-hover, var(--tbw-color-row-hover));
|
|
22
|
+
border-radius: 2px;
|
|
23
|
+
}
|
|
24
|
+
.group-label {
|
|
25
|
+
display: inline-flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
gap: 8px;
|
|
28
|
+
}
|
|
29
|
+
.group-count {
|
|
30
|
+
color: var(--tbw-grouping-rows-count-color, var(--tbw-color-fg-muted));
|
|
31
|
+
font-size: 0.85em;
|
|
32
|
+
font-weight: normal;
|
|
33
|
+
}
|
|
34
|
+
[data-group-depth="0"] .group-label { padding-left: 0; }
|
|
35
|
+
[data-group-depth="1"] .group-label { padding-left: 20px; }
|
|
36
|
+
[data-group-depth="2"] .group-label { padding-left: 40px; }
|
|
37
|
+
[data-group-depth="3"] .group-label { padding-left: 60px; }
|
|
38
|
+
[data-group-depth="4"] .group-label { padding-left: 80px; }
|
|
39
|
+
`}f.GroupingRowsPlugin=K,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})}));
|
|
40
|
+
//# sourceMappingURL=grouping-rows.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grouping-rows.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/grouping-rows/grouping-rows.ts","../../../../../libs/grid/src/lib/plugins/grouping-rows/GroupingRowsPlugin.ts"],"sourcesContent":["/**\n * Row Grouping Core Logic\n *\n * Pure functions for building grouped row models and aggregations.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { RenderRow, RowGroupingConfig } from './types';\n\n// Re-export aggregator functions from core for backward compatibility\nexport {\n getAggregator,\n listAggregators,\n registerAggregator,\n runAggregator,\n} from '../../core/internal/aggregators';\n\ninterface GroupNode {\n key: string; // composite key\n value: any;\n depth: number;\n rows: any[];\n children: Map<string, GroupNode>;\n parent?: GroupNode;\n}\n\ninterface BuildGroupingArgs {\n rows: any[];\n config: RowGroupingConfig;\n expanded: Set<string>;\n}\n\n/**\n * Build a flattened grouping projection (collapsed by default).\n * Returns empty array when grouping disabled or all rows ungrouped.\n *\n * @param args - The grouping arguments\n * @returns Flattened array of render rows (groups + data rows)\n */\nexport function buildGroupedRowModel({ rows, config, expanded }: BuildGroupingArgs): RenderRow[] {\n const groupOn = config.groupOn;\n if (!config.enabled || typeof groupOn !== 'function') {\n return [];\n }\n\n const root: GroupNode = { key: '__root__', value: null, depth: -1, rows: [], children: new Map() };\n\n // Build tree structure\n rows.forEach((r) => {\n let path: any = groupOn(r);\n if (path == null || path === false) path = ['__ungrouped__'];\n else if (!Array.isArray(path)) path = [path];\n\n let parent = root;\n path.forEach((rawVal: any, depthIdx: number) => {\n const seg = rawVal == null ? '∅' : String(rawVal);\n const composite = parent.key === '__root__' ? seg : parent.key + '||' + seg;\n let node = parent.children.get(seg);\n if (!node) {\n node = { key: composite, value: rawVal, depth: depthIdx, rows: [], children: new Map(), parent };\n parent.children.set(seg, node);\n }\n parent = node;\n });\n parent.rows.push(r);\n });\n\n // All ungrouped? treat as no grouping\n if (root.children.size === 1 && root.children.has('__ungrouped__')) {\n const only = root.children.get('__ungrouped__')!;\n if (only.rows.length === rows.length) return [];\n }\n\n // Flatten tree to array\n const flat: RenderRow[] = [];\n const visit = (node: GroupNode) => {\n if (node === root) {\n node.children.forEach((c) => visit(c));\n return;\n }\n\n const isExpanded = expanded.has(node.key);\n flat.push({\n kind: 'group',\n key: node.key,\n value: node.value,\n depth: node.depth,\n rows: node.rows,\n expanded: isExpanded,\n });\n\n if (isExpanded) {\n if (node.children.size) {\n node.children.forEach((c) => visit(c));\n } else {\n node.rows.forEach((r) => flat.push({ kind: 'data', row: r, rowIndex: rows.indexOf(r) }));\n }\n }\n };\n visit(root);\n\n return flat;\n}\n\n/**\n * Toggle expansion state for a group key.\n *\n * @param expandedKeys - Current set of expanded keys\n * @param key - The group key to toggle\n * @returns New set with toggled state\n */\nexport function toggleGroupExpansion(expandedKeys: Set<string>, key: string): Set<string> {\n const newSet = new Set(expandedKeys);\n if (newSet.has(key)) {\n newSet.delete(key);\n } else {\n newSet.add(key);\n }\n return newSet;\n}\n\n/**\n * Expand all groups.\n *\n * @param rows - The flattened render rows\n * @returns Set of all group keys\n */\nexport function expandAllGroups(rows: RenderRow[]): Set<string> {\n const keys = new Set<string>();\n for (const row of rows) {\n if (row.kind === 'group') {\n keys.add(row.key);\n }\n }\n return keys;\n}\n\n/**\n * Collapse all groups.\n *\n * @returns Empty set\n */\nexport function collapseAllGroups(): Set<string> {\n return new Set();\n}\n\n/**\n * Get all group keys from a flattened model.\n *\n * @param rows - The flattened render rows\n * @returns Array of group keys\n */\nexport function getGroupKeys(rows: RenderRow[]): string[] {\n return rows.filter((r) => r.kind === 'group').map((r) => (r as any).key);\n}\n\n/**\n * Count total rows in a group (including nested groups).\n *\n * @param groupRow - The group row\n * @returns Total row count\n */\nexport function getGroupRowCount(groupRow: RenderRow): number {\n if (groupRow.kind !== 'group') return 0;\n return groupRow.rows.length;\n}\n","/**\n * Row Grouping Plugin (Class-based)\n *\n * Enables hierarchical row grouping with expand/collapse and aggregations.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { BaseGridPlugin, CellClickEvent } from '../../core/plugin/base-plugin';\nimport {\n buildGroupedRowModel,\n collapseAllGroups,\n expandAllGroups,\n getGroupRowCount,\n runAggregator,\n toggleGroupExpansion,\n} from './grouping-rows';\nimport type { GroupingRowsConfig, GroupRowModelItem, GroupToggleDetail, RenderRow } from './types';\n\n/**\n * Group state information returned by getGroupState()\n */\nexport interface GroupState {\n /** Whether grouping is currently active */\n isActive: boolean;\n /** Number of expanded groups */\n expandedCount: number;\n /** Total number of groups */\n totalGroups: number;\n /** Array of expanded group keys */\n expandedKeys: string[];\n}\n\n/**\n * Row Grouping Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new GroupingRowsPlugin({\n * enabled: true,\n * groupOn: (row) => row.category,\n * defaultExpanded: false,\n * showRowCount: true,\n * })\n * ```\n */\nexport class GroupingRowsPlugin extends BaseGridPlugin<GroupingRowsConfig> {\n readonly name = 'groupingRows';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<GroupingRowsConfig> {\n return {\n enabled: true,\n defaultExpanded: false,\n showRowCount: true,\n indentWidth: 20,\n aggregators: {},\n };\n }\n\n // ===== Internal State =====\n private expandedKeys: Set<string> = new Set();\n private flattenedRows: RenderRow[] = [];\n private isActive = false;\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.expandedKeys.clear();\n this.flattenedRows = [];\n this.isActive = false;\n }\n\n // ===== Hooks =====\n\n /**\n * Auto-detect grouping configuration from grid config.\n * Called by plugin system to determine if plugin should activate.\n */\n static detect(rows: readonly any[], config: any): boolean {\n return typeof config?.groupOn === 'function' || typeof config?.enableRowGrouping === 'boolean';\n }\n\n override processRows(rows: readonly any[]): any[] {\n const config = this.config;\n\n // Check if grouping is configured\n if (!config.enabled || typeof config.groupOn !== 'function') {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // Build grouped model\n const grouped = buildGroupedRowModel({\n rows: rows as any[],\n config: config,\n expanded: this.expandedKeys,\n });\n\n // If no grouping produced, return original rows\n if (grouped.length === 0) {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n this.isActive = true;\n this.flattenedRows = grouped;\n\n // Return flattened rows for rendering\n // The grid will need to handle group rows specially\n return grouped.map((item) => {\n if (item.kind === 'group') {\n return {\n __isGroupRow: true,\n __groupKey: item.key,\n __groupValue: item.value,\n __groupDepth: item.depth,\n __groupRows: item.rows,\n __groupExpanded: item.expanded,\n __groupRowCount: getGroupRowCount(item),\n };\n }\n return item.row;\n });\n }\n\n override onCellClick(event: CellClickEvent): boolean | void {\n const row = event.row;\n\n // Check if this is a group row toggle\n if (row?.__isGroupRow) {\n const target = event.originalEvent.target as HTMLElement;\n if (target?.closest('.group-toggle')) {\n this.toggle(row.__groupKey);\n return true; // Prevent default\n }\n }\n }\n\n /**\n * Render a row. Returns true if we handled the row (group row), false otherwise.\n */\n override renderRow(row: any, rowEl: HTMLElement, _rowIndex: number): boolean {\n // Only handle group rows\n if (!row?.__isGroupRow) {\n return false;\n }\n\n const config = this.config;\n\n // If a custom renderer is provided, use it\n if (config.groupRowRenderer) {\n const toggleExpand = () => {\n this.toggle(row.__groupKey);\n };\n\n const result = config.groupRowRenderer({\n key: row.__groupKey,\n value: row.__groupValue,\n depth: row.__groupDepth,\n rows: row.__groupRows,\n expanded: row.__groupExpanded,\n toggleExpand,\n });\n\n if (result) {\n rowEl.className = 'group-row';\n (rowEl as any).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n if (typeof result === 'string') {\n rowEl.innerHTML = result;\n } else {\n rowEl.innerHTML = '';\n rowEl.appendChild(result);\n }\n return true;\n }\n }\n\n // Helper to toggle expansion\n const handleToggle = () => {\n this.toggle(row.__groupKey);\n };\n\n // Default group row rendering\n rowEl.className = 'group-row';\n (rowEl as any).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n rowEl.setAttribute('role', 'row');\n rowEl.setAttribute('aria-expanded', String(row.__groupExpanded));\n rowEl.style.paddingLeft = `${(row.__groupDepth || 0) * (config.indentWidth ?? 20)}px`;\n rowEl.innerHTML = '';\n\n const isFullWidth = config.fullWidth !== false; // default true\n\n if (isFullWidth) {\n this.renderFullWidthGroupRow(row, rowEl, handleToggle);\n } else {\n this.renderPerColumnGroupRow(row, rowEl, handleToggle);\n }\n\n return true;\n }\n\n override afterRender(): void {\n // No additional DOM manipulation needed for grouping\n // The renderRow hook handles all group row rendering\n }\n\n // ===== Private Rendering Helpers =====\n\n private renderFullWidthGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n\n // Full-width mode: single spanning cell with toggle + label + count\n const cell = document.createElement('div');\n cell.className = 'cell group-full';\n cell.style.gridColumn = '1 / -1';\n cell.setAttribute('role', 'gridcell');\n\n // Toggle button with click handler\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'group-toggle';\n btn.setAttribute('aria-label', row.__groupExpanded ? 'Collapse group' : 'Expand group');\n btn.textContent = row.__groupExpanded ? '▾' : '▸';\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n handleToggle();\n });\n cell.appendChild(btn);\n\n // Group label - use formatLabel if provided\n const label = document.createElement('span');\n label.className = 'group-label';\n const labelText = config.formatLabel\n ? config.formatLabel(row.__groupValue, row.__groupDepth || 0, row.__groupKey)\n : String(row.__groupValue);\n label.textContent = labelText;\n cell.appendChild(label);\n\n // Row count\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = 'group-count';\n count.textContent = `(${row.__groupRowCount ?? row.__groupRows?.length ?? 0})`;\n cell.appendChild(count);\n }\n\n rowEl.appendChild(cell);\n }\n\n private renderPerColumnGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const columns = this.columns;\n const groupRows = row.__groupRows ?? [];\n\n // Get grid template from the grid element\n const gridEl = this.grid as any;\n const bodyEl = this.shadowRoot?.querySelector('.body') as HTMLElement | null;\n const gridTemplate = bodyEl?.style.gridTemplateColumns || '';\n if (gridTemplate) {\n rowEl.style.display = 'grid';\n rowEl.style.gridTemplateColumns = gridTemplate;\n }\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell group-cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('role', 'gridcell');\n\n if (colIdx === 0) {\n // First column: toggle button + label\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'group-toggle';\n btn.textContent = row.__groupExpanded ? '▾' : '▸';\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n handleToggle();\n });\n cell.appendChild(btn);\n\n const label = document.createElement('span');\n const firstColAgg = aggregators[col.field];\n if (firstColAgg) {\n const aggResult = runAggregator(firstColAgg, groupRows, col.field, col);\n label.textContent = aggResult != null ? String(aggResult) : String(row.__groupValue);\n } else {\n const labelText = config.formatLabel\n ? config.formatLabel(row.__groupValue, row.__groupDepth || 0, row.__groupKey)\n : String(row.__groupValue);\n label.textContent = labelText;\n }\n cell.appendChild(label);\n\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = 'group-count';\n count.textContent = ` (${groupRows.length})`;\n cell.appendChild(count);\n }\n } else {\n // Other columns: run aggregator if defined\n const aggRef = aggregators[col.field];\n if (aggRef) {\n const result = runAggregator(aggRef, groupRows, col.field, col);\n cell.textContent = result != null ? String(result) : '';\n } else {\n cell.textContent = '';\n }\n }\n\n rowEl.appendChild(cell);\n });\n }\n\n // ===== Public API =====\n\n /**\n * Expand all groups.\n */\n expandAll(): void {\n this.expandedKeys = expandAllGroups(this.flattenedRows);\n this.requestRender();\n }\n\n /**\n * Collapse all groups.\n */\n collapseAll(): void {\n this.expandedKeys = collapseAllGroups();\n this.requestRender();\n }\n\n /**\n * Toggle expansion of a specific group.\n * @param key - The group key to toggle\n */\n toggle(key: string): void {\n this.expandedKeys = toggleGroupExpansion(this.expandedKeys, key);\n\n // Find the group to emit event details\n const group = this.flattenedRows.find((r) => r.kind === 'group' && r.key === key) as GroupRowModelItem | undefined;\n\n this.emit<GroupToggleDetail>('group-toggle', {\n key,\n expanded: this.expandedKeys.has(key),\n value: group?.value,\n depth: group?.depth ?? 0,\n });\n\n this.requestRender();\n }\n\n /**\n * Check if a specific group is expanded.\n * @param key - The group key to check\n * @returns Whether the group is expanded\n */\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n /**\n * Expand a specific group.\n * @param key - The group key to expand\n */\n expand(key: string): void {\n if (!this.expandedKeys.has(key)) {\n this.expandedKeys = new Set([...this.expandedKeys, key]);\n this.requestRender();\n }\n }\n\n /**\n * Collapse a specific group.\n * @param key - The group key to collapse\n */\n collapse(key: string): void {\n if (this.expandedKeys.has(key)) {\n const newKeys = new Set(this.expandedKeys);\n newKeys.delete(key);\n this.expandedKeys = newKeys;\n this.requestRender();\n }\n }\n\n /**\n * Get the current group state.\n * @returns Group state information\n */\n getGroupState(): GroupState {\n const groupRows = this.flattenedRows.filter((r) => r.kind === 'group');\n return {\n isActive: this.isActive,\n expandedCount: this.expandedKeys.size,\n totalGroups: groupRows.length,\n expandedKeys: [...this.expandedKeys],\n };\n }\n\n /**\n * Get the total count of visible rows (including group headers).\n * @returns Number of visible rows\n */\n getRowCount(): number {\n return this.flattenedRows.length;\n }\n\n /**\n * Refresh the grouped row model.\n * Call this after modifying groupOn or other config options.\n */\n refreshGroups(): void {\n this.requestRender();\n }\n\n /**\n * Get current expanded group keys.\n * @returns Array of expanded group keys\n */\n getExpandedGroups(): string[] {\n return [...this.expandedKeys];\n }\n\n /**\n * Get the flattened row model.\n * @returns Array of render rows (groups + data rows)\n */\n getFlattenedRows(): RenderRow[] {\n return this.flattenedRows;\n }\n\n /**\n * Check if grouping is currently active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Set the groupOn function dynamically.\n * @param fn - The groupOn function or undefined to disable\n */\n setGroupOn(fn: ((row: any) => any[] | any | null | false) | undefined): void {\n (this.config as GroupingRowsConfig).groupOn = fn;\n this.requestRender();\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n .group-row {\n background: var(--tbw-grouping-rows-bg, var(--tbw-color-panel-bg));\n font-weight: 500;\n }\n .group-row:hover {\n background: var(--tbw-grouping-rows-bg-hover, var(--tbw-color-row-hover));\n }\n .group-toggle {\n cursor: pointer;\n user-select: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n margin-right: 4px;\n font-size: 10px;\n }\n .group-toggle:hover {\n background: var(--tbw-grouping-rows-toggle-hover, var(--tbw-color-row-hover));\n border-radius: 2px;\n }\n .group-label {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n }\n .group-count {\n color: var(--tbw-grouping-rows-count-color, var(--tbw-color-fg-muted));\n font-size: 0.85em;\n font-weight: normal;\n }\n [data-group-depth=\"0\"] .group-label { padding-left: 0; }\n [data-group-depth=\"1\"] .group-label { padding-left: 20px; }\n [data-group-depth=\"2\"] .group-label { padding-left: 40px; }\n [data-group-depth=\"3\"] .group-label { padding-left: 60px; }\n [data-group-depth=\"4\"] .group-label { padding-left: 80px; }\n `;\n}\n"],"names":["buildGroupedRowModel","rows","config","expanded","groupOn","root","r","path","parent","rawVal","depthIdx","seg","composite","node","flat","visit","c","isExpanded","toggleGroupExpansion","expandedKeys","key","newSet","expandAllGroups","keys","row","collapseAllGroups","getGroupRowCount","groupRow","GroupingRowsPlugin","BaseGridPlugin","grouped","item","event","rowEl","_rowIndex","toggleExpand","result","handleToggle","cell","btn","e","label","labelText","count","aggregators","columns","groupRows","gridTemplate","col","colIdx","firstColAgg","aggResult","runAggregator","aggRef","group","newKeys","fn"],"mappings":"iaAwCO,SAASA,EAAqB,CAAE,KAAAC,EAAM,OAAAC,EAAQ,SAAAC,GAA4C,CAC/F,MAAMC,EAAUF,EAAO,QACvB,GAAI,CAACA,EAAO,SAAW,OAAOE,GAAY,WACxC,MAAO,CAAA,EAGT,MAAMC,EAAkB,CAAE,IAAK,WAAY,MAAO,KAAM,MAAO,GAAI,KAAM,CAAA,EAAI,SAAU,IAAI,GAAI,EAuB/F,GApBAJ,EAAK,QAASK,GAAM,CAClB,IAAIC,EAAYH,EAAQE,CAAC,EACrBC,GAAQ,MAAQA,IAAS,GAAOA,EAAO,CAAC,eAAe,EACjD,MAAM,QAAQA,CAAI,IAAGA,EAAO,CAACA,CAAI,GAE3C,IAAIC,EAASH,EACbE,EAAK,QAAQ,CAACE,EAAaC,IAAqB,CAC9C,MAAMC,EAAMF,GAAU,KAAO,IAAM,OAAOA,CAAM,EAC1CG,EAAYJ,EAAO,MAAQ,WAAaG,EAAMH,EAAO,IAAM,KAAOG,EACxE,IAAIE,EAAOL,EAAO,SAAS,IAAIG,CAAG,EAC7BE,IACHA,EAAO,CAAE,IAAKD,EAAW,MAAOH,EAAQ,MAAOC,EAAU,KAAM,CAAA,EAAI,SAAU,IAAI,IAAO,OAAAF,CAAA,EACxFA,EAAO,SAAS,IAAIG,EAAKE,CAAI,GAE/BL,EAASK,CACX,CAAC,EACDL,EAAO,KAAK,KAAKF,CAAC,CACpB,CAAC,EAGGD,EAAK,SAAS,OAAS,GAAKA,EAAK,SAAS,IAAI,eAAe,GAClDA,EAAK,SAAS,IAAI,eAAe,EACrC,KAAK,SAAWJ,EAAK,aAAe,CAAA,EAI/C,MAAMa,EAAoB,CAAA,EACpBC,EAASF,GAAoB,CACjC,GAAIA,IAASR,EAAM,CACjBQ,EAAK,SAAS,QAASG,GAAMD,EAAMC,CAAC,CAAC,EACrC,MACF,CAEA,MAAMC,EAAad,EAAS,IAAIU,EAAK,GAAG,EACxCC,EAAK,KAAK,CACR,KAAM,QACN,IAAKD,EAAK,IACV,MAAOA,EAAK,MACZ,MAAOA,EAAK,MACZ,KAAMA,EAAK,KACX,SAAUI,CAAA,CACX,EAEGA,IACEJ,EAAK,SAAS,KAChBA,EAAK,SAAS,QAASG,GAAMD,EAAMC,CAAC,CAAC,EAErCH,EAAK,KAAK,QAAS,GAAMC,EAAK,KAAK,CAAE,KAAM,OAAQ,IAAK,EAAG,SAAUb,EAAK,QAAQ,CAAC,CAAA,CAAG,CAAC,EAG7F,EACA,OAAAc,EAAMV,CAAI,EAEHS,CACT,CASO,SAASI,EAAqBC,EAA2BC,EAA0B,CACxF,MAAMC,EAAS,IAAI,IAAIF,CAAY,EACnC,OAAIE,EAAO,IAAID,CAAG,EAChBC,EAAO,OAAOD,CAAG,EAEjBC,EAAO,IAAID,CAAG,EAETC,CACT,CAQO,SAASC,EAAgBrB,EAAgC,CAC9D,MAAMsB,MAAW,IACjB,UAAWC,KAAOvB,EACZuB,EAAI,OAAS,SACfD,EAAK,IAAIC,EAAI,GAAG,EAGpB,OAAOD,CACT,CAOO,SAASE,GAAiC,CAC/C,WAAW,GACb,CAkBO,SAASC,EAAiBC,EAA6B,CAC5D,OAAIA,EAAS,OAAS,QAAgB,EAC/BA,EAAS,KAAK,MACvB,CCxHO,MAAMC,UAA2BC,EAAAA,cAAmC,CAChE,KAAO,eACE,QAAU,QAE5B,IAAuB,eAA6C,CAClE,MAAO,CACL,QAAS,GACT,gBAAiB,GACjB,aAAc,GACd,YAAa,GACb,YAAa,CAAA,CAAC,CAElB,CAGQ,iBAAgC,IAChC,cAA6B,CAAA,EAC7B,SAAW,GAIV,QAAe,CACtB,KAAK,aAAa,MAAA,EAClB,KAAK,cAAgB,CAAA,EACrB,KAAK,SAAW,EAClB,CAQA,OAAO,OAAO5B,EAAsBC,EAAsB,CACxD,OAAO,OAAOA,GAAQ,SAAY,YAAc,OAAOA,GAAQ,mBAAsB,SACvF,CAES,YAAYD,EAA6B,CAChD,MAAMC,EAAS,KAAK,OAGpB,GAAI,CAACA,EAAO,SAAW,OAAOA,EAAO,SAAY,WAC/C,YAAK,SAAW,GAChB,KAAK,cAAgB,CAAA,EACd,CAAC,GAAGD,CAAI,EAIjB,MAAM6B,EAAU9B,EAAqB,CACnC,KAAAC,EACA,OAAAC,EACA,SAAU,KAAK,YAAA,CAChB,EAGD,OAAI4B,EAAQ,SAAW,GACrB,KAAK,SAAW,GAChB,KAAK,cAAgB,CAAA,EACd,CAAC,GAAG7B,CAAI,IAGjB,KAAK,SAAW,GAChB,KAAK,cAAgB6B,EAIdA,EAAQ,IAAKC,GACdA,EAAK,OAAS,QACT,CACL,aAAc,GACd,WAAYA,EAAK,IACjB,aAAcA,EAAK,MACnB,aAAcA,EAAK,MACnB,YAAaA,EAAK,KAClB,gBAAiBA,EAAK,SACtB,gBAAiBL,EAAiBK,CAAI,CAAA,EAGnCA,EAAK,GACb,EACH,CAES,YAAYC,EAAuC,CAC1D,MAAMR,EAAMQ,EAAM,IAGlB,GAAIR,GAAK,cACQQ,EAAM,cAAc,QACvB,QAAQ,eAAe,EACjC,YAAK,OAAOR,EAAI,UAAU,EACnB,EAGb,CAKS,UAAUA,EAAUS,EAAoBC,EAA4B,CAE3E,GAAI,CAACV,GAAK,aACR,MAAO,GAGT,MAAMtB,EAAS,KAAK,OAGpB,GAAIA,EAAO,iBAAkB,CAC3B,MAAMiC,EAAe,IAAM,CACzB,KAAK,OAAOX,EAAI,UAAU,CAC5B,EAEMY,EAASlC,EAAO,iBAAiB,CACrC,IAAKsB,EAAI,WACT,MAAOA,EAAI,aACX,MAAOA,EAAI,aACX,KAAMA,EAAI,YACV,SAAUA,EAAI,gBACd,aAAAW,CAAA,CACD,EAED,GAAIC,EACF,OAAAH,EAAM,UAAY,YACjBA,EAAc,cAAgB,GAC/BA,EAAM,aAAa,mBAAoB,OAAOT,EAAI,YAAY,CAAC,EAC3D,OAAOY,GAAW,SACpBH,EAAM,UAAYG,GAElBH,EAAM,UAAY,GAClBA,EAAM,YAAYG,CAAM,GAEnB,EAEX,CAGA,MAAMC,EAAe,IAAM,CACzB,KAAK,OAAOb,EAAI,UAAU,CAC5B,EAGA,OAAAS,EAAM,UAAY,YACjBA,EAAc,cAAgB,GAC/BA,EAAM,aAAa,mBAAoB,OAAOT,EAAI,YAAY,CAAC,EAC/DS,EAAM,aAAa,OAAQ,KAAK,EAChCA,EAAM,aAAa,gBAAiB,OAAOT,EAAI,eAAe,CAAC,EAC/DS,EAAM,MAAM,YAAc,IAAIT,EAAI,cAAgB,IAAMtB,EAAO,aAAe,GAAG,KACjF+B,EAAM,UAAY,GAEE/B,EAAO,YAAc,GAGvC,KAAK,wBAAwBsB,EAAKS,EAAOI,CAAY,EAErD,KAAK,wBAAwBb,EAAKS,EAAOI,CAAY,EAGhD,EACT,CAES,aAAoB,CAG7B,CAIQ,wBAAwBb,EAAUS,EAAoBI,EAAgC,CAC5F,MAAMnC,EAAS,KAAK,OAGdoC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY,kBACjBA,EAAK,MAAM,WAAa,SACxBA,EAAK,aAAa,OAAQ,UAAU,EAGpC,MAAMC,EAAM,SAAS,cAAc,QAAQ,EAC3CA,EAAI,KAAO,SACXA,EAAI,UAAY,eAChBA,EAAI,aAAa,aAAcf,EAAI,gBAAkB,iBAAmB,cAAc,EACtFe,EAAI,YAAcf,EAAI,gBAAkB,IAAM,IAC9Ce,EAAI,iBAAiB,QAAUC,GAAM,CACnCA,EAAE,gBAAA,EACFH,EAAA,CACF,CAAC,EACDC,EAAK,YAAYC,CAAG,EAGpB,MAAME,EAAQ,SAAS,cAAc,MAAM,EAC3CA,EAAM,UAAY,cAClB,MAAMC,EAAYxC,EAAO,YACrBA,EAAO,YAAYsB,EAAI,aAAcA,EAAI,cAAgB,EAAGA,EAAI,UAAU,EAC1E,OAAOA,EAAI,YAAY,EAK3B,GAJAiB,EAAM,YAAcC,EACpBJ,EAAK,YAAYG,CAAK,EAGlBvC,EAAO,eAAiB,GAAO,CACjC,MAAMyC,EAAQ,SAAS,cAAc,MAAM,EAC3CA,EAAM,UAAY,cAClBA,EAAM,YAAc,IAAInB,EAAI,iBAAmBA,EAAI,aAAa,QAAU,CAAC,IAC3Ec,EAAK,YAAYK,CAAK,CACxB,CAEAV,EAAM,YAAYK,CAAI,CACxB,CAEQ,wBAAwBd,EAAUS,EAAoBI,EAAgC,CAC5F,MAAMnC,EAAS,KAAK,OACd0C,EAAc1C,EAAO,aAAe,CAAA,EACpC2C,EAAU,KAAK,QACfC,EAAYtB,EAAI,aAAe,CAAA,EAGtB,KAAK,KAEpB,MAAMuB,EADS,KAAK,YAAY,cAAc,OAAO,GACxB,MAAM,qBAAuB,GACtDA,IACFd,EAAM,MAAM,QAAU,OACtBA,EAAM,MAAM,oBAAsBc,GAGpCF,EAAQ,QAAQ,CAACG,EAAKC,IAAW,CAC/B,MAAMX,EAAO,SAAS,cAAc,KAAK,EAKzC,GAJAA,EAAK,UAAY,kBACjBA,EAAK,aAAa,WAAY,OAAOW,CAAM,CAAC,EAC5CX,EAAK,aAAa,OAAQ,UAAU,EAEhCW,IAAW,EAAG,CAEhB,MAAMV,EAAM,SAAS,cAAc,QAAQ,EAC3CA,EAAI,KAAO,SACXA,EAAI,UAAY,eAChBA,EAAI,YAAcf,EAAI,gBAAkB,IAAM,IAC9Ce,EAAI,iBAAiB,QAAUC,GAAM,CACnCA,EAAE,gBAAA,EACFH,EAAA,CACF,CAAC,EACDC,EAAK,YAAYC,CAAG,EAEpB,MAAME,EAAQ,SAAS,cAAc,MAAM,EACrCS,EAAcN,EAAYI,EAAI,KAAK,EACzC,GAAIE,EAAa,CACf,MAAMC,EAAYC,EAAAA,cAAcF,EAAaJ,EAAWE,EAAI,MAAOA,CAAG,EACtEP,EAAM,YAAcU,GAAa,KAAO,OAAOA,CAAS,EAAI,OAAO3B,EAAI,YAAY,CACrF,KAAO,CACL,MAAMkB,EAAYxC,EAAO,YACrBA,EAAO,YAAYsB,EAAI,aAAcA,EAAI,cAAgB,EAAGA,EAAI,UAAU,EAC1E,OAAOA,EAAI,YAAY,EAC3BiB,EAAM,YAAcC,CACtB,CAGA,GAFAJ,EAAK,YAAYG,CAAK,EAElBvC,EAAO,eAAiB,GAAO,CACjC,MAAMyC,EAAQ,SAAS,cAAc,MAAM,EAC3CA,EAAM,UAAY,cAClBA,EAAM,YAAc,KAAKG,EAAU,MAAM,IACzCR,EAAK,YAAYK,CAAK,CACxB,CACF,KAAO,CAEL,MAAMU,EAAST,EAAYI,EAAI,KAAK,EACpC,GAAIK,EAAQ,CACV,MAAMjB,EAASgB,EAAAA,cAAcC,EAAQP,EAAWE,EAAI,MAAOA,CAAG,EAC9DV,EAAK,YAAcF,GAAU,KAAO,OAAOA,CAAM,EAAI,EACvD,MACEE,EAAK,YAAc,EAEvB,CAEAL,EAAM,YAAYK,CAAI,CACxB,CAAC,CACH,CAOA,WAAkB,CAChB,KAAK,aAAehB,EAAgB,KAAK,aAAa,EACtD,KAAK,cAAA,CACP,CAKA,aAAoB,CAClB,KAAK,aAAeG,EAAA,EACpB,KAAK,cAAA,CACP,CAMA,OAAOL,EAAmB,CACxB,KAAK,aAAeF,EAAqB,KAAK,aAAcE,CAAG,EAG/D,MAAMkC,EAAQ,KAAK,cAAc,KAAMhD,GAAMA,EAAE,OAAS,SAAWA,EAAE,MAAQc,CAAG,EAEhF,KAAK,KAAwB,eAAgB,CAC3C,IAAAA,EACA,SAAU,KAAK,aAAa,IAAIA,CAAG,EACnC,MAAOkC,GAAO,MACd,MAAOA,GAAO,OAAS,CAAA,CACxB,EAED,KAAK,cAAA,CACP,CAOA,WAAWlC,EAAsB,CAC/B,OAAO,KAAK,aAAa,IAAIA,CAAG,CAClC,CAMA,OAAOA,EAAmB,CACnB,KAAK,aAAa,IAAIA,CAAG,IAC5B,KAAK,iBAAmB,IAAI,CAAC,GAAG,KAAK,aAAcA,CAAG,CAAC,EACvD,KAAK,cAAA,EAET,CAMA,SAASA,EAAmB,CAC1B,GAAI,KAAK,aAAa,IAAIA,CAAG,EAAG,CAC9B,MAAMmC,EAAU,IAAI,IAAI,KAAK,YAAY,EACzCA,EAAQ,OAAOnC,CAAG,EAClB,KAAK,aAAemC,EACpB,KAAK,cAAA,CACP,CACF,CAMA,eAA4B,CAC1B,MAAMT,EAAY,KAAK,cAAc,OAAQxC,GAAMA,EAAE,OAAS,OAAO,EACrE,MAAO,CACL,SAAU,KAAK,SACf,cAAe,KAAK,aAAa,KACjC,YAAawC,EAAU,OACvB,aAAc,CAAC,GAAG,KAAK,YAAY,CAAA,CAEvC,CAMA,aAAsB,CACpB,OAAO,KAAK,cAAc,MAC5B,CAMA,eAAsB,CACpB,KAAK,cAAA,CACP,CAMA,mBAA8B,CAC5B,MAAO,CAAC,GAAG,KAAK,YAAY,CAC9B,CAMA,kBAAgC,CAC9B,OAAO,KAAK,aACd,CAMA,kBAA4B,CAC1B,OAAO,KAAK,QACd,CAMA,WAAWU,EAAkE,CAC1E,KAAK,OAA8B,QAAUA,EAC9C,KAAK,cAAA,CACP,CAIkB,OAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAuC7B"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
(function(d,p){typeof exports=="object"&&typeof module<"u"?p(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],p):(d=typeof globalThis<"u"?globalThis:d||self,p(d.TbwGridPlugin_masterDetail={},d.TbwGrid))})(this,(function(d,p){"use strict";function f(o,e){const t=new Set(o);return t.has(e)?t.delete(e):t.add(e),t}function g(o,e){const t=new Set(o);return t.add(e),t}function x(o,e){const t=new Set(o);return t.delete(e),t}function m(o,e){return o.has(e)}function R(o,e,t,r){const n=document.createElement("div");n.className="master-detail-row",n.setAttribute("data-detail-for",String(e)),n.setAttribute("role","row");const s=document.createElement("div");s.className="master-detail-cell",s.setAttribute("role","cell"),s.style.gridColumn=`1 / ${r+1}`;const i=t(o,e);return typeof i=="string"?s.innerHTML=i:i instanceof HTMLElement&&s.appendChild(i),n.appendChild(s),n}class b extends p.BaseGridPlugin{name="masterDetail";version="1.0.0";get defaultConfig(){return{enabled:!0,detailHeight:"auto",expandOnRowClick:!1,collapseOnClickOutside:!1,showExpandColumn:!0}}expandedRows=new Set;detailElements=new Map;detach(){this.expandedRows.clear(),this.detailElements.clear()}processColumns(e){if(!this.config.detailRenderer)return[...e];const t=[...e];if(t.length>0){const r={...t[0]},n=r.viewRenderer;r.viewRenderer=s=>{const{value:i,row:a}=s,l=this.expandedRows.has(a),u=document.createElement("span");u.className="master-detail-cell-wrapper";const c=document.createElement("span");c.className="master-detail-toggle",c.textContent=l?"▼":"▶",c.setAttribute("aria-expanded",String(l)),c.setAttribute("aria-label",l?"Collapse details":"Expand details"),c.addEventListener("click",w=>{w.stopPropagation();const E=this.rows.indexOf(a);this.expandedRows=f(this.expandedRows,a),this.emit("detail-expand",{rowIndex:E,row:a,expanded:this.expandedRows.has(a)}),this.requestRender()}),u.appendChild(c);const h=document.createElement("span");if(n){const w=n(s);w instanceof Node?h.appendChild(w):h.textContent=String(w??i??"")}else h.textContent=String(i??"");return u.appendChild(h),u},t[0]=r}return t}onRowClick(e){if(!(!this.config.expandOnRowClick||!this.config.detailRenderer))return this.expandedRows=f(this.expandedRows,e.row),this.emit("detail-expand",{rowIndex:e.rowIndex,row:e.row,expanded:this.expandedRows.has(e.row)}),this.requestRender(),!1}afterRender(){if(!this.config.detailRenderer)return;const e=this.shadowRoot?.querySelector(".rows");if(!e)return;e.querySelectorAll(".master-detail-row").forEach(n=>n.remove()),this.detailElements.clear();const t=e.querySelectorAll(".data-grid-row"),r=this.columns.length;for(const n of t){const s=n.querySelector(".cell[data-row]"),i=s?parseInt(s.getAttribute("data-row")??"-1",10):-1;if(i<0)continue;const a=this.rows[i];if(!a||!this.expandedRows.has(a))continue;const l=R(a,i,this.config.detailRenderer,r);typeof this.config.detailHeight=="number"&&(l.style.height=`${this.config.detailHeight}px`),n.appendChild(l),this.detailElements.set(a,l)}}expand(e){const t=this.rows[e];t&&(this.expandedRows=g(this.expandedRows,t),this.requestRender())}collapse(e){const t=this.rows[e];t&&(this.expandedRows=x(this.expandedRows,t),this.requestRender())}toggle(e){const t=this.rows[e];t&&(this.expandedRows=f(this.expandedRows,t),this.requestRender())}isExpanded(e){const t=this.rows[e];return t?m(this.expandedRows,t):!1}expandAll(){for(const e of this.rows)this.expandedRows.add(e);this.requestRender()}collapseAll(){this.expandedRows.clear(),this.requestRender()}getExpandedRows(){const e=[];for(const t of this.expandedRows){const r=this.rows.indexOf(t);r>=0&&e.push(r)}return e}getDetailElement(e){const t=this.rows[e];return t?this.detailElements.get(t):void 0}styles=`
|
|
2
|
+
.master-detail-cell-wrapper {
|
|
3
|
+
display: flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
gap: 4px;
|
|
6
|
+
}
|
|
7
|
+
.master-detail-toggle {
|
|
8
|
+
cursor: pointer;
|
|
9
|
+
font-size: 10px;
|
|
10
|
+
opacity: 0.7;
|
|
11
|
+
user-select: none;
|
|
12
|
+
}
|
|
13
|
+
.master-detail-toggle:hover {
|
|
14
|
+
opacity: 1;
|
|
15
|
+
}
|
|
16
|
+
.master-detail-row {
|
|
17
|
+
grid-column: 1 / -1;
|
|
18
|
+
display: grid;
|
|
19
|
+
background: var(--tbw-master-detail-bg, var(--tbw-color-row-alt));
|
|
20
|
+
border-bottom: 1px solid var(--tbw-master-detail-border, var(--tbw-color-border));
|
|
21
|
+
}
|
|
22
|
+
.master-detail-cell {
|
|
23
|
+
padding: 16px;
|
|
24
|
+
overflow: auto;
|
|
25
|
+
}
|
|
26
|
+
`}d.MasterDetailPlugin=b,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
27
|
+
//# sourceMappingURL=master-detail.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"master-detail.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/master-detail/master-detail.ts","../../../../../libs/grid/src/lib/plugins/master-detail/MasterDetailPlugin.ts"],"sourcesContent":["/**\n * Master/Detail Core Logic\n *\n * Pure functions for managing detail row expansion state.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// Uses `any` for maximum flexibility with user-defined row types.\n\n/**\n * Toggle the expansion state of a detail row.\n * Returns a new Set with the updated state.\n */\nexport function toggleDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n if (newExpanded.has(row)) {\n newExpanded.delete(row);\n } else {\n newExpanded.add(row);\n }\n return newExpanded;\n}\n\n/**\n * Expand a detail row.\n * Returns a new Set with the row added.\n */\nexport function expandDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.add(row);\n return newExpanded;\n}\n\n/**\n * Collapse a detail row.\n * Returns a new Set with the row removed.\n */\nexport function collapseDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.delete(row);\n return newExpanded;\n}\n\n/**\n * Check if a detail row is expanded.\n */\nexport function isDetailExpanded(expandedRows: Set<object>, row: object): boolean {\n return expandedRows.has(row);\n}\n\n/**\n * Create a detail element for a given row.\n * The element spans all columns and contains the rendered content.\n */\nexport function createDetailElement(\n row: any,\n rowIndex: number,\n renderer: (row: any, rowIndex: number) => HTMLElement | string,\n columnCount: number\n): HTMLElement {\n const detailRow = document.createElement('div');\n detailRow.className = 'master-detail-row';\n detailRow.setAttribute('data-detail-for', String(rowIndex));\n detailRow.setAttribute('role', 'row');\n\n const detailCell = document.createElement('div');\n detailCell.className = 'master-detail-cell';\n detailCell.setAttribute('role', 'cell');\n detailCell.style.gridColumn = `1 / ${columnCount + 1}`;\n\n const content = renderer(row, rowIndex);\n if (typeof content === 'string') {\n detailCell.innerHTML = content;\n } else if (content instanceof HTMLElement) {\n detailCell.appendChild(content);\n }\n\n detailRow.appendChild(detailCell);\n return detailRow;\n}\n","/**\n * Master/Detail Plugin (Class-based)\n *\n * Enables expandable detail rows showing additional content for each row.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { BaseGridPlugin, RowClickEvent } from '../../core/plugin/base-plugin';\nimport {\n collapseDetailRow,\n createDetailElement,\n expandDetailRow,\n isDetailExpanded,\n toggleDetailRow,\n} from './master-detail';\nimport type { DetailExpandDetail, MasterDetailConfig } from './types';\n\n/**\n * Master/Detail Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new MasterDetailPlugin({\n * enabled: true,\n * detailRenderer: (row) => `<div>Details for ${row.name}</div>`,\n * expandOnRowClick: true,\n * })\n * ```\n */\nexport class MasterDetailPlugin extends BaseGridPlugin<MasterDetailConfig> {\n readonly name = 'masterDetail';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<MasterDetailConfig> {\n return {\n enabled: true,\n detailHeight: 'auto',\n expandOnRowClick: false,\n collapseOnClickOutside: false,\n showExpandColumn: true,\n };\n }\n\n // ===== Internal State =====\n private expandedRows: Set<any> = new Set();\n private detailElements: Map<any, HTMLElement> = new Map();\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.expandedRows.clear();\n this.detailElements.clear();\n }\n\n // ===== Hooks =====\n\n override processColumns(\n columns: readonly import('../../core/types').ColumnConfig[]\n ): import('../../core/types').ColumnConfig[] {\n if (!this.config.detailRenderer) {\n return [...columns];\n }\n\n // Wrap first column's renderer to add expand/collapse toggle\n const cols = [...columns];\n if (cols.length > 0) {\n const firstCol = { ...cols[0] };\n const originalRenderer = firstCol.viewRenderer;\n\n firstCol.viewRenderer = (renderCtx) => {\n const { value, row } = renderCtx;\n const isExpanded = this.expandedRows.has(row);\n\n const container = document.createElement('span');\n container.className = 'master-detail-cell-wrapper';\n\n // Expand/collapse toggle icon\n const toggle = document.createElement('span');\n toggle.className = 'master-detail-toggle';\n toggle.textContent = isExpanded ? '▼' : '▶';\n toggle.setAttribute('aria-expanded', String(isExpanded));\n toggle.setAttribute('aria-label', isExpanded ? 'Collapse details' : 'Expand details');\n toggle.addEventListener('click', (e) => {\n e.stopPropagation();\n const rowIndex = this.rows.indexOf(row);\n this.expandedRows = toggleDetailRow(this.expandedRows, row);\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex,\n row,\n expanded: this.expandedRows.has(row),\n });\n this.requestRender();\n });\n container.appendChild(toggle);\n\n // Cell content\n const content = document.createElement('span');\n if (originalRenderer) {\n const rendered = originalRenderer(renderCtx);\n if (rendered instanceof Node) {\n content.appendChild(rendered);\n } else {\n content.textContent = String(rendered ?? value ?? '');\n }\n } else {\n content.textContent = String(value ?? '');\n }\n container.appendChild(content);\n\n return container;\n };\n\n cols[0] = firstCol;\n }\n\n return cols;\n }\n\n override onRowClick(event: RowClickEvent): boolean | void {\n if (!this.config.expandOnRowClick || !this.config.detailRenderer) return;\n\n this.expandedRows = toggleDetailRow(this.expandedRows, event.row);\n\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex: event.rowIndex,\n row: event.row,\n expanded: this.expandedRows.has(event.row),\n });\n\n this.requestRender();\n return false;\n }\n\n override afterRender(): void {\n if (!this.config.detailRenderer) return;\n\n const body = this.shadowRoot?.querySelector('.rows');\n if (!body) return;\n\n // Remove old detail rows\n body.querySelectorAll('.master-detail-row').forEach((el) => el.remove());\n this.detailElements.clear();\n\n // Insert detail rows as last child of expanded row elements\n const dataRows = body.querySelectorAll('.data-grid-row');\n const columnCount = this.columns.length;\n\n for (const rowEl of dataRows) {\n const firstCell = rowEl.querySelector('.cell[data-row]');\n const rowIndex = firstCell ? parseInt(firstCell.getAttribute('data-row') ?? '-1', 10) : -1;\n if (rowIndex < 0) continue;\n\n const row = this.rows[rowIndex];\n if (!row || !this.expandedRows.has(row)) continue;\n\n const detailEl = createDetailElement(row, rowIndex, this.config.detailRenderer, columnCount);\n\n if (typeof this.config.detailHeight === 'number') {\n detailEl.style.height = `${this.config.detailHeight}px`;\n }\n\n rowEl.appendChild(detailEl);\n this.detailElements.set(row, detailEl);\n }\n }\n\n // ===== Public API =====\n\n /**\n * Expand the detail row at the given index.\n * @param rowIndex - Index of the row to expand\n */\n expand(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = expandDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Collapse the detail row at the given index.\n * @param rowIndex - Index of the row to collapse\n */\n collapse(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = collapseDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Toggle the detail row at the given index.\n * @param rowIndex - Index of the row to toggle\n */\n toggle(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = toggleDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Check if the detail row at the given index is expanded.\n * @param rowIndex - Index of the row to check\n * @returns Whether the detail row is expanded\n */\n isExpanded(rowIndex: number): boolean {\n const row = this.rows[rowIndex];\n return row ? isDetailExpanded(this.expandedRows, row) : false;\n }\n\n /**\n * Expand all detail rows.\n */\n expandAll(): void {\n for (const row of this.rows) {\n this.expandedRows.add(row);\n }\n this.requestRender();\n }\n\n /**\n * Collapse all detail rows.\n */\n collapseAll(): void {\n this.expandedRows.clear();\n this.requestRender();\n }\n\n /**\n * Get the indices of all expanded rows.\n * @returns Array of row indices that are expanded\n */\n getExpandedRows(): number[] {\n const indices: number[] = [];\n for (const row of this.expandedRows) {\n const idx = this.rows.indexOf(row);\n if (idx >= 0) indices.push(idx);\n }\n return indices;\n }\n\n /**\n * Get the detail element for a specific row.\n * @param rowIndex - Index of the row\n * @returns The detail HTMLElement or undefined\n */\n getDetailElement(rowIndex: number): HTMLElement | undefined {\n const row = this.rows[rowIndex];\n return row ? this.detailElements.get(row) : undefined;\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n .master-detail-cell-wrapper {\n display: flex;\n align-items: center;\n gap: 4px;\n }\n .master-detail-toggle {\n cursor: pointer;\n font-size: 10px;\n opacity: 0.7;\n user-select: none;\n }\n .master-detail-toggle:hover {\n opacity: 1;\n }\n .master-detail-row {\n grid-column: 1 / -1;\n display: grid;\n background: var(--tbw-master-detail-bg, var(--tbw-color-row-alt));\n border-bottom: 1px solid var(--tbw-master-detail-border, var(--tbw-color-border));\n }\n .master-detail-cell {\n padding: 16px;\n overflow: auto;\n }\n `;\n}\n"],"names":["toggleDetailRow","expandedRows","row","newExpanded","expandDetailRow","collapseDetailRow","isDetailExpanded","createDetailElement","rowIndex","renderer","columnCount","detailRow","detailCell","content","MasterDetailPlugin","BaseGridPlugin","columns","cols","firstCol","originalRenderer","renderCtx","value","isExpanded","container","toggle","e","rendered","event","body","el","dataRows","rowEl","firstCell","detailEl","indices","idx"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
(function(d,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],l):(d=typeof globalThis<"u"?globalThis:d||self,l(d.TbwGridPlugin_multiSort={},d.TbwGrid))})(this,(function(d,l){"use strict";function m(e,t,i){return t.length?[...e].sort((n,o)=>{for(const r of t){const c=i.find(a=>a.field===r.field)?.sortComparator??x,g=n[r.field],p=o[r.field],s=c(g,p,n,o);if(s!==0)return r.direction==="asc"?s:-s}return 0}):[...e]}function x(e,t){return e==null&&t==null?0:e==null?1:t==null?-1:typeof e=="number"&&typeof t=="number"?e-t:e instanceof Date&&t instanceof Date?e.getTime()-t.getTime():typeof e=="boolean"&&typeof t=="boolean"?e===t?0:e?-1:1:String(e).localeCompare(String(t))}function M(e,t,i,n){const o=e.find(r=>r.field===t);return i?o?o.direction==="asc"?e.map(r=>r.field===t?{...r,direction:"desc"}:r):e.filter(r=>r.field!==t):e.length<n?[...e,{field:t,direction:"asc"}]:e:o?.direction==="asc"?[{field:t,direction:"desc"}]:o?.direction==="desc"?[]:[{field:t,direction:"asc"}]}function f(e,t){const i=e.findIndex(n=>n.field===t);return i>=0?i+1:void 0}function h(e,t){return e.find(i=>i.field===t)?.direction}class y extends l.BaseGridPlugin{name="multiSort";version="1.0.0";get defaultConfig(){return{enabled:!0,maxSortColumns:3,showSortIndex:!0}}sortModel=[];detach(){this.sortModel=[]}processRows(t){return this.sortModel.length===0?[...t]:m([...t],this.sortModel,[...this.columns])}onHeaderClick(t){if(!this.columns.find(r=>r.field===t.field)?.sortable)return!1;const n=t.originalEvent.shiftKey,o=this.config.maxSortColumns??3;return this.sortModel=M(this.sortModel,t.field,n,o),this.emit("sort-change",{sortModel:[...this.sortModel]}),this.requestRender(),!0}afterRender(){const t=this.shadowRoot;if(!t)return;const i=this.config.showSortIndex!==!1;t.querySelectorAll(".header-row .cell[data-field]").forEach(o=>{const r=o.getAttribute("data-field");if(!r)return;const u=f(this.sortModel,r),c=h(this.sortModel,r);if(o.querySelector(".sort-index")?.remove(),c){o.querySelector('[part~="sort-indicator"], .sort-indicator')?.remove(),o.setAttribute("data-sort",c);const s=document.createElement("span");if(s.className="sort-indicator",s.style.marginLeft="4px",s.style.opacity="0.8",s.textContent=c==="asc"?"▲":"▼",o.appendChild(s),i&&this.sortModel.length>1&&u!==void 0){const a=document.createElement("span");a.className="sort-index",a.textContent=String(u),o.appendChild(a)}}else o.removeAttribute("data-sort")})}getSortModel(){return[...this.sortModel]}setSortModel(t){this.sortModel=[...t],this.emit("sort-change",{sortModel:[...t]}),this.requestRender()}clearSort(){this.sortModel=[],this.emit("sort-change",{sortModel:[]}),this.requestRender()}getSortIndex(t){return f(this.sortModel,t)}getSortDirection(t){return h(this.sortModel,t)}getColumnState(t){const i=this.sortModel.findIndex(o=>o.field===t);return i===-1?void 0:{sort:{direction:this.sortModel[i].direction,priority:i}}}applyColumnState(t,i){if(!i.sort){this.sortModel=this.sortModel.filter(r=>r.field!==t);return}const n=this.sortModel.findIndex(r=>r.field===t),o={field:t,direction:i.sort.direction};n!==-1?this.sortModel[n]=o:this.sortModel.splice(i.sort.priority,0,o)}styles=`
|
|
2
|
+
.header-cell[data-sort="asc"]::after {
|
|
3
|
+
content: '↑';
|
|
4
|
+
margin-left: 4px;
|
|
5
|
+
opacity: 0.8;
|
|
6
|
+
}
|
|
7
|
+
.header-cell[data-sort="desc"]::after {
|
|
8
|
+
content: '↓';
|
|
9
|
+
margin-left: 4px;
|
|
10
|
+
opacity: 0.8;
|
|
11
|
+
}
|
|
12
|
+
.sort-index {
|
|
13
|
+
font-size: 10px;
|
|
14
|
+
background: var(--tbw-multi-sort-badge-bg, var(--tbw-color-panel-bg));
|
|
15
|
+
color: var(--tbw-multi-sort-badge-color, var(--tbw-color-fg));
|
|
16
|
+
border-radius: 50%;
|
|
17
|
+
width: 14px;
|
|
18
|
+
height: 14px;
|
|
19
|
+
display: inline-flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
margin-left: 2px;
|
|
23
|
+
font-weight: 600;
|
|
24
|
+
}
|
|
25
|
+
`}d.MultiSortPlugin=y,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
26
|
+
//# sourceMappingURL=multi-sort.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-sort.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/multi-sort/multi-sort.ts","../../../../../libs/grid/src/lib/plugins/multi-sort/MultiSortPlugin.ts"],"sourcesContent":["/**\n * Multi-Sort Core Logic\n *\n * Pure functions for multi-column sorting operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { SortModel } from './types';\n\n/**\n * Apply multiple sort columns to a row array.\n * Sorts are applied in order - first sort has highest priority.\n *\n * @param rows - Array of row objects to sort\n * @param sorts - Ordered array of sort configurations\n * @param columns - Column configurations (for custom comparators)\n * @returns New sorted array (does not mutate original)\n */\nexport function applySorts<TRow = unknown>(rows: TRow[], sorts: SortModel[], columns: ColumnConfig<TRow>[]): TRow[] {\n if (!sorts.length) return [...rows];\n\n return [...rows].sort((a, b) => {\n for (const sort of sorts) {\n const col = columns.find((c) => c.field === sort.field);\n const comparator = col?.sortComparator ?? defaultComparator;\n const aVal = (a as Record<string, unknown>)[sort.field];\n const bVal = (b as Record<string, unknown>)[sort.field];\n const result = comparator(aVal, bVal, a, b);\n if (result !== 0) {\n return sort.direction === 'asc' ? result : -result;\n }\n }\n return 0;\n });\n}\n\n/**\n * Default comparator for sorting values.\n * Handles nulls, numbers, dates, and strings.\n *\n * @param a - First value\n * @param b - Second value\n * @returns Comparison result (-1, 0, 1)\n */\nexport function defaultComparator(a: unknown, b: unknown): number {\n // Handle nulls/undefined - push to end\n if (a == null && b == null) return 0;\n if (a == null) return 1;\n if (b == null) return -1;\n\n // Type-aware comparison\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b;\n }\n\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() - b.getTime();\n }\n\n // Boolean comparison\n if (typeof a === 'boolean' && typeof b === 'boolean') {\n return a === b ? 0 : a ? -1 : 1;\n }\n\n // String comparison (fallback)\n return String(a).localeCompare(String(b));\n}\n\n/**\n * Toggle sort state for a field.\n * With shift key: adds/toggles in multi-sort list\n * Without shift key: replaces entire sort with single column\n *\n * @param current - Current sort model\n * @param field - Field to toggle\n * @param shiftKey - Whether shift key is held (multi-sort mode)\n * @param maxColumns - Maximum columns allowed in sort\n * @returns New sort model\n */\nexport function toggleSort(current: SortModel[], field: string, shiftKey: boolean, maxColumns: number): SortModel[] {\n const existing = current.find((s) => s.field === field);\n\n if (shiftKey) {\n // Multi-sort: add/toggle in list\n if (existing) {\n if (existing.direction === 'asc') {\n // Flip to descending\n return current.map((s) => (s.field === field ? { ...s, direction: 'desc' as const } : s));\n } else {\n // Remove from sort\n return current.filter((s) => s.field !== field);\n }\n } else if (current.length < maxColumns) {\n // Add new sort column\n return [...current, { field, direction: 'asc' as const }];\n }\n // Max columns reached, return unchanged\n return current;\n } else {\n // Single sort: replace all\n if (existing?.direction === 'asc') {\n return [{ field, direction: 'desc' }];\n } else if (existing?.direction === 'desc') {\n return [];\n }\n return [{ field, direction: 'asc' }];\n }\n}\n\n/**\n * Get the sort index (1-based) for a field in the sort model.\n * Returns undefined if the field is not in the sort model.\n *\n * @param sortModel - Current sort model\n * @param field - Field to check\n * @returns 1-based index or undefined\n */\nexport function getSortIndex(sortModel: SortModel[], field: string): number | undefined {\n const index = sortModel.findIndex((s) => s.field === field);\n return index >= 0 ? index + 1 : undefined;\n}\n\n/**\n * Get the sort direction for a field in the sort model.\n *\n * @param sortModel - Current sort model\n * @param field - Field to check\n * @returns Sort direction or undefined if not sorted\n */\nexport function getSortDirection(sortModel: SortModel[], field: string): 'asc' | 'desc' | undefined {\n return sortModel.find((s) => s.field === field)?.direction;\n}\n","/**\r\n * Multi-Sort Plugin (Class-based)\r\n *\r\n * Provides multi-column sorting capabilities for tbw-grid.\r\n * Supports shift+click for adding secondary sort columns.\r\n */\r\n\r\nimport { BaseGridPlugin, HeaderClickEvent } from '../../core/plugin/base-plugin';\r\nimport type { ColumnState } from '../../core/types';\r\nimport { applySorts, getSortDirection, getSortIndex, toggleSort } from './multi-sort';\r\nimport type { MultiSortConfig, SortModel } from './types';\r\n\r\n/**\r\n * Multi-Sort Plugin for tbw-grid\r\n *\r\n * @example\r\n * ```ts\r\n * new MultiSortPlugin({ maxSortColumns: 3, showSortIndex: true })\r\n * ```\r\n */\r\nexport class MultiSortPlugin extends BaseGridPlugin<MultiSortConfig> {\r\n readonly name = 'multiSort';\r\n override readonly version = '1.0.0';\r\n\r\n protected override get defaultConfig(): Partial<MultiSortConfig> {\r\n return {\r\n enabled: true,\r\n maxSortColumns: 3,\r\n showSortIndex: true,\r\n };\r\n }\r\n\r\n // ===== Internal State =====\r\n private sortModel: SortModel[] = [];\r\n\r\n // ===== Lifecycle =====\r\n\r\n override detach(): void {\r\n this.sortModel = [];\r\n }\r\n\r\n // ===== Hooks =====\r\n\r\n override processRows(rows: readonly unknown[]): unknown[] {\r\n if (this.sortModel.length === 0) {\r\n return [...rows];\r\n }\r\n return applySorts([...rows], this.sortModel, [...this.columns]);\r\n }\r\n\r\n override onHeaderClick(event: HeaderClickEvent): boolean {\r\n const column = this.columns.find((c) => c.field === event.field);\r\n if (!column?.sortable) return false;\r\n\r\n const shiftKey = event.originalEvent.shiftKey;\r\n const maxColumns = this.config.maxSortColumns ?? 3;\r\n\r\n this.sortModel = toggleSort(this.sortModel, event.field, shiftKey, maxColumns);\r\n\r\n this.emit('sort-change', { sortModel: [...this.sortModel] });\r\n this.requestRender();\r\n\r\n return true;\r\n }\r\n\r\n override afterRender(): void {\r\n const shadowRoot = this.shadowRoot;\r\n if (!shadowRoot) return;\r\n\r\n const showIndex = this.config.showSortIndex !== false;\r\n\r\n // Update all sortable header cells with sort indicators\r\n const headerCells = shadowRoot.querySelectorAll('.header-row .cell[data-field]');\r\n headerCells.forEach((cell) => {\r\n const field = cell.getAttribute('data-field');\r\n if (!field) return;\r\n\r\n const sortIndex = getSortIndex(this.sortModel, field);\r\n const sortDir = getSortDirection(this.sortModel, field);\r\n\r\n // Remove existing sort index badge (always clean up)\r\n const existingBadge = cell.querySelector('.sort-index');\r\n existingBadge?.remove();\r\n\r\n if (sortDir) {\r\n // Column is sorted - remove base indicator and add our own\r\n const existingIndicator = cell.querySelector('[part~=\"sort-indicator\"], .sort-indicator');\r\n existingIndicator?.remove();\r\n\r\n cell.setAttribute('data-sort', sortDir);\r\n\r\n // Add sort arrow indicator\r\n const indicator = document.createElement('span');\r\n indicator.className = 'sort-indicator';\r\n indicator.style.marginLeft = '4px';\r\n indicator.style.opacity = '0.8';\r\n indicator.textContent = sortDir === 'asc' ? '▲' : '▼';\r\n cell.appendChild(indicator);\r\n\r\n // Add sort index badge if multiple columns sorted and showSortIndex is enabled\r\n if (showIndex && this.sortModel.length > 1 && sortIndex !== undefined) {\r\n const badge = document.createElement('span');\r\n badge.className = 'sort-index';\r\n badge.textContent = String(sortIndex);\r\n cell.appendChild(badge);\r\n }\r\n } else {\r\n cell.removeAttribute('data-sort');\r\n // For unsorted columns, leave the base indicator (⇅) alone\r\n }\r\n });\r\n }\r\n\r\n // ===== Public API =====\r\n\r\n /**\r\n * Get the current sort model.\r\n * @returns Copy of the current sort model\r\n */\r\n getSortModel(): SortModel[] {\r\n return [...this.sortModel];\r\n }\r\n\r\n /**\r\n * Set the sort model programmatically.\r\n * @param model - New sort model to apply\r\n */\r\n setSortModel(model: SortModel[]): void {\r\n this.sortModel = [...model];\r\n this.emit('sort-change', { sortModel: [...model] });\r\n this.requestRender();\r\n }\r\n\r\n /**\r\n * Clear all sorting.\r\n */\r\n clearSort(): void {\r\n this.sortModel = [];\r\n this.emit('sort-change', { sortModel: [] });\r\n this.requestRender();\r\n }\r\n\r\n /**\r\n * Get the sort index (1-based) for a specific field.\r\n * @param field - Field to check\r\n * @returns 1-based index or undefined if not sorted\r\n */\r\n getSortIndex(field: string): number | undefined {\r\n return getSortIndex(this.sortModel, field);\r\n }\r\n\r\n /**\r\n * Get the sort direction for a specific field.\r\n * @param field - Field to check\r\n * @returns Sort direction or undefined if not sorted\r\n */\r\n getSortDirection(field: string): 'asc' | 'desc' | undefined {\r\n return getSortDirection(this.sortModel, field);\r\n }\r\n\r\n // ===== Column State Hooks =====\r\n\r\n /**\r\n * Return sort state for a column if it's in the sort model.\r\n */\r\n override getColumnState(field: string): Partial<ColumnState> | undefined {\r\n const index = this.sortModel.findIndex((s) => s.field === field);\r\n if (index === -1) return undefined;\r\n\r\n const sortEntry = this.sortModel[index];\r\n return {\r\n sort: {\r\n direction: sortEntry.direction,\r\n priority: index,\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Apply sort state from column state.\r\n * Rebuilds the sort model from all column states.\r\n */\r\n override applyColumnState(field: string, state: ColumnState): void {\r\n // Only process if the column has sort state\r\n if (!state.sort) {\r\n // Remove this field from sortModel if it exists\r\n this.sortModel = this.sortModel.filter((s) => s.field !== field);\r\n return;\r\n }\r\n\r\n // Find existing entry or add new one\r\n const existingIndex = this.sortModel.findIndex((s) => s.field === field);\r\n const newEntry: SortModel = {\r\n field,\r\n direction: state.sort.direction,\r\n };\r\n\r\n if (existingIndex !== -1) {\r\n // Update existing entry\r\n this.sortModel[existingIndex] = newEntry;\r\n } else {\r\n // Add at the correct priority position\r\n this.sortModel.splice(state.sort.priority, 0, newEntry);\r\n }\r\n\r\n // Re-sort the model by priority to ensure correct order\r\n // This is handled after all columns are processed, but we maintain order here\r\n }\r\n\r\n // ===== Styles =====\r\n\r\n override readonly styles = `\r\n .header-cell[data-sort=\"asc\"]::after {\r\n content: '↑';\r\n margin-left: 4px;\r\n opacity: 0.8;\r\n }\r\n .header-cell[data-sort=\"desc\"]::after {\r\n content: '↓';\r\n margin-left: 4px;\r\n opacity: 0.8;\r\n }\r\n .sort-index {\r\n font-size: 10px;\r\n background: var(--tbw-multi-sort-badge-bg, var(--tbw-color-panel-bg));\r\n color: var(--tbw-multi-sort-badge-color, var(--tbw-color-fg));\r\n border-radius: 50%;\r\n width: 14px;\r\n height: 14px;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n margin-left: 2px;\r\n font-weight: 600;\r\n }\r\n `;\r\n}\r\n"],"names":["applySorts","rows","sorts","columns","a","b","sort","comparator","c","defaultComparator","aVal","bVal","result","toggleSort","current","field","shiftKey","maxColumns","existing","s","getSortIndex","sortModel","index","getSortDirection","MultiSortPlugin","BaseGridPlugin","event","shadowRoot","showIndex","cell","sortIndex","sortDir","indicator","badge","model","state","existingIndex","newEntry"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(o,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],c):(o=typeof globalThis<"u"?globalThis:o||self,c(o.TbwGridPlugin_pinnedColumns={},o.TbwGrid))})(this,(function(o,c){"use strict";function p(e){return e.filter(t=>t.sticky==="left")}function m(e){return e.filter(t=>t.sticky==="right")}function d(e){return e.some(t=>t.sticky==="left"||t.sticky==="right")}function g(e,t){const i=e.shadowRoot;if(!i)return;const s=Array.from(i.querySelectorAll(".header-row .cell"));if(!s.length)return;const u=new Map;t.forEach((n,f)=>{n.field&&u.set(n.field,f)});let a=0;for(const n of t)if(n.sticky==="left"){const f=u.get(n.field),l=s.find(r=>r.getAttribute("data-field")===n.field);l&&(l.classList.add("sticky-left"),l.style.left=a+"px",f!==void 0&&i.querySelectorAll(`.data-grid-row .cell[data-col="${f}"]`).forEach(r=>{r.classList.add("sticky-left"),r.style.left=a+"px"}),a+=l.offsetWidth)}let h=0;for(const n of[...t].reverse())if(n.sticky==="right"){const f=u.get(n.field),l=s.find(r=>r.getAttribute("data-field")===n.field);l&&(l.classList.add("sticky-right"),l.style.right=h+"px",f!==void 0&&i.querySelectorAll(`.data-grid-row .cell[data-col="${f}"]`).forEach(r=>{r.classList.add("sticky-right"),r.style.right=h+"px"}),h+=l.offsetWidth)}}function y(e){const t=e.shadowRoot;if(!t)return;t.querySelectorAll(".sticky-left, .sticky-right").forEach(s=>{s.classList.remove("sticky-left","sticky-right"),s.style.left="",s.style.right=""})}class k extends c.BaseGridPlugin{name="pinnedColumns";version="1.0.0";get defaultConfig(){return{enabled:!0}}isApplied=!1;leftOffsets=new Map;rightOffsets=new Map;detach(){this.leftOffsets.clear(),this.rightOffsets.clear(),this.isApplied=!1}static detect(t,i){const s=i?.columns;return Array.isArray(s)?d(s):!1}processColumns(t){return this.config.enabled?(this.isApplied=d([...t]),[...t]):(this.isApplied=!1,[...t])}afterRender(){if(!this.config.enabled||!this.isApplied)return;const t=this.grid,i=[...this.columns];if(!d(i)){y(t),this.isApplied=!1;return}queueMicrotask(()=>{g(t,i)})}refreshStickyOffsets(){const t=[...this.columns];g(this.grid,t)}getLeftPinnedColumns(){const t=[...this.columns];return p(t)}getRightPinnedColumns(){const t=[...this.columns];return m(t)}clearStickyPositions(){y(this.grid)}}o.PinnedColumnsPlugin=k,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})}));
|
|
2
|
+
//# sourceMappingURL=pinned-columns.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pinned-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/pinned-columns/pinned-columns.ts","../../../../../libs/grid/src/lib/plugins/pinned-columns/PinnedColumnsPlugin.ts"],"sourcesContent":["/**\n * Sticky Columns Core Logic\n *\n * Pure functions for applying sticky (pinned) column positioning.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { StickyPosition } from './types';\n\n/**\n * Get columns that should be sticky on the left.\n *\n * @param columns - Array of column configurations\n * @returns Array of columns with sticky='left'\n */\nexport function getLeftStickyColumns(columns: any[]): any[] {\n return columns.filter((col) => col.sticky === 'left');\n}\n\n/**\n * Get columns that should be sticky on the right.\n *\n * @param columns - Array of column configurations\n * @returns Array of columns with sticky='right'\n */\nexport function getRightStickyColumns(columns: any[]): any[] {\n return columns.filter((col) => col.sticky === 'right');\n}\n\n/**\n * Check if any columns have sticky positioning.\n *\n * @param columns - Array of column configurations\n * @returns True if any column has sticky position\n */\nexport function hasStickyColumns(columns: any[]): boolean {\n return columns.some((col) => col.sticky === 'left' || col.sticky === 'right');\n}\n\n/**\n * Get the sticky position of a column.\n *\n * @param column - Column configuration\n * @returns The sticky position or null if not sticky\n */\nexport function getColumnStickyPosition(column: any): StickyPosition | null {\n if (column.sticky === 'left') return 'left';\n if (column.sticky === 'right') return 'right';\n return null;\n}\n\n/**\n * Calculate left offsets for sticky-left columns.\n * Returns a map of field -> offset in pixels.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @returns Map of field to left offset\n */\nexport function calculateLeftStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n for (const col of columns) {\n if (col.sticky === 'left') {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Calculate right offsets for sticky-right columns.\n * Processes columns in reverse order.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @returns Map of field to right offset\n */\nexport function calculateRightStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n // Process in reverse for right-sticky columns\n const reversed = [...columns].reverse();\n for (const col of reversed) {\n if (col.sticky === 'right') {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Apply sticky offsets to header and body cells.\n * This modifies the DOM elements in place.\n *\n * @param host - The grid host element\n * @param columns - Array of column configurations\n */\nexport function applyStickyOffsets(host: HTMLElement, columns: any[]): void {\n const shadowRoot = host.shadowRoot;\n if (!shadowRoot) return;\n\n const headerCells = Array.from(shadowRoot.querySelectorAll('.header-row .cell')) as HTMLElement[];\n if (!headerCells.length) return;\n\n // Build column index map for matching body cells (which use data-col, not data-field)\n const fieldToIndex = new Map<string, number>();\n columns.forEach((col, i) => {\n if (col.field) fieldToIndex.set(col.field, i);\n });\n\n // Apply left sticky\n let left = 0;\n for (const col of columns) {\n if (col.sticky === 'left') {\n const colIndex = fieldToIndex.get(col.field);\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-left');\n cell.style.left = left + 'px';\n // Body cells use data-col (column index), not data-field\n if (colIndex !== undefined) {\n shadowRoot.querySelectorAll(`.data-grid-row .cell[data-col=\"${colIndex}\"]`).forEach((el) => {\n el.classList.add('sticky-left');\n (el as HTMLElement).style.left = left + 'px';\n });\n }\n left += cell.offsetWidth;\n }\n }\n }\n\n // Apply right sticky (process in reverse)\n let right = 0;\n for (const col of [...columns].reverse()) {\n if (col.sticky === 'right') {\n const colIndex = fieldToIndex.get(col.field);\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-right');\n cell.style.right = right + 'px';\n // Body cells use data-col (column index), not data-field\n if (colIndex !== undefined) {\n shadowRoot.querySelectorAll(`.data-grid-row .cell[data-col=\"${colIndex}\"]`).forEach((el) => {\n el.classList.add('sticky-right');\n (el as HTMLElement).style.right = right + 'px';\n });\n }\n right += cell.offsetWidth;\n }\n }\n }\n}\n\n/**\n * Clear sticky positioning from all cells.\n *\n * @param host - The grid host element\n */\nexport function clearStickyOffsets(host: HTMLElement): void {\n const shadowRoot = host.shadowRoot;\n if (!shadowRoot) return;\n\n const cells = shadowRoot.querySelectorAll('.sticky-left, .sticky-right');\n cells.forEach((cell) => {\n cell.classList.remove('sticky-left', 'sticky-right');\n (cell as HTMLElement).style.left = '';\n (cell as HTMLElement).style.right = '';\n });\n}\n","/**\n * Pinned Columns Plugin (Class-based)\n *\n * Enables column pinning (sticky left/right positioning).\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n applyStickyOffsets,\n clearStickyOffsets,\n getLeftStickyColumns,\n getRightStickyColumns,\n hasStickyColumns,\n} from './pinned-columns';\nimport type { PinnedColumnsConfig } from './types';\n\n/**\n * Pinned Columns Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new PinnedColumnsPlugin({ enabled: true })\n * ```\n */\nexport class PinnedColumnsPlugin extends BaseGridPlugin<PinnedColumnsConfig> {\n readonly name = 'pinnedColumns';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<PinnedColumnsConfig> {\n return {\n enabled: true,\n };\n }\n\n // ===== Internal State =====\n private isApplied = false;\n private leftOffsets = new Map<string, number>();\n private rightOffsets = new Map<string, number>();\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.leftOffsets.clear();\n this.rightOffsets.clear();\n this.isApplied = false;\n }\n\n // ===== Detection =====\n\n /**\n * Auto-detect sticky columns from column configuration.\n */\n static detect(rows: readonly unknown[], config: { columns?: ColumnConfig[] }): boolean {\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasStickyColumns(columns);\n }\n\n // ===== Hooks =====\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.config.enabled) {\n this.isApplied = false;\n return [...columns];\n }\n\n // Mark that we have sticky columns to apply\n this.isApplied = hasStickyColumns([...columns]);\n return [...columns];\n }\n\n override afterRender(): void {\n if (!this.config.enabled || !this.isApplied) {\n return;\n }\n\n const host = this.grid as unknown as HTMLElement;\n const columns = [...this.columns];\n\n if (!hasStickyColumns(columns)) {\n clearStickyOffsets(host);\n this.isApplied = false;\n return;\n }\n\n // Apply sticky offsets after a microtask to ensure DOM is ready\n queueMicrotask(() => {\n applyStickyOffsets(host, columns);\n });\n }\n\n // ===== Public API =====\n\n /**\n * Re-apply sticky offsets (e.g., after column resize).\n */\n refreshStickyOffsets(): void {\n const columns = [...this.columns];\n applyStickyOffsets(this.grid as unknown as HTMLElement, columns);\n }\n\n /**\n * Get columns pinned to the left.\n */\n getLeftPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n return getLeftStickyColumns(columns);\n }\n\n /**\n * Get columns pinned to the right.\n */\n getRightPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n return getRightStickyColumns(columns);\n }\n\n /**\n * Clear all sticky positioning.\n */\n clearStickyPositions(): void {\n clearStickyOffsets(this.grid as unknown as HTMLElement);\n }\n}\n"],"names":["getLeftStickyColumns","columns","col","getRightStickyColumns","hasStickyColumns","applyStickyOffsets","host","shadowRoot","headerCells","fieldToIndex","i","left","colIndex","cell","c","el","right","clearStickyOffsets","PinnedColumnsPlugin","BaseGridPlugin","rows","config"],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
(function(d,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):(d=typeof globalThis<"u"?globalThis:d||self,h(d.TbwGridPlugin_pinnedRows={},d.TbwGrid,d.TbwGrid))})(this,(function(d,h,b){"use strict";function f(n,t){const o=document.createElement("div");o.className="tbw-pinned-rows",o.setAttribute("role","status"),o.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",n.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(n.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(n.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(n.customPanels)for(const e of n.customPanels){const a=C(e,t);switch(e.position){case"left":r.appendChild(a);break;case"center":s.appendChild(a);break;case"right":i.appendChild(a);break}}return o.appendChild(r),o.appendChild(s),o.appendChild(i),o}function u(n){const t=document.createElement("div");return t.className=`tbw-aggregation-rows tbw-aggregation-rows-${n}`,t.setAttribute("role","rowgroup"),t}function w(n,t,o,r){n.innerHTML="";for(const s of t){const i=document.createElement("div");if(i.className="tbw-aggregation-row",i.setAttribute("role","row"),s.id&&i.setAttribute("data-aggregation-id",s.id),s.fullWidth){const e=document.createElement("div");e.className="tbw-aggregation-cell tbw-aggregation-cell-full",e.style.gridColumn="1 / -1",e.textContent=s.label||"",i.appendChild(e)}else for(const e of o){const a=document.createElement("div");a.className="tbw-aggregation-cell",a.setAttribute("data-field",e.field);let g;const p=s.aggregators?.[e.field];if(p){const c=b.getAggregator(p);c&&(g=c(r,e.field,e))}else if(s.cells&&Object.prototype.hasOwnProperty.call(s.cells,e.field)){const c=s.cells[e.field];typeof c=="function"?g=c(r,e.field,e):g=c}a.textContent=g!=null?String(g):"",i.appendChild(a)}n.appendChild(i)}}function C(n,t){const o=document.createElement("div");o.className="tbw-status-panel tbw-status-panel-custom",o.id=`status-panel-${n.id}`;const r=n.render(t);return typeof r=="string"?o.innerHTML=r:o.appendChild(r),o}function m(n,t,o,r,s){return{totalRows:n.length,filteredRows:s?.cachedResult?.length??n.length,selectedRows:r?.selected?.size??0,columns:t,rows:n,grid:o}}class v extends h.BaseGridPlugin{name="pinnedRows";version="1.0.0";get defaultConfig(){return{enabled:!0,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(){if(!this.config.enabled){this.cleanup();return}const t=this.shadowRoot;if(!t)return;const o=t.querySelector(".tbw-scroll-area")??t.querySelector(".tbw-grid-content")??t.children[0];if(!o)return;const r=this.getSelectionState(),s=this.getFilterState(),i=m(this.rows,this.columns,this.grid,r,s),e=this.config.aggregationRows||[],a=e.filter(l=>l.position==="top"),g=e.filter(l=>l.position!=="top");if(a.length>0){if(!this.topAggregationContainer){this.topAggregationContainer=u("top");const l=t.querySelector(".header");l&&l.nextSibling?o.insertBefore(this.topAggregationContainer,l.nextSibling):o.appendChild(this.topAggregationContainer)}w(this.topAggregationContainer,a,this.visibleColumns,this.rows)}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,c=p&&this.config.position!=="top",R=g.length>0||c;if(p&&this.config.position==="top")if(!this.infoBarElement)this.infoBarElement=f(this.config,i),o.insertBefore(this.infoBarElement,o.firstChild);else{const l=f(this.config,i);this.infoBarElement.replaceWith(l),this.infoBarElement=l}else this.config.position==="top"&&this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null);R?(this.footerWrapper||(this.footerWrapper=document.createElement("div"),this.footerWrapper.className="tbw-footer",o.appendChild(this.footerWrapper)),this.footerWrapper.innerHTML="",g.length>0&&(this.bottomAggregationContainer||(this.bottomAggregationContainer=u("bottom")),this.footerWrapper.appendChild(this.bottomAggregationContainer),w(this.bottomAggregationContainer,g,this.visibleColumns,this.rows)),c&&(this.infoBarElement=f(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(),o=this.getFilterState();return m(this.rows,this.columns,this.grid,t,o)}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(o=>o.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(o=>o.id!==t),this.requestRender())}styles=`
|
|
2
|
+
.tbw-footer {
|
|
3
|
+
position: sticky;
|
|
4
|
+
bottom: 0;
|
|
5
|
+
z-index: var(--tbw-z-layer-pinned-rows, 20);
|
|
6
|
+
background: var(--tbw-color-panel-bg);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.tbw-pinned-rows {
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: space-between;
|
|
13
|
+
padding: 8px 12px;
|
|
14
|
+
background: var(--tbw-pinned-rows-bg, var(--tbw-color-panel-bg));
|
|
15
|
+
border-top: 1px solid var(--tbw-pinned-rows-border, var(--tbw-color-border));
|
|
16
|
+
font-size: 12px;
|
|
17
|
+
color: var(--tbw-pinned-rows-color, var(--tbw-color-fg-muted));
|
|
18
|
+
min-height: 32px;
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
min-width: fit-content;
|
|
21
|
+
}
|
|
22
|
+
.tbw-pinned-rows-left,
|
|
23
|
+
.tbw-pinned-rows-center,
|
|
24
|
+
.tbw-pinned-rows-right {
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
gap: 16px;
|
|
28
|
+
}
|
|
29
|
+
.tbw-pinned-rows-left {
|
|
30
|
+
justify-content: flex-start;
|
|
31
|
+
}
|
|
32
|
+
.tbw-pinned-rows-center {
|
|
33
|
+
justify-content: center;
|
|
34
|
+
flex: 1;
|
|
35
|
+
}
|
|
36
|
+
.tbw-pinned-rows-right {
|
|
37
|
+
justify-content: flex-end;
|
|
38
|
+
}
|
|
39
|
+
.tbw-status-panel {
|
|
40
|
+
white-space: nowrap;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.tbw-aggregation-rows {
|
|
44
|
+
min-width: fit-content;
|
|
45
|
+
background: var(--tbw-aggregation-bg, var(--tbw-color-header-bg));
|
|
46
|
+
}
|
|
47
|
+
.tbw-aggregation-rows-top {
|
|
48
|
+
border-bottom: 1px solid var(--tbw-aggregation-border, var(--tbw-color-border));
|
|
49
|
+
}
|
|
50
|
+
.tbw-aggregation-rows-bottom {
|
|
51
|
+
border-top: 1px solid var(--tbw-aggregation-border, var(--tbw-color-border));
|
|
52
|
+
}
|
|
53
|
+
.tbw-aggregation-row {
|
|
54
|
+
display: grid;
|
|
55
|
+
grid-template-columns: var(--tbw-column-template);
|
|
56
|
+
font-weight: var(--tbw-aggregation-font-weight, 600);
|
|
57
|
+
}
|
|
58
|
+
.tbw-aggregation-cell {
|
|
59
|
+
padding: var(--tbw-cell-padding, 2px 8px);
|
|
60
|
+
min-height: var(--tbw-row-height, 28px);
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
border-right: 1px solid var(--tbw-color-border-cell);
|
|
64
|
+
}
|
|
65
|
+
.tbw-aggregation-cell:last-child {
|
|
66
|
+
border-right: 0;
|
|
67
|
+
}
|
|
68
|
+
.tbw-aggregation-cell-full {
|
|
69
|
+
grid-column: 1 / -1;
|
|
70
|
+
border-right: 0;
|
|
71
|
+
}
|
|
72
|
+
`}d.PinnedRowsPlugin=v,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
73
|
+
//# sourceMappingURL=pinned-rows.umd.js.map
|