@toolbox-web/grid 1.21.0 → 1.21.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all.js +2 -6850
- package/all.js.map +1 -1
- package/index.js +1 -4352
- package/index.js.map +1 -1
- package/lib/core/grid.d.ts +22 -6
- package/lib/core/grid.d.ts.map +1 -1
- package/lib/core/styles/index.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.js +1 -733
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js +1 -560
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js +1 -754
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
- package/lib/plugins/editing/index.d.ts +1 -1
- package/lib/plugins/editing/index.d.ts.map +1 -1
- package/lib/plugins/editing/index.js +1 -1539
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/editing/types.d.ts +23 -0
- package/lib/plugins/editing/types.d.ts.map +1 -1
- package/lib/plugins/export/index.js +1 -589
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
- package/lib/plugins/filtering/filter-model.d.ts.map +1 -1
- package/lib/plugins/filtering/index.js +1 -1283
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/filtering/types.d.ts +4 -2
- package/lib/plugins/filtering/types.d.ts.map +1 -1
- package/lib/plugins/grouping-columns/index.js +1 -726
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/index.js +2 -905
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js +1 -950
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js +1 -553
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/index.js +1 -688
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/index.js +1 -704
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js +1 -1191
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/print/index.js +1 -691
- package/lib/plugins/print/index.js.map +1 -1
- package/lib/plugins/reorder/index.js +1 -703
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/responsive/index.js +1 -971
- package/lib/plugins/responsive/index.js.map +1 -1
- package/lib/plugins/row-reorder/index.js +1 -728
- package/lib/plugins/row-reorder/index.js.map +1 -1
- package/lib/plugins/selection/index.js +1 -1071
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js +1 -521
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/index.js +1 -686
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js +1 -584
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js +1 -792
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +4 -5
- package/umd/grid.all.umd.js +1 -186
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +1 -90
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/clipboard.umd.js +1 -6
- package/umd/plugins/clipboard.umd.js.map +1 -1
- package/umd/plugins/column-virtualization.umd.js +1 -1
- package/umd/plugins/column-virtualization.umd.js.map +1 -1
- package/umd/plugins/context-menu.umd.js +1 -1
- package/umd/plugins/context-menu.umd.js.map +1 -1
- package/umd/plugins/editing.umd.js +1 -1
- package/umd/plugins/editing.umd.js.map +1 -1
- package/umd/plugins/export.umd.js +1 -13
- package/umd/plugins/export.umd.js.map +1 -1
- package/umd/plugins/filtering.umd.js +1 -1
- package/umd/plugins/filtering.umd.js.map +1 -1
- package/umd/plugins/grouping-columns.umd.js +1 -1
- package/umd/plugins/grouping-columns.umd.js.map +1 -1
- package/umd/plugins/grouping-rows.umd.js +1 -4
- package/umd/plugins/grouping-rows.umd.js.map +1 -1
- package/umd/plugins/master-detail.umd.js +1 -1
- package/umd/plugins/master-detail.umd.js.map +1 -1
- package/umd/plugins/multi-sort.umd.js +1 -1
- package/umd/plugins/multi-sort.umd.js.map +1 -1
- package/umd/plugins/pinned-columns.umd.js +1 -1
- package/umd/plugins/pinned-columns.umd.js.map +1 -1
- package/umd/plugins/pinned-rows.umd.js +1 -1
- package/umd/plugins/pinned-rows.umd.js.map +1 -1
- package/umd/plugins/pivot.umd.js +1 -1
- package/umd/plugins/pivot.umd.js.map +1 -1
- package/umd/plugins/print.umd.js +1 -75
- package/umd/plugins/print.umd.js.map +1 -1
- package/umd/plugins/reorder.umd.js +1 -1
- package/umd/plugins/reorder.umd.js.map +1 -1
- package/umd/plugins/responsive.umd.js +1 -1
- package/umd/plugins/responsive.umd.js.map +1 -1
- package/umd/plugins/row-reorder.umd.js +1 -1
- package/umd/plugins/row-reorder.umd.js.map +1 -1
- package/umd/plugins/selection.umd.js +1 -3
- package/umd/plugins/selection.umd.js.map +1 -1
- package/umd/plugins/server-side.umd.js +1 -1
- package/umd/plugins/server-side.umd.js.map +1 -1
- package/umd/plugins/tree.umd.js +1 -1
- package/umd/plugins/tree.umd.js.map +1 -1
- package/umd/plugins/undo-redo.umd.js +1 -1
- package/umd/plugins/undo-redo.umd.js.map +1 -1
- package/umd/plugins/visibility.umd.js +1 -1
- package/umd/plugins/visibility.umd.js.map +1 -1
|
@@ -1 +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// Import types to enable module augmentation\nimport type { ColumnConfig } from '../../core/types';\nimport './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 = col.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 (groupsOrdered.length === 1 && groupsOrdered[0].implicit && groupsOrdered[0].columns.length === columns.length) {\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[],\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.field) {\n fieldToGroup.set(c.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.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 (final array including any plugin-added columns)\n * @returns The group header row element, or null if no groups\n */\nexport function buildGroupHeaderRow(groups: ColumnGroup[], columns: ColumnConfig[]): 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 // Always compute start index from the current columns array, not stored firstIndex.\n // This accounts for plugin-added columns (e.g., expander) that weren't present\n // when the groups were initially computed during processColumns.\n const firstGroupCol = g.columns[0];\n const startIndex = firstGroupCol ? columns.findIndex((c) => c.field === firstGroupCol.field) : -1;\n if (startIndex === -1) continue; // Group columns not in final column list\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[]): boolean {\n return columns.some((col) => col.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): string | undefined {\n const g = column.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 type { AfterCellRenderContext, PluginManifest, PluginQuery } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnGroupInfo } from '../visibility/types';\nimport {\n applyGroupedHeaderCellClasses,\n buildGroupHeaderRow,\n computeColumnGroups,\n hasColumnGroups,\n} from './grouping-columns';\nimport styles from './grouping-columns.css?inline';\nimport type { ColumnGroup, GroupingColumnsConfig } from './types';\n\n/**\n * Column Grouping Plugin for tbw-grid\n *\n * Enables visual grouping of columns under shared headers. Supports two approaches:\n * declarative `columnGroups` at the grid level, or inline `group` property on columns.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingColumnsPlugin } from '@toolbox-web/grid/plugins/grouping-columns';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `showGroupBorders` | `boolean` | `true` | Show borders between groups |\n * | `groupHeaderRenderer` | `function` | - | Custom renderer for group header content |\n *\n * ## Grid Config: `columnGroups`\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `id` | `string` | Unique group identifier |\n * | `header` | `string` | Display label for the group header |\n * | `children` | `string[]` | Array of column field names in this group |\n *\n * ## Column Config: `group`\n *\n * | Type | Description |\n * |------|-------------|\n * | `string` | Simple group ID (used as both id and label) |\n * | `{ id: string; label?: string }` | Group object with explicit id and optional label |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `isGroupingActive` | `() => boolean` | Check if grouping is active |\n * | `getGroups` | `() => ColumnGroup[]` | Get all computed groups |\n * | `getGroupColumns` | `(groupId) => ColumnConfig[]` | Get columns in a specific group |\n * | `refresh` | `() => void` | Force refresh of column groups |\n *\n * @example Declarative columnGroups (Recommended)\n * ```ts\n * import '@toolbox-web/grid';\n * import { GroupingColumnsPlugin } from '@toolbox-web/grid/plugins/grouping-columns';\n *\n * grid.gridConfig = {\n * columnGroups: [\n * { id: 'personal', header: 'Personal Info', children: ['firstName', 'lastName', 'email'] },\n * { id: 'work', header: 'Work Info', children: ['department', 'title', 'salary'] },\n * ],\n * columns: [\n * { field: 'firstName', header: 'First Name' },\n * { field: 'lastName', header: 'Last Name' },\n * // ...\n * ],\n * plugins: [new GroupingColumnsPlugin()],\n * };\n * ```\n *\n * @example Inline group Property\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'firstName', header: 'First Name', group: { id: 'personal', label: 'Personal Info' } },\n * { field: 'lastName', header: 'Last Name', group: 'personal' }, // string shorthand\n * ],\n * plugins: [new GroupingColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link GroupingColumnsConfig} for all configuration options\n * @see {@link ColumnGroup} for the group structure\n * @see {@link ReorderPlugin} for drag-to-reorder within groups\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingColumnsPlugin extends BaseGridPlugin<GroupingColumnsConfig> {\n /**\n * Plugin manifest - declares owned properties for configuration validation.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'group',\n level: 'column',\n description: 'the \"group\" column property',\n },\n {\n property: 'columnGroups',\n level: 'config',\n description: 'the \"columnGroups\" config property',\n isUsed: (v) => Array.isArray(v) && v.length > 0,\n },\n ],\n queries: [{ type: 'getColumnGrouping', description: 'Returns column group metadata for the visibility panel' }],\n };\n\n /** @internal */\n readonly name = 'groupingColumns';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingColumnsConfig> {\n return {\n showGroupBorders: true,\n lockGroupOrder: false,\n };\n }\n\n // #region Internal State\n private groups: ColumnGroup[] = [];\n private isActive = false;\n /** Fields that are the last column in a group (for group-end border class). */\n #groupEndFields = new Set<string>();\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for cancelable column-move events to enforce group contiguity\n (grid as unknown as HTMLElement).addEventListener('column-move', this.#onColumnMove, {\n signal: this.disconnectSignal,\n });\n }\n\n /** @internal */\n override detach(): void {\n this.groups = [];\n this.isActive = false;\n this.#groupEndFields.clear();\n }\n\n // #region Column Move Guard\n\n /**\n * Handle the cancelable column-move event.\n * - When lockGroupOrder is enabled, prevents moves that would break group contiguity.\n * - Always refreshes #groupEndFields after a successful move so that afterCellRender\n * applies group-end borders to the correct (reordered) last column.\n */\n #onColumnMove = (e: Event): void => {\n if (!this.isActive) return;\n\n const event = e as CustomEvent<{ field: string; columnOrder: string[] }>;\n const { field, columnOrder } = event.detail;\n\n if (this.config.lockGroupOrder) {\n // Check ALL explicit groups — moving any column (grouped or not) could break contiguity\n for (const group of this.groups) {\n if (group.id.startsWith('__implicit__')) continue;\n if (!this.#isGroupContiguous(group, columnOrder)) {\n event.preventDefault();\n this.#flashHeaderCell(field);\n return;\n }\n }\n }\n\n // Recompute group-end fields based on proposed column order.\n // setColumnOrder runs synchronously after this handler returns,\n // but afterCellRender (which reads #groupEndFields) fires during\n // the subsequent refreshVirtualWindow. Precompute using the\n // proposed columnOrder so the borders are correct immediately.\n this.#recomputeGroupEndFields(columnOrder);\n };\n\n /**\n * Recompute which fields are group-end based on a column order.\n * The last field of each explicit group in the order gets the group-end class.\n */\n #recomputeGroupEndFields(columnOrder: string[]): void {\n this.#groupEndFields.clear();\n // Find the last field of each group (including implicit groups between explicit ones).\n // Skip the very last group overall — no adjacent group follows it, so no separator needed.\n const lastGroupEndField = this.#findLastGroupEndField(columnOrder);\n for (const group of this.groups) {\n const groupFields = new Set(group.columns.map((c) => c.field));\n // Walk the column order in reverse to find the last member of this group\n for (let i = columnOrder.length - 1; i >= 0; i--) {\n if (groupFields.has(columnOrder[i])) {\n const field = columnOrder[i];\n // Don't mark the last group's trailing field — nothing follows it\n if (field !== lastGroupEndField) {\n this.#groupEndFields.add(field);\n }\n break;\n }\n }\n }\n }\n\n /**\n * Find the trailing field of the last group in column order (to exclude from group-end marking).\n */\n #findLastGroupEndField(columnOrder: string[]): string | null {\n if (this.groups.length === 0) return null;\n // Determine which group contains the last field in column order\n for (let i = columnOrder.length - 1; i >= 0; i--) {\n const field = columnOrder[i];\n for (const group of this.groups) {\n if (group.columns.some((c) => c.field === field)) {\n // This group is the last in display order — find its last field\n const groupFields = new Set(group.columns.map((c) => c.field));\n for (let j = columnOrder.length - 1; j >= 0; j--) {\n if (groupFields.has(columnOrder[j])) return columnOrder[j];\n }\n }\n }\n }\n return null;\n }\n\n /**\n * Check if all columns in a group are contiguous in the proposed column order.\n */\n #isGroupContiguous(group: ColumnGroup, columnOrder: string[]): boolean {\n const indices = group.columns\n .map((c) => columnOrder.indexOf(c.field))\n .filter((i) => i !== -1)\n .sort((a, b) => a - b);\n if (indices.length <= 1) return true;\n return indices.length === indices[indices.length - 1] - indices[0] + 1;\n }\n\n /**\n * Flash the header cell with an error color to indicate a blocked move.\n */\n #flashHeaderCell(field: string): void {\n const headerCell = this.gridElement?.querySelector(\n `.header-row [part~=\"header-cell\"][data-field=\"${field}\"]`,\n ) as HTMLElement;\n if (!headerCell) return;\n\n headerCell.style.setProperty('--_flash-color', 'var(--tbw-color-error)');\n headerCell.animate(\n [{ backgroundColor: 'rgba(from var(--_flash-color) r g b / 30%)' }, { backgroundColor: 'transparent' }],\n { duration: 400, easing: 'ease-out' },\n );\n }\n // #endregion\n\n /** @internal */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'getColumnGrouping') {\n return this.#getStableColumnGrouping();\n }\n return undefined;\n }\n\n /**\n * Get stable column grouping info that includes ALL columns (visible and hidden).\n * Used by the visibility panel to maintain group structure regardless of visibility state.\n * Fields within each group are sorted by current display order.\n */\n #getStableColumnGrouping(): ColumnGroupInfo[] {\n let result: ColumnGroupInfo[];\n\n // 1. Prefer declarative columnGroups - always complete, visibility-independent\n const columnGroups = this.grid?.gridConfig?.columnGroups;\n if (columnGroups && Array.isArray(columnGroups) && columnGroups.length > 0) {\n result = columnGroups\n .filter((g) => g.children.length > 0)\n .map((g) => ({\n id: g.id,\n label: g.header,\n fields: [...g.children],\n }));\n } else if (this.isActive && this.groups.length > 0) {\n // 2. If active groups exist from processColumns, use them\n result = this.groups\n .filter((g) => !g.id.startsWith('__implicit__'))\n .map<ColumnGroupInfo>((g) => ({\n id: g.id,\n label: g.label ?? g.id,\n fields: g.columns.map((c) => c.field),\n }));\n\n // Also check hidden columns for inline group properties not in active groups\n const allCols = this.columns as ColumnConfig[];\n for (const col of allCols) {\n if ((col as any).hidden && col.group) {\n const gId = typeof col.group === 'string' ? col.group : col.group.id;\n const gLabel = typeof col.group === 'string' ? col.group : (col.group.label ?? col.group.id);\n const existing = result.find((g) => g.id === gId);\n if (existing) {\n if (!existing.fields.includes(col.field)) existing.fields.push(col.field);\n } else {\n result.push({ id: gId, label: gLabel, fields: [col.field] });\n }\n }\n }\n } else {\n // 3. Fall back: scan ALL columns (including hidden) for inline group properties\n const allCols = this.columns as ColumnConfig[];\n const groupMap = new Map<string, ColumnGroupInfo>();\n for (const col of allCols) {\n if (!col.group) continue;\n const gId = typeof col.group === 'string' ? col.group : col.group.id;\n const gLabel = typeof col.group === 'string' ? col.group : (col.group.label ?? col.group.id);\n const existing = groupMap.get(gId);\n if (existing) {\n if (!existing.fields.includes(col.field)) existing.fields.push(col.field);\n } else {\n groupMap.set(gId, { id: gId, label: gLabel, fields: [col.field] });\n }\n }\n result = Array.from(groupMap.values());\n }\n\n // Sort fields within each group by current display order so consumers\n // (e.g. the visibility panel) render columns in their reordered positions.\n const displayOrder = this.grid?.getColumnOrder();\n if (displayOrder && displayOrder.length > 0) {\n const orderIndex = new Map(displayOrder.map((f, i) => [f, i]));\n for (const group of result) {\n group.fields.sort((a, b) => (orderIndex.get(a) ?? Infinity) - (orderIndex.get(b) ?? Infinity));\n }\n }\n\n return result;\n }\n // #endregion\n\n // #region Static Detection\n\n /**\n * Auto-detect column groups from column configuration.\n * Detects both inline `column.group` properties and declarative `columnGroups` config.\n */\n static detect(rows: readonly any[], config: any): boolean {\n // Check for declarative columnGroups in config\n if (config?.columnGroups && Array.isArray(config.columnGroups) && config.columnGroups.length > 0) {\n return true;\n }\n // Check for inline group properties on columns\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasColumnGroups(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // First, check if gridConfig.columnGroups is defined and apply to columns\n const columnGroups = this.grid?.gridConfig?.columnGroups;\n let processedColumns: ColumnConfig[];\n\n if (columnGroups && Array.isArray(columnGroups) && columnGroups.length > 0) {\n // Build a map of field -> group info from the declarative config\n const fieldToGroup = new Map<string, { id: string; label: string }>();\n for (const group of columnGroups) {\n for (const field of group.children) {\n fieldToGroup.set(field, { id: group.id, label: group.header });\n }\n }\n\n // Apply group property to columns that don't already have one\n processedColumns = columns.map((col) => {\n const groupInfo = fieldToGroup.get(col.field);\n if (groupInfo && !col.group) {\n return { ...col, group: groupInfo };\n }\n return col;\n });\n } else {\n processedColumns = [...columns];\n }\n\n // Compute groups from column definitions (now including applied groups)\n const groups = computeColumnGroups(processedColumns);\n\n if (groups.length === 0) {\n this.isActive = false;\n this.groups = [];\n return processedColumns;\n }\n\n this.isActive = true;\n this.groups = groups;\n\n // Pre-compute group-end fields for the afterCellRender hook\n this.#groupEndFields.clear();\n for (const g of groups) {\n const lastCol = g.columns[g.columns.length - 1];\n if (lastCol?.field) {\n this.#groupEndFields.add(lastCol.field);\n }\n }\n\n // Return columns with group info applied\n return processedColumns;\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isActive) {\n // Remove any existing group header\n const header = this.gridElement?.querySelector('.header');\n const existingGroupRow = header?.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n return;\n }\n\n const header = this.gridElement?.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 // Recompute groups from visible columns only (hidden columns have no CSS grid track).\n // This also picks up any plugin-added columns (e.g. expander) that weren't present\n // during processColumns.\n const finalColumns = this.visibleColumns as ColumnConfig[];\n const groups = computeColumnGroups(finalColumns);\n if (groups.length === 0) return;\n\n // Keep #groupEndFields in sync for afterCellRender (covers scheduler-driven renders)\n this.#groupEndFields.clear();\n for (let gi = 0; gi < groups.length; gi++) {\n const g = groups[gi];\n const lastCol = g.columns[g.columns.length - 1];\n // Don't mark the last group — no adjacent group follows it\n if (lastCol?.field && gi < groups.length - 1) {\n this.#groupEndFields.add(lastCol.field);\n }\n }\n\n // Build and insert group header row\n const groupRow = buildGroupHeaderRow(groups, finalColumns);\n if (groupRow) {\n // Toggle border visibility class\n groupRow.classList.toggle('no-borders', !this.config.showGroupBorders);\n\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 // Toggle border visibility on header cells\n headerRow.classList.toggle('no-group-borders', !this.config.showGroupBorders);\n applyGroupedHeaderCellClasses(headerRow, groups, finalColumns);\n }\n }\n\n /**\n * Apply group-end class to individual cells during render and scroll.\n * This is more efficient than querySelectorAll in afterRender and ensures\n * cells recycled during scroll also get the class applied.\n * @internal\n */\n override afterCellRender(context: AfterCellRenderContext): void {\n if (!this.isActive || !this.config.showGroupBorders) return;\n context.cellElement.classList.toggle('group-end', this.#groupEndFields.has(context.column.field));\n }\n // #endregion\n\n // #region 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 // #endregion\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","firstGroupCol","startIndex","isImplicit","label","hasColumnGroups","GroupingColumnsPlugin","BaseGridPlugin","v","styles","#groupEndFields","grid","#onColumnMove","e","event","field","columnOrder","#isGroupContiguous","#flashHeaderCell","#recomputeGroupEndFields","lastGroupEndField","#findLastGroupEndField","groupFields","i","j","indices","a","b","headerCell","query","#getStableColumnGrouping","result","columnGroups","allCols","gId","gLabel","existing","groupMap","displayOrder","orderIndex","rows","config","processedColumns","groupInfo","lastCol","existingGroupRow","header","finalColumns","gi","headerRow","context","groupId"],"mappings":"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"}
|
|
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// Import types to enable module augmentation\nimport type { ColumnConfig } from '../../core/types';\nimport './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 = col.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 (groupsOrdered.length === 1 && groupsOrdered[0].implicit && groupsOrdered[0].columns.length === columns.length) {\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[],\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.field) {\n fieldToGroup.set(c.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.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 (final array including any plugin-added columns)\n * @returns The group header row element, or null if no groups\n */\nexport function buildGroupHeaderRow(groups: ColumnGroup[], columns: ColumnConfig[]): 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 // Always compute start index from the current columns array, not stored firstIndex.\n // This accounts for plugin-added columns (e.g., expander) that weren't present\n // when the groups were initially computed during processColumns.\n const firstGroupCol = g.columns[0];\n const startIndex = firstGroupCol ? columns.findIndex((c) => c.field === firstGroupCol.field) : -1;\n if (startIndex === -1) continue; // Group columns not in final column list\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[]): boolean {\n return columns.some((col) => col.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): string | undefined {\n const g = column.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 type { AfterCellRenderContext, PluginManifest, PluginQuery } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnGroupInfo } from '../visibility/types';\nimport {\n applyGroupedHeaderCellClasses,\n buildGroupHeaderRow,\n computeColumnGroups,\n hasColumnGroups,\n} from './grouping-columns';\nimport styles from './grouping-columns.css?inline';\nimport type { ColumnGroup, GroupingColumnsConfig } from './types';\n\n/**\n * Column Grouping Plugin for tbw-grid\n *\n * Enables visual grouping of columns under shared headers. Supports two approaches:\n * declarative `columnGroups` at the grid level, or inline `group` property on columns.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingColumnsPlugin } from '@toolbox-web/grid/plugins/grouping-columns';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `showGroupBorders` | `boolean` | `true` | Show borders between groups |\n * | `groupHeaderRenderer` | `function` | - | Custom renderer for group header content |\n *\n * ## Grid Config: `columnGroups`\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `id` | `string` | Unique group identifier |\n * | `header` | `string` | Display label for the group header |\n * | `children` | `string[]` | Array of column field names in this group |\n *\n * ## Column Config: `group`\n *\n * | Type | Description |\n * |------|-------------|\n * | `string` | Simple group ID (used as both id and label) |\n * | `{ id: string; label?: string }` | Group object with explicit id and optional label |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `isGroupingActive` | `() => boolean` | Check if grouping is active |\n * | `getGroups` | `() => ColumnGroup[]` | Get all computed groups |\n * | `getGroupColumns` | `(groupId) => ColumnConfig[]` | Get columns in a specific group |\n * | `refresh` | `() => void` | Force refresh of column groups |\n *\n * @example Declarative columnGroups (Recommended)\n * ```ts\n * import '@toolbox-web/grid';\n * import { GroupingColumnsPlugin } from '@toolbox-web/grid/plugins/grouping-columns';\n *\n * grid.gridConfig = {\n * columnGroups: [\n * { id: 'personal', header: 'Personal Info', children: ['firstName', 'lastName', 'email'] },\n * { id: 'work', header: 'Work Info', children: ['department', 'title', 'salary'] },\n * ],\n * columns: [\n * { field: 'firstName', header: 'First Name' },\n * { field: 'lastName', header: 'Last Name' },\n * // ...\n * ],\n * plugins: [new GroupingColumnsPlugin()],\n * };\n * ```\n *\n * @example Inline group Property\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'firstName', header: 'First Name', group: { id: 'personal', label: 'Personal Info' } },\n * { field: 'lastName', header: 'Last Name', group: 'personal' }, // string shorthand\n * ],\n * plugins: [new GroupingColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link GroupingColumnsConfig} for all configuration options\n * @see {@link ColumnGroup} for the group structure\n * @see {@link ReorderPlugin} for drag-to-reorder within groups\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingColumnsPlugin extends BaseGridPlugin<GroupingColumnsConfig> {\n /**\n * Plugin manifest - declares owned properties for configuration validation.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'group',\n level: 'column',\n description: 'the \"group\" column property',\n },\n {\n property: 'columnGroups',\n level: 'config',\n description: 'the \"columnGroups\" config property',\n isUsed: (v) => Array.isArray(v) && v.length > 0,\n },\n ],\n queries: [{ type: 'getColumnGrouping', description: 'Returns column group metadata for the visibility panel' }],\n };\n\n /** @internal */\n readonly name = 'groupingColumns';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingColumnsConfig> {\n return {\n showGroupBorders: true,\n lockGroupOrder: false,\n };\n }\n\n // #region Internal State\n private groups: ColumnGroup[] = [];\n private isActive = false;\n /** Fields that are the last column in a group (for group-end border class). */\n #groupEndFields = new Set<string>();\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for cancelable column-move events to enforce group contiguity\n (grid as unknown as HTMLElement).addEventListener('column-move', this.#onColumnMove, {\n signal: this.disconnectSignal,\n });\n }\n\n /** @internal */\n override detach(): void {\n this.groups = [];\n this.isActive = false;\n this.#groupEndFields.clear();\n }\n\n // #region Column Move Guard\n\n /**\n * Handle the cancelable column-move event.\n * - When lockGroupOrder is enabled, prevents moves that would break group contiguity.\n * - Always refreshes #groupEndFields after a successful move so that afterCellRender\n * applies group-end borders to the correct (reordered) last column.\n */\n #onColumnMove = (e: Event): void => {\n if (!this.isActive) return;\n\n const event = e as CustomEvent<{ field: string; columnOrder: string[] }>;\n const { field, columnOrder } = event.detail;\n\n if (this.config.lockGroupOrder) {\n // Check ALL explicit groups — moving any column (grouped or not) could break contiguity\n for (const group of this.groups) {\n if (group.id.startsWith('__implicit__')) continue;\n if (!this.#isGroupContiguous(group, columnOrder)) {\n event.preventDefault();\n this.#flashHeaderCell(field);\n return;\n }\n }\n }\n\n // Recompute group-end fields based on proposed column order.\n // setColumnOrder runs synchronously after this handler returns,\n // but afterCellRender (which reads #groupEndFields) fires during\n // the subsequent refreshVirtualWindow. Precompute using the\n // proposed columnOrder so the borders are correct immediately.\n this.#recomputeGroupEndFields(columnOrder);\n };\n\n /**\n * Recompute which fields are group-end based on a column order.\n * The last field of each explicit group in the order gets the group-end class.\n */\n #recomputeGroupEndFields(columnOrder: string[]): void {\n this.#groupEndFields.clear();\n // Find the last field of each group (including implicit groups between explicit ones).\n // Skip the very last group overall — no adjacent group follows it, so no separator needed.\n const lastGroupEndField = this.#findLastGroupEndField(columnOrder);\n for (const group of this.groups) {\n const groupFields = new Set(group.columns.map((c) => c.field));\n // Walk the column order in reverse to find the last member of this group\n for (let i = columnOrder.length - 1; i >= 0; i--) {\n if (groupFields.has(columnOrder[i])) {\n const field = columnOrder[i];\n // Don't mark the last group's trailing field — nothing follows it\n if (field !== lastGroupEndField) {\n this.#groupEndFields.add(field);\n }\n break;\n }\n }\n }\n }\n\n /**\n * Find the trailing field of the last group in column order (to exclude from group-end marking).\n */\n #findLastGroupEndField(columnOrder: string[]): string | null {\n if (this.groups.length === 0) return null;\n // Determine which group contains the last field in column order\n for (let i = columnOrder.length - 1; i >= 0; i--) {\n const field = columnOrder[i];\n for (const group of this.groups) {\n if (group.columns.some((c) => c.field === field)) {\n // This group is the last in display order — find its last field\n const groupFields = new Set(group.columns.map((c) => c.field));\n for (let j = columnOrder.length - 1; j >= 0; j--) {\n if (groupFields.has(columnOrder[j])) return columnOrder[j];\n }\n }\n }\n }\n return null;\n }\n\n /**\n * Check if all columns in a group are contiguous in the proposed column order.\n */\n #isGroupContiguous(group: ColumnGroup, columnOrder: string[]): boolean {\n const indices = group.columns\n .map((c) => columnOrder.indexOf(c.field))\n .filter((i) => i !== -1)\n .sort((a, b) => a - b);\n if (indices.length <= 1) return true;\n return indices.length === indices[indices.length - 1] - indices[0] + 1;\n }\n\n /**\n * Flash the header cell with an error color to indicate a blocked move.\n */\n #flashHeaderCell(field: string): void {\n const headerCell = this.gridElement?.querySelector(\n `.header-row [part~=\"header-cell\"][data-field=\"${field}\"]`,\n ) as HTMLElement;\n if (!headerCell) return;\n\n headerCell.style.setProperty('--_flash-color', 'var(--tbw-color-error)');\n headerCell.animate(\n [{ backgroundColor: 'rgba(from var(--_flash-color) r g b / 30%)' }, { backgroundColor: 'transparent' }],\n { duration: 400, easing: 'ease-out' },\n );\n }\n // #endregion\n\n /** @internal */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'getColumnGrouping') {\n return this.#getStableColumnGrouping();\n }\n return undefined;\n }\n\n /**\n * Get stable column grouping info that includes ALL columns (visible and hidden).\n * Used by the visibility panel to maintain group structure regardless of visibility state.\n * Fields within each group are sorted by current display order.\n */\n #getStableColumnGrouping(): ColumnGroupInfo[] {\n let result: ColumnGroupInfo[];\n\n // 1. Prefer declarative columnGroups - always complete, visibility-independent\n const columnGroups = this.grid?.gridConfig?.columnGroups;\n if (columnGroups && Array.isArray(columnGroups) && columnGroups.length > 0) {\n result = columnGroups\n .filter((g) => g.children.length > 0)\n .map((g) => ({\n id: g.id,\n label: g.header,\n fields: [...g.children],\n }));\n } else if (this.isActive && this.groups.length > 0) {\n // 2. If active groups exist from processColumns, use them\n result = this.groups\n .filter((g) => !g.id.startsWith('__implicit__'))\n .map<ColumnGroupInfo>((g) => ({\n id: g.id,\n label: g.label ?? g.id,\n fields: g.columns.map((c) => c.field),\n }));\n\n // Also check hidden columns for inline group properties not in active groups\n const allCols = this.columns as ColumnConfig[];\n for (const col of allCols) {\n if ((col as any).hidden && col.group) {\n const gId = typeof col.group === 'string' ? col.group : col.group.id;\n const gLabel = typeof col.group === 'string' ? col.group : (col.group.label ?? col.group.id);\n const existing = result.find((g) => g.id === gId);\n if (existing) {\n if (!existing.fields.includes(col.field)) existing.fields.push(col.field);\n } else {\n result.push({ id: gId, label: gLabel, fields: [col.field] });\n }\n }\n }\n } else {\n // 3. Fall back: scan ALL columns (including hidden) for inline group properties\n const allCols = this.columns as ColumnConfig[];\n const groupMap = new Map<string, ColumnGroupInfo>();\n for (const col of allCols) {\n if (!col.group) continue;\n const gId = typeof col.group === 'string' ? col.group : col.group.id;\n const gLabel = typeof col.group === 'string' ? col.group : (col.group.label ?? col.group.id);\n const existing = groupMap.get(gId);\n if (existing) {\n if (!existing.fields.includes(col.field)) existing.fields.push(col.field);\n } else {\n groupMap.set(gId, { id: gId, label: gLabel, fields: [col.field] });\n }\n }\n result = Array.from(groupMap.values());\n }\n\n // Sort fields within each group by current display order so consumers\n // (e.g. the visibility panel) render columns in their reordered positions.\n const displayOrder = this.grid?.getColumnOrder();\n if (displayOrder && displayOrder.length > 0) {\n const orderIndex = new Map(displayOrder.map((f, i) => [f, i]));\n for (const group of result) {\n group.fields.sort((a, b) => (orderIndex.get(a) ?? Infinity) - (orderIndex.get(b) ?? Infinity));\n }\n }\n\n return result;\n }\n // #endregion\n\n // #region Static Detection\n\n /**\n * Auto-detect column groups from column configuration.\n * Detects both inline `column.group` properties and declarative `columnGroups` config.\n */\n static detect(rows: readonly any[], config: any): boolean {\n // Check for declarative columnGroups in config\n if (config?.columnGroups && Array.isArray(config.columnGroups) && config.columnGroups.length > 0) {\n return true;\n }\n // Check for inline group properties on columns\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasColumnGroups(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // First, check if gridConfig.columnGroups is defined and apply to columns\n const columnGroups = this.grid?.gridConfig?.columnGroups;\n let processedColumns: ColumnConfig[];\n\n if (columnGroups && Array.isArray(columnGroups) && columnGroups.length > 0) {\n // Build a map of field -> group info from the declarative config\n const fieldToGroup = new Map<string, { id: string; label: string }>();\n for (const group of columnGroups) {\n for (const field of group.children) {\n fieldToGroup.set(field, { id: group.id, label: group.header });\n }\n }\n\n // Apply group property to columns that don't already have one\n processedColumns = columns.map((col) => {\n const groupInfo = fieldToGroup.get(col.field);\n if (groupInfo && !col.group) {\n return { ...col, group: groupInfo };\n }\n return col;\n });\n } else {\n processedColumns = [...columns];\n }\n\n // Compute groups from column definitions (now including applied groups)\n const groups = computeColumnGroups(processedColumns);\n\n if (groups.length === 0) {\n this.isActive = false;\n this.groups = [];\n return processedColumns;\n }\n\n this.isActive = true;\n this.groups = groups;\n\n // Pre-compute group-end fields for the afterCellRender hook\n this.#groupEndFields.clear();\n for (const g of groups) {\n const lastCol = g.columns[g.columns.length - 1];\n if (lastCol?.field) {\n this.#groupEndFields.add(lastCol.field);\n }\n }\n\n // Return columns with group info applied\n return processedColumns;\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isActive) {\n // Remove any existing group header\n const header = this.gridElement?.querySelector('.header');\n const existingGroupRow = header?.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n return;\n }\n\n const header = this.gridElement?.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 // Recompute groups from visible columns only (hidden columns have no CSS grid track).\n // This also picks up any plugin-added columns (e.g. expander) that weren't present\n // during processColumns.\n const finalColumns = this.visibleColumns as ColumnConfig[];\n const groups = computeColumnGroups(finalColumns);\n if (groups.length === 0) return;\n\n // Keep #groupEndFields in sync for afterCellRender (covers scheduler-driven renders)\n this.#groupEndFields.clear();\n for (let gi = 0; gi < groups.length; gi++) {\n const g = groups[gi];\n const lastCol = g.columns[g.columns.length - 1];\n // Don't mark the last group — no adjacent group follows it\n if (lastCol?.field && gi < groups.length - 1) {\n this.#groupEndFields.add(lastCol.field);\n }\n }\n\n // Build and insert group header row\n const groupRow = buildGroupHeaderRow(groups, finalColumns);\n if (groupRow) {\n // Toggle border visibility class\n groupRow.classList.toggle('no-borders', !this.config.showGroupBorders);\n\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 // Toggle border visibility on header cells\n headerRow.classList.toggle('no-group-borders', !this.config.showGroupBorders);\n applyGroupedHeaderCellClasses(headerRow, groups, finalColumns);\n }\n }\n\n /**\n * Apply group-end class to individual cells during render and scroll.\n * This is more efficient than querySelectorAll in afterRender and ensures\n * cells recycled during scroll also get the class applied.\n * @internal\n */\n override afterCellRender(context: AfterCellRenderContext): void {\n if (!this.isActive || !this.config.showGroupBorders) return;\n context.cellElement.classList.toggle('group-end', this.#groupEndFields.has(context.column.field));\n }\n // #endregion\n\n // #region 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 // #endregion\n}\n"],"names":["computeColumnGroups","columns","length","explicitMap","Map","groupsOrdered","pushImplicit","startIdx","cols","prev","implicit","firstIndex","push","id","label","run","runStart","forEach","col","idx","g","group","slice","get","set","GroupingColumnsPlugin","BaseGridPlugin","static","ownedProperties","property","level","description","isUsed","v","Array","isArray","queries","type","name","styles","defaultConfig","showGroupBorders","lockGroupOrder","groups","isActive","groupEndFields","Set","attach","grid","super","addEventListener","this","onColumnMove","signal","disconnectSignal","detach","clear","e","event","field","columnOrder","detail","config","startsWith","isGroupContiguous","preventDefault","flashHeaderCell","recomputeGroupEndFields","lastGroupEndField","findLastGroupEndField","groupFields","map","c","i","has","add","some","j","indices","indexOf","filter","sort","a","b","headerCell","gridElement","querySelector","style","setProperty","animate","backgroundColor","duration","easing","handleQuery","query","getStableColumnGrouping","result","columnGroups","gridConfig","children","header","fields","allCols","hidden","gId","gLabel","existing","find","includes","groupMap","from","values","displayOrder","getColumnOrder","orderIndex","f","Infinity","detect","rows","hasColumnGroups","processColumns","processedColumns","fieldToGroup","groupInfo","lastCol","afterRender","existingGroupRow","remove","finalColumns","visibleColumns","gi","groupRow","document","createElement","className","setAttribute","firstGroupCol","startIndex","findIndex","isImplicit","String","cell","classList","gridColumn","textContent","appendChild","buildGroupHeaderRow","toggle","headerRow","insertBefore","headerRowEl","headerCells","querySelectorAll","getAttribute","gid","last","applyGroupedHeaderCellClasses","afterCellRender","context","cellElement","column","isGroupingActive","getGroups","getGroupColumns","groupId","refresh","requestRender"],"mappings":"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"}
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
(function(w,m){typeof exports=="object"&&typeof module<"u"?m(exports,require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column"),require("../../core/internal/aggregators")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin","../../core/plugin/expander-column","../../core/internal/aggregators"],m):(w=typeof globalThis<"u"?globalThis:w||self,m(w.TbwGridPlugin_groupingRows={},w.TbwGrid,w.TbwGrid,w.TbwGrid))})(this,(function(w,m,b,x){"use strict";function _({rows:d,config:e,expanded:t,initialExpanded:s}){const r=e.groupOn;if(typeof r!="function")return[];const o={key:"__root__",value:null,depth:-1,rows:[],children:new Map};if(d.forEach(a=>{let u=r(a);u==null||u===!1?u=["__ungrouped__"]:Array.isArray(u)||(u=[u]);let i=o;u.forEach((y,g)=>{const c=y==null?"∅":String(y),h=i.key==="__root__"?c:i.key+"||"+c;let f=i.children.get(c);f||(f={key:h,value:y,depth:g,rows:[],children:new Map,parent:i},i.children.set(c,f)),i=f}),i.rows.push(a)}),o.children.size===1&&o.children.has("__ungrouped__")&&o.children.get("__ungrouped__").rows.length===d.length)return[];const p=new Set([...t,...s??[]]),n=[],l=a=>{if(a===o){a.children.forEach(i=>l(i));return}const u=p.has(a.key);n.push({kind:"group",key:a.key,value:a.value,depth:a.depth,rows:a.rows,expanded:u}),u&&(a.children.size?a.children.forEach(i=>l(i)):a.rows.forEach(i=>n.push({kind:"data",row:i,rowIndex:d.indexOf(i)})))};return l(o),n}function R(d,e){const t=new Set(d);return t.has(e)?t.delete(e):t.add(e),t}function v(d){const e=new Set;for(const t of d)t.kind==="group"&&e.add(t.key);return e}function K(){return new Set}function A(d,e){if(d===!0)return new Set(e);if(d===!1||d==null)return new Set;if(typeof d=="number"){const t=e[d];return t?new Set([t]):new Set}return typeof d=="string"?new Set([d]):Array.isArray(d)?new Set(d):new Set}function C(d){return d.filter(e=>e.kind==="group").map(e=>e.key)}function E(d){return d.kind!=="group"?0:d.rows.length}const S="@layer tbw-plugins{.group-row{display:grid;grid-template-columns:var(--tbw-column-template);background:var(--tbw-grouping-rows-bg, var(--tbw-color-panel-bg));font-weight:500;border-bottom:var(--tbw-row-divider);min-height:var(--tbw-row-height)}.group-row .cell{display:flex;align-items:center;padding:var(--tbw-cell-padding, .125rem .5rem)}.group-row:hover{background:var(--tbw-grouping-rows-bg-hover, var(--tbw-color-row-hover))}.group-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center;width:var(--tbw-toggle-size, 1.25rem);height:var(--tbw-toggle-size, 1.25rem);margin-right:.25rem;background:none;border:0;font:inherit}.group-toggle:hover{background:var(--tbw-grouping-rows-toggle-hover, var(--tbw-color-row-hover));border-radius:var(--tbw-border-radius, .125rem)}.group-label{display:inline-flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.group-count{color:var(--tbw-grouping-rows-count-color, var(--tbw-color-fg-muted));font-size:var(--tbw-font-size-xs, .85em);font-weight:400}.group-aggregates{display:inline-flex;align-items:center;gap:var(--tbw-spacing-lg, 1rem);margin-left:var(--tbw-spacing-lg, 1rem);font-weight:400;font-size:var(--tbw-font-size-sm, .875em);color:var(--tbw-grouping-rows-aggregate-color, var(--tbw-color-fg-muted))}.group-aggregate{white-space:nowrap}.group-row{padding-left:calc(var(--tbw-group-depth, 0) * var(--tbw-group-indent-width, 1.25em))}.data-grid-row.tbw-group-slide-in{animation:tbw-group-slide-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-group-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}.data-grid-row.tbw-group-fade-in{animation:tbw-group-fade-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-group-fade-in{0%{opacity:0}to{opacity:1}}}";class k extends m.BaseGridPlugin{static manifest={events:[{type:"grouping-state-change",description:"Emitted when groups are expanded/collapsed. Subscribers can react to row visibility changes."}],queries:[{type:"canMoveRow",description:"Returns false for group header rows (cannot be reordered)"}],configRules:[{id:"groupingRows/accordion-defaultExpanded",severity:"warn",message:`"accordion: true" and "defaultExpanded" (non-false) are used together.
|
|
2
|
-
→ In accordion mode, only one group can be open at a time.
|
|
3
|
-
→ Using defaultExpanded with multiple groups will collapse to one on first toggle.
|
|
4
|
-
→ Consider using "defaultExpanded: false" or a single group key/index with accordion mode.`,check:e=>e.accordion===!0&&e.defaultExpanded!==!1&&e.defaultExpanded!==void 0&&typeof e.defaultExpanded!="number"&&typeof e.defaultExpanded!="string"&&(e.defaultExpanded===!0||Array.isArray(e.defaultExpanded)&&e.defaultExpanded.length>1)}]};name="groupingRows";styles=S;get defaultConfig(){return{defaultExpanded:!1,showRowCount:!0,indentWidth:20,aggregators:{},animation:"slide",accordion:!1}}expandedKeys=new Set;flattenedRows=[];isActive=!1;previousVisibleKeys=new Set;keysToAnimate=new Set;hasAppliedDefaultExpanded=!1;get animationStyle(){return this.isAnimationEnabled?this.config.animation??"slide":!1}detach(){this.expandedKeys.clear(),this.flattenedRows=[],this.isActive=!1,this.previousVisibleKeys.clear(),this.keysToAnimate.clear(),this.hasAppliedDefaultExpanded=!1}getRowHeight(e,t){if(this.config.groupRowHeight!=null&&e.__isGroupRow===!0)return this.config.groupRowHeight}handleQuery(e){if(e.type==="canMoveRow"&&e.context?.__isGroupRow===!0)return!1}static detect(e,t){return typeof t?.groupOn=="function"||typeof t?.enableRowGrouping=="boolean"}processRows(e){const t=this.config;if(typeof t.groupOn!="function")return this.isActive=!1,this.flattenedRows=[],[...e];const s=_({rows:[...e],config:t,expanded:new Set});if(s.length===0)return this.isActive=!1,this.flattenedRows=[],[...e];let r;if(!this.hasAppliedDefaultExpanded&&this.expandedKeys.size===0&&t.defaultExpanded!==!1){const n=C(s);r=A(t.defaultExpanded??!1,n),r.size>0&&(this.expandedKeys=new Set(r),this.hasAppliedDefaultExpanded=!0)}const o=_({rows:[...e],config:t,expanded:this.expandedKeys,initialExpanded:r});this.isActive=!0,this.flattenedRows=o,this.keysToAnimate.clear();const p=new Set;return o.forEach((n,l)=>{if(n.kind==="data"){const a=`data-${l}`;p.add(a),this.previousVisibleKeys.has(a)||this.keysToAnimate.add(a)}}),this.previousVisibleKeys=p,o.map(n=>n.kind==="group"?{__isGroupRow:!0,__groupKey:n.key,__groupValue:n.value,__groupDepth:n.depth,__groupRows:n.rows,__groupExpanded:n.expanded,__groupRowCount:E(n),__rowCacheKey:`group:${n.key}`}:n.row)}onCellClick(e){const t=e.row;if(t?.__isGroupRow&&e.originalEvent.target?.closest(".group-toggle"))return this.toggle(t.__groupKey),!0}onKeyDown(e){if(e.key!==" ")return;const t=this.grid._focusRow,s=this.rows[t];if(s?.__isGroupRow)return e.preventDefault(),this.toggle(s.__groupKey),this.requestRenderWithFocus(),!0}renderRow(e,t,s){if(!e?.__isGroupRow)return!1;const r=this.config;if(r.groupRowRenderer){const n=()=>{this.toggle(e.__groupKey)},l=r.groupRowRenderer({key:e.__groupKey,value:e.__groupValue,depth:e.__groupDepth,rows:e.__groupRows,expanded:e.__groupExpanded,toggleExpand:n});if(l)return t.className="data-grid-row group-row",t.__isCustomRow=!0,t.setAttribute("data-group-depth",String(e.__groupDepth)),typeof l=="string"?t.innerHTML=l:(t.innerHTML="",t.appendChild(l)),!0}const o=()=>{this.toggle(e.__groupKey)};return t.className="data-grid-row 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.setProperty("--tbw-group-depth",String(e.__groupDepth||0)),r.indentWidth!==void 0&&t.style.setProperty("--tbw-group-indent-width",`${r.indentWidth}px`),t.style.height="",t.innerHTML="",r.fullWidth!==!1?this.renderFullWidthGroupRow(e,t,o):this.renderPerColumnGroupRow(e,t,o),!0}afterRender(){const e=this.animationStyle;if(e===!1||this.keysToAnimate.size===0)return;const t=this.gridElement?.querySelector(".rows");if(!t)return;const s=e==="fade"?"tbw-group-fade-in":"tbw-group-slide-in";for(const r of t.querySelectorAll(".data-grid-row:not(.group-row)")){const o=r.querySelector(".cell[data-row]"),p=o?parseInt(o.getAttribute("data-row")??"-1",10):-1,l=this.flattenedRows[p]?.kind==="data"?`data-${p}`:void 0;l&&this.keysToAnimate.has(l)&&(r.classList.add(s),r.addEventListener("animationend",()=>r.classList.remove(s),{once:!0}))}this.keysToAnimate.clear()}createToggleButton(e,t){const s=document.createElement("button");return s.type="button",s.className=`group-toggle${e?" expanded":""}`,s.setAttribute("aria-label",e?"Collapse group":"Expand group"),this.setIcon(s,this.resolveIcon(e?"collapse":"expand")),s.addEventListener("click",r=>{r.stopPropagation(),t()}),s}getGroupLabelText(e,t,s){const r=this.config;return r.formatLabel?r.formatLabel(e,t,s):String(e)}renderFullWidthGroupRow(e,t,s){const r=this.config,o=r.aggregators??{},p=e.__groupRows??[],n=document.createElement("div");n.className="cell group-full",n.style.gridColumn="1 / -1",n.setAttribute("role","gridcell"),n.setAttribute("data-col","0"),n.appendChild(this.createToggleButton(e.__groupExpanded,s));const l=document.createElement("span");if(l.className="group-label",l.textContent=this.getGroupLabelText(e.__groupValue,e.__groupDepth||0,e.__groupKey),n.appendChild(l),r.showRowCount!==!1){const u=document.createElement("span");u.className="group-count",u.textContent=`(${e.__groupRowCount??e.__groupRows?.length??0})`,n.appendChild(u)}const a=Object.entries(o);if(a.length>0){const u=document.createElement("span");u.className="group-aggregates";for(const[i,y]of a){const g=this.columns.find(h=>h.field===i),c=x.runAggregator(y,p,i,g);if(c!=null){const h=document.createElement("span");h.className="group-aggregate",h.setAttribute("data-field",i);const f=g?.header??i;h.textContent=`${f}: ${c}`,u.appendChild(h)}}u.children.length>0&&n.appendChild(u)}t.appendChild(n)}renderPerColumnGroupRow(e,t,s){const r=this.config,o=r.aggregators??{},p=this.columns,n=e.__groupRows??[],a=this.gridElement?.querySelector(".body")?.style.gridTemplateColumns||"";a&&(t.style.display="grid",t.style.gridTemplateColumns=a);let u=!1;p.forEach((i,y)=>{const g=document.createElement("div");if(g.className="cell group-cell",g.setAttribute("data-col",String(y)),g.setAttribute("role","gridcell"),b.isExpanderColumn(i)){g.setAttribute("data-field",i.field),t.appendChild(g);return}if(u){const c=o[i.field];if(c){const h=x.runAggregator(c,n,i.field,i);g.textContent=h!=null?String(h):""}else g.textContent=""}else{u=!0,g.appendChild(this.createToggleButton(e.__groupExpanded,s));const c=document.createElement("span"),h=o[i.field];if(h){const f=x.runAggregator(h,n,i.field,i);c.textContent=f!=null?String(f):String(e.__groupValue)}else c.textContent=this.getGroupLabelText(e.__groupValue,e.__groupDepth||0,e.__groupKey);if(g.appendChild(c),r.showRowCount!==!1){const f=document.createElement("span");f.className="group-count",f.textContent=` (${n.length})`,g.appendChild(f)}}t.appendChild(g)})}expandAll(){this.expandedKeys=v(this.flattenedRows),this.emitPluginEvent("grouping-state-change",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}collapseAll(){this.expandedKeys=K(),this.emitPluginEvent("grouping-state-change",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}toggle(e){const t=!this.expandedKeys.has(e),s=this.config,r=this.flattenedRows.find(o=>o.kind==="group"&&o.key===e);if(s.accordion&&t&&r){const o=new Set;for(const p of this.expandedKeys)if(e.startsWith(p+"||")||p.startsWith(e+"||"))e.startsWith(p+"||")&&o.add(p);else{const n=this.flattenedRows.find(l=>l.kind==="group"&&l.key===p);n&&n.depth!==r.depth&&o.add(p)}o.add(e),this.expandedKeys=o}else this.expandedKeys=R(this.expandedKeys,e);this.emit("group-toggle",{key:e,expanded:this.expandedKeys.has(e),value:r?.value,depth:r?.depth??0}),this.emitPluginEvent("grouping-state-change",{expandedKeys:[...this.expandedKeys]}),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()}}w.GroupingRowsPlugin=k,Object.defineProperty(w,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column"),require("../../core/internal/aggregators")):"function"==typeof define&&define.amd?define(["exports","../../core/plugin/base-plugin","../../core/plugin/expander-column","../../core/internal/aggregators"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_groupingRows={},e.TbwGrid,e.TbwGrid,e.TbwGrid)}(this,function(e,t,n,r){"use strict";function o({rows:e,config:t,expanded:n,initialExpanded:r}){const o=t.groupOn;if("function"!=typeof o)return[];const i={key:"__root__",value:null,depth:-1,rows:[],children:new Map};if(e.forEach(e=>{let t=o(e);null==t||!1===t?t=["__ungrouped__"]:Array.isArray(t)||(t=[t]);let n=i;t.forEach((e,t)=>{const r=null==e?"∅":String(e),o="__root__"===n.key?r:n.key+"||"+r;let i=n.children.get(r);i||(i={key:o,value:e,depth:t,rows:[],children:new Map,parent:n},n.children.set(r,i)),n=i}),n.rows.push(e)}),1===i.children.size&&i.children.has("__ungrouped__")){if(i.children.get("__ungrouped__").rows.length===e.length)return[]}const s=new Set([...n,...r??[]]),a=[],d=t=>{if(t===i)return void t.children.forEach(e=>d(e));const n=s.has(t.key);a.push({kind:"group",key:t.key,value:t.value,depth:t.depth,rows:t.rows,expanded:n}),n&&(t.children.size?t.children.forEach(e=>d(e)):t.rows.forEach(t=>a.push({kind:"data",row:t,rowIndex:e.indexOf(t)})))};return d(i),a}class i extends t.BaseGridPlugin{static manifest={events:[{type:"grouping-state-change",description:"Emitted when groups are expanded/collapsed. Subscribers can react to row visibility changes."}],queries:[{type:"canMoveRow",description:"Returns false for group header rows (cannot be reordered)"}],configRules:[{id:"groupingRows/accordion-defaultExpanded",severity:"warn",message:'"accordion: true" and "defaultExpanded" (non-false) are used together.\n → In accordion mode, only one group can be open at a time.\n → Using defaultExpanded with multiple groups will collapse to one on first toggle.\n → Consider using "defaultExpanded: false" or a single group key/index with accordion mode.',check:e=>!0===e.accordion&&!1!==e.defaultExpanded&&void 0!==e.defaultExpanded&&!("number"==typeof e.defaultExpanded)&&!("string"==typeof e.defaultExpanded)&&(!0===e.defaultExpanded||Array.isArray(e.defaultExpanded)&&e.defaultExpanded.length>1)}]};name="groupingRows";styles="@layer tbw-plugins{.group-row{display:grid;grid-template-columns:var(--tbw-column-template);background:var(--tbw-grouping-rows-bg, var(--tbw-color-panel-bg));font-weight:500;border-bottom:var(--tbw-row-divider);min-height:var(--tbw-row-height)}.group-row .cell{display:flex;align-items:center;padding:var(--tbw-cell-padding, .125rem .5rem)}.group-row:hover{background:var(--tbw-grouping-rows-bg-hover, var(--tbw-color-row-hover))}.group-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center;width:var(--tbw-toggle-size, 1.25rem);height:var(--tbw-toggle-size, 1.25rem);margin-right:.25rem;background:none;border:0;font:inherit}.group-toggle:hover{background:var(--tbw-grouping-rows-toggle-hover, var(--tbw-color-row-hover));border-radius:var(--tbw-border-radius, .125rem)}.group-label{display:inline-flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.group-count{color:var(--tbw-grouping-rows-count-color, var(--tbw-color-fg-muted));font-size:var(--tbw-font-size-xs, .85em);font-weight:400}.group-aggregates{display:inline-flex;align-items:center;gap:var(--tbw-spacing-lg, 1rem);margin-left:var(--tbw-spacing-lg, 1rem);font-weight:400;font-size:var(--tbw-font-size-sm, .875em);color:var(--tbw-grouping-rows-aggregate-color, var(--tbw-color-fg-muted))}.group-aggregate{white-space:nowrap}.group-row{padding-left:calc(var(--tbw-group-depth, 0) * var(--tbw-group-indent-width, 1.25em))}.data-grid-row.tbw-group-slide-in{animation:tbw-group-slide-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-group-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}.data-grid-row.tbw-group-fade-in{animation:tbw-group-fade-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-group-fade-in{0%{opacity:0}to{opacity:1}}}";get defaultConfig(){return{defaultExpanded:!1,showRowCount:!0,indentWidth:20,aggregators:{},animation:"slide",accordion:!1}}expandedKeys=new Set;flattenedRows=[];isActive=!1;previousVisibleKeys=new Set;keysToAnimate=new Set;hasAppliedDefaultExpanded=!1;get animationStyle(){return!!this.isAnimationEnabled&&(this.config.animation??"slide")}detach(){this.expandedKeys.clear(),this.flattenedRows=[],this.isActive=!1,this.previousVisibleKeys.clear(),this.keysToAnimate.clear(),this.hasAppliedDefaultExpanded=!1}getRowHeight(e,t){if(null!=this.config.groupRowHeight)return!0===e.__isGroupRow?this.config.groupRowHeight:void 0}handleQuery(e){if("canMoveRow"===e.type){const t=e.context;if(!0===t?.__isGroupRow)return!1}}static detect(e,t){return"function"==typeof t?.groupOn||"boolean"==typeof t?.enableRowGrouping}processRows(e){const t=this.config;if("function"!=typeof t.groupOn)return this.isActive=!1,this.flattenedRows=[],[...e];const n=o({rows:[...e],config:t,expanded:new Set});if(0===n.length)return this.isActive=!1,this.flattenedRows=[],[...e];let r;if(!this.hasAppliedDefaultExpanded&&0===this.expandedKeys.size&&!1!==t.defaultExpanded){const e=function(e){return e.filter(e=>"group"===e.kind).map(e=>e.key)}(n);r=function(e,t){if(!0===e)return new Set(t);if(!1===e||null==e)return new Set;if("number"==typeof e){const n=t[e];return n?new Set([n]):new Set}return"string"==typeof e?new Set([e]):Array.isArray(e)?new Set(e):new Set}(t.defaultExpanded??!1,e),r.size>0&&(this.expandedKeys=new Set(r),this.hasAppliedDefaultExpanded=!0)}const i=o({rows:[...e],config:t,expanded:this.expandedKeys,initialExpanded:r});this.isActive=!0,this.flattenedRows=i,this.keysToAnimate.clear();const s=new Set;return i.forEach((e,t)=>{if("data"===e.kind){const e=`data-${t}`;s.add(e),this.previousVisibleKeys.has(e)||this.keysToAnimate.add(e)}}),this.previousVisibleKeys=s,i.map(e=>{return"group"===e.kind?{__isGroupRow:!0,__groupKey:e.key,__groupValue:e.value,__groupDepth:e.depth,__groupRows:e.rows,__groupExpanded:e.expanded,__groupRowCount:(t=e,"group"!==t.kind?0:t.rows.length),__rowCacheKey:`group:${e.key}`}:e.row;var t})}onCellClick(e){const t=e.row;if(t?.__isGroupRow){const n=e.originalEvent.target;if(n?.closest(".group-toggle"))return this.toggle(t.__groupKey),!0}}onKeyDown(e){if(" "!==e.key)return;const t=this.grid._focusRow,n=this.rows[t];return n?.__isGroupRow?(e.preventDefault(),this.toggle(n.__groupKey),this.requestRenderWithFocus(),!0):void 0}renderRow(e,t,n){if(!e?.__isGroupRow)return!1;const r=this.config;if(r.groupRowRenderer){const n=()=>{this.toggle(e.__groupKey)},o=r.groupRowRenderer({key:e.__groupKey,value:e.__groupValue,depth:e.__groupDepth,rows:e.__groupRows,expanded:e.__groupExpanded,toggleExpand:n});if(o)return t.className="data-grid-row group-row",t.__isCustomRow=!0,t.setAttribute("data-group-depth",String(e.__groupDepth)),"string"==typeof o?t.innerHTML=o:(t.innerHTML="",t.appendChild(o)),!0}const o=()=>{this.toggle(e.__groupKey)};t.className="data-grid-row 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.setProperty("--tbw-group-depth",String(e.__groupDepth||0)),void 0!==r.indentWidth&&t.style.setProperty("--tbw-group-indent-width",`${r.indentWidth}px`),t.style.height="",t.innerHTML="";return!1!==r.fullWidth?this.renderFullWidthGroupRow(e,t,o):this.renderPerColumnGroupRow(e,t,o),!0}afterRender(){const e=this.animationStyle;if(!1===e||0===this.keysToAnimate.size)return;const t=this.gridElement?.querySelector(".rows");if(!t)return;const n="fade"===e?"tbw-group-fade-in":"tbw-group-slide-in";for(const r of t.querySelectorAll(".data-grid-row:not(.group-row)")){const e=r.querySelector(".cell[data-row]"),t=e?parseInt(e.getAttribute("data-row")??"-1",10):-1,o=this.flattenedRows[t],i="data"===o?.kind?`data-${t}`:void 0;i&&this.keysToAnimate.has(i)&&(r.classList.add(n),r.addEventListener("animationend",()=>r.classList.remove(n),{once:!0}))}this.keysToAnimate.clear()}createToggleButton(e,t){const n=document.createElement("button");return n.type="button",n.className="group-toggle"+(e?" expanded":""),n.setAttribute("aria-label",e?"Collapse group":"Expand group"),this.setIcon(n,this.resolveIcon(e?"collapse":"expand")),n.addEventListener("click",e=>{e.stopPropagation(),t()}),n}getGroupLabelText(e,t,n){const r=this.config;return r.formatLabel?r.formatLabel(e,t,n):String(e)}renderFullWidthGroupRow(e,t,n){const o=this.config,i=o.aggregators??{},s=e.__groupRows??[],a=document.createElement("div");a.className="cell group-full",a.style.gridColumn="1 / -1",a.setAttribute("role","gridcell"),a.setAttribute("data-col","0"),a.appendChild(this.createToggleButton(e.__groupExpanded,n));const d=document.createElement("span");if(d.className="group-label",d.textContent=this.getGroupLabelText(e.__groupValue,e.__groupDepth||0,e.__groupKey),a.appendChild(d),!1!==o.showRowCount){const t=document.createElement("span");t.className="group-count",t.textContent=`(${e.__groupRowCount??e.__groupRows?.length??0})`,a.appendChild(t)}const l=Object.entries(i);if(l.length>0){const e=document.createElement("span");e.className="group-aggregates";for(const[t,n]of l){const o=this.columns.find(e=>e.field===t),i=r.runAggregator(n,s,t,o);if(null!=i){const n=document.createElement("span");n.className="group-aggregate",n.setAttribute("data-field",t);const r=o?.header??t;n.textContent=`${r}: ${i}`,e.appendChild(n)}}e.children.length>0&&a.appendChild(e)}t.appendChild(a)}renderPerColumnGroupRow(e,t,o){const i=this.config,s=i.aggregators??{},a=this.columns,d=e.__groupRows??[],l=this.gridElement?.querySelector(".body"),p=l?.style.gridTemplateColumns||"";p&&(t.style.display="grid",t.style.gridTemplateColumns=p);let u=!1;a.forEach((a,l)=>{const p=document.createElement("div");if(p.className="cell group-cell",p.setAttribute("data-col",String(l)),p.setAttribute("role","gridcell"),n.isExpanderColumn(a))return p.setAttribute("data-field",a.field),void t.appendChild(p);if(u){const e=s[a.field];if(e){const t=r.runAggregator(e,d,a.field,a);p.textContent=null!=t?String(t):""}else p.textContent=""}else{u=!0,p.appendChild(this.createToggleButton(e.__groupExpanded,o));const t=document.createElement("span"),n=s[a.field];if(n){const o=r.runAggregator(n,d,a.field,a);t.textContent=String(null!=o?o:e.__groupValue)}else t.textContent=this.getGroupLabelText(e.__groupValue,e.__groupDepth||0,e.__groupKey);if(p.appendChild(t),!1!==i.showRowCount){const e=document.createElement("span");e.className="group-count",e.textContent=` (${d.length})`,p.appendChild(e)}}t.appendChild(p)})}expandAll(){this.expandedKeys=function(e){const t=new Set;for(const n of e)"group"===n.kind&&t.add(n.key);return t}(this.flattenedRows),this.emitPluginEvent("grouping-state-change",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}collapseAll(){this.expandedKeys=new Set,this.emitPluginEvent("grouping-state-change",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}toggle(e){const t=!this.expandedKeys.has(e),n=this.config,r=this.flattenedRows.find(t=>"group"===t.kind&&t.key===e);if(n.accordion&&t&&r){const t=new Set;for(const n of this.expandedKeys)if(e.startsWith(n+"||")||n.startsWith(e+"||"))e.startsWith(n+"||")&&t.add(n);else{const e=this.flattenedRows.find(e=>"group"===e.kind&&e.key===n);e&&e.depth!==r.depth&&t.add(n)}t.add(e),this.expandedKeys=t}else this.expandedKeys=function(e,t){const n=new Set(e);return n.has(t)?n.delete(t):n.add(t),n}(this.expandedKeys,e);this.emit("group-toggle",{key:e,expanded:this.expandedKeys.has(e),value:r?.value,depth:r?.depth??0}),this.emitPluginEvent("grouping-state-change",{expandedKeys:[...this.expandedKeys]}),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(e=>"group"===e.kind);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()}}e.GroupingRowsPlugin=i,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
|
|
5
2
|
//# sourceMappingURL=grouping-rows.umd.js.map
|
|
@@ -1 +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 { DefaultExpandedValue, GroupRowModelItem, RenderRow, RowGroupingConfig } from './types';\n\n// Re-export aggregator functions from core for backward compatibility\nexport { getAggregator, listAggregators, registerAggregator, runAggregator } 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 /** Initial expanded state to apply (processed by the plugin) */\n initialExpanded?: Set<string>;\n}\n\n/**\n * Build a flattened grouping projection (collapsed by default).\n * Returns empty array when groupOn not configured 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, initialExpanded }: BuildGroupingArgs): RenderRow[] {\n const groupOn = config.groupOn;\n if (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 // Merge expanded sets - use initialExpanded on first render, then expanded takes over\n const effectiveExpanded = new Set([...expanded, ...(initialExpanded ?? [])]);\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 = effectiveExpanded.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 * Resolve a defaultExpanded value to a set of keys to expand.\n * This needs to be called AFTER building the group model to get all keys.\n *\n * @param value - The defaultExpanded config value\n * @param allGroupKeys - All group keys from the model\n * @returns Set of keys to expand initially\n */\nexport function resolveDefaultExpanded(value: DefaultExpandedValue, allGroupKeys: string[]): Set<string> {\n if (value === true) {\n // Expand all groups\n return new Set(allGroupKeys);\n }\n if (value === false || value == null) {\n // Collapse all groups\n return new Set();\n }\n if (typeof value === 'number') {\n // Expand group at this index\n const key = allGroupKeys[value];\n return key ? new Set([key]) : new Set();\n }\n if (typeof value === 'string') {\n // Expand group with this key\n return new Set([value]);\n }\n if (Array.isArray(value)) {\n // Expand groups with these keys\n return new Set(value);\n }\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 is GroupRowModelItem => r.kind === 'group').map((r) => r.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\nimport { BaseGridPlugin, CellClickEvent, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport { isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { RowElementInternal } from '../../core/types';\nimport {\n buildGroupedRowModel,\n collapseAllGroups,\n expandAllGroups,\n getGroupKeys,\n getGroupRowCount,\n resolveDefaultExpanded,\n runAggregator,\n toggleGroupExpansion,\n} from './grouping-rows';\nimport styles from './grouping-rows.css?inline';\nimport type {\n ExpandCollapseAnimation,\n GroupingRowsConfig,\n GroupRowModelItem,\n GroupToggleDetail,\n RenderRow,\n} 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 * Organizes rows into collapsible hierarchical groups. Perfect for organizing data\n * by category, department, status, or any other dimension—or even multiple dimensions\n * for nested grouping. Includes aggregation support for summarizing group data.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `groupOn` | `(row) => string[]` | - | Callback returning group path array |\n * | `defaultExpanded` | `boolean \\\\| number \\\\| string \\\\| string[]` | `false` | Initial expanded state |\n * | `showRowCount` | `boolean` | `true` | Show row count in group header |\n * | `indentWidth` | `number` | `20` | Indentation per level (pixels) |\n * | `fullWidth` | `boolean` | `true` | Group row spans full width |\n * | `animation` | `false \\\\| 'slide' \\\\| 'fade'` | `'slide'` | Expand/collapse animation |\n * | `accordion` | `boolean` | `false` | Only one group open at a time |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expandGroup` | `(path: string[]) => void` | Expand a specific group |\n * | `collapseGroup` | `(path: string[]) => void` | Collapse a specific group |\n * | `expandAll` | `() => void` | Expand all groups |\n * | `collapseAll` | `() => void` | Collapse all groups |\n * | `isGroupExpanded` | `(path: string[]) => boolean` | Check if group is expanded |\n * | `getGroupState` | `() => GroupState` | Get current grouping state |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-group-indent-width` | `1.25em` | Indentation per group level |\n * | `--tbw-grouping-rows-bg` | `var(--tbw-color-panel-bg)` | Group row background |\n * | `--tbw-grouping-rows-count-color` | `var(--tbw-color-fg-muted)` | Count badge color |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation |\n *\n * @example Single-Level Grouping by Department\n * ```ts\n * import '@toolbox-web/grid';\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Employee' },\n * { field: 'department', header: 'Department' },\n * { field: 'salary', header: 'Salary', type: 'currency' },\n * ],\n * plugins: [\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.department],\n * showRowCount: true,\n * defaultExpanded: false,\n * }),\n * ],\n * };\n * ```\n *\n * @example Multi-Level Grouping\n * ```ts\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.region, row.department, row.team],\n * indentWidth: 24,\n * animation: 'slide',\n * })\n * ```\n *\n * @see {@link GroupingRowsConfig} for all configuration options\n * @see {@link GroupState} for the group state structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingRowsPlugin extends BaseGridPlugin<GroupingRowsConfig> {\n /**\n * Plugin manifest - declares configuration validation rules and events.\n * @internal\n */\n static override readonly manifest: PluginManifest<GroupingRowsConfig> = {\n events: [\n {\n type: 'grouping-state-change',\n description: 'Emitted when groups are expanded/collapsed. Subscribers can react to row visibility changes.',\n },\n ],\n queries: [\n {\n type: 'canMoveRow',\n description: 'Returns false for group header rows (cannot be reordered)',\n },\n ],\n configRules: [\n {\n id: 'groupingRows/accordion-defaultExpanded',\n severity: 'warn',\n message:\n `\"accordion: true\" and \"defaultExpanded\" (non-false) are used together.\\n` +\n ` → In accordion mode, only one group can be open at a time.\\n` +\n ` → Using defaultExpanded with multiple groups will collapse to one on first toggle.\\n` +\n ` → Consider using \"defaultExpanded: false\" or a single group key/index with accordion mode.`,\n check: (config) =>\n config.accordion === true &&\n config.defaultExpanded !== false &&\n config.defaultExpanded !== undefined &&\n // Allow single group expansion with accordion\n !(typeof config.defaultExpanded === 'number') &&\n !(typeof config.defaultExpanded === 'string') &&\n // Warn if true or array with multiple items\n (config.defaultExpanded === true ||\n (Array.isArray(config.defaultExpanded) && config.defaultExpanded.length > 1)),\n },\n ],\n };\n\n /** @internal */\n readonly name = 'groupingRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingRowsConfig> {\n return {\n defaultExpanded: false,\n showRowCount: true,\n indentWidth: 20,\n aggregators: {},\n animation: 'slide',\n accordion: false,\n };\n }\n\n // #region Internal State\n private expandedKeys: Set<string> = new Set();\n private flattenedRows: RenderRow[] = [];\n private isActive = false;\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n /** Track if initial defaultExpanded has been applied */\n private hasAppliedDefaultExpanded = false;\n // #endregion\n\n // #region Animation\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.expandedKeys.clear();\n this.flattenedRows = [];\n this.isActive = false;\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.hasAppliedDefaultExpanded = false;\n }\n\n /**\n * Provide row height for group header rows.\n *\n * If `groupRowHeight` is configured, returns that value for group rows.\n * This allows the variable row height system to use known heights for\n * group headers without needing to measure them from the DOM.\n *\n * @param row - The row object (may be a group row)\n * @param _index - Index in the processed rows array (unused)\n * @returns Height in pixels for group rows, undefined for data rows\n *\n * @internal Plugin hook for variable row height support\n */\n override getRowHeight(row: unknown, _index: number): number | undefined {\n // Only provide height if groupRowHeight is configured\n if (this.config.groupRowHeight == null) return undefined;\n\n // Check if this is a group row\n if ((row as { __isGroupRow?: boolean }).__isGroupRow === true) {\n return this.config.groupRowHeight;\n }\n\n return undefined;\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'canMoveRow') {\n // Group header rows cannot be reordered\n const row = query.context as { __isGroupRow?: boolean } | null | undefined;\n if (row?.__isGroupRow === true) {\n return false;\n }\n }\n return undefined;\n }\n // #endregion\n\n // #region 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 /** @internal */\n override processRows(rows: readonly any[]): any[] {\n const config = this.config;\n\n // Check if grouping is configured\n if (typeof config.groupOn !== 'function') {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // First build: get structure to know all group keys\n // (needed for index-based defaultExpanded)\n const initialBuild = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: new Set(), // Empty to get all root groups\n });\n\n // If no grouping produced, return original rows\n if (initialBuild.length === 0) {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // Resolve defaultExpanded on first render only\n let initialExpanded: Set<string> | undefined;\n if (!this.hasAppliedDefaultExpanded && this.expandedKeys.size === 0 && config.defaultExpanded !== false) {\n const allKeys = getGroupKeys(initialBuild);\n initialExpanded = resolveDefaultExpanded(config.defaultExpanded ?? false, allKeys);\n\n // Mark as applied and populate expandedKeys for subsequent toggles\n if (initialExpanded.size > 0) {\n this.expandedKeys = new Set(initialExpanded);\n this.hasAppliedDefaultExpanded = true;\n }\n }\n\n // Build with proper expanded state\n const grouped = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: this.expandedKeys,\n initialExpanded,\n });\n\n this.isActive = true;\n this.flattenedRows = grouped;\n\n // Track which data rows are newly visible (for animation)\n this.keysToAnimate.clear();\n const currentVisibleKeys = new Set<string>();\n grouped.forEach((item, idx) => {\n if (item.kind === 'data') {\n const key = `data-${idx}`;\n currentVisibleKeys.add(key);\n if (!this.previousVisibleKeys.has(key)) {\n this.keysToAnimate.add(key);\n }\n }\n });\n this.previousVisibleKeys = currentVisibleKeys;\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 // Cache key for variable row height support - survives expand/collapse\n __rowCacheKey: `group:${item.key}`,\n };\n }\n return item.row;\n });\n }\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean | void {\n const row = event.row as Record<string, unknown> | undefined;\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 as string);\n return true; // Prevent default\n }\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion on group rows\n if (event.key !== ' ') return;\n\n const focusRow = this.grid._focusRow;\n const row = this.rows[focusRow] as Record<string, unknown> | undefined;\n\n // Only handle SPACE on group rows\n if (!row?.__isGroupRow) return;\n\n event.preventDefault();\n this.toggle(row.__groupKey as string);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /**\n * Render a row. Returns true if we handled the row (group row), false otherwise.\n * @internal\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 = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__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 - keep data-grid-row class for focus/keyboard navigation\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__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 // Use CSS variable for depth-based indentation\n rowEl.style.setProperty('--tbw-group-depth', String(row.__groupDepth || 0));\n if (config.indentWidth !== undefined) {\n rowEl.style.setProperty('--tbw-group-indent-width', `${config.indentWidth}px`);\n }\n // Clear any inline height from previous use (e.g., responsive card mode sets height: auto)\n // This ensures group rows use CSS-defined height, not stale inline styles from recycled elements\n rowEl.style.height = '';\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 /** @internal */\n override afterRender(): void {\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-group-fade-in' : 'tbw-group-slide-in';\n for (const rowEl of body.querySelectorAll('.data-grid-row:not(.group-row)')) {\n const cell = rowEl.querySelector('.cell[data-row]');\n const idx = cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n const item = this.flattenedRows[idx];\n const key = item?.kind === 'data' ? `data-${idx}` : undefined;\n\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n // #endregion\n\n // #region Private Rendering Helpers\n\n /**\n * Create a toggle button for expanding/collapsing a group.\n */\n private createToggleButton(expanded: boolean, handleToggle: () => void): HTMLButtonElement {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `group-toggle${expanded ? ' expanded' : ''}`;\n btn.setAttribute('aria-label', expanded ? 'Collapse group' : 'Expand group');\n this.setIcon(btn, this.resolveIcon(expanded ? 'collapse' : 'expand'));\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n handleToggle();\n });\n return btn;\n }\n\n /**\n * Get the formatted label text for a group.\n */\n private getGroupLabelText(value: unknown, depth: number, key: string): string {\n const config = this.config;\n return config.formatLabel ? config.formatLabel(value, depth, key) : String(value);\n }\n\n private renderFullWidthGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const groupRows = row.__groupRows ?? [];\n\n // Full-width mode: single spanning cell with toggle + label + count + aggregates\n const cell = document.createElement('div');\n cell.className = 'cell group-full';\n cell.style.gridColumn = '1 / -1';\n cell.setAttribute('role', 'gridcell');\n cell.setAttribute('data-col', '0'); // Required for focus/click delegation\n\n // Toggle button\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n // Group label\n const label = document.createElement('span');\n label.className = 'group-label';\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\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 // Render aggregates if configured\n const aggregatorEntries = Object.entries(aggregators);\n if (aggregatorEntries.length > 0) {\n const aggregatesContainer = document.createElement('span');\n aggregatesContainer.className = 'group-aggregates';\n\n for (const [field, aggRef] of aggregatorEntries) {\n const col = this.columns.find((c) => c.field === field);\n const result = runAggregator(aggRef, groupRows, field, col);\n if (result != null) {\n const aggSpan = document.createElement('span');\n aggSpan.className = 'group-aggregate';\n aggSpan.setAttribute('data-field', field);\n // Use column header as label if available\n const colHeader = col?.header ?? field;\n aggSpan.textContent = `${colHeader}: ${result}`;\n aggregatesContainer.appendChild(aggSpan);\n }\n }\n\n if (aggregatesContainer.children.length > 0) {\n cell.appendChild(aggregatesContainer);\n }\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 bodyEl = this.gridElement?.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 // Track whether we've rendered the toggle button yet (should be in first non-expander column)\n let toggleRendered = false;\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 // Skip expander columns (they're handled by other plugins like MasterDetail/Tree)\n // but still render an empty cell to maintain grid structure\n if (isExpanderColumn(col)) {\n cell.setAttribute('data-field', col.field);\n rowEl.appendChild(cell);\n return;\n }\n\n // First non-expander column gets the toggle button + label\n if (!toggleRendered) {\n toggleRendered = true;\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\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 label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\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 // #endregion\n\n // #region Public API\n\n /**\n * Expand all groups.\n */\n expandAll(): void {\n this.expandedKeys = expandAllGroups(this.flattenedRows);\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Collapse all groups.\n */\n collapseAll(): void {\n this.expandedKeys = collapseAllGroups();\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Toggle expansion of a specific group.\n * In accordion mode, expanding a group will collapse all sibling groups.\n * @param key - The group key to toggle\n */\n toggle(key: string): void {\n const isExpanding = !this.expandedKeys.has(key);\n const config = this.config;\n\n // Find the group to get its depth for accordion mode\n const group = this.flattenedRows.find((r) => r.kind === 'group' && r.key === key) as GroupRowModelItem | undefined;\n\n // In accordion mode, collapse sibling groups when expanding\n if (config.accordion && isExpanding && group) {\n const newKeys = new Set<string>();\n // Keep only ancestors (keys that are prefixes of the current key) and the current key\n for (const existingKey of this.expandedKeys) {\n // Check if existingKey is an ancestor of the toggled key\n // Ancestors have composite keys that are prefixes of child keys (separated by '||')\n if (key.startsWith(existingKey + '||') || existingKey.startsWith(key + '||')) {\n // This is an ancestor or descendant - keep it only if ancestor\n if (key.startsWith(existingKey + '||')) {\n newKeys.add(existingKey);\n }\n } else {\n // Check depth - only keep groups at different depths\n const existingGroup = this.flattenedRows.find((r) => r.kind === 'group' && r.key === existingKey) as\n | GroupRowModelItem\n | undefined;\n if (existingGroup && existingGroup.depth !== group.depth) {\n newKeys.add(existingKey);\n }\n }\n }\n newKeys.add(key);\n this.expandedKeys = newKeys;\n } else {\n this.expandedKeys = toggleGroupExpansion(this.expandedKeys, key);\n }\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 // Notify other plugins that grouping state changed (row visibility changed)\n this.emitPluginEvent('grouping-state-change', {\n expandedKeys: [...this.expandedKeys],\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 // #endregion\n}\n"],"names":["buildGroupedRowModel","rows","config","expanded","initialExpanded","groupOn","root","r","path","parent","rawVal","depthIdx","seg","composite","node","effectiveExpanded","flat","visit","c","isExpanded","toggleGroupExpansion","expandedKeys","key","newSet","expandAllGroups","keys","row","collapseAllGroups","resolveDefaultExpanded","value","allGroupKeys","getGroupKeys","getGroupRowCount","groupRow","GroupingRowsPlugin","BaseGridPlugin","styles","_index","query","initialBuild","allKeys","grouped","currentVisibleKeys","item","idx","event","focusRow","rowEl","_rowIndex","toggleExpand","result","handleToggle","style","body","animClass","cell","btn","e","depth","aggregators","groupRows","label","count","aggregatorEntries","aggregatesContainer","field","aggRef","col","runAggregator","aggSpan","colHeader","columns","gridTemplate","toggleRendered","colIdx","isExpanderColumn","firstColAgg","aggResult","isExpanding","group","newKeys","existingKey","existingGroup","fn"],"mappings":"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"}
|
|
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 { DefaultExpandedValue, GroupRowModelItem, RenderRow, RowGroupingConfig } from './types';\n\n// Re-export aggregator functions from core for backward compatibility\nexport { getAggregator, listAggregators, registerAggregator, runAggregator } 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 /** Initial expanded state to apply (processed by the plugin) */\n initialExpanded?: Set<string>;\n}\n\n/**\n * Build a flattened grouping projection (collapsed by default).\n * Returns empty array when groupOn not configured 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, initialExpanded }: BuildGroupingArgs): RenderRow[] {\n const groupOn = config.groupOn;\n if (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 // Merge expanded sets - use initialExpanded on first render, then expanded takes over\n const effectiveExpanded = new Set([...expanded, ...(initialExpanded ?? [])]);\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 = effectiveExpanded.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 * Resolve a defaultExpanded value to a set of keys to expand.\n * This needs to be called AFTER building the group model to get all keys.\n *\n * @param value - The defaultExpanded config value\n * @param allGroupKeys - All group keys from the model\n * @returns Set of keys to expand initially\n */\nexport function resolveDefaultExpanded(value: DefaultExpandedValue, allGroupKeys: string[]): Set<string> {\n if (value === true) {\n // Expand all groups\n return new Set(allGroupKeys);\n }\n if (value === false || value == null) {\n // Collapse all groups\n return new Set();\n }\n if (typeof value === 'number') {\n // Expand group at this index\n const key = allGroupKeys[value];\n return key ? new Set([key]) : new Set();\n }\n if (typeof value === 'string') {\n // Expand group with this key\n return new Set([value]);\n }\n if (Array.isArray(value)) {\n // Expand groups with these keys\n return new Set(value);\n }\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 is GroupRowModelItem => r.kind === 'group').map((r) => r.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\nimport { BaseGridPlugin, CellClickEvent, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport { isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { RowElementInternal } from '../../core/types';\nimport {\n buildGroupedRowModel,\n collapseAllGroups,\n expandAllGroups,\n getGroupKeys,\n getGroupRowCount,\n resolveDefaultExpanded,\n runAggregator,\n toggleGroupExpansion,\n} from './grouping-rows';\nimport styles from './grouping-rows.css?inline';\nimport type {\n ExpandCollapseAnimation,\n GroupingRowsConfig,\n GroupRowModelItem,\n GroupToggleDetail,\n RenderRow,\n} 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 * Organizes rows into collapsible hierarchical groups. Perfect for organizing data\n * by category, department, status, or any other dimension—or even multiple dimensions\n * for nested grouping. Includes aggregation support for summarizing group data.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `groupOn` | `(row) => string[]` | - | Callback returning group path array |\n * | `defaultExpanded` | `boolean \\\\| number \\\\| string \\\\| string[]` | `false` | Initial expanded state |\n * | `showRowCount` | `boolean` | `true` | Show row count in group header |\n * | `indentWidth` | `number` | `20` | Indentation per level (pixels) |\n * | `fullWidth` | `boolean` | `true` | Group row spans full width |\n * | `animation` | `false \\\\| 'slide' \\\\| 'fade'` | `'slide'` | Expand/collapse animation |\n * | `accordion` | `boolean` | `false` | Only one group open at a time |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expandGroup` | `(path: string[]) => void` | Expand a specific group |\n * | `collapseGroup` | `(path: string[]) => void` | Collapse a specific group |\n * | `expandAll` | `() => void` | Expand all groups |\n * | `collapseAll` | `() => void` | Collapse all groups |\n * | `isGroupExpanded` | `(path: string[]) => boolean` | Check if group is expanded |\n * | `getGroupState` | `() => GroupState` | Get current grouping state |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-group-indent-width` | `1.25em` | Indentation per group level |\n * | `--tbw-grouping-rows-bg` | `var(--tbw-color-panel-bg)` | Group row background |\n * | `--tbw-grouping-rows-count-color` | `var(--tbw-color-fg-muted)` | Count badge color |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation |\n *\n * @example Single-Level Grouping by Department\n * ```ts\n * import '@toolbox-web/grid';\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Employee' },\n * { field: 'department', header: 'Department' },\n * { field: 'salary', header: 'Salary', type: 'currency' },\n * ],\n * plugins: [\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.department],\n * showRowCount: true,\n * defaultExpanded: false,\n * }),\n * ],\n * };\n * ```\n *\n * @example Multi-Level Grouping\n * ```ts\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.region, row.department, row.team],\n * indentWidth: 24,\n * animation: 'slide',\n * })\n * ```\n *\n * @see {@link GroupingRowsConfig} for all configuration options\n * @see {@link GroupState} for the group state structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingRowsPlugin extends BaseGridPlugin<GroupingRowsConfig> {\n /**\n * Plugin manifest - declares configuration validation rules and events.\n * @internal\n */\n static override readonly manifest: PluginManifest<GroupingRowsConfig> = {\n events: [\n {\n type: 'grouping-state-change',\n description: 'Emitted when groups are expanded/collapsed. Subscribers can react to row visibility changes.',\n },\n ],\n queries: [\n {\n type: 'canMoveRow',\n description: 'Returns false for group header rows (cannot be reordered)',\n },\n ],\n configRules: [\n {\n id: 'groupingRows/accordion-defaultExpanded',\n severity: 'warn',\n message:\n `\"accordion: true\" and \"defaultExpanded\" (non-false) are used together.\\n` +\n ` → In accordion mode, only one group can be open at a time.\\n` +\n ` → Using defaultExpanded with multiple groups will collapse to one on first toggle.\\n` +\n ` → Consider using \"defaultExpanded: false\" or a single group key/index with accordion mode.`,\n check: (config) =>\n config.accordion === true &&\n config.defaultExpanded !== false &&\n config.defaultExpanded !== undefined &&\n // Allow single group expansion with accordion\n !(typeof config.defaultExpanded === 'number') &&\n !(typeof config.defaultExpanded === 'string') &&\n // Warn if true or array with multiple items\n (config.defaultExpanded === true ||\n (Array.isArray(config.defaultExpanded) && config.defaultExpanded.length > 1)),\n },\n ],\n };\n\n /** @internal */\n readonly name = 'groupingRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingRowsConfig> {\n return {\n defaultExpanded: false,\n showRowCount: true,\n indentWidth: 20,\n aggregators: {},\n animation: 'slide',\n accordion: false,\n };\n }\n\n // #region Internal State\n private expandedKeys: Set<string> = new Set();\n private flattenedRows: RenderRow[] = [];\n private isActive = false;\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n /** Track if initial defaultExpanded has been applied */\n private hasAppliedDefaultExpanded = false;\n // #endregion\n\n // #region Animation\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.expandedKeys.clear();\n this.flattenedRows = [];\n this.isActive = false;\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.hasAppliedDefaultExpanded = false;\n }\n\n /**\n * Provide row height for group header rows.\n *\n * If `groupRowHeight` is configured, returns that value for group rows.\n * This allows the variable row height system to use known heights for\n * group headers without needing to measure them from the DOM.\n *\n * @param row - The row object (may be a group row)\n * @param _index - Index in the processed rows array (unused)\n * @returns Height in pixels for group rows, undefined for data rows\n *\n * @internal Plugin hook for variable row height support\n */\n override getRowHeight(row: unknown, _index: number): number | undefined {\n // Only provide height if groupRowHeight is configured\n if (this.config.groupRowHeight == null) return undefined;\n\n // Check if this is a group row\n if ((row as { __isGroupRow?: boolean }).__isGroupRow === true) {\n return this.config.groupRowHeight;\n }\n\n return undefined;\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'canMoveRow') {\n // Group header rows cannot be reordered\n const row = query.context as { __isGroupRow?: boolean } | null | undefined;\n if (row?.__isGroupRow === true) {\n return false;\n }\n }\n return undefined;\n }\n // #endregion\n\n // #region 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 /** @internal */\n override processRows(rows: readonly any[]): any[] {\n const config = this.config;\n\n // Check if grouping is configured\n if (typeof config.groupOn !== 'function') {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // First build: get structure to know all group keys\n // (needed for index-based defaultExpanded)\n const initialBuild = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: new Set(), // Empty to get all root groups\n });\n\n // If no grouping produced, return original rows\n if (initialBuild.length === 0) {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // Resolve defaultExpanded on first render only\n let initialExpanded: Set<string> | undefined;\n if (!this.hasAppliedDefaultExpanded && this.expandedKeys.size === 0 && config.defaultExpanded !== false) {\n const allKeys = getGroupKeys(initialBuild);\n initialExpanded = resolveDefaultExpanded(config.defaultExpanded ?? false, allKeys);\n\n // Mark as applied and populate expandedKeys for subsequent toggles\n if (initialExpanded.size > 0) {\n this.expandedKeys = new Set(initialExpanded);\n this.hasAppliedDefaultExpanded = true;\n }\n }\n\n // Build with proper expanded state\n const grouped = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: this.expandedKeys,\n initialExpanded,\n });\n\n this.isActive = true;\n this.flattenedRows = grouped;\n\n // Track which data rows are newly visible (for animation)\n this.keysToAnimate.clear();\n const currentVisibleKeys = new Set<string>();\n grouped.forEach((item, idx) => {\n if (item.kind === 'data') {\n const key = `data-${idx}`;\n currentVisibleKeys.add(key);\n if (!this.previousVisibleKeys.has(key)) {\n this.keysToAnimate.add(key);\n }\n }\n });\n this.previousVisibleKeys = currentVisibleKeys;\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 // Cache key for variable row height support - survives expand/collapse\n __rowCacheKey: `group:${item.key}`,\n };\n }\n return item.row;\n });\n }\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean | void {\n const row = event.row as Record<string, unknown> | undefined;\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 as string);\n return true; // Prevent default\n }\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion on group rows\n if (event.key !== ' ') return;\n\n const focusRow = this.grid._focusRow;\n const row = this.rows[focusRow] as Record<string, unknown> | undefined;\n\n // Only handle SPACE on group rows\n if (!row?.__isGroupRow) return;\n\n event.preventDefault();\n this.toggle(row.__groupKey as string);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /**\n * Render a row. Returns true if we handled the row (group row), false otherwise.\n * @internal\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 = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__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 - keep data-grid-row class for focus/keyboard navigation\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__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 // Use CSS variable for depth-based indentation\n rowEl.style.setProperty('--tbw-group-depth', String(row.__groupDepth || 0));\n if (config.indentWidth !== undefined) {\n rowEl.style.setProperty('--tbw-group-indent-width', `${config.indentWidth}px`);\n }\n // Clear any inline height from previous use (e.g., responsive card mode sets height: auto)\n // This ensures group rows use CSS-defined height, not stale inline styles from recycled elements\n rowEl.style.height = '';\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 /** @internal */\n override afterRender(): void {\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-group-fade-in' : 'tbw-group-slide-in';\n for (const rowEl of body.querySelectorAll('.data-grid-row:not(.group-row)')) {\n const cell = rowEl.querySelector('.cell[data-row]');\n const idx = cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n const item = this.flattenedRows[idx];\n const key = item?.kind === 'data' ? `data-${idx}` : undefined;\n\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n // #endregion\n\n // #region Private Rendering Helpers\n\n /**\n * Create a toggle button for expanding/collapsing a group.\n */\n private createToggleButton(expanded: boolean, handleToggle: () => void): HTMLButtonElement {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `group-toggle${expanded ? ' expanded' : ''}`;\n btn.setAttribute('aria-label', expanded ? 'Collapse group' : 'Expand group');\n this.setIcon(btn, this.resolveIcon(expanded ? 'collapse' : 'expand'));\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n handleToggle();\n });\n return btn;\n }\n\n /**\n * Get the formatted label text for a group.\n */\n private getGroupLabelText(value: unknown, depth: number, key: string): string {\n const config = this.config;\n return config.formatLabel ? config.formatLabel(value, depth, key) : String(value);\n }\n\n private renderFullWidthGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const groupRows = row.__groupRows ?? [];\n\n // Full-width mode: single spanning cell with toggle + label + count + aggregates\n const cell = document.createElement('div');\n cell.className = 'cell group-full';\n cell.style.gridColumn = '1 / -1';\n cell.setAttribute('role', 'gridcell');\n cell.setAttribute('data-col', '0'); // Required for focus/click delegation\n\n // Toggle button\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n // Group label\n const label = document.createElement('span');\n label.className = 'group-label';\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\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 // Render aggregates if configured\n const aggregatorEntries = Object.entries(aggregators);\n if (aggregatorEntries.length > 0) {\n const aggregatesContainer = document.createElement('span');\n aggregatesContainer.className = 'group-aggregates';\n\n for (const [field, aggRef] of aggregatorEntries) {\n const col = this.columns.find((c) => c.field === field);\n const result = runAggregator(aggRef, groupRows, field, col);\n if (result != null) {\n const aggSpan = document.createElement('span');\n aggSpan.className = 'group-aggregate';\n aggSpan.setAttribute('data-field', field);\n // Use column header as label if available\n const colHeader = col?.header ?? field;\n aggSpan.textContent = `${colHeader}: ${result}`;\n aggregatesContainer.appendChild(aggSpan);\n }\n }\n\n if (aggregatesContainer.children.length > 0) {\n cell.appendChild(aggregatesContainer);\n }\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 bodyEl = this.gridElement?.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 // Track whether we've rendered the toggle button yet (should be in first non-expander column)\n let toggleRendered = false;\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 // Skip expander columns (they're handled by other plugins like MasterDetail/Tree)\n // but still render an empty cell to maintain grid structure\n if (isExpanderColumn(col)) {\n cell.setAttribute('data-field', col.field);\n rowEl.appendChild(cell);\n return;\n }\n\n // First non-expander column gets the toggle button + label\n if (!toggleRendered) {\n toggleRendered = true;\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\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 label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\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 // #endregion\n\n // #region Public API\n\n /**\n * Expand all groups.\n */\n expandAll(): void {\n this.expandedKeys = expandAllGroups(this.flattenedRows);\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Collapse all groups.\n */\n collapseAll(): void {\n this.expandedKeys = collapseAllGroups();\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Toggle expansion of a specific group.\n * In accordion mode, expanding a group will collapse all sibling groups.\n * @param key - The group key to toggle\n */\n toggle(key: string): void {\n const isExpanding = !this.expandedKeys.has(key);\n const config = this.config;\n\n // Find the group to get its depth for accordion mode\n const group = this.flattenedRows.find((r) => r.kind === 'group' && r.key === key) as GroupRowModelItem | undefined;\n\n // In accordion mode, collapse sibling groups when expanding\n if (config.accordion && isExpanding && group) {\n const newKeys = new Set<string>();\n // Keep only ancestors (keys that are prefixes of the current key) and the current key\n for (const existingKey of this.expandedKeys) {\n // Check if existingKey is an ancestor of the toggled key\n // Ancestors have composite keys that are prefixes of child keys (separated by '||')\n if (key.startsWith(existingKey + '||') || existingKey.startsWith(key + '||')) {\n // This is an ancestor or descendant - keep it only if ancestor\n if (key.startsWith(existingKey + '||')) {\n newKeys.add(existingKey);\n }\n } else {\n // Check depth - only keep groups at different depths\n const existingGroup = this.flattenedRows.find((r) => r.kind === 'group' && r.key === existingKey) as\n | GroupRowModelItem\n | undefined;\n if (existingGroup && existingGroup.depth !== group.depth) {\n newKeys.add(existingKey);\n }\n }\n }\n newKeys.add(key);\n this.expandedKeys = newKeys;\n } else {\n this.expandedKeys = toggleGroupExpansion(this.expandedKeys, key);\n }\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 // Notify other plugins that grouping state changed (row visibility changed)\n this.emitPluginEvent('grouping-state-change', {\n expandedKeys: [...this.expandedKeys],\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 // #endregion\n}\n"],"names":["buildGroupedRowModel","rows","config","expanded","initialExpanded","groupOn","root","key","value","depth","children","Map","forEach","r","path","Array","isArray","parent","rawVal","depthIdx","seg","String","composite","node","get","set","push","size","has","length","effectiveExpanded","Set","flat","visit","c","isExpanded","kind","row","rowIndex","indexOf","GroupingRowsPlugin","BaseGridPlugin","static","events","type","description","queries","configRules","id","severity","message","check","accordion","defaultExpanded","name","styles","defaultConfig","showRowCount","indentWidth","aggregators","animation","expandedKeys","flattenedRows","isActive","previousVisibleKeys","keysToAnimate","hasAppliedDefaultExpanded","animationStyle","this","isAnimationEnabled","detach","clear","getRowHeight","_index","groupRowHeight","__isGroupRow","handleQuery","query","context","detect","enableRowGrouping","processRows","initialBuild","allKeys","filter","map","getGroupKeys","allGroupKeys","resolveDefaultExpanded","grouped","currentVisibleKeys","item","idx","add","__groupKey","__groupValue","__groupDepth","__groupRows","__groupExpanded","__groupRowCount","groupRow","__rowCacheKey","onCellClick","event","target","originalEvent","closest","toggle","onKeyDown","focusRow","grid","_focusRow","preventDefault","requestRenderWithFocus","renderRow","rowEl","_rowIndex","groupRowRenderer","toggleExpand","result","className","__isCustomRow","setAttribute","innerHTML","appendChild","handleToggle","style","setProperty","height","fullWidth","renderFullWidthGroupRow","renderPerColumnGroupRow","afterRender","body","gridElement","querySelector","animClass","querySelectorAll","cell","parseInt","getAttribute","classList","addEventListener","remove","once","createToggleButton","btn","document","createElement","setIcon","resolveIcon","e","stopPropagation","getGroupLabelText","formatLabel","groupRows","gridColumn","label","textContent","count","aggregatorEntries","Object","entries","aggregatesContainer","field","aggRef","col","columns","find","runAggregator","aggSpan","colHeader","header","bodyEl","gridTemplate","gridTemplateColumns","display","toggleRendered","colIdx","isExpanderColumn","firstColAgg","aggResult","expandAll","keys","expandAllGroups","emitPluginEvent","requestRender","collapseAll","isExpanding","group","newKeys","existingKey","startsWith","existingGroup","newSet","delete","toggleGroupExpansion","emit","expand","collapse","getGroupState","expandedCount","totalGroups","getRowCount","refreshGroups","getExpandedGroups","getFlattenedRows","isGroupingActive","setGroupOn","fn"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(g,w){typeof exports=="object"&&typeof module<"u"?w(exports,require("../../core/internal/sanitize"),require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/sanitize","../../core/plugin/base-plugin","../../core/plugin/expander-column"],w):(g=typeof globalThis<"u"?globalThis:g||self,w(g.TbwGridPlugin_masterDetail={},g.TbwGrid,g.TbwGrid,g.TbwGrid))})(this,(function(g,w,A,x){"use strict";function R(f,e){const t=new Set(f);return t.has(e)?t.delete(e):t.add(e),t}function v(f,e){const t=new Set(f);return t.add(e),t}function y(f,e){const t=new Set(f);return t.delete(e),t}function D(f,e){return f.has(e)}function H(f,e,t,i){const s=document.createElement("div");s.className="master-detail-row",s.setAttribute("data-detail-for",String(e)),s.setAttribute("role","row");const n=document.createElement("div");n.className="master-detail-cell",n.setAttribute("role","cell"),n.style.gridColumn=`1 / ${i+1}`;const c=t(f,e);return typeof c=="string"?n.innerHTML=c:c instanceof HTMLElement&&n.appendChild(c),s.appendChild(n),s}const C="@layer tbw-plugins{tbw-grid .cell[data-field=__tbw_expander]{border-right:none!important;padding:0;display:flex;align-items:center;justify-content:center}tbw-grid .header-row .cell[data-field=__tbw_expander]{display:none}tbw-grid .header-row .cell[data-field=__tbw_expander]+.cell{grid-column:1 / 3}tbw-grid .master-detail-expander{display:flex;align-items:center;justify-content:center;width:100%;height:100%}tbw-grid .master-detail-toggle{cursor:pointer;opacity:.7;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center}tbw-grid .master-detail-toggle:hover{opacity:1}tbw-grid .master-detail-row{grid-column:1 / -1;display:grid;background:var(--tbw-master-detail-bg, var(--tbw-color-row-alt));border-bottom:1px solid var(--tbw-master-detail-border, var(--tbw-color-border));overflow:hidden}tbw-grid .master-detail-cell{padding:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem));overflow:auto}tbw-grid .master-detail-row.tbw-expanding{animation:tbw-detail-expand var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}tbw-grid .master-detail-row.tbw-collapsing{animation:tbw-detail-collapse var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-detail-expand{0%{opacity:0;max-height:0;padding-top:0;padding-bottom:0}to{opacity:1;max-height:var(--tbw-detail-max-height, 31.25rem);padding-top:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem));padding-bottom:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem))}}@keyframes tbw-detail-collapse{0%{opacity:1;max-height:var(--tbw-detail-max-height, 31.25rem)}to{opacity:0;max-height:0}}}";class b extends A.BaseGridPlugin{name="masterDetail";styles=C;get defaultConfig(){return{detailHeight:"auto",expandOnRowClick:!1,collapseOnClickOutside:!1,animation:"slide"}}attach(e){super.attach(e),this.parseLightDomDetail()}parseLightDomDetail(){const e=this.grid;if(!e||typeof e.querySelector!="function")return;const t=e.querySelector("tbw-grid-detail");if(!t)return;const i=e;if(i.__frameworkAdapter?.parseDetailElement){const r=i.__frameworkAdapter.parseDetailElement(t);if(r){this.config={...this.config,detailRenderer:r};return}}const s=t.getAttribute("animation"),n=t.getAttribute("show-expand-column"),c=t.getAttribute("expand-on-row-click"),h=t.getAttribute("collapse-on-click-outside"),d=t.getAttribute("height"),o={};s!==null&&(o.animation=s==="false"?!1:s),n!==null&&(o.showExpandColumn=n!=="false"),c!==null&&(o.expandOnRowClick=c==="true"),h!==null&&(o.collapseOnClickOutside=h==="true"),d!==null&&(o.detailHeight=d==="auto"?"auto":parseInt(d,10));const a=t.innerHTML.trim();a&&!this.config.detailRenderer&&(o.detailRenderer=(r,l)=>{const u=w.evalTemplateString(a,{value:r,row:r});return w.sanitizeHTML(u)}),Object.keys(o).length>0&&(this.config={...this.config,...o})}get animationStyle(){return this.isAnimationEnabled?this.config.animation??"slide":!1}animateExpand(e,t,i){if(!this.isAnimationEnabled||this.animationStyle===!1)return!1;e.classList.add("tbw-expanding");let s=!1;const n=()=>{s||(s=!0,e.classList.remove("tbw-expanding"),t!==void 0&&i!==void 0&&this.#t(e,t,i))};return e.addEventListener("animationend",n,{once:!0}),setTimeout(n,this.animationDuration+50),!0}animateCollapse(e,t){if(!this.isAnimationEnabled||this.animationStyle===!1){t();return}e.classList.add("tbw-collapsing");const i=()=>{e.classList.remove("tbw-collapsing"),t()};e.addEventListener("animationend",i,{once:!0}),setTimeout(i,this.animationDuration+50)}#t(e,t,i){if(!e.isConnected)return;const s=e.offsetHeight;if(s>0){const n=this.measuredDetailHeights.get(t);this.measuredDetailHeights.set(t,s),n!==s&&this.grid.invalidateRowHeight(i)}}expandedRows=new Set;detailElements=new Map;measuredDetailHeights=new Map;rowsToAnimate=new Set;static DEFAULT_DETAIL_HEIGHT=150;getDetailHeight(e){const t=this.detailElements.get(e);if(t&&!(t.classList.contains("tbw-expanding")||t.classList.contains("tbw-collapsing"))){const n=t.offsetHeight;if(n>0)return this.measuredDetailHeights.set(e,n),n}const i=this.measuredDetailHeights.get(e);return i&&i>0?i:typeof this.config?.detailHeight=="number"?this.config.detailHeight:b.DEFAULT_DETAIL_HEIGHT}toggleAndEmit(e,t){this.expandedRows=R(this.expandedRows,e);const i=this.expandedRows.has(e);i&&this.rowsToAnimate.add(e),this.emit("detail-expand",{rowIndex:t,row:e,expanded:i}),this.requestRender()}detach(){this.expandedRows.clear(),this.detailElements.clear(),this.measuredDetailHeights.clear(),this.rowsToAnimate.clear()}processColumns(e){if(!(this.config.showExpandColumn===!0||this.config.showExpandColumn!==!1&&!!this.config.detailRenderer))return[...e];const i=[...e];if(x.findExpanderColumn(i))return i;const n=x.createExpanderColumnConfig(this.name);return n.viewRenderer=c=>{const{row:h}=c,d=this.expandedRows.has(h),o=document.createElement("span");o.className="master-detail-expander expander-cell";const a=document.createElement("span");return a.className=`master-detail-toggle${d?" expanded":""}`,this.setIcon(a,this.resolveIcon(d?"collapse":"expand")),a.setAttribute("role","button"),a.setAttribute("tabindex","0"),a.setAttribute("aria-expanded",String(d)),a.setAttribute("aria-label",d?"Collapse details":"Expand details"),o.appendChild(a),o},[n,...i]}onRowClick(e){if(!(!this.config.expandOnRowClick||!this.config.detailRenderer))return this.toggleAndEmit(e.row,e.rowIndex),!1}onCellClick(e){if(e.originalEvent?.target?.classList.contains("master-detail-toggle"))return this.toggleAndEmit(e.row,e.rowIndex),!0;this.expandedRows.size>0&&queueMicrotask(()=>this.#e())}onKeyDown(e){if(e.key!==" ")return;const t=this.grid._focusCol,i=this.grid._focusRow,s=this.visibleColumns[t];if(!s||!x.isExpanderColumn(s))return;const n=this.rows[i];if(n)return e.preventDefault(),this.toggleAndEmit(n,i),this.requestRenderWithFocus(),!0}afterRender(){this.#e()}onScrollRender(){!this.config.detailRenderer||this.expandedRows.size===0||this.#e()}#e(){if(!this.config.detailRenderer)return;const e=this.gridElement?.querySelector(".rows");if(!e)return;const t=this.grid,i=t._rowPool,s=t._virtualization?.start??0,n=t._virtualization?.end??0,c=this.columns.length,h=s,d=n,o=new Map;if(i){const a=Math.min(i.length,d-h);for(let r=0;r<a;r++){const l=i[r];l.parentNode===e&&o.set(h+r,l)}}else{const a=e.querySelectorAll(".data-grid-row");for(const r of a){const l=r.querySelector(".cell[data-row]"),u=l?parseInt(l.getAttribute("data-row")??"-1",10):-1;u>=0&&o.set(u,r)}}for(const[a,r]of this.detailElements){const l=this.rows.indexOf(a),u=this.expandedRows.has(a),p=l>=0&&o.has(l);if(!u||!p){const m=this.grid.__frameworkAdapter;if(m?.unmount){const E=r.querySelector(".master-detail-cell")?.firstElementChild;E&&m.unmount(E)}r.parentNode&&r.remove(),this.detailElements.delete(a)}}for(const[a,r]of o){const l=this.rows[a];if(!l||!this.expandedRows.has(l))continue;const u=this.detailElements.get(l);if(u){u.previousElementSibling!==r&&r.after(u);continue}const p=H(l,a,this.config.detailRenderer,c);typeof this.config.detailHeight=="number"&&(p.style.height=`${this.config.detailHeight}px`),r.after(p),this.detailElements.set(l,p);const m=this.rowsToAnimate.has(l);m&&this.rowsToAnimate.delete(l),m&&this.animateExpand(p,l,a)||requestAnimationFrame(()=>{this.#t(p,l,a)})}}getExtraHeight(){let e=0;for(const t of this.expandedRows)e+=this.getDetailHeight(t);return e}getExtraHeightBefore(e){let t=0;for(const i of this.expandedRows){const s=this.rows.indexOf(i);s>=0&&s<e&&(t+=this.getDetailHeight(i))}return t}getRowHeight(e,t){if(!this.expandedRows.has(e))return;const s=this.grid.defaultRowHeight??28,n=this.getDetailHeight(e);return s+n}adjustVirtualStart(e,t,i){if(this.expandedRows.size===0)return e;const s=this.grid?._virtualization?.positionCache;let n=e;if(s&&s.length>0)for(const c of this.expandedRows){const h=this.rows.indexOf(c);if(h<0||h>=e)continue;s[h].offset+s[h].height>t&&h<n&&(n=h)}else{const c=[];for(const d of this.expandedRows){const o=this.rows.indexOf(d);o>=0&&c.push({index:o,row:d})}c.sort((d,o)=>d.index-o.index);let h=0;for(const{index:d,row:o}of c){const a=d*i+h,r=this.getDetailHeight(o),l=a+i+r;h+=r,!(d>=e)&&l>t&&d<n&&(n=d)}}return n}expand(e){const t=this.rows[e];t&&(this.rowsToAnimate.add(t),this.expandedRows=v(this.expandedRows,t),this.requestRender())}collapse(e){const t=this.rows[e];t&&(this.expandedRows=y(this.expandedRows,t),this.requestRender())}toggle(e){const t=this.rows[e];t&&(this.expandedRows=R(this.expandedRows,t),this.expandedRows.has(t)&&this.rowsToAnimate.add(t),this.requestRender())}isExpanded(e){const t=this.rows[e];return t?D(this.expandedRows,t):!1}expandAll(){for(const e of this.rows)this.rowsToAnimate.add(e),this.expandedRows.add(e);this.requestRender()}collapseAll(){this.expandedRows.clear(),this.requestRender()}getExpandedRows(){const e=[];for(const t of this.expandedRows){const i=this.rows.indexOf(t);i>=0&&e.push(i)}return e}getDetailElement(e){const t=this.rows[e];return t?this.detailElements.get(t):void 0}refreshDetailRenderer(){const e=this.config.detailRenderer;if(this.config={...this.config,detailRenderer:void 0},this.parseLightDomDetail(),!this.config.detailRenderer&&e&&(this.config={...this.config,detailRenderer:e}),this.config.detailRenderer){const t=this.grid;typeof t.refreshColumns=="function"?t.refreshColumns():this.requestRender()}}}g.MasterDetailPlugin=b,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/internal/sanitize"),require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column")):"function"==typeof define&&define.amd?define(["exports","../../core/internal/sanitize","../../core/plugin/base-plugin","../../core/plugin/expander-column"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_masterDetail={},e.TbwGrid,e.TbwGrid,e.TbwGrid)}(this,function(e,t,i,n){"use strict";function s(e,t){const i=new Set(e);return i.has(t)?i.delete(t):i.add(t),i}function a(e,t,i,n){const s=document.createElement("div");s.className="master-detail-row",s.setAttribute("data-detail-for",String(t)),s.setAttribute("role","row");const a=document.createElement("div");a.className="master-detail-cell",a.setAttribute("role","cell"),a.style.gridColumn=`1 / ${n+1}`;const o=i(e,t);return"string"==typeof o?a.innerHTML=o:o instanceof HTMLElement&&a.appendChild(o),s.appendChild(a),s}class o extends i.BaseGridPlugin{name="masterDetail";styles="@layer tbw-plugins{tbw-grid .cell[data-field=__tbw_expander]{border-right:none!important;padding:0;display:flex;align-items:center;justify-content:center}tbw-grid .header-row .cell[data-field=__tbw_expander]{display:none}tbw-grid .header-row .cell[data-field=__tbw_expander]+.cell{grid-column:1 / 3}tbw-grid .master-detail-expander{display:flex;align-items:center;justify-content:center;width:100%;height:100%}tbw-grid .master-detail-toggle{cursor:pointer;opacity:.7;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center}tbw-grid .master-detail-toggle:hover{opacity:1}tbw-grid .master-detail-row{grid-column:1 / -1;display:grid;background:var(--tbw-master-detail-bg, var(--tbw-color-row-alt));border-bottom:1px solid var(--tbw-master-detail-border, var(--tbw-color-border));overflow:hidden}tbw-grid .master-detail-cell{padding:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem));overflow:auto}tbw-grid .master-detail-row.tbw-expanding{animation:tbw-detail-expand var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}tbw-grid .master-detail-row.tbw-collapsing{animation:tbw-detail-collapse var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-detail-expand{0%{opacity:0;max-height:0;padding-top:0;padding-bottom:0}to{opacity:1;max-height:var(--tbw-detail-max-height, 31.25rem);padding-top:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem));padding-bottom:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem))}}@keyframes tbw-detail-collapse{0%{opacity:1;max-height:var(--tbw-detail-max-height, 31.25rem)}to{opacity:0;max-height:0}}}";get defaultConfig(){return{detailHeight:"auto",expandOnRowClick:!1,collapseOnClickOutside:!1,animation:"slide"}}attach(e){super.attach(e),this.parseLightDomDetail()}parseLightDomDetail(){const e=this.grid;if(!e||"function"!=typeof e.querySelector)return;const i=e.querySelector("tbw-grid-detail");if(!i)return;const n=e;if(n.__frameworkAdapter?.parseDetailElement){const e=n.__frameworkAdapter.parseDetailElement(i);if(e)return void(this.config={...this.config,detailRenderer:e})}const s=i.getAttribute("animation"),a=i.getAttribute("show-expand-column"),o=i.getAttribute("expand-on-row-click"),r=i.getAttribute("collapse-on-click-outside"),d=i.getAttribute("height"),l={};null!==s&&(l.animation="false"!==s&&s),null!==a&&(l.showExpandColumn="false"!==a),null!==o&&(l.expandOnRowClick="true"===o),null!==r&&(l.collapseOnClickOutside="true"===r),null!==d&&(l.detailHeight="auto"===d?"auto":parseInt(d,10));const c=i.innerHTML.trim();c&&!this.config.detailRenderer&&(l.detailRenderer=(e,i)=>{const n=t.evalTemplateString(c,{value:e,row:e});return t.sanitizeHTML(n)}),Object.keys(l).length>0&&(this.config={...this.config,...l})}get animationStyle(){return!!this.isAnimationEnabled&&(this.config.animation??"slide")}animateExpand(e,t,i){if(!this.isAnimationEnabled||!1===this.animationStyle)return!1;e.classList.add("tbw-expanding");let n=!1;const s=()=>{n||(n=!0,e.classList.remove("tbw-expanding"),void 0!==t&&void 0!==i&&this.#e(e,t,i))};return e.addEventListener("animationend",s,{once:!0}),setTimeout(s,this.animationDuration+50),!0}animateCollapse(e,t){if(!this.isAnimationEnabled||!1===this.animationStyle)return void t();e.classList.add("tbw-collapsing");const i=()=>{e.classList.remove("tbw-collapsing"),t()};e.addEventListener("animationend",i,{once:!0}),setTimeout(i,this.animationDuration+50)}#e(e,t,i){if(!e.isConnected)return;const n=e.offsetHeight;if(n>0){const e=this.measuredDetailHeights.get(t);this.measuredDetailHeights.set(t,n),e!==n&&this.grid.invalidateRowHeight(i)}}expandedRows=new Set;detailElements=new Map;measuredDetailHeights=new Map;rowsToAnimate=new Set;static DEFAULT_DETAIL_HEIGHT=150;getDetailHeight(e){const t=this.detailElements.get(e);if(t){if(!(t.classList.contains("tbw-expanding")||t.classList.contains("tbw-collapsing"))){const i=t.offsetHeight;if(i>0)return this.measuredDetailHeights.set(e,i),i}}const i=this.measuredDetailHeights.get(e);return i&&i>0?i:"number"==typeof this.config?.detailHeight?this.config.detailHeight:o.DEFAULT_DETAIL_HEIGHT}toggleAndEmit(e,t){this.expandedRows=s(this.expandedRows,e);const i=this.expandedRows.has(e);i&&this.rowsToAnimate.add(e),this.emit("detail-expand",{rowIndex:t,row:e,expanded:i}),this.requestRender()}detach(){this.expandedRows.clear(),this.detailElements.clear(),this.measuredDetailHeights.clear(),this.rowsToAnimate.clear()}processColumns(e){if(!(!0===this.config.showExpandColumn||!1!==this.config.showExpandColumn&&!!this.config.detailRenderer))return[...e];const t=[...e];if(n.findExpanderColumn(t))return t;const i=n.createExpanderColumnConfig(this.name);return i.viewRenderer=e=>{const{row:t}=e,i=this.expandedRows.has(t),n=document.createElement("span");n.className="master-detail-expander expander-cell";const s=document.createElement("span");return s.className="master-detail-toggle"+(i?" expanded":""),this.setIcon(s,this.resolveIcon(i?"collapse":"expand")),s.setAttribute("role","button"),s.setAttribute("tabindex","0"),s.setAttribute("aria-expanded",String(i)),s.setAttribute("aria-label",i?"Collapse details":"Expand details"),n.appendChild(s),n},[i,...t]}onRowClick(e){if(this.config.expandOnRowClick&&this.config.detailRenderer)return this.toggleAndEmit(e.row,e.rowIndex),!1}onCellClick(e){const t=e.originalEvent?.target;if(t?.classList.contains("master-detail-toggle"))return this.toggleAndEmit(e.row,e.rowIndex),!0;this.expandedRows.size>0&&queueMicrotask(()=>this.#t())}onKeyDown(e){if(" "!==e.key)return;const t=this.grid._focusCol,i=this.grid._focusRow,s=this.visibleColumns[t];if(!s||!n.isExpanderColumn(s))return;const a=this.rows[i];return a?(e.preventDefault(),this.toggleAndEmit(a,i),this.requestRenderWithFocus(),!0):void 0}afterRender(){this.#t()}onScrollRender(){this.config.detailRenderer&&0!==this.expandedRows.size&&this.#t()}#t(){if(!this.config.detailRenderer)return;const e=this.gridElement?.querySelector(".rows");if(!e)return;const t=this.grid,i=t._rowPool,n=t._virtualization?.start??0,s=t._virtualization?.end??0,o=this.columns.length,r=n,d=s,l=new Map;if(i){const t=Math.min(i.length,d-r);for(let n=0;n<t;n++){const t=i[n];t.parentNode===e&&l.set(r+n,t)}}else{const t=e.querySelectorAll(".data-grid-row");for(const e of t){const t=e.querySelector(".cell[data-row]"),i=t?parseInt(t.getAttribute("data-row")??"-1",10):-1;i>=0&&l.set(i,e)}}for(const[a,c]of this.detailElements){const e=this.rows.indexOf(a),t=this.expandedRows.has(a),i=e>=0&&l.has(e);if(!t||!i){const e=this.grid.__frameworkAdapter;if(e?.unmount){const t=c.querySelector(".master-detail-cell"),i=t?.firstElementChild;i&&e.unmount(i)}c.parentNode&&c.remove(),this.detailElements.delete(a)}}for(const[c,h]of l){const e=this.rows[c];if(!e||!this.expandedRows.has(e))continue;const t=this.detailElements.get(e);if(t){t.previousElementSibling!==h&&h.after(t);continue}const i=a(e,c,this.config.detailRenderer,o);"number"==typeof this.config.detailHeight&&(i.style.height=`${this.config.detailHeight}px`),h.after(i),this.detailElements.set(e,i);const n=this.rowsToAnimate.has(e);n&&this.rowsToAnimate.delete(e);n&&this.animateExpand(i,e,c)||requestAnimationFrame(()=>{this.#e(i,e,c)})}}getExtraHeight(){let e=0;for(const t of this.expandedRows)e+=this.getDetailHeight(t);return e}getExtraHeightBefore(e){let t=0;for(const i of this.expandedRows){const n=this.rows.indexOf(i);n>=0&&n<e&&(t+=this.getDetailHeight(i))}return t}getRowHeight(e,t){if(!this.expandedRows.has(e))return;return(this.grid.defaultRowHeight??28)+this.getDetailHeight(e)}adjustVirtualStart(e,t,i){if(0===this.expandedRows.size)return e;const n=this.grid?._virtualization?.positionCache;let s=e;if(n&&n.length>0)for(const a of this.expandedRows){const i=this.rows.indexOf(a);if(i<0||i>=e)continue;n[i].offset+n[i].height>t&&i<s&&(s=i)}else{const n=[];for(const e of this.expandedRows){const t=this.rows.indexOf(e);t>=0&&n.push({index:t,row:e})}n.sort((e,t)=>e.index-t.index);let a=0;for(const{index:o,row:r}of n){const n=o*i+a,d=this.getDetailHeight(r);a+=d,o>=e||n+i+d>t&&o<s&&(s=o)}}return s}expand(e){const t=this.rows[e];t&&(this.rowsToAnimate.add(t),this.expandedRows=function(e,t){const i=new Set(e);return i.add(t),i}(this.expandedRows,t),this.requestRender())}collapse(e){const t=this.rows[e];t&&(this.expandedRows=function(e,t){const i=new Set(e);return i.delete(t),i}(this.expandedRows,t),this.requestRender())}toggle(e){const t=this.rows[e];t&&(this.expandedRows=s(this.expandedRows,t),this.expandedRows.has(t)&&this.rowsToAnimate.add(t),this.requestRender())}isExpanded(e){const t=this.rows[e];return!!t&&function(e,t){return e.has(t)}(this.expandedRows,t)}expandAll(){for(const e of this.rows)this.rowsToAnimate.add(e),this.expandedRows.add(e);this.requestRender()}collapseAll(){this.expandedRows.clear(),this.requestRender()}getExpandedRows(){const e=[];for(const t of this.expandedRows){const i=this.rows.indexOf(t);i>=0&&e.push(i)}return e}getDetailElement(e){const t=this.rows[e];return t?this.detailElements.get(t):void 0}refreshDetailRenderer(){const e=this.config.detailRenderer;if(this.config={...this.config,detailRenderer:void 0},this.parseLightDomDetail(),!this.config.detailRenderer&&e&&(this.config={...this.config,detailRenderer:e}),this.config.detailRenderer){const e=this.grid;"function"==typeof e.refreshColumns?e.refreshColumns():this.requestRender()}}}e.MasterDetailPlugin=o,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
|
|
2
2
|
//# sourceMappingURL=master-detail.umd.js.map
|