@toolbox-web/grid 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -3
- package/all.d.ts +1 -0
- package/all.js +2 -2
- package/all.js.map +1 -1
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/lib/core/internal/aria.d.ts +4 -0
- package/lib/core/types.d.ts +90 -31
- package/lib/features/sticky-rows.d.ts +9 -0
- package/lib/features/sticky-rows.js +2 -0
- package/lib/features/sticky-rows.js.map +1 -0
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js +1 -1
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts +15 -0
- package/lib/plugins/grouping-rows/index.js +2 -2
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts +28 -4
- package/lib/plugins/pinned-rows/index.d.ts +3 -2
- package/lib/plugins/pinned-rows/index.js +1 -1
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pinned-rows/pinned-rows.d.ts +29 -1
- package/lib/plugins/pinned-rows/types.d.ts +96 -9
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/print/index.js.map +1 -1
- package/lib/plugins/reorder-columns/index.js.map +1 -1
- package/lib/plugins/reorder-rows/index.js.map +1 -1
- package/lib/plugins/responsive/index.js +1 -1
- package/lib/plugins/responsive/index.js.map +1 -1
- package/lib/plugins/row-drag-drop/index.js.map +1 -1
- package/lib/plugins/row-drag-drop/types.d.ts +7 -0
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/sticky-rows/StickyRowsPlugin.d.ts +114 -0
- package/lib/plugins/sticky-rows/index.d.ts +7 -0
- package/lib/plugins/sticky-rows/index.js +2 -0
- package/lib/plugins/sticky-rows/index.js.map +1 -0
- package/lib/plugins/sticky-rows/types.d.ts +67 -0
- package/lib/plugins/tooltip/index.js.map +1 -1
- package/lib/plugins/tree/index.js +1 -1
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/tree/types.d.ts +4 -0
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/VisibilityPlugin.d.ts +0 -11
- package/lib/plugins/visibility/index.d.ts +1 -2
- package/lib/plugins/visibility/index.js.map +1 -1
- package/lib/plugins/visibility/types.d.ts +32 -0
- package/package.json +1 -1
- package/public.d.ts +28 -67
- package/umd/grid.all.umd.js +1 -1
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +1 -1
- package/umd/grid.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/grouping-rows.umd.js +1 -1
- package/umd/plugins/grouping-rows.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/responsive.umd.js +1 -1
- package/umd/plugins/responsive.umd.js.map +1 -1
- package/umd/plugins/sticky-rows.umd.js +2 -0
- package/umd/plugins/sticky-rows.umd.js.map +1 -0
- package/umd/plugins/tree.umd.js +1 -1
- package/umd/plugins/tree.umd.js.map +1 -1
- package/umd/plugins/visibility.umd.js.map +1 -1
|
@@ -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, GroupDefinition, GroupingRowsConfig, GroupRowModelItem, RenderRow } from './types';\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: GroupingRowsConfig;\n expanded: Set<string>;\n /** Initial expanded state to apply (processed by the plugin) */\n initialExpanded?: Set<string>;\n /** Sort direction per group depth level. 1 = ascending, -1 = descending.\n * When omitted, groups at all levels sort ascending. */\n groupSortDirections?: Map<number, 1 | -1>;\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, groupSortDirections }: 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 — push each row into every ancestor along the path\n // so that each group's `rows` array contains ALL data rows in its subtree.\n // This is required for correct counts and aggregations on multi-level groups.\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 node.rows.push(r);\n parent = node;\n });\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 // Pre-build row→index map for O(1) lookups (avoids O(n²) from rows.indexOf)\n const rowIndexMap = new Map<any, number>();\n for (let i = 0; i < rows.length; i++) {\n rowIndexMap.set(rows[i], i);\n }\n\n // Merge expanded sets - use initialExpanded on first render, then expanded takes over\n const effectiveExpanded = new Set([...expanded, ...(initialExpanded ?? [])]);\n\n // Sort sibling groups by their group value so that group header order is\n // deterministic and respects the active sort direction for each depth level.\n // Default: ascending. When a user sorts a grouped column, the corresponding\n // depth level's direction flips, keeping groups in predictable order.\n const sortedChildren = (node: GroupNode): GroupNode[] => {\n const children = [...node.children.values()];\n // Determine direction for this depth level (children are one level deeper than the node)\n const childDepth = node === root ? 0 : node.depth + 1;\n const dir = groupSortDirections?.get(childDepth) ?? 1;\n children.sort((a, b) => {\n const av = a.value;\n const bv = b.value;\n if (av == null && bv == null) return 0;\n if (av == null) return dir;\n if (bv == null) return -dir;\n return av > bv ? dir : av < bv ? -dir : 0;\n });\n return children;\n };\n\n // Flatten tree to array\n const flat: RenderRow[] = [];\n const visit = (node: GroupNode) => {\n if (node === root) {\n for (const c of sortedChildren(node)) 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 for (const c of sortedChildren(node)) visit(c);\n } else {\n node.rows.forEach((r) => flat.push({ kind: 'data', row: r, rowIndex: rowIndexMap.get(r) ?? -1 }));\n }\n }\n };\n visit(root);\n\n return flat;\n}\n\n/**\n * Discover which column field produces the group value at each depth level.\n *\n * Samples the first row's `groupOn` output to get the group path, then checks\n * which column fields produce matching values for that row. This mapping allows\n * the plugin to apply user-invoked column sort directions to the correct group\n * depth levels.\n *\n * @returns Map from depth index to column field name, or empty map if unmappable\n */\nexport function resolveGroupFields(\n rows: any[],\n groupOn: (row: any) => any[] | any | null | false,\n columnFields: string[],\n): Map<number, string> {\n const depthToField = new Map<number, string>();\n if (rows.length === 0) return depthToField;\n\n const sampleRow = rows[0];\n let path: any = groupOn(sampleRow);\n if (path == null || path === false) return depthToField;\n if (!Array.isArray(path)) path = [path];\n\n for (let depth = 0; depth < path.length; depth++) {\n const groupValue = path[depth];\n for (const field of columnFields) {\n if (sampleRow[field] === groupValue) {\n depthToField.set(depth, field);\n break;\n }\n }\n }\n return depthToField;\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// #region Pre-Defined Group Model\n\ninterface PreDefinedGroupModelArgs {\n groups: GroupDefinition[];\n expanded: Set<string>;\n groupRows: Map<string, unknown[]>;\n loadingGroups: Set<string>;\n parentPath?: string[];\n}\n\n/**\n * Build a flattened render model from pre-defined group definitions.\n *\n * Unlike `buildGroupedRowModel`, this does not analyze row data — groups\n * are provided externally (e.g. from a server). Row data for each group\n * is populated lazily via the `groupRows` map.\n *\n * @param args - Pre-defined grouping arguments\n * @returns Flattened array of render rows (groups + data rows)\n */\nexport function buildPreDefinedGroupModel({\n groups,\n expanded,\n groupRows,\n loadingGroups,\n parentPath = [],\n}: PreDefinedGroupModelArgs): RenderRow[] {\n const flat: RenderRow[] = [];\n const depth = parentPath.length;\n\n for (const group of groups) {\n const currentPath = [...parentPath, group.key];\n const isExpanded = expanded.has(group.key);\n const rows = groupRows.get(group.key) ?? [];\n const isLoading = loadingGroups.has(group.key);\n\n flat.push({\n kind: 'group',\n key: group.key,\n value: group.value,\n depth,\n rows,\n expanded: isExpanded,\n });\n\n if (isExpanded) {\n // Nested child groups take priority over leaf rows\n if (group.children?.length) {\n const childRows = buildPreDefinedGroupModel({\n groups: group.children,\n expanded,\n groupRows,\n loadingGroups,\n parentPath: currentPath,\n });\n flat.push(...childRows);\n } else if (isLoading) {\n // Loading placeholder — rendered by the plugin as a loading indicator\n flat.push({ kind: 'data', row: { __loading: true, __groupKey: group.key }, rowIndex: -1 });\n } else {\n // Leaf rows from the groupRows map\n rows.forEach((row, idx) => {\n flat.push({ kind: 'data', row, rowIndex: idx });\n });\n }\n }\n }\n\n return flat;\n}\n\n/**\n * Compute the group path (array of ancestor keys) for a given group key\n * within a pre-defined group structure.\n *\n * @param groups - The group definitions to search\n * @param targetKey - The key to find\n * @param parentPath - Accumulated path (used for recursion)\n * @returns Array of group keys from root to target, or empty array if not found\n */\nexport function getGroupPath(groups: GroupDefinition[], targetKey: string, parentPath: string[] = []): string[] {\n for (const group of groups) {\n const currentPath = [...parentPath, group.key];\n if (group.key === targetKey) {\n return currentPath;\n }\n if (group.children?.length) {\n const found = getGroupPath(group.children, targetKey, currentPath);\n if (found.length > 0) return found;\n }\n }\n return [];\n}\n// #endregion\n","/**\n * Row Grouping Plugin (Class-based)\n *\n * Enables hierarchical row grouping with expand/collapse and aggregations.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport { aggregatorRegistry } from '../../core/internal/aggregators';\nimport { announce, getA11yMessage } from '../../core/internal/aria';\nimport { setRowLoadingState } from '../../core/internal/loading';\nimport {\n BaseGridPlugin,\n CellClickEvent,\n HeaderClickEvent,\n type GridElement,\n type PluginManifest,\n type PluginQuery,\n} from '../../core/plugin/base-plugin';\nimport { isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { RowElementInternal } from '../../core/types';\nimport type {\n DataSourceChildrenDetail,\n DataSourceDataDetail,\n FetchChildrenQuery,\n ViewportMappingQuery,\n ViewportMappingResponse,\n} from '../server-side/datasource-types';\nimport {\n buildGroupedRowModel,\n buildPreDefinedGroupModel,\n collapseAllGroups,\n expandAllGroups,\n getGroupKeys,\n getGroupPath,\n getGroupRowCount,\n resolveDefaultExpanded,\n resolveGroupFields,\n toggleGroupExpansion,\n} from './grouping-rows';\nimport styles from './grouping-rows.css?inline';\nimport type {\n ExpandCollapseAnimation,\n GroupCollapseDetail,\n GroupDefinition,\n GroupExpandDetail,\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 * ## 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 { queryGrid } from '@toolbox-web/grid';\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n *\n * const grid = queryGrid('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 modifiesRowStructure: true,\n hookPriority: {\n processRows: 10, // Run after ServerSide (-10) so we receive managedNodes[]\n // Run before MultiSort so we can intercept header clicks on grouped columns\n onHeaderClick: -1,\n },\n incompatibleWith: [\n {\n name: 'tree',\n reason:\n 'Both plugins transform the entire row model. TreePlugin flattens nested hierarchies while ' +\n 'GroupingRowsPlugin groups flat rows with synthetic headers. Use one approach per grid.',\n },\n {\n name: 'pivot',\n reason:\n 'PivotPlugin creates its own aggregated row and column structure. ' +\n 'Row grouping cannot be applied on top of pivot-generated rows.',\n },\n ],\n events: [\n {\n type: 'group-toggle',\n description: 'Emitted when groups are expanded/collapsed. Broadcast to both DOM consumers and plugin bus.',\n },\n {\n type: 'group-expand',\n description: 'Emitted when a pre-defined group is expanded.',\n },\n {\n type: 'group-collapse',\n description: 'Emitted when a pre-defined group is collapsed.',\n },\n ],\n queries: [\n {\n type: 'canMoveRow',\n description: 'Returns false for group header rows (cannot be reordered)',\n },\n {\n type: 'grouping:get-grouped-fields',\n description: 'Returns the column field names that match group depth levels (string[])',\n },\n {\n type: 'datasource:viewport-mapping',\n description: 'Translates flat viewport row indices to top-level group indices for ServerSide pagination.',\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 /**\n * Optional dependency on MultiSort for coordinated sort management.\n * When MultiSort is loaded, GroupingRows queries its sort model to determine\n * group header ordering. Without it, falls back to core sort state.\n */\n static override readonly dependencies = [\n { name: 'multiSort', required: false, reason: 'Queries sort model for coordinated group sorting' },\n { name: 'serverSide', required: false, reason: 'Consumes datasource events for lazy-loaded grouped data' },\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 /** Pre-defined group definitions (from config, setGroups(), or datasource:data) */\n private preDefinedGroups: GroupDefinition[] = [];\n /** Row data keyed by group key (from setGroupRows() or datasource:children) */\n private groupRowsMap = new Map<string, unknown[]>();\n /** Groups currently in a loading state */\n private loadingGroups = new Set<string>();\n /** Column fields that produce group values (depth 0, 1, ...). Cached for\n * the `grouping:get-grouped-fields` query so MultiSort can filter them out. */\n private groupedFields: string[] = [];\n /** User-specified sort directions per group depth level. Toggled via\n * header clicks on grouped columns. */\n private userGroupSortDirections = new Map<number, 1 | -1>();\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 this.preDefinedGroups = [];\n this.groupRowsMap.clear();\n this.loadingGroups.clear();\n this.groupedFields = [];\n this.userGroupSortDirections.clear();\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 if (query.type === 'grouping:get-grouped-fields') {\n return [...this.groupedFields];\n }\n if (query.type === 'datasource:viewport-mapping') {\n // Translate visible flat row indices → top-level group indices for ServerSide pagination\n const { viewportStart, viewportEnd } = query.context as ViewportMappingQuery;\n if (this.flattenedRows.length === 0) return undefined;\n // Only respond in pre-defined groups mode (when datasource data is being consumed)\n if (this.preDefinedGroups.length === 0 && !Array.isArray(this.config.groups)) return undefined;\n\n const activeGroups = this.getActiveGroups();\n const startNode = this.getTopLevelGroupIndex(viewportStart);\n const endNode = this.getTopLevelGroupIndex(viewportEnd) + 1; // exclusive\n const totalLoadedNodes = activeGroups.length;\n\n return { startNode, endNode, totalLoadedNodes } satisfies ViewportMappingResponse;\n }\n return undefined;\n }\n\n /**\n * Translate a flat row index to the top-level group index it belongs to.\n * Walks flattenedRows up to the given index and counts depth-0 groups encountered.\n */\n private getTopLevelGroupIndex(flatIndex: number): number {\n let groupIndex = -1;\n const clampedIndex = Math.min(flatIndex, this.flattenedRows.length - 1);\n for (let i = 0; i <= clampedIndex; i++) {\n const row = this.flattenedRows[i];\n if (row.kind === 'group' && row.depth === 0) {\n groupIndex++;\n }\n }\n return Math.max(0, groupIndex);\n }\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n // Listen for datasource:data from ServerSidePlugin — claim data as group definitions\n this.on('datasource:data', (detail: unknown) => {\n const d = detail as DataSourceDataDetail;\n if (!d.claimed) {\n d.claimed = true;\n // Interpret incoming rows as group definitions\n this.preDefinedGroups = d.rows as unknown as GroupDefinition[];\n this.groupRowsMap.clear();\n this.loadingGroups.clear();\n this.hasAppliedDefaultExpanded = false;\n this.requestRender();\n }\n });\n\n // Listen for datasource:children — consume child rows from ServerSide\n this.on('datasource:children', (detail: unknown) => {\n const d = detail as DataSourceChildrenDetail;\n if (d.context?.source !== 'grouping-rows') return;\n d.claimed = true;\n\n // Store the rows for the group and clear loading state\n const groupKey = d.context.groupKey as string;\n if (groupKey) {\n this.groupRowsMap.set(groupKey, d.rows);\n this.loadingGroups.delete(groupKey);\n this.requestRender();\n }\n });\n }\n\n /**\n * Intercept header clicks on grouped columns to toggle group sort direction.\n * Returns `true` for grouped columns to prevent MultiSort from handling them.\n * @internal\n */\n override onHeaderClick(event: HeaderClickEvent): boolean | void {\n const fieldIndex = this.groupedFields.indexOf(event.field);\n if (fieldIndex === -1) return; // Not a grouped column — let other plugins handle it\n\n if (!event.column.sortable) return;\n\n // The fieldIndex in groupedFields corresponds to the group depth level\n // (resolveGroupFields populates them in depth order: 0, 1, 2, ...)\n const targetDepth = fieldIndex;\n\n // Toggle direction: asc → desc → asc\n const current = this.userGroupSortDirections.get(targetDepth) ?? 1;\n this.userGroupSortDirections.set(targetDepth, current === 1 ? -1 : 1);\n\n this.requestRender();\n return true; // Prevent MultiSort from handling this column\n }\n // #endregion\n\n // #region Sort State Resolution\n\n /**\n * Build a sort-direction map for each group depth level by cross-referencing\n * the active sort state with the column fields that produce group values.\n *\n * Supports three direction sources (in priority order):\n * 1. User-set directions via header clicks on grouped columns\n * 2. MultiSort plugin model (for backwards compatibility / state restore)\n * 3. Core single-column sort state\n */\n private resolveGroupSortDirections(rows: readonly any[]): Map<number, 1 | -1> | undefined {\n const config = this.config;\n if (typeof config.groupOn !== 'function' || rows.length === 0) {\n this.groupedFields = [];\n return undefined;\n }\n\n // Discover depth → field mapping by sampling rows\n const columnFields = this.columns.map((c) => c.field);\n const depthToField = resolveGroupFields([...rows], config.groupOn, columnFields);\n\n // Cache grouped field names for the grouping:get-grouped-fields query\n this.groupedFields = [...depthToField.values()];\n\n if (depthToField.size === 0) return undefined;\n\n // Start with user-set directions (from header clicks on grouped columns)\n const directions = new Map<number, 1 | -1>(this.userGroupSortDirections);\n\n // Fill missing depths from MultiSort model or core sort state\n if (directions.size < depthToField.size) {\n const activeSorts = new Map<string, 1 | -1>();\n\n const multiSortResults = this.grid?.query?.('sort:get-model', null);\n if (Array.isArray(multiSortResults) && multiSortResults.length > 0) {\n const sortModel = multiSortResults[0] as Array<{ field: string; direction: 'asc' | 'desc' }>;\n if (Array.isArray(sortModel)) {\n for (const entry of sortModel) {\n activeSorts.set(entry.field, entry.direction === 'desc' ? -1 : 1);\n }\n }\n }\n\n if (activeSorts.size === 0) {\n const gridHost = this.grid as unknown as { _sortState?: { field: string; direction: 1 | -1 } | null };\n if (gridHost._sortState) {\n activeSorts.set(gridHost._sortState.field, gridHost._sortState.direction);\n }\n }\n\n for (const [depth, field] of depthToField) {\n if (!directions.has(depth)) {\n const dir = activeSorts.get(field);\n if (dir !== undefined) {\n directions.set(depth, dir);\n }\n }\n }\n }\n\n return directions.size > 0 ? directions : undefined;\n }\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 (\n typeof config?.groupOn === 'function' ||\n typeof config?.enableRowGrouping === 'boolean' ||\n Array.isArray(config?.groups)\n );\n }\n\n /** @internal */\n override processRows(rows: readonly any[]): any[] {\n // Pre-defined groups path — use external group structure instead of groupOn analysis\n if (this.preDefinedGroups.length > 0 || Array.isArray(this.config.groups)) {\n if (this.preDefinedGroups.length > 0 || (Array.isArray(this.config.groups) && this.config.groups.length > 0)) {\n return this.processPreDefinedGroups();\n }\n this.isActive = false;\n this.flattenedRows = [];\n return [];\n }\n\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 groupSortDirections = this.resolveGroupSortDirections(rows);\n const initialBuild = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: new Set(), // Empty to get all root groups\n groupSortDirections,\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 groupSortDirections,\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(`.${GridClasses.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 or loading placeholder), false otherwise.\n * @internal\n */\n override renderRow(row: any, rowEl: HTMLElement, _rowIndex: number): boolean {\n // Handle loading placeholder rows for pre-defined groups\n // Uses the grid's built-in row loading API for consistent, customizable spinners\n if (row?.__loading === true && row?.__groupKey) {\n rowEl.className = 'data-grid-row';\n (rowEl as RowElementInternal).__isCustomRow = true;\n rowEl.innerHTML = '';\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.style.gridColumn = '1 / -1';\n cell.setAttribute('role', 'gridcell');\n cell.textContent = '\\u00A0'; // for row height\n rowEl.appendChild(cell);\n setRowLoadingState(rowEl, true);\n return true;\n }\n\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 Pre-Defined Groups\n\n /**\n * Build the row model from pre-defined group definitions.\n * Used when `groups` config or `setGroups()` provides external group structure.\n */\n private processPreDefinedGroups(): any[] {\n const groups =\n this.preDefinedGroups.length > 0\n ? this.preDefinedGroups\n : Array.isArray(this.config.groups)\n ? this.config.groups\n : [];\n\n if (groups.length === 0) {\n this.isActive = false;\n this.flattenedRows = [];\n return [];\n }\n\n // Resolve defaultExpanded on first render only\n if (!this.hasAppliedDefaultExpanded && this.expandedKeys.size === 0 && this.config.defaultExpanded !== false) {\n const allKeys = this.collectGroupKeys(groups);\n const initialExpanded = resolveDefaultExpanded(this.config.defaultExpanded ?? false, allKeys);\n if (initialExpanded.size > 0) {\n this.expandedKeys = new Set(initialExpanded);\n this.hasAppliedDefaultExpanded = true;\n }\n }\n\n const grouped = buildPreDefinedGroupModel({\n groups,\n expanded: this.expandedKeys,\n groupRows: this.groupRowsMap,\n loadingGroups: this.loadingGroups,\n });\n\n this.isActive = true;\n this.flattenedRows = grouped;\n\n // Track visible data rows 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 return grouped.map((item) => {\n if (item.kind === 'group') {\n // Look up the pre-defined group to get rowCount from server\n const groupDef = this.findGroupDefinition(groups, item.key);\n const rowCount = groupDef?.rowCount ?? item.rows.length;\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: rowCount,\n __rowCacheKey: `group:${item.key}`,\n };\n }\n return item.row;\n });\n }\n\n /**\n * Collect all group keys from a pre-defined group tree.\n */\n private collectGroupKeys(groups: GroupDefinition[]): string[] {\n const keys: string[] = [];\n for (const g of groups) {\n keys.push(g.key);\n if (g.children?.length) {\n keys.push(...this.collectGroupKeys(g.children));\n }\n }\n return keys;\n }\n\n /**\n * Get the active group definitions (pre-defined or empty).\n */\n private getActiveGroups(): GroupDefinition[] {\n if (this.preDefinedGroups.length > 0) return this.preDefinedGroups;\n return Array.isArray(this.config.groups) ? this.config.groups : [];\n }\n\n /**\n * Find a group definition by key in the pre-defined group tree.\n */\n private findGroupDefinition(groups: GroupDefinition[], key: string): GroupDefinition | undefined {\n for (const g of groups) {\n if (g.key === key) return g;\n if (g.children?.length) {\n const found = this.findGroupDefinition(g.children, key);\n if (found) return found;\n }\n }\n return undefined;\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 = `${GridClasses.GROUP_TOGGLE}${expanded ? ` ${GridClasses.EXPANDED}` : ''}`;\n btn.setAttribute('aria-label', expanded ? 'Collapse group' : 'Expand group');\n this.setIcon(btn, 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 = GridClasses.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 = GridClasses.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 = aggregatorRegistry.run(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 = aggregatorRegistry.run(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 = GridClasses.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 = aggregatorRegistry.run(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('group-toggle', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Collapse all groups.\n */\n collapseAll(): void {\n this.expandedKeys = collapseAllGroups();\n this.emitPluginEvent('group-toggle', { 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.broadcast<GroupToggleDetail>('group-toggle', {\n key,\n expanded: this.expandedKeys.has(key),\n value: group?.value,\n depth: group?.depth ?? 0,\n expandedKeys: [...this.expandedKeys],\n });\n\n // Emit group-expand/group-collapse events for pre-defined mode\n const activeGroups = this.getActiveGroups();\n if (activeGroups.length > 0) {\n const groupPath = getGroupPath(activeGroups, key);\n if (isExpanding) {\n this.emit<GroupExpandDetail>('group-expand', { groupKey: key, groupPath });\n\n // Request child rows via unified DataSource if not already cached\n if (!this.groupRowsMap.has(key)) {\n const groupDef = this.findGroupDefinition(activeGroups, key);\n if (groupDef) {\n this.loadingGroups.add(key);\n this.grid?.query?.('datasource:fetch-children', {\n context: { source: 'grouping-rows', groupKey: key, group: groupDef, groupPath },\n } satisfies FetchChildrenQuery);\n }\n }\n } else {\n this.emit<GroupCollapseDetail>('group-collapse', { groupKey: key, groupPath });\n }\n }\n\n // Announce group state change for screen readers\n const expanded = this.expandedKeys.has(key);\n const groupName = group?.value != null ? String(group.value) : key;\n if (expanded) {\n const rowCount = group?.rows?.length ?? 0;\n announce(this.gridElement, getA11yMessage(this.gridElement, 'groupExpanded', groupName, rowCount));\n } else {\n announce(this.gridElement, getA11yMessage(this.gridElement, 'groupCollapsed', groupName));\n }\n\n this.requestRender();\n }\n\n /**\n * Check if a specific group is expanded.\n * @param key - The group key to check\n * @returns Whether the group is expanded\n */\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n /**\n * Expand a specific group.\n * @param key - The group key to expand\n */\n expand(key: string): void {\n if (!this.expandedKeys.has(key)) {\n this.expandedKeys = new Set([...this.expandedKeys, key]);\n this.requestRender();\n }\n }\n\n /**\n * Collapse a specific group.\n * @param key - The group key to collapse\n */\n collapse(key: string): void {\n if (this.expandedKeys.has(key)) {\n const newKeys = new Set(this.expandedKeys);\n newKeys.delete(key);\n this.expandedKeys = newKeys;\n this.requestRender();\n }\n }\n\n /**\n * Get the current group state.\n * @returns Group state information\n */\n getGroupState(): GroupState {\n const groupRows = this.flattenedRows.filter((r) => r.kind === 'group');\n return {\n isActive: this.isActive,\n expandedCount: this.expandedKeys.size,\n totalGroups: groupRows.length,\n expandedKeys: [...this.expandedKeys],\n };\n }\n\n /**\n * Get the total count of visible rows (including group headers).\n * @returns Number of visible rows\n */\n getRowCount(): number {\n return this.flattenedRows.length;\n }\n\n /**\n * Refresh the grouped row model.\n * Call this after modifying groupOn or other config options.\n */\n refreshGroups(): void {\n this.requestRender();\n }\n\n /**\n * Get current expanded group keys.\n * @returns Array of expanded group keys\n */\n getExpandedGroups(): string[] {\n return [...this.expandedKeys];\n }\n\n /**\n * Get the flattened row model.\n * @returns Array of render rows (groups + data rows)\n */\n getFlattenedRows(): RenderRow[] {\n return this.flattenedRows;\n }\n\n /**\n * Check if grouping is currently active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Set the groupOn function dynamically.\n * @param fn - The groupOn function or undefined to disable\n */\n setGroupOn(fn: ((row: any) => any[] | any | null | false) | undefined): void {\n (this.config as GroupingRowsConfig).groupOn = fn;\n this.requestRender();\n }\n\n // --- Pre-defined group API ---\n\n /**\n * Replace auto-detected groups with an externally provided group structure.\n *\n * When groups are set, the plugin switches to pre-defined mode — `groupOn`\n * is ignored and the plugin renders the provided group headers instead.\n * Row data for each group must be populated via {@link setGroupRows}.\n *\n * @param groups - Array of group definitions, or empty array to clear\n */\n setGroups(groups: GroupDefinition[]): void {\n this.preDefinedGroups = groups;\n this.groupRowsMap.clear();\n this.loadingGroups.clear();\n this.expandedKeys.clear();\n this.hasAppliedDefaultExpanded = false;\n this.requestRender();\n }\n\n /**\n * Get the current pre-defined group structure.\n *\n * Returns the groups set via {@link setGroups}, received from `datasource:data`,\n * or the `groups` config array. Returns an empty array when using `groupOn`-based grouping.\n *\n * @returns Current group definitions\n */\n getGroups(): GroupDefinition[] {\n if (this.preDefinedGroups.length > 0) return [...this.preDefinedGroups];\n return Array.isArray(this.config.groups) ? [...this.config.groups] : [];\n }\n\n /**\n * Populate row data for an expanded group.\n *\n * Call this in response to a `group-expand` event after fetching rows\n * from the server. The plugin will re-render to show the rows.\n *\n * When `ServerSidePlugin` is loaded, this is handled automatically via\n * `datasource:children` events — you only need this for the imperative API.\n *\n * @param groupKey - The group key to populate\n * @param rows - The row data for this group\n */\n setGroupRows(groupKey: string, rows: unknown[]): void {\n this.groupRowsMap.set(groupKey, rows);\n this.loadingGroups.delete(groupKey);\n this.requestRender();\n }\n\n /**\n * Toggle loading indicator for a group.\n *\n * When loading is true, the group shows a loading spinner instead of row data.\n * Call with `false` after rows are loaded (also cleared by {@link setGroupRows}).\n *\n * @param groupKey - The group key\n * @param loading - Whether the group is loading\n */\n setGroupLoading(groupKey: string, loading: boolean): void {\n if (loading) {\n this.loadingGroups.add(groupKey);\n } else {\n this.loadingGroups.delete(groupKey);\n }\n this.requestRender();\n }\n\n /**\n * Clear cached row data for one or all groups.\n *\n * Use when the server data has changed and groups need to be re-fetched\n * on next expand.\n *\n * @param groupKey - Specific group key to clear, or omit to clear all\n */\n clearGroupRows(groupKey?: string): void {\n if (groupKey != null) {\n this.groupRowsMap.delete(groupKey);\n } else {\n this.groupRowsMap.clear();\n }\n this.requestRender();\n }\n // #endregion\n}\n"],"names":["buildGroupedRowModel","rows","config","expanded","initialExpanded","groupSortDirections","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","rowIndexMap","i","effectiveExpanded","Set","sortedChildren","values","childDepth","dir","sort","a","b","av","bv","flat","visit","c","isExpanded","kind","row","rowIndex","resolveDefaultExpanded","allGroupKeys","buildPreDefinedGroupModel","groups","groupRows","loadingGroups","parentPath","group","currentPath","isLoading","childRows","__loading","__groupKey","idx","getGroupPath","targetKey","found","GroupingRowsPlugin","BaseGridPlugin","static","modifiesRowStructure","hookPriority","processRows","onHeaderClick","incompatibleWith","name","reason","events","type","description","queries","configRules","id","severity","message","check","accordion","defaultExpanded","required","styles","defaultConfig","showRowCount","indentWidth","aggregators","animation","expandedKeys","flattenedRows","isActive","previousVisibleKeys","keysToAnimate","hasAppliedDefaultExpanded","preDefinedGroups","groupRowsMap","groupedFields","userGroupSortDirections","animationStyle","this","isAnimationEnabled","detach","clear","getRowHeight","_index","groupRowHeight","__isGroupRow","handleQuery","query","context","viewportStart","viewportEnd","activeGroups","getActiveGroups","startNode","getTopLevelGroupIndex","endNode","totalLoadedNodes","flatIndex","groupIndex","clampedIndex","Math","min","max","attach","grid","super","on","detail","d","claimed","requestRender","source","groupKey","delete","event","fieldIndex","indexOf","field","column","sortable","targetDepth","current","resolveGroupSortDirections","columnFields","columns","map","depthToField","sampleRow","groupValue","resolveGroupFields","directions","activeSorts","multiSortResults","sortModel","entry","direction","gridHost","_sortState","detect","enableRowGrouping","processPreDefinedGroups","initialBuild","allKeys","filter","getGroupKeys","grouped","currentVisibleKeys","item","add","__groupValue","__groupDepth","__groupRows","__groupExpanded","__groupRowCount","groupRow","__rowCacheKey","onCellClick","target","originalEvent","closest","GridClasses","GROUP_TOGGLE","toggle","onKeyDown","focusRow","_focusRow","preventDefault","requestRenderWithFocus","renderRow","rowEl","_rowIndex","className","__isCustomRow","innerHTML","cell","document","createElement","style","gridColumn","setAttribute","textContent","appendChild","setRowLoadingState","groupRowRenderer","toggleExpand","result","handleToggle","setProperty","height","fullWidth","renderFullWidthGroupRow","renderPerColumnGroupRow","afterRender","body","gridElement","querySelector","animClass","querySelectorAll","parseInt","getAttribute","classList","addEventListener","remove","once","collectGroupKeys","groupDef","findGroupDefinition","rowCount","keys","g","createToggleButton","btn","EXPANDED","setIcon","e","stopPropagation","getGroupLabelText","formatLabel","label","GROUP_LABEL","count","GROUP_COUNT","aggregatorEntries","Object","entries","aggregatesContainer","aggRef","col","find","aggregatorRegistry","run","aggSpan","colHeader","header","bodyEl","gridTemplate","gridTemplateColumns","display","toggleRendered","colIdx","isExpanderColumn","firstColAgg","aggResult","expandAll","expandAllGroups","emitPluginEvent","collapseAll","isExpanding","newKeys","existingKey","startsWith","existingGroup","newSet","toggleGroupExpansion","broadcast","groupPath","emit","groupName","announce","getA11yMessage","expand","collapse","getGroupState","expandedCount","totalGroups","getRowCount","refreshGroups","getExpandedGroups","getFlattenedRows","isGroupingActive","setGroupOn","fn","setGroups","getGroups","setGroupRows","setGroupLoading","loading","clearGroupRows"],"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, GroupDefinition, GroupingRowsConfig, GroupRowModelItem, RenderRow } from './types';\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: GroupingRowsConfig;\n expanded: Set<string>;\n /** Initial expanded state to apply (processed by the plugin) */\n initialExpanded?: Set<string>;\n /** Sort direction per group depth level. 1 = ascending, -1 = descending.\n * When omitted, groups at all levels sort ascending. */\n groupSortDirections?: Map<number, 1 | -1>;\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, groupSortDirections }: 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 — push each row into every ancestor along the path\n // so that each group's `rows` array contains ALL data rows in its subtree.\n // This is required for correct counts and aggregations on multi-level groups.\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 node.rows.push(r);\n parent = node;\n });\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 // Pre-build row→index map for O(1) lookups (avoids O(n²) from rows.indexOf)\n const rowIndexMap = new Map<any, number>();\n for (let i = 0; i < rows.length; i++) {\n rowIndexMap.set(rows[i], i);\n }\n\n // Merge expanded sets - use initialExpanded on first render, then expanded takes over\n const effectiveExpanded = new Set([...expanded, ...(initialExpanded ?? [])]);\n\n // Sort sibling groups by their group value so that group header order is\n // deterministic and respects the active sort direction for each depth level.\n // Default: ascending. When a user sorts a grouped column, the corresponding\n // depth level's direction flips, keeping groups in predictable order.\n const sortedChildren = (node: GroupNode): GroupNode[] => {\n const children = [...node.children.values()];\n // Determine direction for this depth level (children are one level deeper than the node)\n const childDepth = node === root ? 0 : node.depth + 1;\n const dir = groupSortDirections?.get(childDepth) ?? 1;\n children.sort((a, b) => {\n const av = a.value;\n const bv = b.value;\n if (av == null && bv == null) return 0;\n if (av == null) return dir;\n if (bv == null) return -dir;\n return av > bv ? dir : av < bv ? -dir : 0;\n });\n return children;\n };\n\n // Flatten tree to array\n const flat: RenderRow[] = [];\n const visit = (node: GroupNode) => {\n if (node === root) {\n for (const c of sortedChildren(node)) 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 for (const c of sortedChildren(node)) visit(c);\n } else {\n node.rows.forEach((r) => flat.push({ kind: 'data', row: r, rowIndex: rowIndexMap.get(r) ?? -1 }));\n }\n }\n };\n visit(root);\n\n return flat;\n}\n\n/**\n * Discover which column field produces the group value at each depth level.\n *\n * Samples the first row's `groupOn` output to get the group path, then checks\n * which column fields produce matching values for that row. This mapping allows\n * the plugin to apply user-invoked column sort directions to the correct group\n * depth levels.\n *\n * @returns Map from depth index to column field name, or empty map if unmappable\n */\nexport function resolveGroupFields(\n rows: any[],\n groupOn: (row: any) => any[] | any | null | false,\n columnFields: string[],\n): Map<number, string> {\n const depthToField = new Map<number, string>();\n if (rows.length === 0) return depthToField;\n\n const sampleRow = rows[0];\n let path: any = groupOn(sampleRow);\n if (path == null || path === false) return depthToField;\n if (!Array.isArray(path)) path = [path];\n\n for (let depth = 0; depth < path.length; depth++) {\n const groupValue = path[depth];\n for (const field of columnFields) {\n if (sampleRow[field] === groupValue) {\n depthToField.set(depth, field);\n break;\n }\n }\n }\n return depthToField;\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// #region Pre-Defined Group Model\n\ninterface PreDefinedGroupModelArgs {\n groups: GroupDefinition[];\n expanded: Set<string>;\n groupRows: Map<string, unknown[]>;\n loadingGroups: Set<string>;\n parentPath?: string[];\n}\n\n/**\n * Build a flattened render model from pre-defined group definitions.\n *\n * Unlike `buildGroupedRowModel`, this does not analyze row data — groups\n * are provided externally (e.g. from a server). Row data for each group\n * is populated lazily via the `groupRows` map.\n *\n * @param args - Pre-defined grouping arguments\n * @returns Flattened array of render rows (groups + data rows)\n */\nexport function buildPreDefinedGroupModel({\n groups,\n expanded,\n groupRows,\n loadingGroups,\n parentPath = [],\n}: PreDefinedGroupModelArgs): RenderRow[] {\n const flat: RenderRow[] = [];\n const depth = parentPath.length;\n\n for (const group of groups) {\n const currentPath = [...parentPath, group.key];\n const isExpanded = expanded.has(group.key);\n const rows = groupRows.get(group.key) ?? [];\n const isLoading = loadingGroups.has(group.key);\n\n flat.push({\n kind: 'group',\n key: group.key,\n value: group.value,\n depth,\n rows,\n expanded: isExpanded,\n });\n\n if (isExpanded) {\n // Nested child groups take priority over leaf rows\n if (group.children?.length) {\n const childRows = buildPreDefinedGroupModel({\n groups: group.children,\n expanded,\n groupRows,\n loadingGroups,\n parentPath: currentPath,\n });\n flat.push(...childRows);\n } else if (isLoading) {\n // Loading placeholder — rendered by the plugin as a loading indicator\n flat.push({ kind: 'data', row: { __loading: true, __groupKey: group.key }, rowIndex: -1 });\n } else {\n // Leaf rows from the groupRows map\n rows.forEach((row, idx) => {\n flat.push({ kind: 'data', row, rowIndex: idx });\n });\n }\n }\n }\n\n return flat;\n}\n\n/**\n * Compute the group path (array of ancestor keys) for a given group key\n * within a pre-defined group structure.\n *\n * @param groups - The group definitions to search\n * @param targetKey - The key to find\n * @param parentPath - Accumulated path (used for recursion)\n * @returns Array of group keys from root to target, or empty array if not found\n */\nexport function getGroupPath(groups: GroupDefinition[], targetKey: string, parentPath: string[] = []): string[] {\n for (const group of groups) {\n const currentPath = [...parentPath, group.key];\n if (group.key === targetKey) {\n return currentPath;\n }\n if (group.children?.length) {\n const found = getGroupPath(group.children, targetKey, currentPath);\n if (found.length > 0) return found;\n }\n }\n return [];\n}\n// #endregion\n","/**\n * Row Grouping Plugin (Class-based)\n *\n * Enables hierarchical row grouping with expand/collapse and aggregations.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport { aggregatorRegistry } from '../../core/internal/aggregators';\nimport { announce, getA11yMessage } from '../../core/internal/aria';\nimport { setRowLoadingState } from '../../core/internal/loading';\nimport {\n BaseGridPlugin,\n CellClickEvent,\n HeaderClickEvent,\n type GridElement,\n type PluginManifest,\n type PluginQuery,\n} from '../../core/plugin/base-plugin';\nimport { isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { RowElementInternal } from '../../core/types';\nimport type {\n DataSourceChildrenDetail,\n DataSourceDataDetail,\n FetchChildrenQuery,\n ViewportMappingQuery,\n ViewportMappingResponse,\n} from '../server-side/datasource-types';\nimport {\n buildGroupedRowModel,\n buildPreDefinedGroupModel,\n collapseAllGroups,\n expandAllGroups,\n getGroupKeys,\n getGroupPath,\n getGroupRowCount,\n resolveDefaultExpanded,\n resolveGroupFields,\n toggleGroupExpansion,\n} from './grouping-rows';\nimport styles from './grouping-rows.css?inline';\nimport type {\n ExpandCollapseAnimation,\n GroupCollapseDetail,\n GroupDefinition,\n GroupExpandDetail,\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 * ## 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 { queryGrid } from '@toolbox-web/grid';\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n *\n * const grid = queryGrid('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 modifiesRowStructure: true,\n hookPriority: {\n processRows: 10, // Run after ServerSide (-10) so we receive managedNodes[]\n // Run before MultiSort so we can intercept header clicks on grouped columns\n onHeaderClick: -1,\n },\n incompatibleWith: [\n {\n name: 'tree',\n reason:\n 'Both plugins transform the entire row model. TreePlugin flattens nested hierarchies while ' +\n 'GroupingRowsPlugin groups flat rows with synthetic headers. Use one approach per grid.',\n },\n {\n name: 'pivot',\n reason:\n 'PivotPlugin creates its own aggregated row and column structure. ' +\n 'Row grouping cannot be applied on top of pivot-generated rows.',\n },\n ],\n events: [\n {\n type: 'group-toggle',\n description: 'Emitted when groups are expanded/collapsed. Broadcast to both DOM consumers and plugin bus.',\n },\n {\n type: 'group-expand',\n description: 'Emitted when a pre-defined group is expanded.',\n },\n {\n type: 'group-collapse',\n description: 'Emitted when a pre-defined group is collapsed.',\n },\n ],\n queries: [\n {\n type: 'canMoveRow',\n description: 'Returns false for group header rows (cannot be reordered)',\n },\n {\n type: 'grouping:get-grouped-fields',\n description: 'Returns the column field names that match group depth levels (string[])',\n },\n {\n type: 'datasource:viewport-mapping',\n description: 'Translates flat viewport row indices to top-level group indices for ServerSide pagination.',\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 /**\n * Optional dependency on MultiSort for coordinated sort management.\n * When MultiSort is loaded, GroupingRows queries its sort model to determine\n * group header ordering. Without it, falls back to core sort state.\n */\n static override readonly dependencies = [\n { name: 'multiSort', required: false, reason: 'Queries sort model for coordinated group sorting' },\n { name: 'serverSide', required: false, reason: 'Consumes datasource events for lazy-loaded grouped data' },\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 /** Pre-defined group definitions (from config, setGroups(), or datasource:data) */\n private preDefinedGroups: GroupDefinition[] = [];\n /** Row data keyed by group key (from setGroupRows() or datasource:children) */\n private groupRowsMap = new Map<string, unknown[]>();\n /** Groups currently in a loading state */\n private loadingGroups = new Set<string>();\n /** Column fields that produce group values (depth 0, 1, ...). Cached for\n * the `grouping:get-grouped-fields` query so MultiSort can filter them out. */\n private groupedFields: string[] = [];\n /** User-specified sort directions per group depth level. Toggled via\n * header clicks on grouped columns. */\n private userGroupSortDirections = new Map<number, 1 | -1>();\n /**\n * Per-flatten-index ARIA position metadata for `aria-level` /\n * `aria-setsize` / `aria-posinset` (WAI-ARIA Treegrid pattern). Computed\n * once per `flattenedRows` rebuild by {@link computeFlatMeta}; consumed\n * by {@link afterRender} on every visible row.\n */\n private flatMeta: Array<{ level: number; setSize: number; posInSet: number }> = [];\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 // Restore default `role=\"grid\"` on the rows-body so the grid stays\n // ARIA-valid after the plugin is removed (template default lives in\n // `core/internal/dom-builder.ts`). See WAI-ARIA Treegrid pattern.\n const rowsBody = this.gridElement?.querySelector('.rows-body');\n rowsBody?.setAttribute('role', 'grid');\n\n this.expandedKeys.clear();\n this.flattenedRows = [];\n this.flatMeta = [];\n this.isActive = false;\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.hasAppliedDefaultExpanded = false;\n this.preDefinedGroups = [];\n this.groupRowsMap.clear();\n this.loadingGroups.clear();\n this.groupedFields = [];\n this.userGroupSortDirections.clear();\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 if (query.type === 'grouping:get-grouped-fields') {\n return [...this.groupedFields];\n }\n if (query.type === 'datasource:viewport-mapping') {\n // Translate visible flat row indices → top-level group indices for ServerSide pagination\n const { viewportStart, viewportEnd } = query.context as ViewportMappingQuery;\n if (this.flattenedRows.length === 0) return undefined;\n // Only respond in pre-defined groups mode (when datasource data is being consumed)\n if (this.preDefinedGroups.length === 0 && !Array.isArray(this.config.groups)) return undefined;\n\n const activeGroups = this.getActiveGroups();\n const startNode = this.getTopLevelGroupIndex(viewportStart);\n const endNode = this.getTopLevelGroupIndex(viewportEnd) + 1; // exclusive\n const totalLoadedNodes = activeGroups.length;\n\n return { startNode, endNode, totalLoadedNodes } satisfies ViewportMappingResponse;\n }\n return undefined;\n }\n\n /**\n * Translate a flat row index to the top-level group index it belongs to.\n * Walks flattenedRows up to the given index and counts depth-0 groups encountered.\n */\n private getTopLevelGroupIndex(flatIndex: number): number {\n let groupIndex = -1;\n const clampedIndex = Math.min(flatIndex, this.flattenedRows.length - 1);\n for (let i = 0; i <= clampedIndex; i++) {\n const row = this.flattenedRows[i];\n if (row.kind === 'group' && row.depth === 0) {\n groupIndex++;\n }\n }\n return Math.max(0, groupIndex);\n }\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n // Listen for datasource:data from ServerSidePlugin — claim data as group definitions\n this.on('datasource:data', (detail: unknown) => {\n const d = detail as DataSourceDataDetail;\n if (!d.claimed) {\n d.claimed = true;\n // Interpret incoming rows as group definitions\n this.preDefinedGroups = d.rows as unknown as GroupDefinition[];\n this.groupRowsMap.clear();\n this.loadingGroups.clear();\n this.hasAppliedDefaultExpanded = false;\n this.requestRender();\n }\n });\n\n // Listen for datasource:children — consume child rows from ServerSide\n this.on('datasource:children', (detail: unknown) => {\n const d = detail as DataSourceChildrenDetail;\n if (d.context?.source !== 'grouping-rows') return;\n d.claimed = true;\n\n // Store the rows for the group and clear loading state\n const groupKey = d.context.groupKey as string;\n if (groupKey) {\n this.groupRowsMap.set(groupKey, d.rows);\n this.loadingGroups.delete(groupKey);\n this.requestRender();\n }\n });\n }\n\n /**\n * Intercept header clicks on grouped columns to toggle group sort direction.\n * Returns `true` for grouped columns to prevent MultiSort from handling them.\n * @internal\n */\n override onHeaderClick(event: HeaderClickEvent): boolean | void {\n const fieldIndex = this.groupedFields.indexOf(event.field);\n if (fieldIndex === -1) return; // Not a grouped column — let other plugins handle it\n\n if (!event.column.sortable) return;\n\n // The fieldIndex in groupedFields corresponds to the group depth level\n // (resolveGroupFields populates them in depth order: 0, 1, 2, ...)\n const targetDepth = fieldIndex;\n\n // Toggle direction: asc → desc → asc\n const current = this.userGroupSortDirections.get(targetDepth) ?? 1;\n this.userGroupSortDirections.set(targetDepth, current === 1 ? -1 : 1);\n\n this.requestRender();\n return true; // Prevent MultiSort from handling this column\n }\n // #endregion\n\n // #region Sort State Resolution\n\n /**\n * Build a sort-direction map for each group depth level by cross-referencing\n * the active sort state with the column fields that produce group values.\n *\n * Supports three direction sources (in priority order):\n * 1. User-set directions via header clicks on grouped columns\n * 2. MultiSort plugin model (for backwards compatibility / state restore)\n * 3. Core single-column sort state\n */\n private resolveGroupSortDirections(rows: readonly any[]): Map<number, 1 | -1> | undefined {\n const config = this.config;\n if (typeof config.groupOn !== 'function' || rows.length === 0) {\n this.groupedFields = [];\n return undefined;\n }\n\n // Discover depth → field mapping by sampling rows\n const columnFields = this.columns.map((c) => c.field);\n const depthToField = resolveGroupFields([...rows], config.groupOn, columnFields);\n\n // Cache grouped field names for the grouping:get-grouped-fields query\n this.groupedFields = [...depthToField.values()];\n\n if (depthToField.size === 0) return undefined;\n\n // Start with user-set directions (from header clicks on grouped columns)\n const directions = new Map<number, 1 | -1>(this.userGroupSortDirections);\n\n // Fill missing depths from MultiSort model or core sort state\n if (directions.size < depthToField.size) {\n const activeSorts = new Map<string, 1 | -1>();\n\n const multiSortResults = this.grid?.query?.('sort:get-model', null);\n if (Array.isArray(multiSortResults) && multiSortResults.length > 0) {\n const sortModel = multiSortResults[0] as Array<{ field: string; direction: 'asc' | 'desc' }>;\n if (Array.isArray(sortModel)) {\n for (const entry of sortModel) {\n activeSorts.set(entry.field, entry.direction === 'desc' ? -1 : 1);\n }\n }\n }\n\n if (activeSorts.size === 0) {\n const gridHost = this.grid as unknown as { _sortState?: { field: string; direction: 1 | -1 } | null };\n if (gridHost._sortState) {\n activeSorts.set(gridHost._sortState.field, gridHost._sortState.direction);\n }\n }\n\n for (const [depth, field] of depthToField) {\n if (!directions.has(depth)) {\n const dir = activeSorts.get(field);\n if (dir !== undefined) {\n directions.set(depth, dir);\n }\n }\n }\n }\n\n return directions.size > 0 ? directions : undefined;\n }\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 (\n typeof config?.groupOn === 'function' ||\n typeof config?.enableRowGrouping === 'boolean' ||\n Array.isArray(config?.groups)\n );\n }\n\n /** @internal */\n override processRows(rows: readonly any[]): any[] {\n // Pre-defined groups path — use external group structure instead of groupOn analysis\n if (this.preDefinedGroups.length > 0 || Array.isArray(this.config.groups)) {\n if (this.preDefinedGroups.length > 0 || (Array.isArray(this.config.groups) && this.config.groups.length > 0)) {\n return this.processPreDefinedGroups();\n }\n this.isActive = false;\n this.flattenedRows = [];\n return [];\n }\n\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 groupSortDirections = this.resolveGroupSortDirections(rows);\n const initialBuild = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: new Set(), // Empty to get all root groups\n groupSortDirections,\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 groupSortDirections,\n });\n\n this.isActive = true;\n this.flattenedRows = grouped;\n this.computeFlatMeta();\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(`.${GridClasses.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 or loading placeholder), false otherwise.\n * @internal\n */\n override renderRow(row: any, rowEl: HTMLElement, _rowIndex: number): boolean {\n // Handle loading placeholder rows for pre-defined groups\n // Uses the grid's built-in row loading API for consistent, customizable spinners\n if (row?.__loading === true && row?.__groupKey) {\n rowEl.className = 'data-grid-row';\n (rowEl as RowElementInternal).__isCustomRow = true;\n rowEl.innerHTML = '';\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.style.gridColumn = '1 / -1';\n cell.setAttribute('role', 'gridcell');\n cell.textContent = '\\u00A0'; // for row height\n rowEl.appendChild(cell);\n setRowLoadingState(rowEl, true);\n return true;\n }\n\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 // WAI-ARIA Treegrid: group rows announce their hierarchical position.\n const groupMeta = this.flatMeta[_rowIndex];\n if (groupMeta) {\n rowEl.setAttribute('aria-level', String(groupMeta.level));\n rowEl.setAttribute('aria-setsize', String(groupMeta.setSize));\n rowEl.setAttribute('aria-posinset', String(groupMeta.posInSet));\n }\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 body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n // Hierarchy is now in play \\u2192 swap the rows-body role from `grid` to\n // `treegrid` per WAI-ARIA so per-row level/setsize/posinset are valid\n // in context. Idempotent on the hot path.\n const rowsBody = this.gridElement?.querySelector('.rows-body');\n if (rowsBody && rowsBody.getAttribute('role') !== 'treegrid') {\n rowsBody.setAttribute('role', 'treegrid');\n }\n\n // Apply ARIA position metadata to every visible DATA row (group rows are\n // populated by `renderRow` directly since they bypass the default cell\n // template that exposes `data-row`).\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 meta = this.flatMeta[idx];\n if (!meta) continue;\n rowEl.setAttribute('aria-level', String(meta.level));\n rowEl.setAttribute('aria-setsize', String(meta.setSize));\n rowEl.setAttribute('aria-posinset', String(meta.posInSet));\n }\n\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) 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\n /**\n * Compute per-row ARIA hierarchy metadata (`aria-level` / `aria-setsize` /\n * `aria-posinset`) from `flattenedRows`. Two-pass over the flat list:\n * first pass tallies group siblings per (parent-prefix, depth) and counts\n * the data rows that follow each group header; second pass assigns per-row\n * meta. Called once per `flattenedRows` rebuild (not per render).\n */\n private computeFlatMeta(): void {\n const flat = this.flattenedRows;\n const meta: Array<{ level: number; setSize: number; posInSet: number }> = new Array(flat.length);\n\n // Pass 1: count group siblings per (parent-prefix, depth) and direct\n // data-row children per group (used as `setSize` for data rows).\n const groupSiblingCount = new Map<string, number>();\n const groupDataCount = new Map<string, number>();\n let activeGroup: GroupRowModelItem | null = null;\n let activeDataCount = 0;\n for (const item of flat) {\n if (item.kind === 'group') {\n if (activeGroup) groupDataCount.set(activeGroup.key, activeDataCount);\n activeGroup = item;\n activeDataCount = 0;\n const parts = item.key.split('||');\n const parent = parts.slice(0, -1).join('||');\n const k = `${parent}@${item.depth}`;\n groupSiblingCount.set(k, (groupSiblingCount.get(k) ?? 0) + 1);\n } else {\n activeDataCount++;\n }\n }\n if (activeGroup) groupDataCount.set(activeGroup.key, activeDataCount);\n\n // Pass 2: assign per-row level/setsize/posinset.\n const groupSiblingPos = new Map<string, number>();\n let currentGroup: GroupRowModelItem | null = null;\n let dataPos = 0;\n for (let i = 0; i < flat.length; i++) {\n const item = flat[i];\n if (item.kind === 'group') {\n const parts = item.key.split('||');\n const parent = parts.slice(0, -1).join('||');\n const k = `${parent}@${item.depth}`;\n const pos = (groupSiblingPos.get(k) ?? 0) + 1;\n groupSiblingPos.set(k, pos);\n meta[i] = { level: item.depth + 1, setSize: groupSiblingCount.get(k) ?? 1, posInSet: pos };\n currentGroup = item;\n dataPos = 0;\n } else {\n dataPos++;\n const baseDepth = currentGroup?.depth ?? -1;\n const setSize = currentGroup ? (groupDataCount.get(currentGroup.key) ?? 1) : flat.length;\n meta[i] = { level: baseDepth + 2, setSize, posInSet: dataPos };\n }\n }\n\n this.flatMeta = meta;\n }\n // #endregion\n\n // #region Pre-Defined Groups\n\n /**\n * Build the row model from pre-defined group definitions.\n * Used when `groups` config or `setGroups()` provides external group structure.\n */\n private processPreDefinedGroups(): any[] {\n const groups =\n this.preDefinedGroups.length > 0\n ? this.preDefinedGroups\n : Array.isArray(this.config.groups)\n ? this.config.groups\n : [];\n\n if (groups.length === 0) {\n this.isActive = false;\n this.flattenedRows = [];\n return [];\n }\n\n // Resolve defaultExpanded on first render only\n if (!this.hasAppliedDefaultExpanded && this.expandedKeys.size === 0 && this.config.defaultExpanded !== false) {\n const allKeys = this.collectGroupKeys(groups);\n const initialExpanded = resolveDefaultExpanded(this.config.defaultExpanded ?? false, allKeys);\n if (initialExpanded.size > 0) {\n this.expandedKeys = new Set(initialExpanded);\n this.hasAppliedDefaultExpanded = true;\n }\n }\n\n const grouped = buildPreDefinedGroupModel({\n groups,\n expanded: this.expandedKeys,\n groupRows: this.groupRowsMap,\n loadingGroups: this.loadingGroups,\n });\n\n this.isActive = true;\n this.flattenedRows = grouped;\n this.computeFlatMeta();\n\n // Track visible data rows 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 return grouped.map((item) => {\n if (item.kind === 'group') {\n // Look up the pre-defined group to get rowCount from server\n const groupDef = this.findGroupDefinition(groups, item.key);\n const rowCount = groupDef?.rowCount ?? item.rows.length;\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: rowCount,\n __rowCacheKey: `group:${item.key}`,\n };\n }\n return item.row;\n });\n }\n\n /**\n * Collect all group keys from a pre-defined group tree.\n */\n private collectGroupKeys(groups: GroupDefinition[]): string[] {\n const keys: string[] = [];\n for (const g of groups) {\n keys.push(g.key);\n if (g.children?.length) {\n keys.push(...this.collectGroupKeys(g.children));\n }\n }\n return keys;\n }\n\n /**\n * Get the active group definitions (pre-defined or empty).\n */\n private getActiveGroups(): GroupDefinition[] {\n if (this.preDefinedGroups.length > 0) return this.preDefinedGroups;\n return Array.isArray(this.config.groups) ? this.config.groups : [];\n }\n\n /**\n * Find a group definition by key in the pre-defined group tree.\n */\n private findGroupDefinition(groups: GroupDefinition[], key: string): GroupDefinition | undefined {\n for (const g of groups) {\n if (g.key === key) return g;\n if (g.children?.length) {\n const found = this.findGroupDefinition(g.children, key);\n if (found) return found;\n }\n }\n return undefined;\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 = `${GridClasses.GROUP_TOGGLE}${expanded ? ` ${GridClasses.EXPANDED}` : ''}`;\n btn.setAttribute('aria-label', expanded ? 'Collapse group' : 'Expand group');\n this.setIcon(btn, 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 = GridClasses.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 = GridClasses.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 = aggregatorRegistry.run(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 = aggregatorRegistry.run(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 = GridClasses.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 = aggregatorRegistry.run(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('group-toggle', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Collapse all groups.\n */\n collapseAll(): void {\n this.expandedKeys = collapseAllGroups();\n this.emitPluginEvent('group-toggle', { 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.broadcast<GroupToggleDetail>('group-toggle', {\n key,\n expanded: this.expandedKeys.has(key),\n value: group?.value,\n depth: group?.depth ?? 0,\n expandedKeys: [...this.expandedKeys],\n });\n\n // Emit group-expand/group-collapse events for pre-defined mode\n const activeGroups = this.getActiveGroups();\n if (activeGroups.length > 0) {\n const groupPath = getGroupPath(activeGroups, key);\n if (isExpanding) {\n this.emit<GroupExpandDetail>('group-expand', { groupKey: key, groupPath });\n\n // Request child rows via unified DataSource if not already cached\n if (!this.groupRowsMap.has(key)) {\n const groupDef = this.findGroupDefinition(activeGroups, key);\n if (groupDef) {\n this.loadingGroups.add(key);\n this.grid?.query?.('datasource:fetch-children', {\n context: { source: 'grouping-rows', groupKey: key, group: groupDef, groupPath },\n } satisfies FetchChildrenQuery);\n }\n }\n } else {\n this.emit<GroupCollapseDetail>('group-collapse', { groupKey: key, groupPath });\n }\n }\n\n // Announce group state change for screen readers\n const expanded = this.expandedKeys.has(key);\n const groupName = group?.value != null ? String(group.value) : key;\n if (expanded) {\n const rowCount = group?.rows?.length ?? 0;\n announce(this.gridElement, getA11yMessage(this.gridElement, 'groupExpanded', groupName, rowCount));\n } else {\n announce(this.gridElement, getA11yMessage(this.gridElement, 'groupCollapsed', groupName));\n }\n\n this.requestRender();\n }\n\n /**\n * Check if a specific group is expanded.\n * @param key - The group key to check\n * @returns Whether the group is expanded\n */\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n /**\n * Expand a specific group.\n * @param key - The group key to expand\n */\n expand(key: string): void {\n if (!this.expandedKeys.has(key)) {\n this.expandedKeys = new Set([...this.expandedKeys, key]);\n this.requestRender();\n }\n }\n\n /**\n * Collapse a specific group.\n * @param key - The group key to collapse\n */\n collapse(key: string): void {\n if (this.expandedKeys.has(key)) {\n const newKeys = new Set(this.expandedKeys);\n newKeys.delete(key);\n this.expandedKeys = newKeys;\n this.requestRender();\n }\n }\n\n /**\n * Get the current group state.\n * @returns Group state information\n */\n getGroupState(): GroupState {\n const groupRows = this.flattenedRows.filter((r) => r.kind === 'group');\n return {\n isActive: this.isActive,\n expandedCount: this.expandedKeys.size,\n totalGroups: groupRows.length,\n expandedKeys: [...this.expandedKeys],\n };\n }\n\n /**\n * Get the total count of visible rows (including group headers).\n * @returns Number of visible rows\n */\n getRowCount(): number {\n return this.flattenedRows.length;\n }\n\n /**\n * Refresh the grouped row model.\n * Call this after modifying groupOn or other config options.\n */\n refreshGroups(): void {\n this.requestRender();\n }\n\n /**\n * Get current expanded group keys.\n * @returns Array of expanded group keys\n */\n getExpandedGroups(): string[] {\n return [...this.expandedKeys];\n }\n\n /**\n * Get the flattened row model.\n * @returns Array of render rows (groups + data rows)\n */\n getFlattenedRows(): RenderRow[] {\n return this.flattenedRows;\n }\n\n /**\n * Check if grouping is currently active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Set the groupOn function dynamically.\n * @param fn - The groupOn function or undefined to disable\n */\n setGroupOn(fn: ((row: any) => any[] | any | null | false) | undefined): void {\n (this.config as GroupingRowsConfig).groupOn = fn;\n this.requestRender();\n }\n\n // --- Pre-defined group API ---\n\n /**\n * Replace auto-detected groups with an externally provided group structure.\n *\n * When groups are set, the plugin switches to pre-defined mode — `groupOn`\n * is ignored and the plugin renders the provided group headers instead.\n * Row data for each group must be populated via {@link setGroupRows}.\n *\n * @param groups - Array of group definitions, or empty array to clear\n */\n setGroups(groups: GroupDefinition[]): void {\n this.preDefinedGroups = groups;\n this.groupRowsMap.clear();\n this.loadingGroups.clear();\n this.expandedKeys.clear();\n this.hasAppliedDefaultExpanded = false;\n this.requestRender();\n }\n\n /**\n * Get the current pre-defined group structure.\n *\n * Returns the groups set via {@link setGroups}, received from `datasource:data`,\n * or the `groups` config array. Returns an empty array when using `groupOn`-based grouping.\n *\n * @returns Current group definitions\n */\n getGroups(): GroupDefinition[] {\n if (this.preDefinedGroups.length > 0) return [...this.preDefinedGroups];\n return Array.isArray(this.config.groups) ? [...this.config.groups] : [];\n }\n\n /**\n * Populate row data for an expanded group.\n *\n * Call this in response to a `group-expand` event after fetching rows\n * from the server. The plugin will re-render to show the rows.\n *\n * When `ServerSidePlugin` is loaded, this is handled automatically via\n * `datasource:children` events — you only need this for the imperative API.\n *\n * @param groupKey - The group key to populate\n * @param rows - The row data for this group\n */\n setGroupRows(groupKey: string, rows: unknown[]): void {\n this.groupRowsMap.set(groupKey, rows);\n this.loadingGroups.delete(groupKey);\n this.requestRender();\n }\n\n /**\n * Toggle loading indicator for a group.\n *\n * When loading is true, the group shows a loading spinner instead of row data.\n * Call with `false` after rows are loaded (also cleared by {@link setGroupRows}).\n *\n * @param groupKey - The group key\n * @param loading - Whether the group is loading\n */\n setGroupLoading(groupKey: string, loading: boolean): void {\n if (loading) {\n this.loadingGroups.add(groupKey);\n } else {\n this.loadingGroups.delete(groupKey);\n }\n this.requestRender();\n }\n\n /**\n * Clear cached row data for one or all groups.\n *\n * Use when the server data has changed and groups need to be re-fetched\n * on next expand.\n *\n * @param groupKey - Specific group key to clear, or omit to clear all\n */\n clearGroupRows(groupKey?: string): void {\n if (groupKey != null) {\n this.groupRowsMap.delete(groupKey);\n } else {\n this.groupRowsMap.clear();\n }\n this.requestRender();\n }\n // #endregion\n}\n"],"names":["buildGroupedRowModel","rows","config","expanded","initialExpanded","groupSortDirections","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","rowIndexMap","i","effectiveExpanded","Set","sortedChildren","values","childDepth","dir","sort","a","b","av","bv","flat","visit","c","isExpanded","kind","row","rowIndex","resolveDefaultExpanded","allGroupKeys","buildPreDefinedGroupModel","groups","groupRows","loadingGroups","parentPath","group","currentPath","isLoading","childRows","__loading","__groupKey","idx","getGroupPath","targetKey","found","GroupingRowsPlugin","BaseGridPlugin","static","modifiesRowStructure","hookPriority","processRows","onHeaderClick","incompatibleWith","name","reason","events","type","description","queries","configRules","id","severity","message","check","accordion","defaultExpanded","required","styles","defaultConfig","showRowCount","indentWidth","aggregators","animation","expandedKeys","flattenedRows","isActive","previousVisibleKeys","keysToAnimate","hasAppliedDefaultExpanded","preDefinedGroups","groupRowsMap","groupedFields","userGroupSortDirections","flatMeta","animationStyle","this","isAnimationEnabled","detach","rowsBody","gridElement","querySelector","setAttribute","clear","getRowHeight","_index","groupRowHeight","__isGroupRow","handleQuery","query","context","viewportStart","viewportEnd","activeGroups","getActiveGroups","startNode","getTopLevelGroupIndex","endNode","totalLoadedNodes","flatIndex","groupIndex","clampedIndex","Math","min","max","attach","grid","super","on","detail","d","claimed","requestRender","source","groupKey","delete","event","fieldIndex","indexOf","field","column","sortable","targetDepth","current","resolveGroupSortDirections","columnFields","columns","map","depthToField","sampleRow","groupValue","resolveGroupFields","directions","activeSorts","multiSortResults","sortModel","entry","direction","gridHost","_sortState","detect","enableRowGrouping","processPreDefinedGroups","initialBuild","allKeys","filter","getGroupKeys","grouped","computeFlatMeta","currentVisibleKeys","item","add","__groupValue","__groupDepth","__groupRows","__groupExpanded","__groupRowCount","groupRow","__rowCacheKey","onCellClick","target","originalEvent","closest","GridClasses","GROUP_TOGGLE","toggle","onKeyDown","focusRow","_focusRow","preventDefault","requestRenderWithFocus","renderRow","rowEl","_rowIndex","className","__isCustomRow","innerHTML","cell","document","createElement","style","gridColumn","textContent","appendChild","setRowLoadingState","groupRowRenderer","toggleExpand","result","handleToggle","groupMeta","level","setSize","posInSet","setProperty","height","fullWidth","renderFullWidthGroupRow","renderPerColumnGroupRow","afterRender","body","getAttribute","querySelectorAll","parseInt","meta","animClass","classList","addEventListener","remove","once","groupSiblingCount","groupDataCount","activeGroup","activeDataCount","k","split","slice","join","groupSiblingPos","currentGroup","dataPos","pos","baseDepth","collectGroupKeys","groupDef","findGroupDefinition","rowCount","keys","g","createToggleButton","btn","EXPANDED","setIcon","e","stopPropagation","getGroupLabelText","formatLabel","label","GROUP_LABEL","count","GROUP_COUNT","aggregatorEntries","Object","entries","aggregatesContainer","aggRef","col","find","aggregatorRegistry","run","aggSpan","colHeader","header","bodyEl","gridTemplate","gridTemplateColumns","display","toggleRendered","colIdx","isExpanderColumn","firstColAgg","aggResult","expandAll","expandAllGroups","emitPluginEvent","collapseAll","isExpanding","newKeys","existingKey","startsWith","existingGroup","newSet","toggleGroupExpansion","broadcast","groupPath","emit","groupName","announce","getA11yMessage","expand","collapse","getGroupState","expandedCount","totalGroups","getRowCount","refreshGroups","getExpandedGroups","getFlattenedRows","isGroupingActive","setGroupOn","fn","setGroups","getGroups","setGroupRows","setGroupLoading","loading","clearGroupRows"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("../../core/plugin/base-plugin"),require("../../core/internal/aggregators")):"function"==typeof define&&define.amd?define(["exports","../../core/plugin/base-plugin","../../core/internal/aggregators"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TbwGridPlugin_pinnedRows={},t.TbwGrid,t.TbwGrid)}(this,function(t,e,n){"use strict";function o(t,e){const n=document.createElement("div");n.className="tbw-pinned-rows",n.setAttribute("role","presentation"),n.setAttribute("aria-live","polite");const o=document.createElement("div");o.className="tbw-pinned-rows-left";const i=document.createElement("div");i.className="tbw-pinned-rows-center";const r=document.createElement("div");if(r.className="tbw-pinned-rows-right",!1!==t.showRowCount){const t=document.createElement("span");t.className="tbw-status-panel tbw-status-panel-row-count",t.textContent=`Total: ${e.totalRows} rows`,o.appendChild(t)}if(t.showFilteredCount&&e.filteredRows!==e.totalRows){const t=document.createElement("span");t.className="tbw-status-panel tbw-status-panel-filtered-count",t.textContent=`Filtered: ${e.filteredRows}`,o.appendChild(t)}if(t.showSelectedCount&&e.selectedRows>0){const t=document.createElement("span");t.className="tbw-status-panel tbw-status-panel-selected-count",t.textContent=`Selected: ${e.selectedRows}`,r.appendChild(t)}if(t.customPanels)for(const a of t.customPanels){const t=g(a,e);switch(a.position){case"left":o.appendChild(t);break;case"center":i.appendChild(t);break;case"right":r.appendChild(t)}}return n.appendChild(o),n.appendChild(i),n.appendChild(r),n}function i(t){const e=document.createElement("div");return e.className=`tbw-aggregation-rows tbw-aggregation-rows-${t}`,e.setAttribute("role","presentation"),e}function r(t,e,n,o,i=!1){t.innerHTML="";for(const r of e){const e=document.createElement("div");e.className="tbw-aggregation-row",e.setAttribute("role","presentation"),r.id&&e.setAttribute("data-aggregation-id",r.id);r.fullWidth??i?a(e,r,n,o):s(e,r,n,o),t.appendChild(e)}}function a(t,e,n,o){const i=document.createElement("div");i.className="tbw-aggregation-cell tbw-aggregation-cell-full",i.style.gridColumn="1 / -1";const r="function"==typeof e.label?e.label(o,n):e.label;if(r){const t=document.createElement("span");t.className="tbw-aggregation-label",t.textContent=r,i.appendChild(t)}const a=function(t,e,n){const o=t.aggregators&&Object.keys(t.aggregators).length>0,i=t.cells&&Object.keys(t.cells).length>0;if(!o&&!i)return null;const r=document.createElement("span");r.className="tbw-aggregation-aggregates";for(const a of e){const{value:e,formatter:o}=l(t,a,n);if(null!=e){const t=document.createElement("span");t.className="tbw-aggregation-aggregate",t.setAttribute("data-field",a.field);const n=a.header??a.field,i=o?o(e,a.field,a):String(e);t.textContent=`${n}: ${i}`,r.appendChild(t)}}return r.children.length>0?r:null}(e,n,o);a&&i.appendChild(a),t.appendChild(i)}function s(t,e,n,o){for(const r of n){const n=document.createElement("div");n.className="tbw-aggregation-cell",n.setAttribute("data-field",r.field);const{value:i,formatter:a}=l(e,r,o);n.textContent=null!=i?a?a(i,r.field,r):String(i):"",t.appendChild(n)}const i="function"==typeof e.label?e.label(o,n):e.label;if(i){const e=document.createElement("span");e.className="tbw-aggregation-label",e.textContent=i,t.appendChild(e)}}function l(t,e,o){let i,r;const a=t.aggregators?.[e.field];if(a)if("object"==typeof(s=a)&&null!==s&&"aggFunc"in s){const t=n.aggregatorRegistry.get(a.aggFunc);t&&(i=t(o,e.field,e)),r=a.formatter}else{const t=n.aggregatorRegistry.get(a);t&&(i=t(o,e.field,e))}else if(t.cells&&Object.prototype.hasOwnProperty.call(t.cells,e.field)){const n=t.cells[e.field];i="function"==typeof n?n(o,e.field,e):n}var s;return{value:i,formatter:r}}function g(t,e){const n=document.createElement("div");n.className="tbw-status-panel tbw-status-panel-custom",n.id=`status-panel-${t.id}`;const o=t.render(e);return"string"==typeof o?n.innerHTML=o:n.appendChild(o),n}function c(t,e,n,o,i){const r=n?.sourceRows,a=n?.rows,s=Array.isArray(r)?r.length:t.length,l=Array.isArray(a)?a.length:t.length,g=s>0?s:l;return{totalRows:g,filteredRows:i?.cachedResult?.length??(l<s?l:g),selectedRows:o?.selected?.size??0,columns:e,rows:t,grid:n}}class d extends e.BaseGridPlugin{name="pinnedRows";styles="@layer tbw-plugins{.tbw-scroll-area{container-type:inline-size}.tbw-footer{flex-shrink:0;z-index:var(--tbw-z-layer-pinned-rows, 20);background:var(--tbw-color-panel-bg);min-width:fit-content}.tbw-pinned-rows{display:flex;align-items:center;justify-content:space-between;padding:var(--tbw-button-padding, var(--tbw-spacing-md, .5rem) var(--tbw-spacing-lg, .75rem));background:var(--tbw-pinned-rows-bg, var(--tbw-color-panel-bg));border-top:1px solid var(--tbw-pinned-rows-border, var(--tbw-color-border));font-size:var(--tbw-font-size-xs, .75rem);color:var(--tbw-pinned-rows-color, var(--tbw-color-fg-muted));min-height:32px;box-sizing:border-box;position:sticky;left:0;min-width:0;width:100cqi}.tbw-pinned-rows-left,.tbw-pinned-rows-center,.tbw-pinned-rows-right{display:flex;align-items:center;gap:var(--tbw-spacing-xl, 1rem)}.tbw-pinned-rows-left{justify-content:flex-start}.tbw-pinned-rows-center{justify-content:center;flex:1}.tbw-pinned-rows-right{justify-content:flex-end}.tbw-status-panel{white-space:nowrap}.tbw-aggregation-rows{min-width:fit-content;background:var(--tbw-aggregation-bg, var(--tbw-color-header-bg))}.tbw-aggregation-rows-top{border-bottom:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-rows-bottom{border-top:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-row{display:grid;grid-template-columns:var(--tbw-column-template);font-size:var(--tbw-aggregation-font-size, .8em);font-weight:var(--tbw-aggregation-font-weight, 600);position:relative;background:inherit}.tbw-aggregation-row>.tbw-aggregation-label{position:sticky;left:0;grid-row:1;grid-column:1;display:flex;align-items:center;padding:var(--tbw-cell-padding, .125rem .5rem);background:inherit;z-index:1;pointer-events:none}.tbw-aggregation-row>.tbw-aggregation-cell:first-child{grid-column:1;grid-row:1}.tbw-aggregation-cell:not(:empty){position:relative;z-index:2;background:inherit}.tbw-aggregation-cell{padding:var(--tbw-cell-padding, .125rem .5rem);min-height:var(--tbw-row-height, 1.75rem);display:block;align-items:center;align-content:center;border-right:1px solid var(--tbw-color-border-cell);overflow:hidden;text-overflow:ellipsis;white-space:var(--tbw-cell-white-space, nowrap)}.tbw-aggregation-cell:last-child{border-right:0}.tbw-aggregation-cell-full{grid-column:1 / -1;border-right:0;display:flex;align-items:center;gap:var(--tbw-spacing-lg, .75rem)}.tbw-aggregation-label{white-space:nowrap}.tbw-aggregation-aggregates{display:flex;align-items:center;gap:var(--tbw-spacing-lg, .75rem);font-weight:400;opacity:.85}.tbw-aggregation-aggregate{white-space:nowrap}}";get defaultConfig(){return{position:"bottom",showRowCount:!0,showSelectedCount:!0,showFilteredCount:!0}}infoBarElement=null;topAggregationContainer=null;bottomAggregationContainer=null;footerWrapper=null;detach(){this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null),this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null)}afterRender(){const t=this.gridElement;if(!t)return;const e=t.querySelector(".tbw-scroll-area")??t.querySelector(".tbw-grid-content")??t.querySelector(".tbw-grid-root");if(!e)return;this.footerWrapper&&!e.contains(this.footerWrapper)&&(this.footerWrapper=null,this.bottomAggregationContainer=null,this.infoBarElement=null),this.topAggregationContainer&&!e.contains(this.topAggregationContainer)&&(this.topAggregationContainer=null),this.infoBarElement&&!e.contains(this.infoBarElement)&&(this.infoBarElement=null);const n=this.getSelectionState(),a=this.getFilterState(),s=c(this.sourceRows,this.columns,this.gridElement,n,a),l=this.config.aggregationRows||[],g=l.filter(t=>"top"===t.position),d=l.filter(t=>"top"!==t.position);if(g.length>0){if(!this.topAggregationContainer){this.topAggregationContainer=i("top");const n=t.querySelector(".header");n&&n.nextSibling?e.insertBefore(this.topAggregationContainer,n.nextSibling):e.appendChild(this.topAggregationContainer)}r(this.topAggregationContainer,g,this.visibleColumns,this.sourceRows,this.config.fullWidth)}else this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null);const p=!1!==this.config.showRowCount||this.config.showSelectedCount&&s.selectedRows>0||this.config.showFilteredCount&&s.filteredRows!==s.totalRows||this.config.customPanels&&this.config.customPanels.length>0,h=p&&"top"!==this.config.position,f=d.length>0||h;if(p&&"top"===this.config.position)if(this.infoBarElement){const t=o(this.config,s);this.infoBarElement.replaceWith(t),this.infoBarElement=t}else this.infoBarElement=o(this.config,s),e.insertBefore(this.infoBarElement,e.firstChild);else"top"===this.config.position&&this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null);f?(this.footerWrapper||(this.footerWrapper=document.createElement("div"),this.footerWrapper.className="tbw-footer",e.appendChild(this.footerWrapper)),this.footerWrapper.innerHTML="",d.length>0&&(this.bottomAggregationContainer||(this.bottomAggregationContainer=i("bottom")),this.footerWrapper.appendChild(this.bottomAggregationContainer),r(this.bottomAggregationContainer,d,this.visibleColumns,this.sourceRows,this.config.fullWidth)),h&&(this.infoBarElement=o(this.config,s),this.footerWrapper.appendChild(this.infoBarElement))):this.cleanupFooter()}cleanup(){this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null),this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null)}cleanupFooter(){this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.infoBarElement&&"top"!==this.config.position&&(this.infoBarElement.remove(),this.infoBarElement=null)}getSelectionState(){try{return this.grid?.getPluginState?.("selection")??null}catch{return null}}getFilterState(){try{return this.grid?.getPluginState?.("filtering")??null}catch{return null}}refresh(){this.requestRender()}getContext(){const t=this.getSelectionState(),e=this.getFilterState();return c(this.rows,this.columns,this.gridElement,t,e)}addPanel(t){this.config.customPanels||(this.config.customPanels=[]),this.config.customPanels.push(t),this.requestRender()}removePanel(t){this.config.customPanels&&(this.config.customPanels=this.config.customPanels.filter(e=>e.id!==t),this.requestRender())}addAggregationRow(t){this.config.aggregationRows||(this.config.aggregationRows=[]),this.config.aggregationRows.push(t),this.requestRender()}removeAggregationRow(t){this.config.aggregationRows&&(this.config.aggregationRows=this.config.aggregationRows.filter(e=>e.id!==t),this.requestRender())}}t.PinnedRowsPlugin=d,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("../../core/internal/aggregators"),require("../../core/internal/sanitize"),require("../../core/plugin/base-plugin")):"function"==typeof define&&define.amd?define(["exports","../../core/internal/aggregators","../../core/internal/sanitize","../../core/plugin/base-plugin"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TbwGridPlugin_pinnedRows={},t.TbwGrid,t.TbwGrid,t.TbwGrid)}(this,function(t,e,n,o){"use strict";function r(t,e){const n=document.createElement("div");n.className="tbw-pinned-rows",n.setAttribute("role","presentation"),n.setAttribute("aria-live","polite");const o=document.createElement("div");o.className="tbw-pinned-rows-left";const r=document.createElement("div");r.className="tbw-pinned-rows-center";const i=document.createElement("div");if(i.className="tbw-pinned-rows-right",!1!==t.showRowCount){const t=document.createElement("span");t.className="tbw-status-panel tbw-status-panel-row-count",t.textContent=`Total: ${e.totalRows} rows`,o.appendChild(t)}if(t.showFilteredCount&&e.filteredRows!==e.totalRows){const t=document.createElement("span");t.className="tbw-status-panel tbw-status-panel-filtered-count",t.textContent=`Filtered: ${e.filteredRows}`,o.appendChild(t)}if(t.showSelectedCount&&e.selectedRows>0){const t=document.createElement("span");t.className="tbw-status-panel tbw-status-panel-selected-count",t.textContent=`Selected: ${e.selectedRows}`,i.appendChild(t)}if(t.customPanels)for(const a of t.customPanels){const t=c(a,e);switch(a.position){case"left":o.appendChild(t);break;case"center":r.appendChild(t);break;case"right":i.appendChild(t)}}return n.appendChild(o),n.appendChild(r),n.appendChild(i),n}function i(t){const e=document.createElement("div");return e.className=`tbw-aggregation-rows tbw-aggregation-rows-${t}`,e.setAttribute("role","presentation"),e}function a(t,e,n,o,r=!1){t.innerHTML="";for(const i of e){const e=document.createElement("div");e.className="tbw-aggregation-row",e.setAttribute("role","presentation"),i.id&&e.setAttribute("data-aggregation-id",i.id);i.fullWidth??r?s(e,i,n,o):l(e,i,n,o),t.appendChild(e)}}function s(t,e,n,o){const r=document.createElement("div");r.className="tbw-aggregation-cell tbw-aggregation-cell-full",r.style.gridColumn="1 / -1";const i="function"==typeof e.label?e.label(o,n):e.label;if(i){const t=document.createElement("span");t.className="tbw-aggregation-label",t.textContent=i,r.appendChild(t)}const a=function(t,e,n){const o=t.aggregators&&Object.keys(t.aggregators).length>0,r=t.cells&&Object.keys(t.cells).length>0;if(!o&&!r)return null;const i=document.createElement("span");i.className="tbw-aggregation-aggregates";for(const a of e){const{value:e,formatter:o}=g(t,a,n);if(null!=e){const t=document.createElement("span");t.className="tbw-aggregation-aggregate",t.setAttribute("data-field",a.field);const n=a.header??a.field,r=o?o(e,a.field,a):String(e);t.textContent=`${n}: ${r}`,i.appendChild(t)}}return i.children.length>0?i:null}(e,n,o);a&&r.appendChild(a),t.appendChild(r)}function l(t,e,n,o){for(const i of n){const n=document.createElement("div");n.className="tbw-aggregation-cell",n.setAttribute("data-field",i.field);const{value:r,formatter:a}=g(e,i,o);n.textContent=null!=r?a?a(r,i.field,i):String(r):"",t.appendChild(n)}const r="function"==typeof e.label?e.label(o,n):e.label;if(r){const e=document.createElement("span");e.className="tbw-aggregation-label",e.textContent=r,t.appendChild(e)}}function g(t,n,o){let r,i;const a=t.aggregators?.[n.field];if(a)if("object"==typeof(s=a)&&null!==s&&"aggFunc"in s){const t=e.aggregatorRegistry.get(a.aggFunc);t&&(r=t(o,n.field,n)),i=a.formatter}else{const t=e.aggregatorRegistry.get(a);t&&(r=t(o,n.field,n))}else if(t.cells&&Object.prototype.hasOwnProperty.call(t.cells,n.field)){const e=t.cells[n.field];r="function"==typeof e?e(o,n.field,n):e}var s;return{value:r,formatter:i}}function c(t,e){const o=document.createElement("div");o.className="tbw-status-panel tbw-status-panel-custom",o.id=`status-panel-${t.id}`;const r=t.render(e);return"string"==typeof r?o.innerHTML=n.sanitizeHTML(r):o.appendChild(r),o}function p(t,e,n,o,r){const i=n?.sourceRows,a=n?.rows,s=Array.isArray(i)?i.length:t.length,l=Array.isArray(a)?a.length:t.length,g=s>0?s:l;return{totalRows:g,filteredRows:r?.cachedResult?.length??(l<s?l:g),selectedRows:o?.selected?.size??0,columns:e,rows:t,grid:n}}function d(t){const e=document.createElement("div");return e.className="tbw-status-panel tbw-status-panel-custom",e.appendChild(t),e}function h(t,e){const n=document.createElement("div");n.className="tbw-pinned-rows",n.setAttribute("role","presentation"),n.setAttribute("aria-live","polite"),t.id&&n.setAttribute("data-pinned-row-id",t.id);const o=document.createElement("div");o.className="tbw-pinned-rows-left";const r=document.createElement("div");r.className="tbw-pinned-rows-center";const i=document.createElement("div");i.className="tbw-pinned-rows-right";const a={left:o,center:r,right:i},s=Array.isArray(t.render)?t.render:[{zone:"left",render:t.render}];for(const l of s){const t=l.zone??"left",n=l.render(e);if(null==n)continue;const o=n.classList?.contains("tbw-status-panel");a[t].appendChild(o?n:d(n))}return 0===o.children.length&&0===r.children.length&&0===i.children.length?null:(n.appendChild(o),n.appendChild(r),n.appendChild(i),n)}function u(t,e,n,o,r=!1){const s=i(e);return a(s,[t],n,o,r),s}class f extends o.BaseGridPlugin{name="pinnedRows";styles="@layer tbw-plugins{.tbw-scroll-area{container-type:inline-size}.tbw-footer,.tbw-header-pinned{flex-shrink:0;z-index:var(--tbw-z-layer-pinned-rows, 20);background:var(--tbw-color-panel-bg);min-width:fit-content}.tbw-pinned-rows{display:flex;align-items:center;justify-content:space-between;padding:var(--tbw-button-padding, var(--tbw-spacing-md, .5rem) var(--tbw-spacing-lg, .75rem));background:var(--tbw-pinned-rows-bg, var(--tbw-color-panel-bg));border-top:1px solid var(--tbw-pinned-rows-border, var(--tbw-color-border));font-size:var(--tbw-font-size-xs, .75rem);color:var(--tbw-pinned-rows-color, var(--tbw-color-fg-muted));min-height:32px;box-sizing:border-box;position:sticky;left:0;min-width:0;width:100cqi}.tbw-pinned-rows+.tbw-pinned-rows{border-top:0}.tbw-pinned-rows-left,.tbw-pinned-rows-center,.tbw-pinned-rows-right{display:flex;align-items:center;gap:var(--tbw-spacing-xl, 1rem)}.tbw-pinned-rows-left{justify-content:flex-start}.tbw-pinned-rows-center{justify-content:center;flex:1}.tbw-pinned-rows-right{justify-content:flex-end}.tbw-status-panel{white-space:nowrap}.tbw-aggregation-rows{min-width:fit-content;background:var(--tbw-aggregation-bg, var(--tbw-color-header-bg))}.tbw-aggregation-rows-top{border-bottom:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-rows-bottom{border-top:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-row{display:grid;grid-template-columns:var(--tbw-column-template);font-size:var(--tbw-aggregation-font-size, .8em);font-weight:var(--tbw-aggregation-font-weight, 600);position:relative;background:inherit}.tbw-aggregation-row>.tbw-aggregation-label{position:sticky;left:0;grid-row:1;grid-column:1;display:flex;align-items:center;padding:var(--tbw-cell-padding, .125rem .5rem);background:inherit;z-index:1;pointer-events:none}.tbw-aggregation-row>.tbw-aggregation-cell:first-child{grid-column:1;grid-row:1}.tbw-aggregation-cell:not(:empty){position:relative;z-index:2;background:inherit}.tbw-aggregation-cell{padding:var(--tbw-cell-padding, .125rem .5rem);min-height:var(--tbw-row-height, 1.75rem);display:block;align-items:center;align-content:center;border-right:1px solid var(--tbw-color-border-cell);overflow:hidden;text-overflow:ellipsis;white-space:var(--tbw-cell-white-space, nowrap)}.tbw-aggregation-cell:last-child{border-right:0}.tbw-aggregation-cell-full{grid-column:1 / -1;border-right:0;display:flex;align-items:center;gap:var(--tbw-spacing-lg, .75rem)}.tbw-aggregation-label{white-space:nowrap}.tbw-aggregation-aggregates{display:flex;align-items:center;gap:var(--tbw-spacing-lg, .75rem);font-weight:400;opacity:.85}.tbw-aggregation-aggregate{white-space:nowrap}}";get defaultConfig(){return{position:"bottom",showRowCount:!0,showSelectedCount:!0,showFilteredCount:!0}}infoBarElement=null;topAggregationContainer=null;bottomAggregationContainer=null;footerWrapper=null;headerWrapper=null;lastModeWasSlots=!1;detach(){this.cleanup()}afterRender(){const t=this.gridElement;if(!t)return;const e=t.querySelector(".tbw-scroll-area")??t.querySelector(".tbw-grid-content")??t.querySelector(".tbw-grid-root");if(!e)return;this.footerWrapper&&!e.contains(this.footerWrapper)&&(this.footerWrapper=null,this.bottomAggregationContainer=null,this.infoBarElement=null),this.topAggregationContainer&&!e.contains(this.topAggregationContainer)&&(this.topAggregationContainer=null),this.infoBarElement&&!e.contains(this.infoBarElement)&&(this.infoBarElement=null),this.headerWrapper&&!e.contains(this.headerWrapper)&&(this.headerWrapper=null);const n=this.getSelectionState(),o=this.getFilterState(),r=p(this.sourceRows,this.columns,this.gridElement,n,o);this.config.slots?this.renderSlotMode(e,t,r):this.renderLegacyMode(e,t,r)}renderSlotMode(t,e,n){this.lastModeWasSlots||this.detachLegacyOnly(),this.lastModeWasSlots=!0;const o=this.config.slots??[],r=o.filter(t=>"top"===t.position),i=o.filter(t=>"top"!==t.position);r.length>0?(this.headerWrapper||(this.headerWrapper=document.createElement("div"),this.headerWrapper.className="tbw-header-pinned",t.insertBefore(this.headerWrapper,t.firstChild)),this.populateSlotWrapper(this.headerWrapper,r,"top",n)):this.headerWrapper&&(this.headerWrapper.remove(),this.headerWrapper=null),i.length>0?(this.footerWrapper||(this.footerWrapper=document.createElement("div"),this.footerWrapper.className="tbw-footer",t.appendChild(this.footerWrapper)),this.populateSlotWrapper(this.footerWrapper,i,"bottom",n)):this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null)}populateSlotWrapper(t,e,n,o){t.innerHTML="";for(const r of e){const e="render"in r&&null!=r.render?h(r,o):u(r,n,this.visibleColumns,this.sourceRows,this.config.fullWidth);e&&t.appendChild(e)}}renderLegacyMode(t,e,n){this.lastModeWasSlots&&(this.headerWrapper&&(this.headerWrapper.remove(),this.headerWrapper=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.bottomAggregationContainer=null,this.infoBarElement=null),this.lastModeWasSlots=!1;const o=this.config.aggregationRows||[],s=o.filter(t=>"top"===t.position),l=o.filter(t=>"top"!==t.position);s.length>0?(this.topAggregationContainer||(this.topAggregationContainer=i("top"),t.insertBefore(this.topAggregationContainer,t.firstChild)),a(this.topAggregationContainer,s,this.visibleColumns,this.sourceRows,this.config.fullWidth)):this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null);const g=!1!==this.config.showRowCount||this.config.showSelectedCount&&n.selectedRows>0||this.config.showFilteredCount&&n.filteredRows!==n.totalRows||this.config.customPanels&&this.config.customPanels.length>0,c=g&&"top"!==this.config.position,p=l.length>0||c;if(g&&"top"===this.config.position)if(this.infoBarElement){const t=r(this.config,n);this.infoBarElement.replaceWith(t),this.infoBarElement=t}else this.infoBarElement=r(this.config,n),t.insertBefore(this.infoBarElement,t.firstChild);else"top"===this.config.position&&this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null);p?(this.footerWrapper||(this.footerWrapper=document.createElement("div"),this.footerWrapper.className="tbw-footer",t.appendChild(this.footerWrapper)),this.footerWrapper.innerHTML="",l.length>0&&(this.bottomAggregationContainer||(this.bottomAggregationContainer=i("bottom")),this.footerWrapper.appendChild(this.bottomAggregationContainer),a(this.bottomAggregationContainer,l,this.visibleColumns,this.sourceRows,this.config.fullWidth)),c&&(this.infoBarElement=r(this.config,n),this.footerWrapper.appendChild(this.infoBarElement))):this.cleanupFooter()}cleanup(){this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null),this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.headerWrapper&&(this.headerWrapper.remove(),this.headerWrapper=null)}detachLegacyOnly(){this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null)}cleanupFooter(){this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.infoBarElement&&"top"!==this.config.position&&(this.infoBarElement.remove(),this.infoBarElement=null)}getSelectionState(){try{return this.grid?.getPluginState?.("selection")??null}catch{return null}}getFilterState(){try{return this.grid?.getPluginState?.("filtering")??null}catch{return null}}refresh(){this.requestRender()}getContext(){const t=this.getSelectionState(),e=this.getFilterState();return p(this.rows,this.columns,this.gridElement,t,e)}addPanel(t){this.config.customPanels||(this.config.customPanels=[]),this.config.customPanels.push(t),this.requestRender()}removePanel(t){this.config.customPanels&&(this.config.customPanels=this.config.customPanels.filter(e=>e.id!==t),this.requestRender())}addAggregationRow(t){this.config.aggregationRows||(this.config.aggregationRows=[]),this.config.aggregationRows.push(t),this.requestRender()}removeAggregationRow(t){this.config.aggregationRows&&(this.config.aggregationRows=this.config.aggregationRows.filter(e=>e.id!==t),this.requestRender())}}t.PinnedRowsPlugin=f,t.filteredCountPanel=function(){return t=>{if(t.filteredRows===t.totalRows)return null;const e=document.createElement("span");return e.className="tbw-status-panel tbw-status-panel-filtered-count",e.textContent=`Filtered: ${t.filteredRows}`,e}},t.rowCountPanel=function(){return t=>{const e=document.createElement("span");return e.className="tbw-status-panel tbw-status-panel-row-count",e.textContent=`Total: ${t.totalRows} rows`,e}},t.selectedCountPanel=function(){return t=>{if(t.selectedRows<=0)return null;const e=document.createElement("span");return e.className="tbw-status-panel tbw-status-panel-selected-count",e.textContent=`Selected: ${t.selectedRows}`,e}},Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
|
|
2
2
|
//# sourceMappingURL=pinned-rows.umd.js.map
|