@toolbox-web/grid 3.9.2 → 3.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/all.js +1 -1
  2. package/all.js.map +1 -1
  3. package/index.js +1 -1
  4. package/index.js.map +1 -1
  5. package/lib/core/types.d.ts +6 -3
  6. package/lib/features/clipboard.js.map +1 -1
  7. package/lib/features/column-virtualization.js.map +1 -1
  8. package/lib/features/context-menu.js.map +1 -1
  9. package/lib/features/editing.js.map +1 -1
  10. package/lib/features/export.js.map +1 -1
  11. package/lib/features/filtering.js.map +1 -1
  12. package/lib/features/grouping-columns.js.map +1 -1
  13. package/lib/features/grouping-rows.js.map +1 -1
  14. package/lib/features/master-detail.js.map +1 -1
  15. package/lib/features/multi-sort.js.map +1 -1
  16. package/lib/features/pinned-columns.js.map +1 -1
  17. package/lib/features/pinned-rows.js.map +1 -1
  18. package/lib/features/pivot.js.map +1 -1
  19. package/lib/features/print.js.map +1 -1
  20. package/lib/features/registry.js +1 -1
  21. package/lib/features/registry.js.map +1 -1
  22. package/lib/features/reorder-columns.js.map +1 -1
  23. package/lib/features/responsive.js.map +1 -1
  24. package/lib/features/row-drag-drop.js.map +1 -1
  25. package/lib/features/selection.js.map +1 -1
  26. package/lib/features/server-side.js.map +1 -1
  27. package/lib/features/shell.js.map +1 -1
  28. package/lib/features/sticky-rows.js.map +1 -1
  29. package/lib/features/tooltip.js.map +1 -1
  30. package/lib/features/tree.js.map +1 -1
  31. package/lib/features/undo-redo.js.map +1 -1
  32. package/lib/features/visibility.js.map +1 -1
  33. package/lib/plugins/clipboard/index.js.map +1 -1
  34. package/lib/plugins/column-virtualization/index.js.map +1 -1
  35. package/lib/plugins/context-menu/index.js.map +1 -1
  36. package/lib/plugins/editing/editors.d.ts +15 -0
  37. package/lib/plugins/editing/index.js +1 -1
  38. package/lib/plugins/editing/index.js.map +1 -1
  39. package/lib/plugins/editing/internal/helpers.d.ts +23 -0
  40. package/lib/plugins/editing/types.d.ts +16 -1
  41. package/lib/plugins/export/index.js +1 -1
  42. package/lib/plugins/export/index.js.map +1 -1
  43. package/lib/plugins/filtering/filter-model.d.ts +20 -1
  44. package/lib/plugins/filtering/filter-panel-default.d.ts +15 -1
  45. package/lib/plugins/filtering/index.d.ts +1 -1
  46. package/lib/plugins/filtering/index.js +1 -1
  47. package/lib/plugins/filtering/index.js.map +1 -1
  48. package/lib/plugins/filtering/types.d.ts +58 -3
  49. package/lib/plugins/grouping-columns/index.js.map +1 -1
  50. package/lib/plugins/grouping-rows/index.js +1 -1
  51. package/lib/plugins/grouping-rows/index.js.map +1 -1
  52. package/lib/plugins/hierarchy/index.js.map +1 -1
  53. package/lib/plugins/master-detail/index.js +1 -1
  54. package/lib/plugins/master-detail/index.js.map +1 -1
  55. package/lib/plugins/master-detail/master-detail-plugin.d.ts +6 -1
  56. package/lib/plugins/multi-sort/index.js.map +1 -1
  57. package/lib/plugins/pinned-columns/index.js.map +1 -1
  58. package/lib/plugins/pinned-rows/index.js.map +1 -1
  59. package/lib/plugins/pivot/index.js +1 -1
  60. package/lib/plugins/pivot/index.js.map +1 -1
  61. package/lib/plugins/pivot/pivot-plugin.d.ts +2 -0
  62. package/lib/plugins/pivot/types.d.ts +2 -0
  63. package/lib/plugins/print/index.js.map +1 -1
  64. package/lib/plugins/reorder-columns/index.js.map +1 -1
  65. package/lib/plugins/responsive/index.js.map +1 -1
  66. package/lib/plugins/row-drag-drop/index.js.map +1 -1
  67. package/lib/plugins/selection/index.js +1 -1
  68. package/lib/plugins/selection/index.js.map +1 -1
  69. package/lib/plugins/server-side/index.js.map +1 -1
  70. package/lib/plugins/shared/form-field-target.d.ts +10 -0
  71. package/lib/plugins/shared/format-value.d.ts +12 -0
  72. package/lib/plugins/shared/structural-toggle.d.ts +14 -0
  73. package/lib/plugins/shell/index.js.map +1 -1
  74. package/lib/plugins/sticky-rows/index.js.map +1 -1
  75. package/lib/plugins/tooltip/index.js.map +1 -1
  76. package/lib/plugins/tree/index.js +1 -1
  77. package/lib/plugins/tree/index.js.map +1 -1
  78. package/lib/plugins/undo-redo/index.d.ts +1 -1
  79. package/lib/plugins/undo-redo/index.js +1 -1
  80. package/lib/plugins/undo-redo/index.js.map +1 -1
  81. package/lib/plugins/undo-redo/types.d.ts +20 -2
  82. package/lib/plugins/undo-redo/undo-redo-plugin.d.ts +8 -0
  83. package/lib/plugins/visibility/index.js.map +1 -1
  84. package/package.json +1 -1
  85. package/umd/grid.all.umd.js +1 -1
  86. package/umd/grid.all.umd.js.map +1 -1
  87. package/umd/grid.umd.js +1 -1
  88. package/umd/grid.umd.js.map +1 -1
  89. package/umd/plugins/clipboard.umd.js.map +1 -1
  90. package/umd/plugins/column-virtualization.umd.js.map +1 -1
  91. package/umd/plugins/context-menu.umd.js.map +1 -1
  92. package/umd/plugins/editing.umd.js +1 -1
  93. package/umd/plugins/editing.umd.js.map +1 -1
  94. package/umd/plugins/export.umd.js +1 -1
  95. package/umd/plugins/export.umd.js.map +1 -1
  96. package/umd/plugins/filtering.umd.js +1 -1
  97. package/umd/plugins/filtering.umd.js.map +1 -1
  98. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  99. package/umd/plugins/grouping-rows.umd.js +1 -1
  100. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  101. package/umd/plugins/hierarchy.umd.js.map +1 -1
  102. package/umd/plugins/master-detail.umd.js +1 -1
  103. package/umd/plugins/master-detail.umd.js.map +1 -1
  104. package/umd/plugins/multi-sort.umd.js.map +1 -1
  105. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  106. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  107. package/umd/plugins/pivot.umd.js +1 -1
  108. package/umd/plugins/pivot.umd.js.map +1 -1
  109. package/umd/plugins/print.umd.js.map +1 -1
  110. package/umd/plugins/reorder-columns.umd.js.map +1 -1
  111. package/umd/plugins/responsive.umd.js.map +1 -1
  112. package/umd/plugins/row-drag-drop.umd.js.map +1 -1
  113. package/umd/plugins/selection.umd.js +1 -1
  114. package/umd/plugins/selection.umd.js.map +1 -1
  115. package/umd/plugins/server-side.umd.js.map +1 -1
  116. package/umd/plugins/shell.umd.js.map +1 -1
  117. package/umd/plugins/sticky-rows.umd.js.map +1 -1
  118. package/umd/plugins/tooltip.umd.js.map +1 -1
  119. package/umd/plugins/tree.umd.js +1 -1
  120. package/umd/plugins/tree.umd.js.map +1 -1
  121. package/umd/plugins/undo-redo.umd.js +1 -1
  122. package/umd/plugins/undo-redo.umd.js.map +1 -1
  123. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../../../libs/grid/src/lib/core/internal/aria.ts","../../../../../../libs/grid/src/lib/plugins/pivot/pivot-model.ts","../../../../../../libs/grid/src/lib/plugins/pivot/pivot-engine.ts","../../../../../../libs/grid/src/lib/core/constants.ts","../../../../../../libs/grid/src/lib/plugins/shared/drag-alternative-menu.ts","../../../../../../libs/grid/src/lib/plugins/pivot/pivot-panel.ts","../../../../../../libs/grid/src/lib/plugins/pivot/pivot-rows.ts","../../../../../../libs/grid/src/lib/plugins/pivot/pivot.css?inline","../../../../../../libs/grid/src/lib/plugins/pivot/pivot-plugin.ts"],"sourcesContent":["/**\n * ARIA Accessibility Helpers\n *\n * Pure functions for managing ARIA attributes on grid elements.\n * Implements caching to avoid redundant DOM writes during scroll.\n *\n * @module internal/aria\n */\n\nimport { DEFAULT_A11Y_MESSAGES } from '../defaults';\nimport type { A11yMessages, GridConfig } from '../types';\n\n// #region Types\n\n/**\n * State for caching ARIA attributes to avoid redundant DOM writes.\n */\nexport interface AriaState {\n /** Last set row count */\n rowCount: number;\n /** Last set column count */\n colCount: number;\n /** Last set aria-label */\n ariaLabel: string | undefined;\n /** Last set aria-labelledby */\n ariaLabelledBy: string | undefined;\n /** Last set aria-describedby */\n ariaDescribedBy: string | undefined;\n /** Last set aria-roledescription */\n ariaRoleDescription: string | undefined;\n /** Last source row count announced via `dataLoaded`; used to suppress duplicate announcements */\n lastAnnouncedSourceCount: number;\n}\n\n/**\n * Create initial ARIA state.\n */\nexport function createAriaState(): AriaState {\n return {\n rowCount: -1,\n colCount: -1,\n ariaLabel: undefined,\n ariaLabelledBy: undefined,\n ariaDescribedBy: undefined,\n ariaRoleDescription: undefined,\n lastAnnouncedSourceCount: -1,\n };\n}\n\n// #endregion\n\n// #region Count Updates\n\n/**\n * Update ARIA row and column counts on grid elements.\n * Uses caching to avoid redundant DOM writes on every scroll frame.\n *\n * @param state - ARIA state for caching\n * @param rowsBodyEl - Element to set aria-rowcount/aria-colcount on\n * @param bodyEl - Element to set role=\"rowgroup\" on\n * @param rowCount - Current row count\n * @param colCount - Current column count\n * @returns true if anything was updated\n */\nexport function updateAriaCounts(\n state: AriaState,\n rowsBodyEl: HTMLElement | null,\n bodyEl: HTMLElement | null,\n rowCount: number,\n colCount: number,\n): boolean {\n // Skip if nothing changed (hot path optimization for scroll)\n if (rowCount === state.rowCount && colCount === state.colCount) {\n return false;\n }\n\n const prevRowCount = state.rowCount;\n state.rowCount = rowCount;\n state.colCount = colCount;\n\n // Update ARIA counts on inner grid element\n if (rowsBodyEl) {\n rowsBodyEl.setAttribute('aria-rowcount', String(rowCount));\n rowsBodyEl.setAttribute('aria-colcount', String(colCount));\n }\n\n // Set role=\"rowgroup\" on .rows only when there are rows (ARIA compliance)\n if (rowCount !== prevRowCount && bodyEl) {\n if (rowCount > 0) {\n bodyEl.setAttribute('role', 'rowgroup');\n } else {\n bodyEl.removeAttribute('role');\n }\n }\n\n return true;\n}\n\n// #endregion\n\n// #region Label Updates\n\n/**\n * Determine the effective aria-label for the grid.\n * Priority: explicit config > shell title > nothing\n *\n * The shell title is read from `config.shell.header.title`. The shell plugin\n * folds any light-DOM `<tbw-grid-header title>` into that field via its\n * `processConfig` hook (extraction #370), so core needs no plugin state here.\n *\n * @param config - Grid configuration (the merged effective config)\n * @returns The aria-label to use, or undefined\n */\nexport function getEffectiveAriaLabel<T>(config: GridConfig<T> | undefined): string | undefined {\n const explicitLabel = config?.gridAriaLabel;\n if (explicitLabel) return explicitLabel;\n\n return config?.shell?.header?.title ?? undefined;\n}\n\n/**\n * Update ARIA label and describedby attributes on the grid container.\n * Uses caching to avoid redundant DOM writes.\n *\n * @param state - ARIA state for caching\n * @param rowsBodyEl - Element to set aria-label/aria-describedby on\n * @param config - Grid configuration (the merged effective config)\n * @returns true if anything was updated\n */\nexport function updateAriaLabels<T>(\n state: AriaState,\n rowsBodyEl: HTMLElement | null,\n config: GridConfig<T> | undefined,\n): boolean {\n if (!rowsBodyEl) return false;\n\n let updated = false;\n\n // aria-labelledby wins per WAI-ARIA accessible-name precedence — when set,\n // `aria-label` is suppressed even if explicitly configured to avoid two\n // competing names being computed by AT.\n const ariaLabelledBy = config?.gridAriaLabelledBy;\n const effectiveLabel = ariaLabelledBy ? undefined : getEffectiveAriaLabel(config);\n\n // Update aria-labelledby only if changed\n if (ariaLabelledBy !== state.ariaLabelledBy) {\n state.ariaLabelledBy = ariaLabelledBy;\n if (ariaLabelledBy) {\n rowsBodyEl.setAttribute('aria-labelledby', ariaLabelledBy);\n } else {\n rowsBodyEl.removeAttribute('aria-labelledby');\n }\n updated = true;\n }\n\n // Update aria-label only if changed\n if (effectiveLabel !== state.ariaLabel) {\n state.ariaLabel = effectiveLabel;\n if (effectiveLabel) {\n rowsBodyEl.setAttribute('aria-label', effectiveLabel);\n } else {\n rowsBodyEl.removeAttribute('aria-label');\n }\n updated = true;\n }\n\n // Update aria-describedby only if changed\n const ariaDescribedBy = config?.gridAriaDescribedBy;\n if (ariaDescribedBy !== state.ariaDescribedBy) {\n state.ariaDescribedBy = ariaDescribedBy;\n if (ariaDescribedBy) {\n rowsBodyEl.setAttribute('aria-describedby', ariaDescribedBy);\n } else {\n rowsBodyEl.removeAttribute('aria-describedby');\n }\n updated = true;\n }\n\n // Update aria-roledescription only if changed. Apps can override the\n // announced role name (e.g. localized \"Tabell\" instead of generic \"grid\").\n const ariaRoleDescription = config?.gridAriaRoleDescription;\n if (ariaRoleDescription !== state.ariaRoleDescription) {\n state.ariaRoleDescription = ariaRoleDescription;\n if (ariaRoleDescription) {\n rowsBodyEl.setAttribute('aria-roledescription', ariaRoleDescription);\n } else {\n rowsBodyEl.removeAttribute('aria-roledescription');\n }\n updated = true;\n }\n\n return updated;\n}\n\n// #endregion\n\n// #region Live Announcements\n\n/**\n * Announce a message to screen readers via the grid's aria-live region.\n * Clears then sets text to ensure repeated messages are re-announced.\n *\n * Respects `effectiveConfig.a11y.announcements` — if set to `false`,\n * the announcement is silently skipped.\n *\n * @param gridEl - The grid host element (or any ancestor containing `.tbw-sr-only`)\n * @param message - The message to announce\n */\nexport function announce(gridEl: HTMLElement, message: string): void {\n // Check if announcements are disabled via config.\n // gridEl is always a DataGridElement at runtime — use property access safely.\n if (!gridEl) return;\n const config =\n 'effectiveConfig' in gridEl\n ? (gridEl as HTMLElement & { effectiveConfig?: GridConfig }).effectiveConfig\n : undefined;\n if (config?.a11y?.announcements === false) return;\n\n const el = gridEl.querySelector?.('.tbw-sr-only');\n if (!el) return;\n // Clear first so identical consecutive messages are still announced\n el.textContent = '';\n // `requestAnimationFrame` can be undefined if a debounced announcement fires\n // after the DOM/host environment is torn down (notably in unit tests). Fall\n // back to a direct assignment so `announce` never throws out of band.\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(() => {\n el.textContent = message;\n });\n } else {\n el.textContent = message;\n }\n}\n\n/**\n * Get an announcement message, preferring custom overrides from config.\n * Falls back to default English messages when no override is provided.\n *\n * @param gridEl - The grid host element\n * @param key - The message key from A11yMessages\n * @param args - Arguments to pass to the message function\n */\nexport function getA11yMessage<K extends keyof A11yMessages>(\n gridEl: HTMLElement,\n key: K,\n ...args: Parameters<Required<A11yMessages>[K]>\n): string {\n type MessageFn = (...a: Parameters<Required<A11yMessages>[K]>) => string;\n const config =\n gridEl && 'effectiveConfig' in gridEl\n ? (gridEl as HTMLElement & { effectiveConfig?: GridConfig }).effectiveConfig\n : undefined;\n const customFn = config?.a11y?.messages?.[key] as MessageFn | undefined;\n if (customFn) return customFn(...args);\n return (DEFAULT_A11Y_MESSAGES[key] as MessageFn)(...args);\n}\n\n/**\n * Announce a `dataLoaded` message — but only when the source row count has\n * actually changed since the last announcement. This prevents announcement\n * spam from internal `data-change` emits triggered by sort/filter/edit, which\n * have their own dedicated announcements.\n *\n * @param gridEl - The grid host element\n * @param state - ARIA state (tracks last announced count)\n * @param sourceRowCount - Current source row count\n */\nexport function announceDataLoaded(gridEl: HTMLElement, state: AriaState, sourceRowCount: number): void {\n if (sourceRowCount === state.lastAnnouncedSourceCount) return;\n const previousAnnouncedSourceCount = state.lastAnnouncedSourceCount;\n state.lastAnnouncedSourceCount = sourceRowCount;\n // Skip only the initial sentinel → 0 transition (empty grid mount). A later\n // transition to 0 (e.g. data cleared / reloaded to an empty result set) should\n // still announce so screen-reader users hear that the grid is now empty.\n if (sourceRowCount === 0 && previousAnnouncedSourceCount < 0) return;\n announce(gridEl, getA11yMessage(gridEl, 'dataLoaded', sourceRowCount));\n}\n\n// #endregion\n","import { getValueAggregator } from '../../core/internal/aggregators';\nimport type { AggFunc, PivotConfig, PivotValueField } from './types';\n\n/**\n * Resolve an AggFunc to an executable function.\n * Supports both built-in string names and custom functions.\n */\nexport function getPivotAggregator(aggFunc: AggFunc): (values: number[]) => number {\n if (typeof aggFunc === 'function') return aggFunc;\n return getValueAggregator(aggFunc);\n}\n\nexport function validatePivotConfig(config: PivotConfig): string[] {\n const errors: string[] = [];\n\n if (!config.rowGroupFields?.length && !config.columnGroupFields?.length) {\n errors.push('At least one row or column group field is required');\n }\n\n if (!config.valueFields?.length) {\n errors.push('At least one value field is required');\n }\n\n return errors;\n}\n\nexport function createValueKey(columnValues: string[], valueField: string): string {\n return [...columnValues, valueField].join('|');\n}\n\nexport function createValueKeys(columnValues: string[], valueFields: PivotValueField[]): string[] {\n const fieldCounts = new Map<string, number>();\n for (const valueField of valueFields) {\n fieldCounts.set(valueField.field, (fieldCounts.get(valueField.field) ?? 0) + 1);\n }\n\n // NUL-joined so a `|` inside a field name cannot collide with an aggregate key.\n const seenFieldAggregates = new Set<string>();\n\n return valueFields.map((valueField, index) => {\n const baseKey = createValueKey(columnValues, valueField.field);\n if ((fieldCounts.get(valueField.field) ?? 0) < 2) return baseKey;\n\n const aggregateKey = typeof valueField.aggFunc === 'string' ? valueField.aggFunc : 'custom';\n const key = `${baseKey}|${aggregateKey}`;\n const pairKey = `${valueField.field}\\u0000${aggregateKey}`;\n if (seenFieldAggregates.has(pairKey)) return `${key}|${index + 1}`;\n\n seenFieldAggregates.add(pairKey);\n return key;\n });\n}\n","import { createValueKeys } from './pivot-model';\nimport type { PivotConfig, PivotResult, PivotRow, PivotSortConfig, PivotSortDir, PivotValueField } from './types';\n\n/** @since 0.1.1 */\nexport type PivotDataRow = Record<string, unknown>;\n\n/**\n * Build a hierarchical pivot result from flat data.\n * Supports multiple row group fields for nested hierarchy.\n */\nexport function buildPivot(rows: PivotDataRow[], config: PivotConfig): PivotResult {\n const rowGroupFields = config.rowGroupFields ?? [];\n const columnGroupFields = config.columnGroupFields ?? [];\n const valueFields = config.valueFields ?? [];\n\n // Get unique column combinations\n const columnKeys = getUniqueColumnKeys(rows, columnGroupFields, config.sortColumns);\n\n // Build hierarchical pivot rows\n const pivotRows = buildHierarchicalPivotRows(\n rows,\n rowGroupFields,\n columnGroupFields,\n columnKeys,\n valueFields,\n 0, // starting depth\n '', // parent key prefix\n );\n\n // Sort row groups if configured\n if (config.sortRows) {\n sortPivotRows(pivotRows, config.sortRows, valueFields);\n }\n\n // Calculate grand totals\n const totals = calculateTotals(pivotRows, columnKeys, valueFields);\n const grandTotal = Object.values(totals).reduce((a, b) => a + b, 0);\n\n return {\n rows: pivotRows,\n columnKeys,\n totals,\n grandTotal,\n };\n}\n\n/**\n * Get unique column key combinations from the data.\n */\nexport function getUniqueColumnKeys(\n rows: PivotDataRow[],\n columnFields: string[],\n sortDir: PivotSortDir = 'asc',\n): string[] {\n if (columnFields.length === 0) return ['value'];\n\n const keys = new Set<string>();\n for (const row of rows) {\n const key = columnFields.map((f) => String(row[f] ?? '')).join('|');\n keys.add(key);\n }\n const sorted = [...keys].sort();\n return sortDir === 'desc' ? sorted.reverse() : sorted;\n}\n\n/**\n * Group rows by a single field.\n */\nexport function groupByField(rows: PivotDataRow[], field: string): Map<string, PivotDataRow[]> {\n const groups = new Map<string, PivotDataRow[]>();\n\n for (const row of rows) {\n const key = String(row[field] ?? '');\n const existing = groups.get(key);\n if (existing) {\n existing.push(row);\n } else {\n groups.set(key, [row]);\n }\n }\n\n return groups;\n}\n\n/**\n * Group rows by multiple fields (legacy flat grouping).\n */\nexport function groupByFields(rows: PivotDataRow[], fields: string[]): Map<string, PivotDataRow[]> {\n const groups = new Map<string, PivotDataRow[]>();\n\n for (const row of rows) {\n const key = fields.map((f) => String(row[f] ?? '')).join('|');\n const existing = groups.get(key);\n if (existing) {\n existing.push(row);\n } else {\n groups.set(key, [row]);\n }\n }\n\n return groups;\n}\n\n// Built-in branches must stay in sync with `builtInValueAggregators` in core/internal/aggregators.ts;\n// they are inlined here to aggregate without materializing a number[] per group.\nfunction aggregateValueField(rows: PivotDataRow[], valueField: PivotValueField): number | null {\n const { aggFunc, field } = valueField;\n if (typeof aggFunc === 'function') {\n const numbers: number[] = [];\n for (let i = 0; i < rows.length; i++) {\n const raw = rows[i][field];\n if (raw == null || raw === '') continue;\n const value = Number(raw);\n if (!isNaN(value)) numbers.push(value);\n }\n return numbers.length > 0 ? aggFunc(numbers) : null;\n }\n\n if (aggFunc === 'first') {\n for (let i = 0; i < rows.length; i++) {\n const raw = rows[i][field];\n if (raw == null || raw === '') continue;\n const value = Number(raw);\n if (!isNaN(value)) return value;\n }\n return null;\n }\n\n if (aggFunc === 'last') {\n for (let i = rows.length - 1; i >= 0; i--) {\n const raw = rows[i][field];\n if (raw == null || raw === '') continue;\n const value = Number(raw);\n if (!isNaN(value)) return value;\n }\n return null;\n }\n\n let result = aggFunc === 'min' ? Infinity : aggFunc === 'max' ? -Infinity : 0;\n let count = 0;\n for (let i = 0; i < rows.length; i++) {\n const raw = rows[i][field];\n if (raw == null || raw === '') continue;\n const value = Number(raw);\n if (isNaN(value)) continue;\n\n count++;\n if (aggFunc === 'min') {\n if (value < result) result = value;\n } else if (aggFunc === 'max') {\n if (value > result) result = value;\n } else if (aggFunc !== 'count') {\n result += value;\n }\n }\n\n if (count === 0) return null;\n if (aggFunc === 'avg') return result / count;\n if (aggFunc === 'count') return count;\n return result;\n}\n\nfunction aggregateSharedBuiltIns(\n rows: PivotDataRow[],\n field: string,\n valueFields: PivotValueField[],\n valueKeys: string[],\n values: Record<string, number | null>,\n): void {\n let sum = 0;\n let count = 0;\n let min = Infinity;\n let max = -Infinity;\n let first: number | null = null;\n let last: number | null = null;\n\n for (let i = 0; i < rows.length; i++) {\n const raw = rows[i][field];\n if (raw == null || raw === '') continue;\n const value = Number(raw);\n if (isNaN(value)) continue;\n\n if (count === 0) first = value;\n last = value;\n sum += value;\n count++;\n if (value < min) min = value;\n if (value > max) max = value;\n }\n\n for (let i = 0; i < valueFields.length; i++) {\n const valueField = valueFields[i];\n if (valueField.field !== field || typeof valueField.aggFunc === 'function') continue;\n\n let result: number | null;\n if (count === 0) {\n result = null;\n } else if (valueField.aggFunc === 'avg') {\n result = sum / count;\n } else if (valueField.aggFunc === 'count') {\n result = count;\n } else if (valueField.aggFunc === 'min') {\n result = min;\n } else if (valueField.aggFunc === 'max') {\n result = max;\n } else if (valueField.aggFunc === 'first') {\n result = first;\n } else if (valueField.aggFunc === 'last') {\n result = last;\n } else {\n result = sum;\n }\n values[valueKeys[i]] = result;\n }\n}\n\nfunction aggregateValueFields(\n rows: PivotDataRow[],\n valueFields: PivotValueField[],\n valueKeys: string[],\n values: Record<string, number | null>,\n): void {\n for (let i = 0; i < valueFields.length; i++) {\n const valueField = valueFields[i];\n let fieldHandled = false;\n let builtInCount = 0;\n for (let j = 0; j < valueFields.length; j++) {\n if (valueFields[j].field !== valueField.field) continue;\n if (j < i) fieldHandled = true;\n if (typeof valueFields[j].aggFunc !== 'function') builtInCount++;\n }\n if (fieldHandled) continue;\n\n if (builtInCount > 1) {\n aggregateSharedBuiltIns(rows, valueField.field, valueFields, valueKeys, values);\n }\n for (let j = i; j < valueFields.length; j++) {\n const candidate = valueFields[j];\n if (candidate.field !== valueField.field) continue;\n if (builtInCount > 1 && typeof candidate.aggFunc !== 'function') continue;\n values[valueKeys[j]] = aggregateValueField(rows, candidate);\n }\n }\n}\n\n/**\n * Build hierarchical pivot rows recursively.\n * Each level of rowGroupFields creates a new depth level.\n */\nexport function buildHierarchicalPivotRows(\n rows: PivotDataRow[],\n rowGroupFields: string[],\n columnFields: string[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n depth: number,\n parentKey: string,\n): PivotRow[] {\n const result: PivotRow[] = [];\n\n // If no more row group fields, we're at the leaf level - aggregate the data\n if (rowGroupFields.length === 0) {\n // This shouldn't normally happen as we need at least one grouping field\n // But handle it by creating a single aggregated row\n const values = aggregateValues(rows, columnFields, columnKeys, valueFields);\n const total = calculateRowTotal(values);\n result.push({\n rowKey: parentKey || 'all',\n rowLabel: parentKey || 'All',\n depth,\n values,\n total,\n isGroup: false,\n rowCount: rows.length,\n });\n return result;\n }\n\n // Get the current grouping field\n const currentField = rowGroupFields[0];\n const remainingFields = rowGroupFields.slice(1);\n const hasChildren = remainingFields.length > 0;\n\n // Group rows by current field\n const grouped = groupByField(rows, currentField);\n\n for (const [groupValue, groupRows] of grouped) {\n const rowKey = parentKey ? `${parentKey}|${groupValue}` : groupValue;\n\n // Aggregate values for this group (sum of all child rows)\n const values = aggregateValues(groupRows, columnFields, columnKeys, valueFields);\n const total = calculateRowTotal(values);\n\n // Build children if there are more grouping levels\n let children: PivotRow[] | undefined;\n if (hasChildren) {\n children = buildHierarchicalPivotRows(\n groupRows,\n remainingFields,\n columnFields,\n columnKeys,\n valueFields,\n depth + 1,\n rowKey,\n );\n }\n\n result.push({\n rowKey,\n rowLabel: groupValue || '(blank)',\n depth,\n values,\n total,\n isGroup: hasChildren,\n children,\n rowCount: groupRows.length,\n });\n }\n\n return result;\n}\n\n/**\n * Aggregate values for a set of rows across all column keys.\n * Pre-groups rows by column key in a single pass to avoid O(rows × colKeys) repeated filtering.\n */\nexport function aggregateValues(\n rows: PivotDataRow[],\n columnFields: string[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n): Record<string, number | null> {\n const values: Record<string, number | null> = {};\n\n if (columnFields.length === 0) {\n // No column grouping — all rows match every key\n const valueKeys = createValueKeys(['value'], valueFields);\n aggregateValueFields(rows, valueFields, valueKeys, values);\n return values;\n }\n\n // Pre-group rows by column key in a single O(rows) pass\n const rowsByColKey = new Map<string, PivotDataRow[]>();\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n let key = String(row[columnFields[0]] ?? '');\n for (let j = 1; j < columnFields.length; j++) {\n key += '|';\n key += String(row[columnFields[j]] ?? '');\n }\n const existing = rowsByColKey.get(key);\n if (existing) {\n existing.push(row);\n } else {\n rowsByColKey.set(key, [row]);\n }\n }\n\n for (const colKey of columnKeys) {\n const valueKeys = createValueKeys([colKey], valueFields);\n const matchingRows = rowsByColKey.get(colKey);\n if (!matchingRows || matchingRows.length === 0) {\n for (const valueKey of valueKeys) {\n values[valueKey] = null;\n }\n continue;\n }\n\n aggregateValueFields(matchingRows, valueFields, valueKeys, values);\n }\n\n return values;\n}\n\n/**\n * Calculate the total for a row's values.\n */\nexport function calculateRowTotal(values: Record<string, number | null>): number {\n let sum = 0;\n for (const val of Object.values(values)) {\n sum += val ?? 0;\n }\n return sum;\n}\n\n/**\n * Calculate grand totals across all pivot rows.\n */\nexport function calculateTotals(\n pivotRows: PivotRow[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n): Record<string, number> {\n const totals: Record<string, number> = {};\n const allValueKeys = columnKeys.flatMap((colKey) => createValueKeys([colKey], valueFields));\n\n // Recursively sum all rows (including nested children)\n function sumRows(rows: PivotRow[]) {\n for (const row of rows) {\n // Only count leaf rows to avoid double-counting\n if (!row.isGroup || !row.children?.length) {\n for (const valueKey of allValueKeys) {\n totals[valueKey] = (totals[valueKey] ?? 0) + (row.values[valueKey] ?? 0);\n }\n } else if (row.children) {\n sumRows(row.children);\n }\n }\n }\n\n sumRows(pivotRows);\n return totals;\n}\n\n/**\n * Flatten hierarchical pivot rows for rendering.\n * Respects expanded state - only includes children of expanded groups.\n */\nexport function flattenPivotRows(rows: PivotRow[], expandedKeys?: Set<string>, defaultExpanded = true): PivotRow[] {\n const result: PivotRow[] = [];\n\n function flatten(row: PivotRow) {\n result.push(row);\n\n // Check if this group is expanded\n const isExpanded = expandedKeys ? expandedKeys.has(row.rowKey) : defaultExpanded;\n\n // Only include children if expanded\n if (row.children && isExpanded) {\n for (const child of row.children) {\n flatten(child);\n }\n }\n }\n\n for (const row of rows) {\n flatten(row);\n }\n\n return result;\n}\n\n/**\n * Get all group keys from pivot rows (for expand all / collapse all).\n */\nexport function getAllGroupKeys(rows: PivotRow[]): string[] {\n const keys: string[] = [];\n\n function collectKeys(row: PivotRow) {\n if (row.isGroup) {\n keys.push(row.rowKey);\n }\n if (row.children) {\n for (const child of row.children) {\n collectKeys(child);\n }\n }\n }\n\n for (const row of rows) {\n collectKeys(row);\n }\n\n return keys;\n}\n\n/**\n * Recursively sort pivot rows at each level.\n */\nexport function sortPivotRows(rows: PivotRow[], sortConfig: PivotSortConfig, valueFields: PivotValueField[]): void {\n const dir = sortConfig.direction === 'desc' ? -1 : 1;\n\n rows.sort((a, b) => {\n if (sortConfig.by === 'value') {\n const field = sortConfig.valueField ?? valueFields[0]?.field;\n if (field) {\n const aVal = a.total ?? 0;\n const bVal = b.total ?? 0;\n return (aVal - bVal) * dir;\n }\n }\n // Default: sort by label\n return a.rowLabel.localeCompare(b.rowLabel) * dir;\n });\n\n for (const row of rows) {\n if (row.children?.length) {\n sortPivotRows(row.children, sortConfig, valueFields);\n }\n }\n}\n\n/**\n * Sort pivot rows by multiple criteria (from MultiSort's sort model).\n * Each criterion is a PivotSortConfig; earlier entries take precedence.\n * Maintains hierarchy by sorting children recursively at each level.\n */\nexport function sortPivotMulti(rows: PivotRow[], configs: PivotSortConfig[], valueFields: PivotValueField[]): void {\n if (configs.length === 0) return;\n\n const knownValueFields = new Set(valueFields.map((vf) => vf.field));\n\n /** Resolve the numeric sort value for a row given a valueField key. */\n const getSortValue = (row: PivotRow, valueField: string): number => {\n // Direct match (full value key like \"Q1|sales\")\n if (row.values[valueField] != null) {\n return typeof row.values[valueField] === 'number' ? (row.values[valueField] as number) : 0;\n }\n // Suffix match — sum all matching columns for a bare field name\n if (knownValueFields.has(valueField)) {\n const suffix = `|${valueField}`;\n let sum = 0;\n let found = false;\n for (const key of Object.keys(row.values)) {\n if (key.endsWith(suffix)) {\n sum += typeof row.values[key] === 'number' ? (row.values[key] as number) : 0;\n found = true;\n }\n }\n if (found) return sum;\n }\n return row.total ?? 0;\n };\n\n // Precompute accessors for deterministic ordering (same accessor for both a and b)\n const accessors = configs.map((cfg) => {\n if (cfg.by === 'value') {\n if (cfg.valueField) {\n return (row: PivotRow) => getSortValue(row, cfg.valueField!);\n }\n return (row: PivotRow) => row.total ?? 0;\n }\n return null; // label sort — uses localeCompare directly\n });\n\n rows.sort((a, b) => {\n for (let i = 0; i < configs.length; i++) {\n const cfg = configs[i];\n const dir = cfg.direction === 'desc' ? -1 : 1;\n let cmp = 0;\n\n if (cfg.by === 'value') {\n const accessor = accessors[i] as (row: PivotRow) => number;\n cmp = (accessor(a) - accessor(b)) * dir;\n } else {\n cmp = a.rowLabel.localeCompare(b.rowLabel) * dir;\n }\n\n if (cmp !== 0) return cmp;\n }\n return 0;\n });\n\n for (const row of rows) {\n if (row.children?.length) {\n sortPivotMulti(row.children, configs, valueFields);\n }\n }\n}\n\n/**\n * Resolve `defaultExpanded` config to a set of keys.\n *\n * Deliberately NOT the same function as `resolveDefaultExpanded` in\n * `plugins/grouping-rows/grouping-rows.ts`: pivot treats `undefined` as\n * expand-all (a pivot with everything collapsed shows nothing useful), whereas\n * grouping-rows treats it as collapse-all. Merging them would silently flip one\n * plugin's default.\n */\nexport function resolveDefaultExpanded(\n value: boolean | number | string | string[] | undefined,\n allGroupKeys: string[],\n): Set<string> {\n if (value === true || value === undefined) return new Set(allGroupKeys);\n if (value === false) return new Set();\n if (typeof value === 'number') {\n const key = allGroupKeys[value];\n return key ? new Set([key]) : new Set();\n }\n if (typeof value === 'string') return new Set([value]);\n if (Array.isArray(value)) return new Set(value);\n return new Set();\n}\n\n/**\n * Calculate column totals (sum of all leaf row values per column key).\n * Used for percentage-of-column calculations.\n */\nexport function getColumnTotals(\n rows: PivotRow[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n): Record<string, number> {\n const totals: Record<string, number> = {};\n const allValueKeys = columnKeys.flatMap((colKey) => createValueKeys([colKey], valueFields));\n\n function sumLeaves(rows: PivotRow[]) {\n for (const row of rows) {\n if (!row.isGroup || !row.children?.length) {\n for (const valueKey of allValueKeys) {\n totals[valueKey] = (totals[valueKey] ?? 0) + (row.values[valueKey] ?? 0);\n }\n } else if (row.children) {\n sumLeaves(row.children);\n }\n }\n }\n\n sumLeaves(rows);\n return totals;\n}\n","/**\n * Grid DOM Constants\n *\n * Centralized constants for CSS classes, data attributes, and selectors\n * used throughout the grid. Use these instead of magic strings.\n *\n * Note: Some constants here are used by plugins but defined in core because:\n * 1. The core CSS needs to style these elements (e.g., sticky positioning)\n * 2. Multiple plugins may share the same class names\n *\n * Plugins can define their own additional constants and export them.\n * See each plugin's index.ts for plugin-specific exports.\n */\n\n/**\n * CSS selector for focusable editor elements within a cell.\n * Used by EditingPlugin, UndoRedoPlugin, and keyboard navigation. Defined here\n * (a leaf module) rather than in `rows.ts` so `keyboard.ts` can consume it\n * without creating a rows ↔ keyboard import cycle.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// #region CSS Classes\n\n/**\n * CSS class names used in the grid's DOM.\n * Use these when adding/removing classes or querying elements.\n *\n * Classes are organized by:\n * - Core: Used by the grid core for structure and basic functionality\n * - Shared: Used by multiple features/plugins, styled by core CSS\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridClasses = {\n // ─── Core Structure ───────────────────────────────────────────────\n ROOT: 'tbw-grid-root',\n HEADER: 'header',\n HEADER_ROW: 'header-row',\n HEADER_CELL: 'header-cell',\n\n // Body structure\n ROWS_VIEWPORT: 'rows-viewport',\n ROWS_SPACER: 'rows-spacer',\n ROWS_CONTAINER: 'rows',\n\n // Row elements\n DATA_ROW: 'data-grid-row',\n GROUP_ROW: 'group-row',\n\n // Cell elements\n DATA_CELL: 'cell',\n\n // ─── Core States ──────────────────────────────────────────────────\n SELECTED: 'selected',\n FOCUSED: 'focused',\n EDITING: 'editing',\n EXPANDED: 'expanded',\n COLLAPSED: 'collapsed',\n DRAGGING: 'dragging',\n RESIZING: 'resizing',\n\n // Sorting (core feature)\n SORTABLE: 'sortable',\n SORTED_ASC: 'sorted-asc',\n SORTED_DESC: 'sorted-desc',\n\n // Visibility\n HIDDEN: 'hidden',\n\n // ─── Shared Classes (used by plugins, styled by core CSS) ────────\n // These are defined here because core CSS provides the base styling.\n // Plugins apply these classes; core CSS defines how they look.\n\n // Sticky positioning (PinnedColumnsPlugin applies, core CSS styles)\n STICKY_LEFT: 'sticky-left',\n STICKY_RIGHT: 'sticky-right',\n\n // Pinned rows (PinnedRowsPlugin applies, core CSS styles)\n PINNED_TOP: 'pinned-top',\n PINNED_BOTTOM: 'pinned-bottom',\n\n // Tree structure (TreePlugin applies, core CSS styles)\n TREE_TOGGLE: 'tree-toggle',\n TREE_INDENT: 'tree-indent',\n\n // Grouping (GroupingRowsPlugin applies, core CSS styles)\n GROUP_TOGGLE: 'group-toggle',\n GROUP_LABEL: 'group-label',\n GROUP_COUNT: 'group-count',\n\n // Selection (SelectionPlugin applies, core CSS styles)\n RANGE_SELECTION: 'range-selection',\n SELECTION_OVERLAY: 'selection-overlay',\n} as const;\n\n// #endregion\n\n// #region Data Attributes\n\n/**\n * Data attribute names used on grid elements.\n * Use these when getting/setting data attributes.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridDataAttrs = {\n // ─── Core Attributes ──────────────────────────────────────────────\n FIELD: 'data-field',\n\n /**\n * Marks a `title` the grid itself put on a cell to reveal ellipsised text\n * (WCAG SC 1.4.12). Anything that sets its own `title` on a cell must clear\n * this, or the grid will later mistake that title for its own and replace it.\n *\n * @since 3.6.0\n */\n TRUNCATED: 'data-tbw-truncated',\n\n // ─── Shared Attributes (used by plugins) ──────────────────────────\n GROUP_KEY: 'data-group-key', // GroupingRowsPlugin\n} as const;\n\n// #endregion\n\n// #region Selectors\n\n/**\n * Common CSS selectors for querying grid elements.\n * Built from the class constants for consistency.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridSelectors = {\n ROOT: `.${GridClasses.ROOT}`,\n HEADER: `.${GridClasses.HEADER}`,\n HEADER_ROW: `.${GridClasses.HEADER_ROW}`,\n HEADER_CELL: `.${GridClasses.HEADER_CELL}`,\n ROWS_VIEWPORT: `.${GridClasses.ROWS_VIEWPORT}`,\n ROWS_CONTAINER: `.${GridClasses.ROWS_CONTAINER}`,\n DATA_ROW: `.${GridClasses.DATA_ROW}`,\n DATA_CELL: `.${GridClasses.DATA_CELL}`,\n GROUP_ROW: `.${GridClasses.GROUP_ROW}`,\n\n // By data attribute\n CELL_BY_FIELD: (field: string) => `.${GridClasses.DATA_CELL}[${GridDataAttrs.FIELD}=\"${field}\"]`,\n\n // State selectors\n SELECTED_ROWS: `.${GridClasses.DATA_ROW}.${GridClasses.SELECTED}`,\n EDITING_CELL: `.${GridClasses.DATA_CELL}.${GridClasses.EDITING}`,\n} as const;\n\n// #endregion\n\n// #region CSS Custom Properties\n\n/**\n * CSS custom property names for theming.\n * Use these when programmatically setting styles.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridCSSVars = {\n // Colors\n COLOR_BG: '--tbw-color-bg',\n COLOR_FG: '--tbw-color-fg',\n COLOR_FG_MUTED: '--tbw-color-fg-muted',\n COLOR_BORDER: '--tbw-color-border',\n COLOR_ACCENT: '--tbw-color-accent',\n COLOR_HEADER_BG: '--tbw-color-header-bg',\n COLOR_HEADER_FG: '--tbw-color-header-fg',\n COLOR_SELECTION: '--tbw-color-selection',\n COLOR_ROW_HOVER: '--tbw-color-row-hover',\n COLOR_ROW_ALT: '--tbw-color-row-alt',\n\n // Sizing\n ROW_HEIGHT: '--tbw-row-height',\n HEADER_HEIGHT: '--tbw-header-height',\n CELL_PADDING: '--tbw-cell-padding',\n\n // Typography\n FONT_FAMILY: '--tbw-font-family',\n FONT_SIZE: '--tbw-font-size',\n\n // Borders\n BORDER_RADIUS: '--tbw-border-radius',\n FOCUS_OUTLINE: '--tbw-focus-outline',\n} as const;\n\n// #endregion\n\n// Type helpers\n\n/**\n * Union of all CSS class names used in the grid's DOM.\n * Use `GridClasses` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridClassName = (typeof GridClasses)[keyof typeof GridClasses];\n\n/**\n * Union of all `data-*` attribute names used on grid elements.\n * Use `GridDataAttrs` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridDataAttr = (typeof GridDataAttrs)[keyof typeof GridDataAttrs];\n\n/**\n * Union of all CSS custom property names (`--tbw-*`) available for theming.\n * Use `GridCSSVars` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridCSSVar = (typeof GridCSSVars)[keyof typeof GridCSSVars];\n","/**\n * Generic click-only action menu — the shared building block for WCAG 2.2\n * SC 2.5.7 \"Dragging Movements\" alternatives in plugins.\n *\n * Every drag affordance in the grid needs a path that works with plain clicks\n * or taps. A keyboard equivalent is **not** enough: SC 2.5.7 is specifically\n * about pointer input, and the W3C Understanding text calls out \"a pop-up menu\n * after tap\" as a conforming alternative. This module is that pop-up.\n *\n * Lives under `plugins/shared/` on purpose: each plugin bundle is built\n * self-contained, so this code is duplicated into the plugins that use it and\n * never lands in the core `index.js` budget.\n *\n * @since 3.6.0\n * @internal\n */\n\n/** Gap between the anchor element and the menu, in px. */\nconst ANCHOR_GAP_PX = 6;\n\n/** Runtime check — happy-dom and older browsers may lack the Popover API. */\nfunction supportsPopover(): boolean {\n return typeof HTMLElement.prototype?.showPopover === 'function';\n}\n\nlet hoverlessQuery: MediaQueryList | null | undefined;\n\n/** Whether the primary pointer cannot hover (touch, most switch devices). */\nfunction isHoverless(): boolean {\n if (hoverlessQuery === undefined) hoverlessQuery = globalThis.matchMedia?.('(hover: none)') ?? null;\n return hoverlessQuery?.matches ?? false;\n}\n\n/**\n * Whether a plugin should also expose its drag alternative as a dedicated\n * inline control, on top of the menu that is always available.\n *\n * Hover-less pointers get one unconditionally — a control revealed on hover is\n * unreachable when nothing can hover, and long-press is the wrong fallback for\n * the tremor and low-dexterity users SC 2.5.7 exists for.\n *\n * Driven by `a11y.dragAlternatives` on the grid config.\n */\nexport function prefersInlineDragAlternative(grid: {\n effectiveConfig?: { a11y?: { dragAlternatives?: 'menu' | 'inline' } };\n}): boolean {\n return grid.effectiveConfig?.a11y?.dragAlternatives === 'inline' || isHoverless();\n}\n\n/** One clickable entry in the menu. */\nexport interface DragAlternativeAction {\n /** Visible text, also the accessible name. */\n label: string;\n /** Render the entry but refuse the click (e.g. \"Move up\" on the first row). */\n disabled?: boolean;\n /** Invoked on click; the menu closes first so focus handling is predictable. */\n run(): void;\n}\n\nexport interface DragAlternativeMenu {\n /**\n * Show `actions` anchored to `anchor`, with `label` as the group's accessible\n * name. Mounted inside the anchor's grid host so it inherits theme tokens.\n */\n open(anchor: HTMLElement, label: string, actions: readonly DragAlternativeAction[]): void;\n /** Hide the menu. Safe to call when already closed. */\n close(): void;\n /** Remove the element and every document-level listener. */\n dispose(): void;\n /** The menu element, or `null` before the first {@link DragAlternativeMenu.open}. */\n readonly element: HTMLElement | null;\n}\n\n/**\n * Create a menu instance.\n *\n * @param id - DOM id for the menu element, unique per owning plugin.\n * @param className - Root class name, so each plugin can style its own menu.\n */\nexport function createDragAlternativeMenu(id: string, className: string): DragAlternativeMenu {\n let root: HTMLDivElement | null = null;\n let opened = false;\n let disposed = false;\n const abort = new AbortController();\n\n function close(): void {\n if (!opened || !root) return;\n opened = false;\n if (supportsPopover()) root.hidePopover();\n root.hidden = true;\n }\n\n function build(): HTMLDivElement {\n const el = document.createElement('div');\n el.id = id;\n el.className = className;\n el.setAttribute('popover', 'manual');\n el.setAttribute('role', 'group');\n // Without the Popover API there is no UA `display: none`, so hide it here.\n el.hidden = true;\n // Tabbing past either end dismisses instead of stranding the user — the\n // menu has no visible trigger to tab back to. A null `relatedTarget` (click\n // focus in Firefox/Safari, focus leaving the document) is ignored.\n el.addEventListener(\n 'focusout',\n (e) => {\n const next = (e as FocusEvent).relatedTarget;\n if (next instanceof Node && !el.contains(next)) close();\n },\n { signal: abort.signal },\n );\n return el;\n }\n\n /**\n * Position against the anchor in viewport coordinates. The menu lives in the\n * top layer, so its containing block is the viewport regardless of where the\n * anchor sits in the DOM.\n */\n function place(anchor: HTMLElement): void {\n if (!root) return;\n const a = anchor.getBoundingClientRect();\n const box = root.getBoundingClientRect();\n const left = Math.max(4, Math.min(a.left, window.innerWidth - box.width - 4));\n const below = a.bottom + ANCHOR_GAP_PX;\n const top = below + box.height <= window.innerHeight ? below : Math.max(4, a.top - ANCHOR_GAP_PX - box.height);\n root.style.left = `${Math.round(left)}px`;\n root.style.top = `${Math.round(top)}px`;\n }\n\n // Any pointer press outside the menu dismisses it. Capture phase so the press\n // that lands on a grid cell still reaches its own handler afterwards.\n document.addEventListener(\n 'pointerdown',\n (e) => {\n if (opened && root && !e.composedPath().includes(root)) close();\n },\n { capture: true, signal: abort.signal },\n );\n document.addEventListener(\n 'keydown',\n (e) => {\n if ((e as KeyboardEvent).key === 'Escape') close();\n },\n { capture: true, signal: abort.signal },\n );\n\n return {\n get element() {\n return root;\n },\n open(anchor, label, actions) {\n if (disposed || actions.length === 0) return;\n if (!root) root = build();\n // Mount inside the grid host (light DOM) so theme custom properties and\n // `color-scheme` cascade in; the top layer keeps it out of overflow.\n const host = anchor.closest<HTMLElement>('tbw-grid, [data-tbw-grid]') ?? document.body;\n if (root.parentNode !== host) host.appendChild(root);\n root.setAttribute('aria-label', label);\n root.replaceChildren();\n for (const action of actions) {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `${className}-item`;\n btn.textContent = action.label;\n btn.disabled = action.disabled === true;\n btn.addEventListener('click', () => {\n close();\n action.run();\n });\n root.appendChild(btn);\n }\n root.hidden = false;\n if (!opened && supportsPopover()) root.showPopover();\n opened = true;\n place(anchor);\n root.querySelector<HTMLButtonElement>('button:not([disabled])')?.focus();\n },\n close,\n dispose() {\n disposed = true;\n close();\n abort.abort();\n root?.remove();\n root = null;\n },\n };\n}\n","/**\n * Pivot Tool Panel Rendering\n *\n * Pure functions for rendering the pivot configuration panel.\n * Separated from PivotPlugin for better code organization.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport type { Translate } from '../../core/types';\nimport {\n createDragAlternativeMenu,\n type DragAlternativeAction,\n type DragAlternativeMenu,\n} from '../shared/drag-alternative-menu';\nimport type { AggFunc, CustomAggFunc, PivotConfig, PivotValueField } from './types';\n\n/** Built-in aggregation function names (excludes custom functions) */\ntype BuiltInAggFunc = Exclude<AggFunc, CustomAggFunc>;\n\n/** Zones a field can be dragged into. */\ntype GroupZone = 'rowGroups' | 'columnGroups';\n\n/**\n * One menu shared by every chip. Each action re-renders the whole panel, so a\n * menu owned by a chip would be torn out from under itself mid-click.\n */\nlet chipMenu: DragAlternativeMenu | null = null;\n\n/** All available built-in aggregation functions for the panel UI */\nexport const AGG_FUNCS: BuiltInAggFunc[] = ['sum', 'avg', 'count', 'min', 'max', 'first', 'last'];\n\n/** Field info for available fields */\nexport interface FieldInfo {\n field: string;\n header: string;\n}\n\n/** Callbacks for panel interactions */\nexport interface PanelCallbacks {\n onTogglePivot: (enabled: boolean) => void;\n onAddFieldToZone: (field: string, zone: 'rowGroups' | 'columnGroups') => void;\n onRemoveFieldFromZone: (field: string, zone: 'rowGroups' | 'columnGroups') => void;\n onReorderFieldInZone: (field: string, zone: 'rowGroups' | 'columnGroups', newIndex: number) => void;\n onMoveFieldBetweenZones: (\n field: string,\n fromZone: 'rowGroups' | 'columnGroups',\n toZone: 'rowGroups' | 'columnGroups',\n ) => void;\n onAddValueField: (field: string, aggFunc: AggFunc) => void;\n onRemoveValueField: (field: string) => void;\n onUpdateValueAggFunc: (field: string, aggFunc: AggFunc) => void;\n onOptionChange: (option: 'showTotals' | 'showGrandTotal', value: boolean) => void;\n getAvailableFields: () => FieldInfo[];\n}\n\n/** Internal context passed to rendering functions */\ninterface RenderContext {\n config: PivotConfig;\n callbacks: PanelCallbacks;\n signal: AbortSignal;\n t: Translate;\n}\n\n/**\n * Render the complete pivot panel content.\n * Returns a cleanup function that removes all event listeners and DOM elements.\n */\nexport function renderPivotPanel(\n container: HTMLElement,\n config: PivotConfig,\n isActive: boolean,\n callbacks: PanelCallbacks,\n t: Translate = (_key, fallback) => fallback,\n): () => void {\n // Create AbortController for automatic listener cleanup\n const controller = new AbortController();\n const ctx: RenderContext = { config, callbacks, signal: controller.signal, t };\n\n const wrapper = document.createElement('div');\n wrapper.className = 'tbw-pivot-panel';\n\n // Options section (at top, includes pivot toggle)\n wrapper.appendChild(createSection(t('pivot.options', 'Options'), () => createOptionsPanel(isActive, ctx)));\n\n // Row Groups section\n wrapper.appendChild(createSection(t('pivot.rowGroups', 'Row Groups'), () => createFieldZone('rowGroups', ctx)));\n\n // Column Groups section\n wrapper.appendChild(\n createSection(t('pivot.columnGroups', 'Column Groups'), () => createFieldZone('columnGroups', ctx)),\n );\n\n // Values section\n wrapper.appendChild(createSection(t('pivot.values', 'Values'), () => createValuesZone(ctx)));\n\n // Available fields section\n wrapper.appendChild(\n createSection(t('pivot.availableFields', 'Available Fields'), () => createAvailableFieldsZone(ctx)),\n );\n\n container.appendChild(wrapper);\n\n // Cleanup: abort all listeners, then remove DOM\n return () => {\n controller.abort();\n chipMenu?.dispose();\n chipMenu = null;\n wrapper.remove();\n };\n}\n\n/**\n * Show the click-only alternative to dragging `chip` (WCAG 2.2 SC 2.5.7).\n */\nfunction openChipMenu(chip: HTMLElement, label: string, actions: readonly DragAlternativeAction[]): void {\n chipMenu ??= createDragAlternativeMenu('tbw-pivot-chip-menu', 'tbw-pivot-chip-menu');\n chipMenu.open(chip, label, actions);\n}\n\n/**\n * Mark a chip as the pointer trigger for its move menu.\n *\n * The role goes on `target`, which for group chips is the label rather than the\n * chip itself — `role=\"button\"` makes descendants presentational, which would\n * hide the chip's own remove button from assistive technology.\n */\nfunction markAsMenuTrigger(target: HTMLElement, name: string, t: Translate): void {\n target.setAttribute('role', 'button');\n target.tabIndex = -1;\n target.setAttribute('aria-label', `${name} — ${t('pivot.chipHint', 'drag, or activate for move options')}`);\n}\n\n/** Menu entries mirroring every drag the chip supports, plus its remove button. */\nfunction fieldChipActions(field: string, zoneType: GroupZone, ctx: RenderContext): DragAlternativeAction[] {\n const { config, callbacks, t } = ctx;\n const fields = zoneType === 'rowGroups' ? (config.rowGroupFields ?? []) : (config.columnGroupFields ?? []);\n const index = fields.indexOf(field);\n const otherZone: GroupZone = zoneType === 'rowGroups' ? 'columnGroups' : 'rowGroups';\n\n return [\n {\n label: t('pivot.moveUp', 'Move up'),\n disabled: index <= 0,\n run: () => callbacks.onReorderFieldInZone(field, zoneType, index - 1),\n },\n {\n label: t('pivot.moveDown', 'Move down'),\n disabled: index < 0 || index >= fields.length - 1,\n // The callback takes an insert-before index in the pre-move list, so\n // stepping down one place means aiming past the field that follows.\n run: () => callbacks.onReorderFieldInZone(field, zoneType, index + 2),\n },\n {\n label:\n otherZone === 'columnGroups'\n ? t('pivot.moveToColumnGroups', 'Move to Column Groups')\n : t('pivot.moveToRowGroups', 'Move to Row Groups'),\n run: () => callbacks.onMoveFieldBetweenZones(field, zoneType, otherZone),\n },\n {\n // Matches dropping the chip on the values zone, which also only adds.\n label: t('pivot.moveToValues', 'Move to Values'),\n run: () => callbacks.onAddValueField(field, 'sum'),\n },\n {\n label: t('pivot.removeField', 'Remove field'),\n run: () => callbacks.onRemoveFieldFromZone(field, zoneType),\n },\n ];\n}\n\n/** Menu entries for an unused field, mirroring a drag into each zone. */\nfunction availableChipActions(field: string, ctx: RenderContext): DragAlternativeAction[] {\n const { callbacks, t } = ctx;\n return [\n {\n label: t('pivot.addToRowGroups', 'Add to Row Groups'),\n run: () => callbacks.onAddFieldToZone(field, 'rowGroups'),\n },\n {\n label: t('pivot.addToColumnGroups', 'Add to Column Groups'),\n run: () => callbacks.onAddFieldToZone(field, 'columnGroups'),\n },\n {\n label: t('pivot.addToValues', 'Add to Values'),\n run: () => callbacks.onAddValueField(field, 'sum'),\n },\n ];\n}\n\n/**\n * Create a collapsible section wrapper.\n */\nfunction createSection(title: string, contentFactory: () => HTMLElement): HTMLElement {\n const section = document.createElement('div');\n section.className = 'tbw-pivot-section';\n\n const header = document.createElement('div');\n header.className = 'tbw-pivot-section-header';\n header.textContent = title;\n\n const content = document.createElement('div');\n content.className = 'tbw-pivot-section-content';\n content.appendChild(contentFactory());\n\n section.appendChild(header);\n section.appendChild(content);\n\n return section;\n}\n\n/**\n * Create a drop zone for row/column group fields.\n */\nfunction createFieldZone(zoneType: 'rowGroups' | 'columnGroups', ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const zone = document.createElement('div');\n zone.className = 'tbw-pivot-drop-zone';\n zone.setAttribute('data-zone', zoneType);\n\n const currentFields = zoneType === 'rowGroups' ? (config.rowGroupFields ?? []) : (config.columnGroupFields ?? []);\n\n if (currentFields.length === 0) {\n const placeholder = document.createElement('div');\n placeholder.className = 'tbw-pivot-placeholder';\n placeholder.textContent = ctx.t('pivot.dropFields', 'Drag fields here');\n zone.appendChild(placeholder);\n } else {\n for (const field of currentFields) {\n zone.appendChild(createFieldChip(field, zoneType, ctx));\n }\n }\n\n // Drop handling — supports reorder within zone and cross-zone moves\n zone.addEventListener(\n 'dragover',\n (e) => {\n e.preventDefault();\n zone.classList.add('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'dragleave',\n () => {\n zone.classList.remove('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'drop',\n (e) => {\n e.preventDefault();\n zone.classList.remove('drag-over');\n\n const field = e.dataTransfer?.getData('text/plain');\n const sourceZone = e.dataTransfer?.getData('source-zone') as 'rowGroups' | 'columnGroups' | undefined;\n if (!field) return;\n\n // Determine drop position for reordering\n const chips = zone.querySelectorAll('.tbw-pivot-field-chip');\n let dropIndex = chips.length;\n for (let i = 0; i < chips.length; i++) {\n const rect = chips[i].getBoundingClientRect();\n if (e.clientY < rect.top + rect.height / 2) {\n dropIndex = i;\n break;\n }\n }\n\n if (sourceZone && sourceZone !== zoneType && (sourceZone === 'rowGroups' || sourceZone === 'columnGroups')) {\n // Cross-zone move\n callbacks.onMoveFieldBetweenZones(field, sourceZone, zoneType);\n } else if (sourceZone === zoneType) {\n // Reorder within same zone\n callbacks.onReorderFieldInZone(field, zoneType, dropIndex);\n } else {\n // New field from available fields\n callbacks.onAddFieldToZone(field, zoneType);\n }\n },\n { signal },\n );\n\n return zone;\n}\n\n/**\n * Create a field chip for row/column zones.\n */\nfunction createFieldChip(field: string, zoneType: GroupZone, ctx: RenderContext): HTMLElement {\n const { callbacks, signal } = ctx;\n const chip = document.createElement('div');\n chip.className = 'tbw-pivot-field-chip';\n chip.draggable = true;\n\n const fieldInfo = callbacks.getAvailableFields().find((f) => f.field === field);\n const name = fieldInfo?.header ?? field;\n const label = document.createElement('span');\n label.className = 'tbw-pivot-chip-label';\n label.textContent = name;\n markAsMenuTrigger(label, name, ctx.t);\n\n const removeBtn = document.createElement('button');\n removeBtn.type = 'button';\n removeBtn.className = 'tbw-pivot-chip-remove';\n removeBtn.innerHTML = '×';\n removeBtn.title = ctx.t('pivot.removeField', 'Remove field');\n removeBtn.addEventListener(\n 'click',\n (e) => {\n e.stopPropagation();\n callbacks.onRemoveFieldFromZone(field, zoneType);\n },\n { signal },\n );\n\n chip.appendChild(label);\n chip.appendChild(removeBtn);\n\n // Drag handling for reordering\n chip.addEventListener(\n 'dragstart',\n (e) => {\n e.dataTransfer?.setData('text/plain', field);\n e.dataTransfer?.setData('source-zone', zoneType);\n chip.classList.add(GridClasses.DRAGGING);\n },\n { signal },\n );\n\n chip.addEventListener(\n 'dragend',\n () => {\n chip.classList.remove(GridClasses.DRAGGING);\n },\n { signal },\n );\n\n // A press-and-release without movement never fires `dragstart`, so a plain\n // click is the tap that SC 2.5.7 asks us to honour.\n chip.addEventListener('click', () => openChipMenu(chip, name, fieldChipActions(field, zoneType, ctx)), { signal });\n\n return chip;\n}\n\n/**\n * Create the values zone with aggregation controls.\n */\nfunction createValuesZone(ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const zone = document.createElement('div');\n zone.className = 'tbw-pivot-drop-zone tbw-pivot-values-zone';\n zone.setAttribute('data-zone', 'values');\n\n const currentValues = config.valueFields ?? [];\n\n if (currentValues.length === 0) {\n const placeholder = document.createElement('div');\n placeholder.className = 'tbw-pivot-placeholder';\n placeholder.textContent = ctx.t('pivot.dropNumericFields', 'Drag numeric fields here');\n zone.appendChild(placeholder);\n } else {\n for (const valueField of currentValues) {\n zone.appendChild(createValueChip(valueField, ctx));\n }\n }\n\n // Drop handling with signal for cleanup\n zone.addEventListener(\n 'dragover',\n (e) => {\n e.preventDefault();\n zone.classList.add('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'dragleave',\n () => {\n zone.classList.remove('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'drop',\n (e) => {\n e.preventDefault();\n zone.classList.remove('drag-over');\n const field = e.dataTransfer?.getData('text/plain');\n if (field) {\n callbacks.onAddValueField(field, 'sum');\n }\n },\n { signal },\n );\n\n return zone;\n}\n\n/**\n * Create a value chip with aggregation selector.\n */\nfunction createValueChip(valueField: PivotValueField, ctx: RenderContext): HTMLElement {\n const { callbacks, signal } = ctx;\n const chip = document.createElement('div');\n chip.className = 'tbw-pivot-field-chip tbw-pivot-value-chip';\n\n const fieldInfo = callbacks.getAvailableFields().find((f) => f.field === valueField.field);\n\n const labelWrapper = document.createElement('div');\n labelWrapper.className = 'tbw-pivot-value-label-wrapper';\n\n const label = document.createElement('span');\n label.className = 'tbw-pivot-chip-label';\n label.textContent = fieldInfo?.header ?? valueField.field;\n\n const isCustomAgg = typeof valueField.aggFunc === 'function';\n\n const aggSelect = document.createElement('select');\n aggSelect.className = 'tbw-pivot-agg-select';\n aggSelect.title = ctx.t('pivot.aggFunction', 'Aggregation function');\n\n if (isCustomAgg) {\n const option = document.createElement('option');\n option.value = '__custom__';\n option.textContent = ctx.t('pivot.customAgg', 'CUSTOM');\n option.selected = true;\n aggSelect.appendChild(option);\n aggSelect.disabled = true;\n }\n\n for (const aggFunc of AGG_FUNCS) {\n const option = document.createElement('option');\n option.value = aggFunc;\n option.textContent = aggFunc.toUpperCase();\n if (!isCustomAgg) option.selected = aggFunc === valueField.aggFunc;\n aggSelect.appendChild(option);\n }\n\n aggSelect.addEventListener(\n 'change',\n () => {\n callbacks.onUpdateValueAggFunc(valueField.field, aggSelect.value as BuiltInAggFunc);\n },\n { signal },\n );\n\n const removeBtn = document.createElement('button');\n removeBtn.type = 'button';\n removeBtn.className = 'tbw-pivot-chip-remove';\n removeBtn.innerHTML = '×';\n removeBtn.title = ctx.t('pivot.removeValueField', 'Remove value field');\n removeBtn.addEventListener(\n 'click',\n (e) => {\n e.stopPropagation();\n callbacks.onRemoveValueField(valueField.field);\n },\n { signal },\n );\n\n labelWrapper.appendChild(label);\n labelWrapper.appendChild(aggSelect);\n\n chip.appendChild(labelWrapper);\n chip.appendChild(removeBtn);\n\n return chip;\n}\n\n/**\n * Create the available fields zone.\n */\nfunction createAvailableFieldsZone(ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const zone = document.createElement('div');\n zone.className = 'tbw-pivot-available-fields';\n\n const allFields = callbacks.getAvailableFields();\n const usedFields = new Set([\n ...(config.rowGroupFields ?? []),\n ...(config.columnGroupFields ?? []),\n ...(config.valueFields?.map((v) => v.field) ?? []),\n ]);\n\n // Filter to show only unused fields\n const availableFields = allFields.filter((f) => !usedFields.has(f.field));\n\n if (availableFields.length === 0) {\n const empty = document.createElement('div');\n empty.className = 'tbw-pivot-placeholder';\n empty.textContent = ctx.t('pivot.allFieldsUsed', 'All fields are in use');\n zone.appendChild(empty);\n } else {\n // Search filter for available fields (only when 6+ fields)\n const fieldsContainer = document.createElement('div');\n fieldsContainer.className = 'tbw-pivot-fields-list';\n\n if (availableFields.length >= 6) {\n const search = document.createElement('input');\n search.type = 'text';\n search.placeholder = ctx.t('pivot.filterFields', 'Filter fields\\u2026');\n search.className = 'tbw-pivot-field-search';\n search.addEventListener(\n 'input',\n () => {\n const query = search.value.toLowerCase();\n for (const chip of fieldsContainer.querySelectorAll('.tbw-pivot-field-chip')) {\n const el = chip as HTMLElement;\n el.style.display = el.textContent?.toLowerCase().includes(query) ? '' : 'none';\n }\n },\n { signal },\n );\n zone.appendChild(search);\n }\n\n for (const field of availableFields) {\n const chip = document.createElement('div');\n chip.className = 'tbw-pivot-field-chip available';\n chip.textContent = field.header;\n chip.draggable = true;\n chip.title = ctx.t('pivot.availableChipHint', 'Drag into a zone, or click to choose one');\n markAsMenuTrigger(chip, field.header, ctx.t);\n\n chip.addEventListener(\n 'dragstart',\n (e) => {\n e.dataTransfer?.setData('text/plain', field.field);\n chip.classList.add(GridClasses.DRAGGING);\n },\n { signal },\n );\n\n chip.addEventListener(\n 'dragend',\n () => {\n chip.classList.remove(GridClasses.DRAGGING);\n },\n { signal },\n );\n\n chip.addEventListener('click', () => openChipMenu(chip, field.header, availableChipActions(field.field, ctx)), {\n signal,\n });\n\n fieldsContainer.appendChild(chip);\n }\n\n zone.appendChild(fieldsContainer);\n }\n\n return zone;\n}\n\n/**\n * Create the options panel with pivot toggle and checkboxes for totals.\n */\nfunction createOptionsPanel(isActive: boolean, ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal, t } = ctx;\n const panel = document.createElement('div');\n panel.className = 'tbw-pivot-options';\n\n // Pivot Mode toggle\n panel.appendChild(\n createCheckbox(\n t('pivot.enable', 'Enable Pivot View'),\n isActive,\n (checked) => {\n callbacks.onTogglePivot(checked);\n },\n signal,\n ),\n );\n\n // Show Totals checkbox\n panel.appendChild(\n createCheckbox(\n t('pivot.showRowTotals', 'Show Row Totals'),\n config.showTotals ?? true,\n (checked) => {\n callbacks.onOptionChange('showTotals', checked);\n },\n signal,\n ),\n );\n\n // Show Grand Total checkbox\n panel.appendChild(\n createCheckbox(\n t('pivot.showGrandTotal', 'Show Grand Total'),\n config.showGrandTotal ?? true,\n (checked) => {\n callbacks.onOptionChange('showGrandTotal', checked);\n },\n signal,\n ),\n );\n\n return panel;\n}\n\n/**\n * Create a checkbox with label.\n */\nfunction createCheckbox(\n label: string,\n checked: boolean,\n onChange: (checked: boolean) => void,\n signal: AbortSignal,\n): HTMLElement {\n const wrapper = document.createElement('label');\n wrapper.className = 'tbw-pivot-checkbox';\n\n const input = document.createElement('input');\n input.type = 'checkbox';\n input.checked = checked;\n input.addEventListener('change', () => onChange(input.checked), { signal });\n\n const span = document.createElement('span');\n span.textContent = label;\n\n wrapper.appendChild(input);\n wrapper.appendChild(span);\n\n return wrapper;\n}\n","/**\n * Pivot Row Rendering\n *\n * Pure functions for rendering pivot rows (group rows, leaf rows, grand total).\n * Separated from PivotPlugin for better code organization.\n *\n * IMPORTANT: These functions bypass the grid's normal cell rendering pipeline.\n * Column-level features (format, cellRenderer, cellClass) must be applied manually.\n * When adding new cell rendering logic, always check for `col.format` on value columns.\n */\n\nimport type { ColumnConfig, GridIcons, Translate } from '../../core/types';\n\n/** Row data with pivot metadata */\nexport interface PivotRowData {\n __pivotRowKey?: string;\n __pivotLabel?: string;\n __pivotDepth?: number;\n __pivotIndent?: number;\n __pivotExpanded?: boolean;\n __pivotHasChildren?: boolean;\n __pivotRowCount?: number;\n __pivotIsGrandTotal?: boolean;\n [key: string]: unknown;\n}\n\n/** Context for row rendering */\nexport interface RowRenderContext {\n columns: ColumnConfig[];\n rowIndex: number;\n onToggle: (key: string) => void;\n setIcon: (element: HTMLElement, iconKey: keyof GridIcons) => void;\n}\n\n/**\n * Render a pivot group row (has children, can expand/collapse).\n */\nexport function renderPivotGroupRow(row: PivotRowData, rowEl: HTMLElement, ctx: RowRenderContext): boolean {\n rowEl.className = 'data-grid-row pivot-group-row';\n rowEl.setAttribute('data-pivot-depth', String(row.__pivotDepth ?? 0));\n rowEl.setAttribute('data-pivot-key', String(row.__pivotRowKey ?? ''));\n rowEl.setAttribute('role', 'row');\n // Note: aria-expanded is not set here because it's only valid in treegrid, not grid\n // The expand/collapse state is conveyed via the toggle button's aria-label\n rowEl.innerHTML = '';\n\n ctx.columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('data-row', String(ctx.rowIndex));\n cell.setAttribute('role', 'gridcell');\n\n if (colIdx === 0) {\n // First column: indent + toggle + label + count\n const indent = Number(row.__pivotIndent) || 0;\n cell.style.paddingLeft = `${indent}px`;\n\n // Toggle button\n const rowKey = String(row.__pivotRowKey);\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'pivot-toggle';\n btn.setAttribute('aria-label', row.__pivotExpanded ? 'Collapse group' : 'Expand group');\n ctx.setIcon(btn, row.__pivotExpanded ? 'collapse' : 'expand');\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n ctx.onToggle(rowKey);\n });\n cell.appendChild(btn);\n\n // Group label\n const label = document.createElement('span');\n label.className = 'pivot-label';\n label.textContent = String(row.__pivotLabel ?? '');\n cell.appendChild(label);\n\n // Row count\n const count = document.createElement('span');\n count.className = 'pivot-count';\n count.textContent = ` (${Number(row.__pivotRowCount) || 0})`;\n cell.appendChild(count);\n } else {\n // Other columns: render value, applying column format when available\n const value = row[col.field];\n cell.textContent = value != null ? (col.format ? col.format(value, row) : String(value)) : '';\n }\n\n rowEl.appendChild(cell);\n });\n\n return true;\n}\n\n/**\n * Render a pivot leaf row (no children, just indentation).\n */\nexport function renderPivotLeafRow(\n row: PivotRowData,\n rowEl: HTMLElement,\n columns: ColumnConfig[],\n rowIndex: number,\n): boolean {\n rowEl.className = 'data-grid-row pivot-leaf-row';\n rowEl.setAttribute('data-pivot-depth', String(row.__pivotDepth ?? 0));\n rowEl.setAttribute('data-pivot-key', String(row.__pivotRowKey ?? ''));\n rowEl.innerHTML = '';\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('data-row', String(rowIndex));\n cell.setAttribute('role', 'gridcell');\n\n if (colIdx === 0) {\n // First column: indent + label (no toggle for leaves)\n const indent = Number(row.__pivotIndent) || 0;\n // Add extra indent for alignment with toggle button\n cell.style.paddingLeft = `${indent + 20}px`;\n\n const label = document.createElement('span');\n label.className = 'pivot-label';\n label.textContent = String(row.__pivotLabel ?? '');\n cell.appendChild(label);\n } else {\n // Other columns: render value, applying column format when available\n const value = row[col.field];\n cell.textContent = value != null ? (col.format ? col.format(value, row) : String(value)) : '';\n }\n\n rowEl.appendChild(cell);\n });\n\n return true;\n}\n\n/**\n * Render the grand total row.\n * Used both for the sticky footer and for in-row-model rendering.\n */\nexport function renderPivotGrandTotalRow(\n row: PivotRowData,\n rowEl: HTMLElement,\n columns: ColumnConfig[],\n t: Translate = (_key, fallback) => fallback,\n): boolean {\n rowEl.className = 'data-grid-row pivot-grand-total-row';\n rowEl.innerHTML = '';\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.setAttribute('data-col', String(colIdx));\n // No role attribute - parent row has role=presentation so children don't need grid semantics\n\n if (colIdx === 0) {\n // First column: Grand Total label\n const label = document.createElement('span');\n label.className = 'pivot-label';\n label.textContent = t('pivot.grandTotal', 'Grand Total');\n cell.appendChild(label);\n } else {\n // Other columns: render totals, applying column format when available\n const value = row[col.field];\n cell.textContent = value != null ? (col.format ? col.format(value, row) : String(value)) : '';\n }\n\n rowEl.appendChild(cell);\n });\n\n return true;\n}\n","/**\n * Pivot Plugin Styles\n *\n * Uses CSS custom properties with plugin-specific overrides\n * falling back to grid-level variables.\n */\n\n@layer tbw-plugins {\n /* ===== Pivot Row Styles ===== */\n\n .pivot-group-row {\n display: grid;\n grid-template-columns: var(--tbw-column-template);\n font-weight: 600;\n background: var(--tbw-pivot-group-bg, var(--tbw-color-row-alt));\n min-height: var(--tbw-row-height);\n border-bottom: var(--tbw-row-divider);\n }\n\n @media (hover: hover) {\n .pivot-group-row:hover {\n background: var(--tbw-pivot-group-hover, var(--tbw-color-row-hover));\n }\n }\n\n .pivot-leaf-row {\n display: grid;\n grid-template-columns: var(--tbw-column-template);\n background: var(--tbw-pivot-leaf-bg, var(--tbw-color-bg));\n min-height: var(--tbw-row-height);\n border-bottom: var(--tbw-row-divider);\n }\n\n .pivot-grand-total-row {\n display: grid;\n grid-template-columns: var(--tbw-column-template);\n font-weight: 700;\n background: var(--tbw-pivot-grand-total-bg, var(--tbw-color-header-bg));\n min-height: var(--tbw-row-height);\n border-top: 2px solid var(--tbw-color-border-strong);\n }\n\n .pivot-grand-total-row > .cell {\n display: flex;\n align-items: center;\n padding: var(--tbw-cell-padding);\n border-right: 1px solid var(--tbw-color-border-cell);\n overflow: hidden;\n min-width: 0;\n }\n\n .pivot-grand-total-row > .cell:last-child {\n border-right: 0;\n }\n\n /* ===== Sticky Grand Total Footer ===== */\n\n .pivot-grand-total-footer {\n position: sticky;\n bottom: 0;\n z-index: var(--tbw-z-layer-pinned-rows, 20);\n background: var(--tbw-pivot-grand-total-bg, var(--tbw-color-header-bg));\n min-width: fit-content;\n }\n\n /* Cells within pivot rows */\n .pivot-group-row > .cell,\n .pivot-leaf-row > .cell {\n display: flex;\n align-items: center;\n padding: var(--tbw-cell-padding);\n border-right: 1px solid var(--tbw-color-border-cell);\n overflow: hidden;\n min-width: 0;\n }\n\n .pivot-group-row > .cell:last-child,\n .pivot-leaf-row > .cell:last-child {\n border-right: 0;\n }\n\n /* ===== Toggle Button ===== */\n\n .pivot-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--tbw-pivot-toggle-size, var(--tbw-toggle-size, 1.25em));\n height: var(--tbw-pivot-toggle-size, var(--tbw-toggle-size, 1.25em));\n margin-right: var(--tbw-spacing-sm, 0.375em);\n border: none;\n background: transparent;\n cursor: pointer;\n color: var(--tbw-pivot-toggle-color, var(--tbw-color-fg-muted));\n border-radius: var(--tbw-border-radius);\n transition:\n background var(--tbw-transition-duration, 120ms) var(--tbw-transition-ease, ease),\n color var(--tbw-transition-duration, 120ms) var(--tbw-transition-ease, ease);\n }\n\n .pivot-toggle:hover {\n background: var(--tbw-pivot-toggle-hover-bg, var(--tbw-color-row-hover));\n color: var(--tbw-pivot-toggle-hover-color, var(--tbw-color-fg));\n }\n\n /* SC 2.5.8: grow the target, not the box, so the pivot row keeps its height. */\n .pivot-toggle {\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset-block-start: 50%;\n inset-inline-start: 50%;\n translate: -50% -50%;\n inline-size: var(--tbw-touch-target-min, 24px);\n block-size: var(--tbw-touch-target-min, 24px);\n }\n }\n\n @media (pointer: coarse) {\n .pivot-toggle {\n min-width: var(--tbw-touch-target-min, 24px);\n min-height: var(--tbw-touch-target-min, 24px);\n }\n }\n\n .pivot-toggle:focus {\n outline: var(--tbw-focus-outline);\n outline-offset: var(--tbw-focus-outline-offset);\n }\n\n /* ===== Labels & Counts ===== */\n\n .pivot-label {\n font-weight: inherit;\n }\n\n .pivot-count {\n color: var(--tbw-pivot-count-color, var(--tbw-color-fg-muted));\n font-size: 0.9em;\n font-weight: normal;\n }\n\n .pivot-total-row {\n font-weight: bold;\n border-top: 2px solid var(--tbw-pivot-border, var(--tbw-color-border-strong));\n }\n\n /* Depth-based styling (for non-custom rendered rows) */\n [data-pivot-depth='1'] {\n --tbw-pivot-depth: 1;\n }\n [data-pivot-depth='2'] {\n --tbw-pivot-depth: 2;\n }\n [data-pivot-depth='3'] {\n --tbw-pivot-depth: 3;\n }\n [data-pivot-depth='4'] {\n --tbw-pivot-depth: 4;\n }\n\n /* ===== Tool Panel Styles ===== */\n\n .tbw-pivot-panel {\n display: flex;\n flex-direction: column;\n gap: var(--tbw-panel-padding, var(--tbw-spacing-lg, 0.75rem));\n padding: var(--tbw-panel-padding, var(--tbw-spacing-lg, 0.75rem));\n height: 100%;\n overflow-y: auto;\n font-size: var(--tbw-font-size-sm, 0.8125rem);\n }\n\n .tbw-pivot-section {\n border: 1px solid var(--tbw-pivot-border, var(--tbw-color-border));\n border-radius: var(--tbw-border-radius);\n background: var(--tbw-pivot-section-bg, var(--tbw-color-bg));\n }\n\n .tbw-pivot-section-header {\n padding: var(--tbw-button-padding, var(--tbw-spacing-md, 0.5rem) var(--tbw-spacing-lg, 0.75rem));\n font-weight: 600;\n background: var(--tbw-pivot-header-bg, var(--tbw-color-header-bg));\n border-bottom: 1px solid var(--tbw-pivot-border, var(--tbw-color-border));\n border-radius: var(--tbw-border-radius) var(--tbw-border-radius) 0 0;\n }\n\n .tbw-pivot-section-content {\n padding: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n }\n\n .tbw-pivot-toggle-wrapper {\n display: flex;\n align-items: center;\n }\n\n .tbw-pivot-toggle-label {\n display: flex;\n align-items: center;\n gap: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n cursor: pointer;\n }\n\n .tbw-pivot-toggle-label input {\n width: var(--tbw-icon-size, 1rem);\n height: var(--tbw-icon-size, 1rem);\n cursor: pointer;\n }\n\n /* ===== Drop Zones ===== */\n\n .tbw-pivot-drop-zone {\n min-height: 60px;\n padding: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n border: 2px dashed var(--tbw-pivot-drop-border, var(--tbw-color-border));\n border-radius: var(--tbw-border-radius);\n background: var(--tbw-pivot-drop-bg, var(--tbw-color-row-alt));\n display: flex;\n flex-wrap: wrap;\n gap: var(--tbw-spacing-sm, 0.375rem);\n align-content: flex-start;\n transition: all 0.15s ease;\n }\n\n .tbw-pivot-drop-zone.drag-over {\n border-color: var(--tbw-color-accent);\n background: var(--tbw-pivot-drop-active, var(--tbw-focus-background));\n }\n\n .tbw-pivot-placeholder {\n color: var(--tbw-color-fg-muted);\n font-style: italic;\n padding: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n text-align: center;\n width: 100%;\n }\n\n /* ===== Field Chips ===== */\n\n .tbw-pivot-field-chip {\n display: inline-flex;\n align-items: center;\n gap: var(--tbw-spacing-sm, 0.375rem);\n padding: var(--tbw-button-padding-sm, var(--tbw-spacing-xs, 0.25rem) var(--tbw-spacing-md, 0.5rem));\n background: var(--tbw-pivot-chip-bg, var(--tbw-color-header-bg));\n border: 1px solid var(--tbw-pivot-chip-border, var(--tbw-color-border));\n border-radius: var(--tbw-border-radius);\n cursor: grab;\n font-size: var(--tbw-font-size-xs, 0.75rem);\n transition: all 0.15s ease;\n }\n\n .tbw-pivot-field-chip:hover {\n background: var(--tbw-pivot-chip-hover, var(--tbw-color-row-hover));\n border-color: var(--tbw-color-accent);\n }\n\n .tbw-pivot-field-chip.available {\n background: var(--tbw-color-bg);\n }\n\n .tbw-pivot-field-chip.dragging {\n opacity: 0.5;\n cursor: grabbing;\n }\n\n .tbw-pivot-chip-label {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 120px;\n }\n\n .tbw-pivot-chip-remove {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--tbw-icon-size, 1rem);\n height: var(--tbw-icon-size, 1rem);\n padding: 0;\n border: none;\n background: transparent;\n color: var(--tbw-color-fg-muted);\n font-size: var(--tbw-font-size-sm, 0.875rem);\n font-weight: bold;\n cursor: pointer;\n border-radius: 50%;\n transition: all 0.15s ease;\n }\n\n .tbw-pivot-chip-remove:hover {\n background: var(--tbw-pivot-chip-remove-hover-bg, var(--tbw-color-accent));\n color: var(--tbw-pivot-chip-remove-hover-fg, var(--tbw-color-accent-fg));\n }\n\n /* ===== Chip move menu (WCAG 2.2 SC 2.5.7) ===== */\n\n .tbw-pivot-chip-menu {\n position: fixed;\n margin: 0;\n display: flex;\n flex-direction: column;\n min-width: 12rem;\n padding: var(--tbw-spacing-xs, 0.25rem);\n background: var(--tbw-color-panel-bg, light-dark(#ffffff, #1f1f1f));\n color: var(--tbw-color-fg, light-dark(#1a1a1a, #f0f0f0));\n border: 1px solid var(--tbw-color-border, light-dark(#d0d0d0, #3a3a3a));\n border-radius: var(--tbw-border-radius, 0.25rem);\n box-shadow: var(--tbw-shadow-md, 0 4px 12px rgb(0 0 0 / 25%));\n font: inherit;\n z-index: 10001;\n\n &[hidden] {\n display: none;\n }\n }\n\n .tbw-pivot-chip-menu-item {\n min-height: var(--tbw-touch-target-min, 24px);\n padding: var(--tbw-spacing-xs, 0.25rem) var(--tbw-spacing-sm, 0.5rem);\n text-align: start;\n background: none;\n border: none;\n /* Themes set radii for much larger surfaces (Material uses 12px), which\n turns a 24px row into a lozenge. */\n border-radius: min(var(--tbw-border-radius, 0.25rem), 6px);\n color: inherit;\n font: inherit;\n cursor: pointer;\n\n &:hover:not(:disabled) {\n background: var(--tbw-color-row-hover, light-dark(#f0f0f0, #2a2a2a));\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: default;\n }\n }\n\n /* ===== Value Chips ===== */\n\n .tbw-pivot-value-chip {\n /* Keep inline-flex from parent, just adjust padding */\n padding: var(--tbw-button-padding-sm, var(--tbw-spacing-xs, 0.25rem) var(--tbw-spacing-md, 0.5rem));\n }\n\n .tbw-pivot-value-label-wrapper {\n display: flex;\n align-items: center;\n gap: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n flex: 1;\n min-width: 0;\n }\n\n .tbw-pivot-agg-select {\n padding: var(--tbw-spacing-xs, 0.125rem) var(--tbw-spacing-xs, 0.25rem);\n font-size: var(--tbw-font-size-xs, 0.6875rem);\n border: 1px solid var(--tbw-color-border);\n border-radius: var(--tbw-border-radius);\n background: var(--tbw-color-bg);\n cursor: pointer;\n }\n\n /* ===== Available Fields ===== */\n\n .tbw-pivot-available-fields {\n display: flex;\n flex-direction: column;\n gap: var(--tbw-spacing-sm, 0.375rem);\n min-height: 40px;\n }\n\n .tbw-pivot-field-search {\n width: 100%;\n padding: 0.25rem 0.5rem;\n border: 1px solid var(--tbw-color-border, #d0d5dd);\n border-radius: var(--tbw-radius-sm, 0.25rem);\n font-size: var(--tbw-font-size-sm, 0.875rem);\n background: var(--tbw-color-bg, #fff);\n color: var(--tbw-color-fg, #1a1a2e);\n outline: none;\n }\n\n .tbw-pivot-field-search:focus {\n border-color: var(--tbw-color-primary, #3b82f6);\n }\n\n .tbw-pivot-fields-list {\n display: flex;\n flex-wrap: wrap;\n gap: var(--tbw-spacing-sm, 0.375rem);\n }\n\n /* ===== Options Panel ===== */\n\n .tbw-pivot-options {\n display: flex;\n flex-direction: column;\n gap: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n }\n\n .tbw-pivot-checkbox {\n display: flex;\n align-items: center;\n gap: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n cursor: pointer;\n }\n\n .tbw-pivot-checkbox input {\n width: var(--tbw-icon-size-sm, 0.875rem);\n height: var(--tbw-icon-size-sm, 0.875rem);\n cursor: pointer;\n }\n\n /* ===== Expand/Collapse Animations ===== */\n\n /* Slide animation for rows appearing */\n .pivot-group-row.tbw-pivot-slide-in,\n .pivot-leaf-row.tbw-pivot-slide-in {\n animation: tbw-pivot-slide-in var(--tbw-animation-duration, 200ms) var(--tbw-animation-easing, ease-out) forwards;\n }\n\n @keyframes tbw-pivot-slide-in {\n from {\n opacity: 0;\n transform: translateX(-8px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n }\n\n /* Fade animation for rows appearing */\n .pivot-group-row.tbw-pivot-fade-in,\n .pivot-leaf-row.tbw-pivot-fade-in {\n animation: tbw-pivot-fade-in var(--tbw-animation-duration, 200ms) var(--tbw-animation-easing, ease-out) forwards;\n }\n\n @keyframes tbw-pivot-fade-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n }\n} /* End @layer tbw-plugins */\n","/**\n * Pivot Plugin (Class-based)\n *\n * Provides pivot table functionality for tbw-grid.\n * Transforms flat data into grouped, aggregated pivot views.\n * Includes a tool panel for interactive pivot configuration.\n */\n\nimport { announce, getA11yMessage } from '../../core/internal/aria';\nimport {\n BaseGridPlugin,\n HeaderClickEvent,\n type PluginDependency,\n type PluginManifest,\n type PluginQuery,\n} from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport type { ToolPanelDefinition } from '../shell/types';\nimport {\n buildPivot,\n flattenPivotRows,\n getAllGroupKeys,\n getColumnTotals,\n resolveDefaultExpanded,\n sortPivotMulti,\n type PivotDataRow,\n} from './pivot-engine';\nimport { createValueKeys, validatePivotConfig } from './pivot-model';\nimport { renderPivotPanel, type FieldInfo, type PanelCallbacks } from './pivot-panel';\nimport { renderPivotGrandTotalRow, renderPivotGroupRow, renderPivotLeafRow, type PivotRowData } from './pivot-rows';\nimport type {\n AggFunc,\n ExpandCollapseAnimation,\n PivotConfig,\n PivotConfigChangeDetail,\n PivotResult,\n PivotSortConfig,\n PivotSortDir,\n PivotStateChangeDetail,\n PivotToggleDetail,\n PivotValueField,\n} from './types';\n\n// Import CSS as inline string (Vite handles this)\nimport styles from './pivot.css?inline';\n\n/**\n * Pivot Table Plugin for tbw-grid\n *\n * Transforms flat data into a pivot table view with grouped rows, grouped columns,\n * and aggregated values. Includes an interactive tool panel for configuring\n * row groups, column groups, and value aggregations at runtime.\n *\n * ## Installation\n *\n * ```ts\n * import { PivotPlugin } from '@toolbox-web/grid/plugins/pivot';\n * ```\n *\n * ## Aggregation Functions\n *\n * `sum`, `avg`, `count`, `min`, `max`, `first`, `last`\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pivot-group-bg` | `var(--tbw-color-row-alt)` | Group row background |\n * | `--tbw-pivot-grand-total-bg` | `var(--tbw-color-header-bg)` | Grand total row |\n *\n * @example Basic Pivot Table\n * ```ts\n * import '@toolbox-web/grid';\n * import { PivotPlugin } from '@toolbox-web/grid/plugins/pivot';\n *\n * grid.gridConfig = {\n * columns: [...],\n * plugins: [\n * new PivotPlugin({\n * rowGroupFields: ['region', 'product'],\n * columnGroupFields: ['quarter'],\n * valueFields: [{ field: 'sales', aggFunc: 'sum', header: 'Total' }],\n * }),\n * ],\n * };\n * ```\n *\n * @example Programmatic-Only (No Tool Panel)\n * ```ts\n * new PivotPlugin({\n * showToolPanel: false,\n * rowGroupFields: ['category'],\n * valueFields: [{ field: 'amount', aggFunc: 'sum' }],\n * })\n * ```\n *\n * @see {@link PivotConfig} for all configuration options\n * @see {@link PivotValueField} for value field structure\n *\n * @internal Extends BaseGridPlugin\n * @since 0.1.1\n */\nexport class PivotPlugin extends BaseGridPlugin<PivotConfig> {\n /**\n * Plugin manifest declaring incompatibilities with other plugins.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n modifiesRowStructure: true,\n hookPriority: {\n // Run before MultiSortPlugin so pivot columns are intercepted first\n // when MultiSort is NOT present; non-pivot columns fall through.\n onHeaderClick: -10,\n // Run AFTER MultiSortPlugin's processRows so MultiSort sorts raw rows\n // (a no-op for pivot fields) before Pivot builds and sorts its own rows.\n processRows: 100,\n },\n incompatibleWith: [\n {\n name: 'groupingRows',\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 name: 'tree',\n reason:\n 'PivotPlugin replaces the entire row and column structure with aggregated pivot data. ' +\n 'Tree hierarchy cannot coexist with pivot aggregation.',\n },\n {\n name: 'serverSide',\n reason:\n 'PivotPlugin requires the full dataset to compute aggregations. ' +\n 'ServerSidePlugin lazy-loads rows in blocks, so pivot aggregation cannot be performed client-side.',\n },\n ],\n queries: [{ type: 'sort:get-sort-config', description: 'Returns the current pivot sort configuration' }],\n };\n\n /**\n * Plugin dependencies.\n *\n * - `multiSort` (optional): when present, Pivot queries its sort model to\n * apply sorting to pivot columns.\n * - `shell` (optional, warn): only when the pivot tool panel is enabled\n * (`showToolPanel: true`, the default) does Pivot need a shell host for\n * its panel. Soft dependency — recommends but does not require. The shell\n * auto-registers today; this only surfaces once auto-register is removed\n * at v3 (#370).\n */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'multiSort', required: false, reason: 'Queries sort model for pivot column sorting' },\n {\n name: 'shell',\n required: false,\n severity: 'warn',\n when: (cfg) => (cfg as PivotConfig).showToolPanel === true,\n reason: 'PivotPlugin renders its tool panel in a shell host when showToolPanel is enabled',\n },\n ];\n\n /** @internal */\n readonly name = 'pivot';\n /** @internal */\n override readonly styles = styles;\n\n /** Tool panel ID for shell integration */\n static readonly PANEL_ID = 'pivot';\n\n /** @internal */\n protected override get defaultConfig(): Partial<PivotConfig> {\n return {\n active: true,\n showTotals: true,\n showGrandTotal: true,\n showToolPanel: true,\n animation: 'slide',\n };\n }\n\n // #region Internal State\n private isActive = false;\n private hasInitialized = false;\n private pivotResult: PivotResult | null = null;\n private fieldHeaderMap: Map<string, string> = new Map();\n private expandedKeys: Set<string> = new Set();\n private defaultExpanded: boolean | number | string | string[] = true;\n /** Tracks whether user has manually interacted with expand/collapse */\n private userHasToggledExpand = false;\n private originalColumns: Array<{ field: string; header: string }> = [];\n private panelContainer: HTMLElement | null = null;\n private grandTotalFooter: HTMLElement | null = null;\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n /** Cached value formatters keyed by value field name */\n private valueFormatters: Map<number, (value: number) => string> = new Map();\n /** Column totals for percentage mode */\n private columnTotals: Record<string, number> = {};\n /** Current interactive sort state for pivot columns (managed by onHeaderClick) */\n private activeSortField: string | null = null;\n private activeSortDir: PivotSortDir | null = null;\n\n /**\n * Check if the plugin has valid pivot configuration (at least value fields).\n */\n private hasValidPivotConfig(): boolean {\n return (this.config.valueFields?.length ?? 0) > 0;\n }\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.isActive = false;\n this.hasInitialized = false;\n this.pivotResult = null;\n this.fieldHeaderMap.clear();\n this.originalColumns = [];\n this.panelContainer = null;\n this.cleanupGrandTotalFooter();\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.userHasToggledExpand = false;\n this.valueFormatters.clear();\n this.columnTotals = {};\n this.activeSortField = null;\n this.activeSortDir = null;\n this.config.sortRows = undefined;\n }\n\n // #endregion\n\n // #region Shell Integration\n\n /** @internal */\n override getToolPanel(): ToolPanelDefinition | undefined {\n // Allow users to disable the tool panel for programmatic-only pivot\n // Check userConfig first (works before attach), then merged config\n const showToolPanel = this.config?.showToolPanel ?? this.userConfig?.showToolPanel ?? true;\n if (showToolPanel === false) {\n return undefined;\n }\n\n return {\n id: PivotPlugin.PANEL_ID,\n title: this.t('pivot.panelTitle', 'Pivot'),\n icon: '⊞',\n tooltip: 'Configure pivot table',\n order: 90,\n render: (container) => this.renderPanel(container),\n };\n }\n\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processRows(rows: readonly unknown[]): PivotDataRow[] {\n // Auto-enable pivot if config.active is true and we have valid pivot fields\n if (!this.hasInitialized && this.config.active !== false && this.hasValidPivotConfig()) {\n this.hasInitialized = true;\n this.isActive = true;\n }\n\n if (!this.isActive) {\n return [...rows] as PivotDataRow[];\n }\n\n const errors = validatePivotConfig(this.config);\n if (errors.length > 0) {\n this.warn(`Config errors: ${errors.join(', ')}`);\n return [...rows] as PivotDataRow[];\n }\n\n this.buildFieldHeaderMap();\n this.buildValueFormatters();\n this.defaultExpanded = this.config.defaultExpanded ?? true;\n\n // Build pivot first so we have the rows structure\n this.pivotResult = buildPivot(rows as PivotDataRow[], this.config);\n\n // When MultiSort is active, apply its sort model to pivot rows.\n // This overrides config.sortRows when MultiSort has active sorts.\n const multiSortConfigs = this.getMultiSortConfigs();\n if (multiSortConfigs) {\n sortPivotMulti(this.pivotResult.rows, multiSortConfigs, this.config.valueFields ?? []);\n }\n\n // Initialize expanded state with defaults if first build AND user hasn't manually toggled\n // This prevents re-expanding when user collapses all groups\n if (this.expandedKeys.size === 0 && !this.userHasToggledExpand) {\n const allKeys = getAllGroupKeys(this.pivotResult.rows);\n this.expandedKeys = resolveDefaultExpanded(this.defaultExpanded, allKeys);\n }\n\n // Cache column totals for percentage mode\n if (this.config.valueDisplayMode && this.config.valueDisplayMode !== 'raw') {\n this.columnTotals = getColumnTotals(\n this.pivotResult.rows,\n this.pivotResult.columnKeys,\n this.config.valueFields ?? [],\n );\n }\n\n // Return flattened pivot rows respecting expanded state\n const indentWidth = this.config.indentWidth ?? 20;\n const flattenedDefExpanded = this.defaultExpanded === true || this.defaultExpanded === undefined;\n const flatRows: PivotDataRow[] = flattenPivotRows(\n this.pivotResult.rows,\n this.expandedKeys,\n flattenedDefExpanded,\n ).map((pr) => ({\n __pivotRowKey: pr.rowKey,\n __pivotLabel: pr.rowLabel,\n __pivotDepth: pr.depth,\n __pivotIsGroup: pr.isGroup,\n __pivotHasChildren: Boolean(pr.children?.length),\n __pivotExpanded: this.expandedKeys.has(pr.rowKey),\n __pivotRowCount: pr.rowCount ?? 0,\n __pivotIndent: pr.depth * indentWidth,\n __pivotTotal: pr.total,\n ...pr.values,\n }));\n\n // Track which rows are newly visible (for animation)\n this.keysToAnimate.clear();\n const currentVisibleKeys = new Set<string>();\n for (const row of flatRows) {\n const key = row.__pivotRowKey as string;\n currentVisibleKeys.add(key);\n // Animate non-root rows that weren't previously visible\n if (!this.previousVisibleKeys.has(key) && (row.__pivotDepth as number) > 0) {\n this.keysToAnimate.add(key);\n }\n }\n this.previousVisibleKeys = currentVisibleKeys;\n\n // Grand total: include in row model when configured, otherwise rendered as sticky footer\n if (this.config.grandTotalInRowModel && this.config.showGrandTotal && this.pivotResult) {\n flatRows.push({\n __pivotRowKey: '__grandTotal',\n __pivotLabel: 'Grand Total',\n __pivotIsGrandTotal: true,\n __pivotDepth: 0,\n __pivotTotal: this.pivotResult.grandTotal,\n ...this.pivotResult.totals,\n });\n }\n\n return flatRows;\n }\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.isActive || !this.pivotResult) {\n return [...columns];\n }\n\n const pivotColumns: ColumnConfig[] = [];\n\n // Row label column\n const rowGroupHeaders = (this.config.rowGroupFields ?? []).map((f) => this.fieldHeaderMap.get(f) ?? f).join(' / ');\n pivotColumns.push({\n field: '__pivotLabel',\n header: rowGroupHeaders || 'Group',\n width: 200,\n sortable: true,\n });\n\n // Value columns for each column key\n const valueFields = this.config.valueFields ?? [];\n for (const colKey of this.pivotResult.columnKeys) {\n const valueKeys = createValueKeys([colKey], valueFields);\n for (let valueIndex = 0; valueIndex < valueFields.length; valueIndex++) {\n const vf = valueFields[valueIndex];\n const valueKey = valueKeys[valueIndex];\n const valueHeader = vf.header || this.fieldHeaderMap.get(vf.field) || vf.field;\n const aggLabel = typeof vf.aggFunc === 'function' ? 'custom' : vf.aggFunc;\n const formatter = this.valueFormatters.get(valueIndex);\n pivotColumns.push({\n field: valueKey,\n header: `${colKey} - ${valueHeader} (${aggLabel})`,\n width: 120,\n type: 'number',\n sortable: true,\n ...(formatter ? { format: (v: unknown) => (v != null ? formatter(Number(v)) : '') } : {}),\n } as ColumnConfig);\n }\n }\n\n // Totals column\n if (this.config.showTotals) {\n pivotColumns.push({\n field: '__pivotTotal',\n header: 'Total',\n width: 100,\n type: 'number',\n sortable: true,\n });\n }\n\n return pivotColumns;\n }\n\n /** @internal */\n override renderRow(row: Record<string, unknown>, rowEl: HTMLElement, rowIndex: number): boolean {\n const pivotRow = row as PivotRowData;\n\n // Handle grand total row in row model (when grandTotalInRowModel is true)\n if (pivotRow.__pivotIsGrandTotal) {\n return renderPivotGrandTotalRow(pivotRow, rowEl, this.gridColumns, this.translate);\n }\n\n // Handle pivot group row (has children)\n if (pivotRow.__pivotRowKey && pivotRow.__pivotHasChildren) {\n return renderPivotGroupRow(pivotRow, rowEl, {\n columns: this.gridColumns,\n rowIndex,\n onToggle: (key) => this.toggle(key),\n setIcon: (el, iconKey) => this.setIcon(el, iconKey),\n });\n }\n\n // Handle pivot leaf row (no children but in pivot mode)\n if (pivotRow.__pivotRowKey !== undefined && this.isActive) {\n return renderPivotLeafRow(pivotRow, rowEl, this.gridColumns, rowIndex);\n }\n\n // Clean up any leftover pivot styling from pooled row elements\n this.cleanupPivotStyling(rowEl);\n\n return false;\n }\n\n /**\n * Remove pivot-specific classes, attributes, and inline styles from a row element.\n * Called when pivot mode is disabled to clean up reused DOM elements.\n * Clears innerHTML so the grid's default renderer can rebuild the row.\n */\n private cleanupPivotStyling(rowEl: HTMLElement): void {\n // Check if this row was previously rendered by pivot (has pivot classes)\n const wasPivotRow =\n rowEl.classList.contains('pivot-group-row') ||\n rowEl.classList.contains('pivot-leaf-row') ||\n rowEl.classList.contains('pivot-grand-total-row');\n\n if (wasPivotRow) {\n // Remove pivot row classes and restore the default grid row class\n rowEl.classList.remove('pivot-group-row', 'pivot-leaf-row', 'pivot-grand-total-row');\n rowEl.classList.add('data-grid-row');\n\n // Remove pivot-specific attributes\n rowEl.removeAttribute('data-pivot-depth');\n\n // Clear the row content so the default renderer can rebuild it\n rowEl.innerHTML = '';\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion on pivot group rows\n if (event.key !== ' ') return;\n if (!this.isActive) 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 pivot group rows with children\n if (!row?.__pivotIsGroup || !row.__pivotHasChildren) return;\n\n event.preventDefault();\n this.toggle(row.__pivotRowKey as string);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /** @internal */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'sort:get-sort-config') {\n // When MultiSort is driving sort state, return its translated config\n const multiConfigs = this.getMultiSortConfigs();\n if (multiConfigs) return multiConfigs;\n return this.config.sortRows ?? null;\n }\n return undefined;\n }\n\n /** @internal */\n override onHeaderClick(event: HeaderClickEvent): boolean {\n if (!this.isActive) return false;\n\n const field = event.field;\n\n // Only handle clicks on pivot-generated columns\n if (!this.isPivotField(field)) return false;\n\n // When MultiSort is present, let it handle the click (supports shift-click\n // for multi-column sort). PivotPlugin reads MultiSort's sort model in processRows.\n if (this.isMultiSortActive()) return false;\n\n // Map the clicked column to a PivotSortConfig\n const sortConfig = this.mapFieldToSortConfig(field);\n if (!sortConfig) return false;\n\n // Toggle direction: none → asc → desc → none\n const currentDir = this.activeSortDir;\n const currentField = this.activeSortField;\n let newDir: PivotSortDir | null;\n\n if (currentField !== field) {\n // Different column — start ascending\n newDir = 'asc';\n } else if (currentDir === 'asc') {\n newDir = 'desc';\n } else {\n newDir = null; // Clear sort\n }\n\n this.activeSortField = newDir ? field : null;\n this.activeSortDir = newDir;\n\n // Apply to pivot config\n if (newDir) {\n this.config.sortRows = { ...sortConfig, direction: newDir };\n } else {\n this.config.sortRows = undefined;\n }\n\n this.emit<PivotConfigChangeDetail>('pivot-config-change', {\n property: 'sortRows',\n });\n\n this.refresh();\n\n // Announce for screen readers\n const gridEl = this.gridElement;\n if (gridEl) {\n const colHeader = event.column.header ?? field;\n if (newDir) {\n announce(\n gridEl,\n getA11yMessage(gridEl, 'sortApplied', colHeader, newDir === 'asc' ? 'ascending' : 'descending'),\n );\n } else {\n announce(gridEl, getA11yMessage(gridEl, 'sortCleared'));\n }\n }\n\n return true;\n }\n\n /** Check if a field belongs to a pivot-generated column. */\n private isPivotField(field: string): boolean {\n return field === '__pivotLabel' || field === '__pivotTotal' || field.includes('|');\n }\n\n /** Check whether the MultiSort plugin is loaded alongside Pivot. */\n private isMultiSortActive(): boolean {\n const results = this.grid?.query?.('sort:get-model', null);\n return Array.isArray(results) && results.length > 0;\n }\n\n /**\n * Read MultiSort's sort model and translate entries to PivotSortConfig[].\n * Returns null when MultiSort is not present or has no active sorts.\n */\n private getMultiSortConfigs(): PivotSortConfig[] | null {\n const results = this.grid?.query?.('sort:get-model', null);\n if (!results || results.length === 0) return null;\n\n const sortModel = results[0] as Array<{ field: string; direction: 'asc' | 'desc' }>;\n if (!Array.isArray(sortModel) || sortModel.length === 0) return null;\n\n const configs: PivotSortConfig[] = [];\n for (const entry of sortModel) {\n if (!this.isPivotField(entry.field)) continue;\n const base = this.mapFieldToSortConfig(entry.field);\n if (base) {\n configs.push({ ...base, direction: entry.direction });\n }\n }\n return configs.length > 0 ? configs : null;\n }\n\n /** Map a pivot column field to the appropriate PivotSortConfig (without direction). */\n private mapFieldToSortConfig(field: string): Omit<PivotSortConfig, 'direction'> | null {\n if (field === '__pivotLabel') {\n return { by: 'label' };\n }\n if (field === '__pivotTotal') {\n return { by: 'value' };\n }\n // Value columns use format: colKey|field[|aggFunc[|n]] (from createValueKeys).\n // Preserve the full value key so sorting targets the exact pivot column\n // the user clicked, rather than collapsing distinct columns like\n // \"Q1|sales\" and \"Q2|sales\" into the same \"sales\" config.\n if (field.includes('|')) {\n return { by: 'value', valueField: field };\n }\n return null;\n }\n\n /** @internal */\n override afterRender(): void {\n // Render grand total as a sticky pinned footer when pivot is active\n // Skip when grandTotalInRowModel is true (grand total is already in the row model)\n if (this.isActive && this.config.showGrandTotal && this.pivotResult && !this.config.grandTotalInRowModel) {\n this.renderGrandTotalFooter();\n } else {\n this.cleanupGrandTotalFooter();\n }\n\n // Update sort indicators on pivot header cells.\n // When MultiSort is active it manages all indicators (including unsorted state).\n if (this.isActive && !this.isMultiSortActive()) {\n this.updateSortIndicators();\n }\n\n // Apply animations to newly visible rows\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-pivot-fade-in' : 'tbw-pivot-slide-in';\n for (const rowEl of body.querySelectorAll('.pivot-group-row, .pivot-leaf-row')) {\n const key = (rowEl as HTMLElement).dataset.pivotKey;\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 * Update sort indicator icons on pivot header cells.\n * Core's indicator rendering reads `_sortState`, which doesn't reflect pivot sort.\n * We manage indicators directly, following the same pattern as MultiSortPlugin.\n */\n private updateSortIndicators(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Derive effective sort state: interactive state takes priority,\n // then fall back to programmatic config.sortRows.\n const effectiveField = this.activeSortField;\n const effectiveDir = this.activeSortDir;\n const programmatic = !effectiveField ? this.config.sortRows : null;\n\n const headerCells = gridEl.querySelectorAll('.header-row .cell[data-field]');\n for (const cell of headerCells) {\n const field = cell.getAttribute('data-field');\n if (!field || !this.isPivotField(field)) continue;\n\n let sortDir: 'asc' | 'desc' | null = null;\n\n if (effectiveField === field) {\n // Interactive sort is active on this column\n sortDir = effectiveDir;\n } else if (programmatic) {\n // Programmatic sortRows — match by sort type\n if (programmatic.by === 'label' && field === '__pivotLabel') {\n sortDir = programmatic.direction ?? 'asc';\n } else if (programmatic.by === 'value' && field === '__pivotTotal' && !programmatic.valueField) {\n sortDir = programmatic.direction ?? 'asc';\n } else if (programmatic.by === 'value' && programmatic.valueField && field === programmatic.valueField) {\n sortDir = programmatic.direction ?? 'asc';\n }\n }\n\n this.updateSortIndicator(cell, sortDir);\n }\n }\n\n /**\n * Render the grand total row as a sticky footer pinned to the bottom.\n */\n private renderGrandTotalFooter(): void {\n if (!this.pivotResult) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Find the scroll container to append the footer\n const container =\n gridEl.querySelector('.tbw-scroll-area') ??\n gridEl.querySelector('.tbw-grid-content') ??\n gridEl.querySelector('.tbw-grid-root');\n if (!container) return;\n\n // Create footer if it doesn't exist\n if (!this.grandTotalFooter) {\n this.grandTotalFooter = document.createElement('div');\n this.grandTotalFooter.className = 'pivot-grand-total-footer';\n container.appendChild(this.grandTotalFooter);\n }\n\n // Build the row data for grand total\n const grandTotalRow: PivotRowData = {\n __pivotRowKey: '__grandTotal',\n __pivotLabel: 'Grand Total',\n __pivotIsGrandTotal: true,\n __pivotTotal: this.pivotResult.grandTotal,\n ...this.pivotResult.totals,\n };\n\n // Render the grand total row into the footer\n renderPivotGrandTotalRow(grandTotalRow, this.grandTotalFooter, this.gridColumns, this.translate);\n // Footer is outside the grid's role=grid element — use presentation role\n this.grandTotalFooter.setAttribute('role', 'presentation');\n }\n\n /**\n * Remove the grand total footer element.\n */\n private cleanupGrandTotalFooter(): void {\n if (this.grandTotalFooter) {\n this.grandTotalFooter.remove();\n this.grandTotalFooter = null;\n }\n }\n\n // #endregion\n\n // #region Expand/Collapse API\n\n toggle(key: string): void {\n this.userHasToggledExpand = true;\n const wasExpanded = this.expandedKeys.has(key);\n if (wasExpanded) {\n this.expandedKeys.delete(key);\n } else {\n this.expandedKeys.add(key);\n }\n this.emitToggle(key, !wasExpanded);\n this.requestRender();\n }\n\n /**\n * Expand a specific pivot group row, revealing its children.\n *\n * @param key - The pivot row key (hierarchical path, e.g. `'Engineering'` or `'Engineering||Frontend'`)\n */\n expand(key: string): void {\n this.userHasToggledExpand = true;\n this.expandedKeys.add(key);\n this.emitToggle(key, true);\n this.requestRender();\n }\n\n /**\n * Collapse a specific pivot group row, hiding its children.\n *\n * @param key - The pivot row key to collapse\n */\n collapse(key: string): void {\n this.userHasToggledExpand = true;\n this.expandedKeys.delete(key);\n this.emitToggle(key, false);\n this.requestRender();\n }\n\n /**\n * Expand all pivot group rows, revealing the full hierarchy.\n */\n expandAll(): void {\n this.userHasToggledExpand = true;\n this.expandAllKeys();\n this.requestRender();\n }\n\n /**\n * Collapse all pivot group rows, showing only top-level groups.\n */\n collapseAll(): void {\n this.userHasToggledExpand = true;\n this.expandedKeys.clear();\n this.requestRender();\n }\n\n /**\n * Add all group keys from the current pivot result to expandedKeys.\n */\n private expandAllKeys(): void {\n if (!this.pivotResult) return;\n const allKeys = getAllGroupKeys(this.pivotResult.rows);\n for (const key of allKeys) {\n this.expandedKeys.add(key);\n }\n }\n\n /**\n * Check whether a specific pivot group row is currently expanded.\n *\n * @param key - The pivot row key to check\n * @returns `true` if the group is expanded\n */\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n /**\n * Get all currently expanded group keys.\n *\n * @returns Array of expanded pivot row keys\n */\n getExpandedGroups(): string[] {\n return [...this.expandedKeys];\n }\n\n // #endregion\n\n // #region Public API\n\n /**\n * Enable pivot mode.\n *\n * Captures the original column set (if not already captured) and activates\n * the pivot transformation. The grid will re-render with pivot columns and rows.\n *\n * @example\n * ```ts\n * const pivot = grid.getPluginByName('pivot');\n * pivot.enablePivot();\n * ```\n */\n enablePivot(): void {\n if (this.originalColumns.length === 0) {\n this.captureOriginalColumns();\n }\n this.isActive = true;\n this.emit<PivotStateChangeDetail>('pivot-state-change', { active: true });\n this.requestRender();\n }\n\n /**\n * Disable pivot mode and restore the original grid columns.\n *\n * The grid reverts to its normal tabular layout with the original column definitions.\n */\n disablePivot(): void {\n this.isActive = false;\n this.pivotResult = null;\n this.emit<PivotStateChangeDetail>('pivot-state-change', { active: false });\n this.requestRender();\n }\n\n /**\n * Check whether pivot mode is currently active.\n *\n * @returns `true` if the grid is in pivot mode\n */\n isPivotActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Get the current pivot computation result.\n *\n * Returns the full {@link PivotResult} with rows, column keys, totals,\n * and grand total. Returns `null` if pivot is inactive or not yet computed.\n *\n * @returns The computed pivot result, or `null`\n */\n getPivotResult(): PivotResult | null {\n return this.pivotResult;\n }\n\n /**\n * Set the fields used for row grouping (vertical axis).\n *\n * Triggers a re-render with the new pivot structure.\n *\n * @param fields - Array of field names to group rows by\n *\n * @example\n * ```ts\n * const pivot = grid.getPluginByName('pivot');\n * pivot.setRowGroupFields(['region', 'department']);\n * ```\n */\n setRowGroupFields(fields: string[]): void {\n this.config.rowGroupFields = fields;\n this.emitConfigChange('rowGroupFields');\n this.refresh();\n }\n\n /**\n * Set the fields whose unique values become column headers (horizontal axis).\n *\n * Triggers a re-render with the new pivot column structure.\n *\n * @param fields - Array of field names for column grouping\n *\n * @example\n * ```ts\n * const pivot = grid.getPluginByName('pivot');\n * pivot.setColumnGroupFields(['quarter']);\n * ```\n */\n setColumnGroupFields(fields: string[]): void {\n this.config.columnGroupFields = fields;\n this.emitConfigChange('columnGroupFields');\n this.refresh();\n }\n\n /**\n * Set the value fields and their aggregation functions.\n *\n * Each value field defines which data field to aggregate and how.\n * Triggers a re-render with the new aggregation.\n *\n * @param fields - Array of {@link PivotValueField} definitions\n *\n * @example\n * ```ts\n * const pivot = grid.getPluginByName('pivot');\n * pivot.setValueFields([\n * { field: 'revenue', aggFunc: 'sum', header: 'Total Revenue' },\n * { field: 'orders', aggFunc: 'count', header: '# Orders' },\n * ]);\n * ```\n */\n setValueFields(fields: PivotValueField[]): void {\n this.config.valueFields = fields;\n this.emitConfigChange('valueFields');\n this.refresh();\n }\n\n /**\n * Force re-computation of the pivot result.\n *\n * Clears the cached pivot result and triggers a full re-render.\n * Call this after changing the underlying row data.\n */\n refresh(): void {\n this.pivotResult = null;\n this.requestRender();\n }\n\n // #endregion\n\n // #region Tool Panel API\n\n /**\n * Resolve the shell plugin (#370). The pivot tool panel lives in the shell,\n * so panel open/close/toggle is delegated to the ShellPlugin instance rather\n * than core `grid.*` shell delegates (removed in v3).\n */\n #shell() {\n return this.grid.getPluginByName('shell');\n }\n\n /**\n * Show the pivot tool panel.\n * Opens the tool panel and ensures this section is expanded.\n */\n showPanel(): void {\n const shell = this.#shell();\n shell?.openToolPanel();\n // Ensure our section is expanded\n if (!shell?.expandedToolPanelSections.includes(PivotPlugin.PANEL_ID)) {\n shell?.toggleToolPanelSection(PivotPlugin.PANEL_ID);\n }\n }\n\n /**\n * Hide the tool panel.\n */\n hidePanel(): void {\n this.#shell()?.closeToolPanel();\n }\n\n /**\n * Toggle the pivot tool panel section.\n */\n togglePanel(): void {\n const shell = this.#shell();\n // If tool panel is closed, open it first\n if (!shell?.isToolPanelOpen) {\n shell?.openToolPanel();\n }\n shell?.toggleToolPanelSection(PivotPlugin.PANEL_ID);\n }\n\n /**\n * Check if the pivot panel section is currently expanded.\n */\n isPanelVisible(): boolean {\n const shell = this.#shell();\n return !!shell?.isToolPanelOpen && shell.expandedToolPanelSections.includes(PivotPlugin.PANEL_ID);\n }\n\n // #endregion\n\n // #region Private Helpers\n\n private get gridColumns(): ColumnConfig[] {\n return (this.grid.columns ?? []) as ColumnConfig[];\n }\n\n /**\n * Refresh pivot and update tool panel if active.\n */\n private refreshIfActive(): void {\n if (this.isActive) this.refresh();\n this.refreshPanel();\n }\n\n private buildFieldHeaderMap(): void {\n const availableFields = this.getAvailableFields();\n this.fieldHeaderMap.clear();\n for (const field of availableFields) {\n this.fieldHeaderMap.set(field.field, field.header);\n }\n }\n\n private getAvailableFields(): FieldInfo[] {\n if (this.originalColumns.length > 0) {\n return this.originalColumns;\n }\n return this.captureOriginalColumns();\n }\n\n private captureOriginalColumns(): FieldInfo[] {\n try {\n const columns = this.grid.getAllColumns?.() ?? this.grid.columns ?? [];\n this.originalColumns = columns\n .filter((col: { field: string }) => !col.field.startsWith('__pivot'))\n .map((col: { field: string; header?: string }) => ({\n field: col.field,\n header: col.header ?? col.field,\n }));\n return this.originalColumns;\n } catch {\n return [];\n }\n }\n\n private renderPanel(container: HTMLElement): (() => void) | void {\n this.panelContainer = container;\n\n if (this.originalColumns.length === 0) {\n this.captureOriginalColumns();\n }\n\n const callbacks: PanelCallbacks = {\n onTogglePivot: (enabled) => {\n if (enabled) {\n this.enablePivot();\n } else {\n this.disablePivot();\n }\n this.refreshPanel();\n },\n onAddFieldToZone: (field, zone) => {\n this.addFieldToZone(field, zone);\n this.emitConfigChange(zone === 'rowGroups' ? 'rowGroupFields' : 'columnGroupFields', field, zone);\n },\n onRemoveFieldFromZone: (field, zone) => {\n this.removeFieldFromZone(field, zone);\n this.emitConfigChange(zone === 'rowGroups' ? 'rowGroupFields' : 'columnGroupFields', field, zone);\n },\n onReorderFieldInZone: (field, zone, newIndex) => {\n this.reorderFieldInZone(field, zone, newIndex);\n this.emitConfigChange(zone === 'rowGroups' ? 'rowGroupFields' : 'columnGroupFields', field, zone);\n },\n onMoveFieldBetweenZones: (field, fromZone, toZone) => {\n this.moveFieldBetweenZones(field, fromZone, toZone);\n this.emitConfigChange(toZone === 'rowGroups' ? 'rowGroupFields' : 'columnGroupFields', field, toZone);\n },\n onAddValueField: (field, aggFunc) => {\n this.addValueField(field, aggFunc);\n this.emitConfigChange('valueFields', field, 'values');\n },\n onRemoveValueField: (field) => {\n this.removeValueField(field);\n this.emitConfigChange('valueFields', field, 'values');\n },\n onUpdateValueAggFunc: (field, aggFunc) => {\n this.updateValueAggFunc(field, aggFunc);\n this.emitConfigChange('valueFields', field, 'values');\n },\n onOptionChange: (option, value) => {\n this.config[option] = value;\n this.emitConfigChange(option);\n if (this.isActive) this.refresh();\n },\n getAvailableFields: () => this.getAvailableFields(),\n };\n\n return renderPivotPanel(container, this.config, this.isActive, callbacks, this.translate);\n }\n\n private refreshPanel(): void {\n if (!this.panelContainer) return;\n this.panelContainer.innerHTML = '';\n this.renderPanel(this.panelContainer);\n }\n\n private addFieldToZone(field: string, zoneType: 'rowGroups' | 'columnGroups'): void {\n if (zoneType === 'rowGroups') {\n const current = this.config.rowGroupFields ?? [];\n if (!current.includes(field)) {\n this.config.rowGroupFields = [...current, field];\n }\n } else {\n const current = this.config.columnGroupFields ?? [];\n if (!current.includes(field)) {\n this.config.columnGroupFields = [...current, field];\n }\n }\n\n this.removeFromOtherZones(field, zoneType);\n this.refreshIfActive();\n }\n\n private removeFieldFromZone(field: string, zoneType: 'rowGroups' | 'columnGroups'): void {\n if (zoneType === 'rowGroups') {\n this.config.rowGroupFields = (this.config.rowGroupFields ?? []).filter((f) => f !== field);\n } else {\n this.config.columnGroupFields = (this.config.columnGroupFields ?? []).filter((f) => f !== field);\n }\n\n this.refreshIfActive();\n }\n\n private reorderFieldInZone(field: string, zoneType: 'rowGroups' | 'columnGroups', newIndex: number): void {\n const fields =\n zoneType === 'rowGroups' ? [...(this.config.rowGroupFields ?? [])] : [...(this.config.columnGroupFields ?? [])];\n const oldIndex = fields.indexOf(field);\n if (oldIndex === -1 || oldIndex === newIndex) return;\n fields.splice(oldIndex, 1);\n fields.splice(newIndex > oldIndex ? newIndex - 1 : newIndex, 0, field);\n if (zoneType === 'rowGroups') {\n this.config.rowGroupFields = fields;\n } else {\n this.config.columnGroupFields = fields;\n }\n this.refreshIfActive();\n }\n\n private moveFieldBetweenZones(\n field: string,\n fromZone: 'rowGroups' | 'columnGroups',\n toZone: 'rowGroups' | 'columnGroups',\n ): void {\n // Remove from source\n if (fromZone === 'rowGroups') {\n this.config.rowGroupFields = (this.config.rowGroupFields ?? []).filter((f) => f !== field);\n } else {\n this.config.columnGroupFields = (this.config.columnGroupFields ?? []).filter((f) => f !== field);\n }\n // Add to target\n if (toZone === 'rowGroups') {\n this.config.rowGroupFields = [...(this.config.rowGroupFields ?? []), field];\n } else {\n this.config.columnGroupFields = [...(this.config.columnGroupFields ?? []), field];\n }\n this.refreshIfActive();\n }\n\n private removeFromOtherZones(field: string, targetZone: 'rowGroups' | 'columnGroups' | 'values'): void {\n if (targetZone !== 'rowGroups') {\n this.config.rowGroupFields = (this.config.rowGroupFields ?? []).filter((f) => f !== field);\n }\n if (targetZone !== 'columnGroups') {\n this.config.columnGroupFields = (this.config.columnGroupFields ?? []).filter((f) => f !== field);\n }\n if (targetZone !== 'values') {\n this.config.valueFields = (this.config.valueFields ?? []).filter((v) => v.field !== field);\n }\n }\n\n private addValueField(field: string, aggFunc: AggFunc): void {\n const current = this.config.valueFields ?? [];\n if (!current.some((v) => v.field === field)) {\n this.config.valueFields = [...current, { field, aggFunc }];\n }\n\n this.removeFromOtherZones(field, 'values');\n this.refreshIfActive();\n }\n\n private removeValueField(field: string): void {\n this.config.valueFields = (this.config.valueFields ?? []).filter((v) => v.field !== field);\n this.refreshIfActive();\n }\n\n private updateValueAggFunc(field: string, aggFunc: AggFunc): void {\n const valueFields = this.config.valueFields ?? [];\n const fieldIndex = valueFields.findIndex((v) => v.field === field);\n if (fieldIndex >= 0) {\n valueFields[fieldIndex] = { ...valueFields[fieldIndex], aggFunc };\n this.config.valueFields = [...valueFields];\n }\n if (this.isActive) this.refresh();\n }\n\n // #region Event Helpers\n\n private emitToggle(key: string, expanded: boolean): void {\n const flatRow = this.rows.find((r) => (r as PivotDataRow).__pivotRowKey === key) as PivotDataRow | undefined;\n this.emit<PivotToggleDetail>('pivot-toggle', {\n key,\n expanded,\n label: (flatRow?.__pivotLabel as string) ?? key,\n depth: (flatRow?.__pivotDepth as number) ?? 0,\n });\n }\n\n private emitConfigChange(property: string, field?: string, zone?: 'rowGroups' | 'columnGroups' | 'values'): void {\n this.emit<PivotConfigChangeDetail>('pivot-config-change', { property, field, zone });\n }\n\n // #endregion\n\n // #region Value Formatting\n\n /**\n * Build value formatters from PivotValueField.format or original column format.\n * Keyed by `valueFields` index — the same field may appear multiple times with\n * different aggregators and formatters.\n */\n private buildValueFormatters(): void {\n this.valueFormatters.clear();\n const valueFields = this.config.valueFields ?? [];\n for (let index = 0; index < valueFields.length; index++) {\n const vf = valueFields[index];\n if (vf.format) {\n this.valueFormatters.set(index, vf.format);\n } else {\n // Check if the original column had a format function\n const origCol = this.originalColumns.find((c) => c.field === vf.field);\n if (origCol) {\n const allCols = this.grid.getAllColumns?.() ?? this.grid.columns ?? [];\n const col = allCols.find((c: { field: string }) => c.field === vf.field) as\n { field: string; format?: (value: unknown, row: unknown) => string } | undefined;\n if (col?.format) {\n const fmt = col.format;\n this.valueFormatters.set(index, (v: number) => fmt(v, {}));\n }\n }\n }\n }\n }\n\n // #endregion\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["styles","BaseGridPlugin","DEFAULT_A11Y_MESSAGES","announce","gridEl","message","effectiveConfig","a11y","announcements","el","querySelector","textContent","requestAnimationFrame","getA11yMessage","key","args","customFn","messages","tryFormat","format","value","row","onError","formatted","String","error","createValueKeys","columnValues","valueFields","fieldCounts","Map","valueField","set","field","get","seenFieldAggregates","Set","map","index","baseKey","join","createValueKey","aggregateKey","aggFunc","pairKey","has","add","buildPivot","rows","config","rowGroupFields","columnGroupFields","columnKeys","columnFields","sortDir","length","keys","f","sorted","sort","reverse","getUniqueColumnKeys","sortColumns","pivotRows","buildHierarchicalPivotRows","sortRows","sortPivotRows","totals","allValueKeys","flatMap","colKey","sumRows","isGroup","children","valueKey","values","calculateTotals","grandTotal","Object","reduce","a","b","aggregateValueField","numbers","i","raw","Number","isNaN","push","result","Infinity","count","aggregateSharedBuiltIns","valueKeys","sum","min","max","first","last","aggregateValueFields","fieldHandled","builtInCount","j","candidate","depth","parentKey","aggregateValues","total","calculateRowTotal","rowKey","rowLabel","rowCount","currentField","remainingFields","slice","hasChildren","grouped","groups","existing","groupByField","groupValue","groupRows","rowsByColKey","matchingRows","val","getAllGroupKeys","collectKeys","child","sortConfig","dir","direction","by","localeCompare","sortPivotMulti","configs","knownValueFields","vf","accessors","cfg","suffix","found","endsWith","getSortValue","cmp","accessor","GridClasses","supportsPopover","HTMLElement","prototype","showPopover","createDragAlternativeMenu","id","className","root","opened","disposed","abort","AbortController","close","hidePopover","hidden","document","addEventListener","e","composedPath","includes","capture","signal","element","open","anchor","label","actions","createElement","setAttribute","next","relatedTarget","Node","contains","build","host","closest","body","parentNode","appendChild","replaceChildren","action","btn","type","disabled","run","getBoundingClientRect","box","left","Math","window","innerWidth","width","below","bottom","top","height","innerHeight","style","round","place","focus","dispose","remove","chipMenu","AGG_FUNCS","renderPivotPanel","container","isActive","callbacks","t","_key","fallback","controller","ctx","wrapper","createSection","panel","createCheckbox","checked","onTogglePivot","showTotals","onOptionChange","showGrandTotal","createOptionsPanel","createFieldZone","zone","currentValues","placeholder","createValueChip","preventDefault","classList","dataTransfer","getData","onAddValueField","createValuesZone","allFields","getAvailableFields","usedFields","v","availableFields","filter","empty","fieldsContainer","search","query","toLowerCase","chip","querySelectorAll","display","header","draggable","title","markAsMenuTrigger","setData","openChipMenu","availableChipActions","createAvailableFieldsZone","target","name","tabIndex","onAddFieldToZone","contentFactory","section","content","zoneType","currentFields","createFieldChip","sourceZone","chips","dropIndex","rect","clientY","onReorderFieldInZone","onMoveFieldBetweenZones","find","removeBtn","innerHTML","stopPropagation","onRemoveFieldFromZone","fields","indexOf","otherZone","fieldChipActions","fieldInfo","labelWrapper","isCustomAgg","aggSelect","option","selected","toUpperCase","onUpdateValueAggFunc","onRemoveValueField","onChange","input","span","renderPivotGrandTotalRow","rowEl","columns","forEach","col","colIdx","cell","PivotPlugin","static","modifiesRowStructure","hookPriority","onHeaderClick","processRows","incompatibleWith","reason","queries","description","required","severity","when","showToolPanel","styles","defaultConfig","active","animation","hasInitialized","pivotResult","fieldHeaderMap","expandedKeys","defaultExpanded","userHasToggledExpand","originalColumns","panelContainer","grandTotalFooter","previousVisibleKeys","keysToAnimate","valueFormatters","formatErrorFields","columnTotals","activeSortField","activeSortDir","hasValidPivotConfig","this","animationStyle","isAnimationEnabled","detach","clear","cleanupGrandTotalFooter","getToolPanel","userConfig","PANEL_ID","icon","tooltip","order","render","renderPanel","errors","validatePivotConfig","warn","buildFieldHeaderMap","buildValueFormatters","multiSortConfigs","getMultiSortConfigs","size","allKeys","allGroupKeys","Array","isArray","resolveDefaultExpanded","valueDisplayMode","sumLeaves","getColumnTotals","indentWidth","flattenedDefExpanded","flatRows","flatten","isExpanded","flattenPivotRows","pr","__pivotRowKey","__pivotLabel","__pivotDepth","__pivotIsGroup","__pivotHasChildren","Boolean","__pivotExpanded","__pivotRowCount","__pivotIndent","__pivotTotal","currentVisibleKeys","grandTotalInRowModel","__pivotIsGrandTotal","processColumns","pivotColumns","rowGroupHeaders","sortable","valueIndex","valueHeader","aggLabel","formatter","renderRow","rowIndex","pivotRow","gridColumns","translate","indent","paddingLeft","setIcon","onToggle","renderPivotGroupRow","toggle","iconKey","renderPivotLeafRow","cleanupPivotStyling","removeAttribute","onKeyDown","event","path","tag","tagName","isContentEditable","isFormFieldKeyTarget","focusRow","grid","_focusRow","requestRenderWithFocus","handleQuery","multiConfigs","isPivotField","isMultiSortActive","mapFieldToSortConfig","currentDir","newDir","emit","property","refresh","gridElement","colHeader","column","results","sortModel","entry","base","afterRender","renderGrandTotalFooter","updateSortIndicators","animClass","dataset","pivotKey","once","effectiveField","effectiveDir","programmatic","headerCells","getAttribute","updateSortIndicator","wasExpanded","delete","emitToggle","requestRender","expand","collapse","expandAll","expandAllKeys","collapseAll","getExpandedGroups","enablePivot","captureOriginalColumns","disablePivot","isPivotActive","getPivotResult","setRowGroupFields","emitConfigChange","setColumnGroupFields","setValueFields","shell","getPluginByName","showPanel","openToolPanel","expandedToolPanelSections","toggleToolPanelSection","hidePanel","closeToolPanel","togglePanel","isToolPanelOpen","isPanelVisible","refreshIfActive","refreshPanel","getAllColumns","startsWith","enabled","addFieldToZone","removeFieldFromZone","newIndex","reorderFieldInZone","fromZone","toZone","moveFieldBetweenZones","addValueField","removeValueField","updateValueAggFunc","current","removeFromOtherZones","oldIndex","splice","targetZone","some","fieldIndex","findIndex","expanded","flatRow","r","c","fmt"],"sources":["../../../../../../libs/grid/src/lib/core/internal/aria.ts","../../../../../../libs/grid/src/lib/core/internal/diagnostics.ts","../../../../../../libs/grid/src/lib/plugins/shared/form-field-target.ts","../../../../../../libs/grid/src/lib/plugins/shared/format-value.ts","../../../../../../libs/grid/src/lib/plugins/pivot/pivot-model.ts","../../../../../../libs/grid/src/lib/plugins/pivot/pivot-engine.ts","../../../../../../libs/grid/src/lib/core/constants.ts","../../../../../../libs/grid/src/lib/plugins/shared/drag-alternative-menu.ts","../../../../../../libs/grid/src/lib/plugins/pivot/pivot-panel.ts","../../../../../../libs/grid/src/lib/plugins/pivot/pivot-rows.ts","../../../../../../libs/grid/src/lib/plugins/pivot/pivot.css?inline","../../../../../../libs/grid/src/lib/plugins/pivot/pivot-plugin.ts"],"sourcesContent":["/**\n * ARIA Accessibility Helpers\n *\n * Pure functions for managing ARIA attributes on grid elements.\n * Implements caching to avoid redundant DOM writes during scroll.\n *\n * @module internal/aria\n */\n\nimport { DEFAULT_A11Y_MESSAGES } from '../defaults';\nimport type { A11yMessages, GridConfig } from '../types';\n\n// #region Types\n\n/**\n * State for caching ARIA attributes to avoid redundant DOM writes.\n */\nexport interface AriaState {\n /** Last set row count */\n rowCount: number;\n /** Last set column count */\n colCount: number;\n /** Last set aria-label */\n ariaLabel: string | undefined;\n /** Last set aria-labelledby */\n ariaLabelledBy: string | undefined;\n /** Last set aria-describedby */\n ariaDescribedBy: string | undefined;\n /** Last set aria-roledescription */\n ariaRoleDescription: string | undefined;\n /** Last source row count announced via `dataLoaded`; used to suppress duplicate announcements */\n lastAnnouncedSourceCount: number;\n}\n\n/**\n * Create initial ARIA state.\n */\nexport function createAriaState(): AriaState {\n return {\n rowCount: -1,\n colCount: -1,\n ariaLabel: undefined,\n ariaLabelledBy: undefined,\n ariaDescribedBy: undefined,\n ariaRoleDescription: undefined,\n lastAnnouncedSourceCount: -1,\n };\n}\n\n// #endregion\n\n// #region Count Updates\n\n/**\n * Update ARIA row and column counts on grid elements.\n * Uses caching to avoid redundant DOM writes on every scroll frame.\n *\n * @param state - ARIA state for caching\n * @param rowsBodyEl - Element to set aria-rowcount/aria-colcount on\n * @param bodyEl - Element to set role=\"rowgroup\" on\n * @param rowCount - Current row count\n * @param colCount - Current column count\n * @returns true if anything was updated\n */\nexport function updateAriaCounts(\n state: AriaState,\n rowsBodyEl: HTMLElement | null,\n bodyEl: HTMLElement | null,\n rowCount: number,\n colCount: number,\n): boolean {\n // Skip if nothing changed (hot path optimization for scroll)\n if (rowCount === state.rowCount && colCount === state.colCount) {\n return false;\n }\n\n const prevRowCount = state.rowCount;\n state.rowCount = rowCount;\n state.colCount = colCount;\n\n // Update ARIA counts on inner grid element\n if (rowsBodyEl) {\n rowsBodyEl.setAttribute('aria-rowcount', String(rowCount));\n rowsBodyEl.setAttribute('aria-colcount', String(colCount));\n }\n\n // Set role=\"rowgroup\" on .rows only when there are rows (ARIA compliance)\n if (rowCount !== prevRowCount && bodyEl) {\n if (rowCount > 0) {\n bodyEl.setAttribute('role', 'rowgroup');\n } else {\n bodyEl.removeAttribute('role');\n }\n }\n\n return true;\n}\n\n// #endregion\n\n// #region Label Updates\n\n/**\n * Determine the effective aria-label for the grid.\n * Priority: explicit config > shell title > nothing\n *\n * The shell title is read from `config.shell.header.title`. The shell plugin\n * folds any light-DOM `<tbw-grid-header title>` into that field via its\n * `processConfig` hook (extraction #370), so core needs no plugin state here.\n *\n * @param config - Grid configuration (the merged effective config)\n * @returns The aria-label to use, or undefined\n */\nexport function getEffectiveAriaLabel<T>(config: GridConfig<T> | undefined): string | undefined {\n const explicitLabel = config?.gridAriaLabel;\n if (explicitLabel) return explicitLabel;\n\n return config?.shell?.header?.title ?? undefined;\n}\n\n/**\n * Update ARIA label and describedby attributes on the grid container.\n * Uses caching to avoid redundant DOM writes.\n *\n * @param state - ARIA state for caching\n * @param rowsBodyEl - Element to set aria-label/aria-describedby on\n * @param config - Grid configuration (the merged effective config)\n * @returns true if anything was updated\n */\nexport function updateAriaLabels<T>(\n state: AriaState,\n rowsBodyEl: HTMLElement | null,\n config: GridConfig<T> | undefined,\n): boolean {\n if (!rowsBodyEl) return false;\n\n let updated = false;\n\n // aria-labelledby wins per WAI-ARIA accessible-name precedence — when set,\n // `aria-label` is suppressed even if explicitly configured to avoid two\n // competing names being computed by AT.\n const ariaLabelledBy = config?.gridAriaLabelledBy;\n const effectiveLabel = ariaLabelledBy ? undefined : getEffectiveAriaLabel(config);\n\n // Update aria-labelledby only if changed\n if (ariaLabelledBy !== state.ariaLabelledBy) {\n state.ariaLabelledBy = ariaLabelledBy;\n if (ariaLabelledBy) {\n rowsBodyEl.setAttribute('aria-labelledby', ariaLabelledBy);\n } else {\n rowsBodyEl.removeAttribute('aria-labelledby');\n }\n updated = true;\n }\n\n // Update aria-label only if changed\n if (effectiveLabel !== state.ariaLabel) {\n state.ariaLabel = effectiveLabel;\n if (effectiveLabel) {\n rowsBodyEl.setAttribute('aria-label', effectiveLabel);\n } else {\n rowsBodyEl.removeAttribute('aria-label');\n }\n updated = true;\n }\n\n // Update aria-describedby only if changed\n const ariaDescribedBy = config?.gridAriaDescribedBy;\n if (ariaDescribedBy !== state.ariaDescribedBy) {\n state.ariaDescribedBy = ariaDescribedBy;\n if (ariaDescribedBy) {\n rowsBodyEl.setAttribute('aria-describedby', ariaDescribedBy);\n } else {\n rowsBodyEl.removeAttribute('aria-describedby');\n }\n updated = true;\n }\n\n // Update aria-roledescription only if changed. Apps can override the\n // announced role name (e.g. localized \"Tabell\" instead of generic \"grid\").\n const ariaRoleDescription = config?.gridAriaRoleDescription;\n if (ariaRoleDescription !== state.ariaRoleDescription) {\n state.ariaRoleDescription = ariaRoleDescription;\n if (ariaRoleDescription) {\n rowsBodyEl.setAttribute('aria-roledescription', ariaRoleDescription);\n } else {\n rowsBodyEl.removeAttribute('aria-roledescription');\n }\n updated = true;\n }\n\n return updated;\n}\n\n// #endregion\n\n// #region Live Announcements\n\n/**\n * Announce a message to screen readers via the grid's aria-live region.\n * Clears then sets text to ensure repeated messages are re-announced.\n *\n * Respects `effectiveConfig.a11y.announcements` — if set to `false`,\n * the announcement is silently skipped.\n *\n * @param gridEl - The grid host element (or any ancestor containing `.tbw-sr-only`)\n * @param message - The message to announce\n */\nexport function announce(gridEl: HTMLElement, message: string): void {\n // Check if announcements are disabled via config.\n // gridEl is always a DataGridElement at runtime — use property access safely.\n if (!gridEl) return;\n const config =\n 'effectiveConfig' in gridEl\n ? (gridEl as HTMLElement & { effectiveConfig?: GridConfig }).effectiveConfig\n : undefined;\n if (config?.a11y?.announcements === false) return;\n\n const el = gridEl.querySelector?.('.tbw-sr-only');\n if (!el) return;\n // Clear first so identical consecutive messages are still announced\n el.textContent = '';\n // `requestAnimationFrame` can be undefined if a debounced announcement fires\n // after the DOM/host environment is torn down (notably in unit tests). Fall\n // back to a direct assignment so `announce` never throws out of band.\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(() => {\n el.textContent = message;\n });\n } else {\n el.textContent = message;\n }\n}\n\n/**\n * Get an announcement message, preferring custom overrides from config.\n * Falls back to default English messages when no override is provided.\n *\n * @param gridEl - The grid host element\n * @param key - The message key from A11yMessages\n * @param args - Arguments to pass to the message function\n */\nexport function getA11yMessage<K extends keyof A11yMessages>(\n gridEl: HTMLElement,\n key: K,\n ...args: Parameters<Required<A11yMessages>[K]>\n): string {\n type MessageFn = (...a: Parameters<Required<A11yMessages>[K]>) => string;\n const config =\n gridEl && 'effectiveConfig' in gridEl\n ? (gridEl as HTMLElement & { effectiveConfig?: GridConfig }).effectiveConfig\n : undefined;\n const customFn = config?.a11y?.messages?.[key] as MessageFn | undefined;\n if (customFn) return customFn(...args);\n return (DEFAULT_A11Y_MESSAGES[key] as MessageFn)(...args);\n}\n\n/**\n * Announce a `dataLoaded` message — but only when the source row count has\n * actually changed since the last announcement. This prevents announcement\n * spam from internal `data-change` emits triggered by sort/filter/edit, which\n * have their own dedicated announcements.\n *\n * @param gridEl - The grid host element\n * @param state - ARIA state (tracks last announced count)\n * @param sourceRowCount - Current source row count\n */\nexport function announceDataLoaded(gridEl: HTMLElement, state: AriaState, sourceRowCount: number): void {\n if (sourceRowCount === state.lastAnnouncedSourceCount) return;\n const previousAnnouncedSourceCount = state.lastAnnouncedSourceCount;\n state.lastAnnouncedSourceCount = sourceRowCount;\n // Skip only the initial sentinel → 0 transition (empty grid mount). A later\n // transition to 0 (e.g. data cleared / reloaded to an empty result set) should\n // still announce so screen-reader users hear that the grid is now empty.\n if (sourceRowCount === 0 && previousAnnouncedSourceCount < 0) return;\n announce(gridEl, getA11yMessage(gridEl, 'dataLoaded', sourceRowCount));\n}\n\n// #endregion\n","/**\n * Centralized diagnostic messages for @toolbox-web/grid.\n *\n * Every user-facing warning, error, or info message in the grid has a unique\n * diagnostic code (e.g. `TBW001`). Each code maps to a section on the online\n * troubleshooting page, giving developers a direct link to resolution steps.\n *\n * ## Usage\n *\n * ```ts\n * import { MISSING_BREAKPOINT, warnDiagnostic, throwDiagnostic } from './diagnostics';\n *\n * // Warn with a code\n * warnDiagnostic(MISSING_BREAKPOINT, 'Set a breakpoint...', gridId);\n *\n * // Throw with a code\n * throwDiagnostic(MISSING_ROW_ID, 'Configure getRowId...', gridId);\n * ```\n *\n * Plugins should prefer `this.warn(MISSING_BREAKPOINT, message)` via BaseGridPlugin\n * instead of importing this module directly.\n *\n * @internal\n */\n\n// #region Grid Prefix\n\n/**\n * Build the `[tbw-grid]` or `[tbw-grid#my-id]` log prefix.\n * Pass `pluginName` for a scoped prefix like `[tbw-grid:SelectionPlugin]`.\n */\nexport function gridPrefix(gridId?: string, pluginName?: string): string {\n const id = gridId ? `#${gridId}` : '';\n const plugin = pluginName ? `:${pluginName}` : '';\n return `[tbw-grid${id}${plugin}]`;\n}\n\n// #endregion\n\n// #region Diagnostic Codes\n\n/**\n * Diagnostic codes used across the grid library.\n *\n * Each code is an individual export so that unused codes are tree-shaken\n * from bundles that don't reference them (esbuild can't tree-shake\n * properties from a single object).\n *\n * Naming: TBW + 3-digit number.\n * Ranges:\n * 001–019 Configuration validation (missing plugins, bad config)\n * 020–029 Plugin lifecycle (dependencies, incompatibilities, alias collapse)\n * 030–039 Feature registry\n * 040–049 Row operations (row ID, row mutations)\n * 050–059 Column operations (width, template)\n * 060–069 Rendering (callbacks, formatters, external views)\n * 070–079 Shell (tool panels, header/toolbar content)\n * 080–089 Editing & editors\n * 090–099 Print\n * 100–109 Clipboard\n * 110–119 Plugin-specific (responsive, undo-redo, grouping-columns)\n * 120–129 Style injection\n * 130–139 Attribute parsing\n * 140–149 DataSource / ServerSide\n */\n\n// --- Config validation (001–019) ---\n/** Column uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN = 'TBW001' as const;\n/** Grid config uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN_CONFIG = 'TBW002' as const;\n/** Plugin config rule violation (error severity). */\nexport const CONFIG_RULE_ERROR = 'TBW003' as const;\n/** Plugin config rule violation (warning severity). */\nexport const CONFIG_RULE_WARN = 'TBW004' as const;\n\n// --- Plugin lifecycle (020–029) ---\n/** Required plugin dependency is missing. */\nexport const MISSING_DEPENDENCY = 'TBW020' as const;\n/** Optional plugin dependency is missing. */\nexport const OPTIONAL_DEPENDENCY = 'TBW021' as const;\n/** Two loaded plugins are incompatible. */\nexport const INCOMPATIBLE_PLUGINS = 'TBW022' as const;\n/** Two plugin instances resolve to the same canonical name (alias collapse). */\nexport const PLUGIN_ALIAS_COLLAPSE = 'TBW023' as const;\n/** Error thrown inside a plugin event handler. */\nexport const PLUGIN_EVENT_ERROR = 'TBW024' as const;\n/** Conflicting config values found while merging alias-collapsed plugin instances. */\nexport const PLUGIN_ALIAS_CONFIG_CONFLICT = 'TBW025' as const;\n\n// --- Feature registry (030–039) ---\n/** Feature was re-registered (overwritten). */\nexport const FEATURE_REREGISTERED = 'TBW030' as const;\n/** Feature configured but not imported. */\nexport const FEATURE_NOT_IMPORTED = 'TBW031' as const;\n/** Feature depends on another feature that is not enabled. */\nexport const FEATURE_MISSING_DEP = 'TBW032' as const;\n\n// --- Row operations (040–049) ---\n/** Cannot determine row ID (no getRowId and no id property). */\nexport const MISSING_ROW_ID = 'TBW040' as const;\n/** Row with given ID not found. */\nexport const ROW_NOT_FOUND = 'TBW041' as const;\n\n// --- Column operations (050–059) ---\n/** Column has an invalid CSS width value. */\nexport const INVALID_COLUMN_WIDTH = 'TBW050' as const;\n\n// --- Rendering callbacks (060–069) ---\n/** rowClass callback threw an error. */\nexport const ROW_CLASS_ERROR = 'TBW060' as const;\n/** cellClass callback threw an error. */\nexport const CELL_CLASS_ERROR = 'TBW061' as const;\n/** Column format function threw an error. */\nexport const FORMAT_ERROR = 'TBW062' as const;\n/** External view mount() threw an error. */\nexport const VIEW_MOUNT_ERROR = 'TBW063' as const;\n/** External view event dispatch error. */\nexport const VIEW_DISPATCH_ERROR = 'TBW064' as const;\n\n// --- Shell (070–079) ---\n/** Tool panel missing required id. */\nexport const TOOL_PANEL_MISSING_ATTR = 'TBW070' as const;\n/** No tool panels registered. */\nexport const NO_TOOL_PANELS = 'TBW071' as const;\n/** Tool panel section not found. */\nexport const TOOL_PANEL_NOT_FOUND = 'TBW072' as const;\n/** Tool panel already registered. */\nexport const TOOL_PANEL_DUPLICATE = 'TBW073' as const;\n/** Header content already registered. */\nexport const HEADER_CONTENT_DUPLICATE = 'TBW074' as const;\n/** Toolbar content already registered. */\nexport const TOOLBAR_CONTENT_DUPLICATE = 'TBW075' as const;\n\n// --- Editing & editors (080–089) ---\n/** External editor mount() threw an error. */\nexport const EDITOR_MOUNT_ERROR = 'TBW080' as const;\n\n// --- Print (090–099) ---\n/** Print already in progress. */\nexport const PRINT_IN_PROGRESS = 'TBW090' as const;\n/** Grid not available for printing. */\nexport const PRINT_NO_GRID = 'TBW091' as const;\n/** Print operation failed. */\nexport const PRINT_FAILED = 'TBW092' as const;\n/** Multiple elements share the same grid ID (print isolation issue). */\nexport const PRINT_DUPLICATE_ID = 'TBW093' as const;\n\n// --- Clipboard (100–109) ---\n/** Clipboard API write failed. */\nexport const CLIPBOARD_FAILED = 'TBW100' as const;\n\n// --- Plugin-specific (110–119) ---\n/** ResponsivePlugin: no breakpoint configured. */\nexport const MISSING_BREAKPOINT = 'TBW110' as const;\n/** UndoRedoPlugin: transaction already in progress. */\nexport const TRANSACTION_IN_PROGRESS = 'TBW111' as const;\n/** UndoRedoPlugin: no transaction in progress. */\nexport const NO_TRANSACTION = 'TBW112' as const;\n/** GroupingColumnsPlugin: missing id or header on column group definition. */\nexport const COLUMN_GROUP_NO_ID = 'TBW113' as const;\n/** GroupingColumnsPlugin: conflicting columnGroups sources. */\nexport const COLUMN_GROUPS_CONFLICT = 'TBW114' as const;\n\n// --- Style injection (120–129) ---\n/** Failed to extract grid.css from document stylesheets. */\nexport const STYLE_EXTRACT_FAILED = 'TBW120' as const;\n/** Could not find grid.css in document.styleSheets. */\nexport const STYLE_NOT_FOUND = 'TBW121' as const;\n\n// --- Attribute parsing (130–139) ---\n/** Invalid JSON in an HTML attribute. */\nexport const INVALID_ATTRIBUTE_JSON = 'TBW130' as const;\n\n// --- DataSource / ServerSide (140–149) ---\n/** ServerSidePlugin: getRows() rejected. */\nexport const DATASOURCE_FETCH_ERROR = 'TBW140' as const;\n/** ServerSidePlugin: getChildRows() rejected. */\nexport const DATASOURCE_CHILD_FETCH_ERROR = 'TBW141' as const;\n/** ServerSidePlugin: getChildRows() not implemented but a plugin queried for children. */\nexport const DATASOURCE_NO_CHILD_HANDLER = 'TBW142' as const;\n/** ServerSidePlugin: concurrent request limit reached, request deferred. */\nexport const DATASOURCE_THROTTLED = 'TBW143' as const;\n\nexport type DiagnosticCode =\n | typeof MISSING_PLUGIN\n | typeof MISSING_PLUGIN_CONFIG\n | typeof CONFIG_RULE_ERROR\n | typeof CONFIG_RULE_WARN\n | typeof MISSING_DEPENDENCY\n | typeof OPTIONAL_DEPENDENCY\n | typeof INCOMPATIBLE_PLUGINS\n | typeof PLUGIN_ALIAS_COLLAPSE\n | typeof PLUGIN_EVENT_ERROR\n | typeof PLUGIN_ALIAS_CONFIG_CONFLICT\n | typeof FEATURE_REREGISTERED\n | typeof FEATURE_NOT_IMPORTED\n | typeof FEATURE_MISSING_DEP\n | typeof MISSING_ROW_ID\n | typeof ROW_NOT_FOUND\n | typeof INVALID_COLUMN_WIDTH\n | typeof ROW_CLASS_ERROR\n | typeof CELL_CLASS_ERROR\n | typeof FORMAT_ERROR\n | typeof VIEW_MOUNT_ERROR\n | typeof VIEW_DISPATCH_ERROR\n | typeof TOOL_PANEL_MISSING_ATTR\n | typeof NO_TOOL_PANELS\n | typeof TOOL_PANEL_NOT_FOUND\n | typeof TOOL_PANEL_DUPLICATE\n | typeof HEADER_CONTENT_DUPLICATE\n | typeof TOOLBAR_CONTENT_DUPLICATE\n | typeof EDITOR_MOUNT_ERROR\n | typeof PRINT_IN_PROGRESS\n | typeof PRINT_NO_GRID\n | typeof PRINT_FAILED\n | typeof PRINT_DUPLICATE_ID\n | typeof CLIPBOARD_FAILED\n | typeof MISSING_BREAKPOINT\n | typeof TRANSACTION_IN_PROGRESS\n | typeof NO_TRANSACTION\n | typeof COLUMN_GROUP_NO_ID\n | typeof COLUMN_GROUPS_CONFLICT\n | typeof STYLE_EXTRACT_FAILED\n | typeof STYLE_NOT_FOUND\n | typeof INVALID_ATTRIBUTE_JSON\n | typeof DATASOURCE_FETCH_ERROR\n | typeof DATASOURCE_CHILD_FETCH_ERROR\n | typeof DATASOURCE_NO_CHILD_HANDLER\n | typeof DATASOURCE_THROTTLED;\n\n// #endregion\n\n// #region Docs URL\n\nconst DOCS_BASE = 'https://toolboxjs.com/grid/errors';\n\n/** Build a direct link to the troubleshooting section for a code. */\nfunction docsUrl(code: DiagnosticCode): string {\n return `${DOCS_BASE}#${code.toLowerCase()}`;\n}\n\n// #endregion\n\n// #region Formatting\n\n/**\n * Format a diagnostic message with prefix, code, and docs link.\n *\n * Output format:\n * ```\n * [tbw-grid#my-id] TBW001: Your message here.\n *\n * → More info: https://toolboxjs.com/grid/errors#tbw001\n * ```\n */\nexport function formatDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): string {\n const prefix = gridPrefix(gridId, pluginName);\n return `${prefix} ${code}: ${message}\\n\\n → More info: ${docsUrl(code)}`;\n}\n\n// #endregion\n\n// #region Public API\n\n/**\n * Throw an error with a diagnostic code and docs link.\n * Use for configuration errors and API misuse that should halt execution.\n */\nexport function throwDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): never {\n throw new Error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log a warning with a diagnostic code and docs link.\n * Use for recoverable issues the developer should fix.\n */\nexport function warnDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.warn(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log a debug message with a diagnostic code and docs link.\n * Use for optional/soft dependency notifications — visible only when\n * the browser DevTools \"Verbose\" log level is enabled.\n */\nexport function debugDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.debug(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log an error with a diagnostic code and docs link.\n * Use for non-throwing errors (e.g., failed async operations).\n */\nexport function errorDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n// #endregion\n","/**\n * True when a keyboard event originates in a text-entry control — `<input>`, `<select>`,\n * `<textarea>` or a contenteditable element. Plugin key handlers (e.g. Space to toggle a tree\n * row) must defer to the control so the keystroke is typed rather than swallowed.\n *\n * Reads `composedPath()[0]` so controls inside an editor's open shadow root are recognised;\n * `event.target` is retargeted to the shadow host by the time the grid sees the event.\n * @internal\n */\nexport function isFormFieldKeyTarget(event: Event): boolean {\n const path = event.composedPath?.() ?? [];\n const target = path.length ? path[0] : event.target;\n if (!(target instanceof HTMLElement)) return false;\n const tag = target.tagName;\n return tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA' || target.isContentEditable;\n}\n","/**\n * Call a column `format` outside the core cell pipeline (filter-panel labels, Pivot aggregates).\n *\n * Some of these values have no source row — `valuesHandler` results and Pivot aggregates — so\n * `row` is `undefined` there even though `format` is typed `(value, row: TRow)`. A formatter\n * that reads the row then throws; this guard reports the error and lets the caller fall back\n * to the raw value, mirroring the core cell pipeline (TBW062).\n *\n * @returns The formatted string (`''` for a nullish result), or `undefined` when `format` threw.\n * @internal\n */\nexport function tryFormat<TRow>(\n format: (value: unknown, row: TRow) => unknown,\n value: unknown,\n row: TRow | undefined,\n onError: (error: unknown) => void,\n): string | undefined {\n try {\n // `row` is only undefined where no source row exists (see above); the catch covers it.\n const formatted = format(value, row as TRow);\n return formatted == null ? '' : String(formatted);\n } catch (error) {\n onError(error);\n return undefined;\n }\n}\n","import { getValueAggregator } from '../../core/internal/aggregators';\nimport type { AggFunc, PivotConfig, PivotValueField } from './types';\n\n/**\n * Resolve an AggFunc to an executable function.\n * Supports both built-in string names and custom functions.\n */\nexport function getPivotAggregator(aggFunc: AggFunc): (values: number[]) => number {\n if (typeof aggFunc === 'function') return aggFunc;\n return getValueAggregator(aggFunc);\n}\n\nexport function validatePivotConfig(config: PivotConfig): string[] {\n const errors: string[] = [];\n\n if (!config.rowGroupFields?.length && !config.columnGroupFields?.length) {\n errors.push('At least one row or column group field is required');\n }\n\n if (!config.valueFields?.length) {\n errors.push('At least one value field is required');\n }\n\n return errors;\n}\n\nexport function createValueKey(columnValues: string[], valueField: string): string {\n return [...columnValues, valueField].join('|');\n}\n\nexport function createValueKeys(columnValues: string[], valueFields: PivotValueField[]): string[] {\n const fieldCounts = new Map<string, number>();\n for (const valueField of valueFields) {\n fieldCounts.set(valueField.field, (fieldCounts.get(valueField.field) ?? 0) + 1);\n }\n\n // NUL-joined so a `|` inside a field name cannot collide with an aggregate key.\n const seenFieldAggregates = new Set<string>();\n\n return valueFields.map((valueField, index) => {\n const baseKey = createValueKey(columnValues, valueField.field);\n if ((fieldCounts.get(valueField.field) ?? 0) < 2) return baseKey;\n\n const aggregateKey = typeof valueField.aggFunc === 'string' ? valueField.aggFunc : 'custom';\n const key = `${baseKey}|${aggregateKey}`;\n const pairKey = `${valueField.field}\\u0000${aggregateKey}`;\n if (seenFieldAggregates.has(pairKey)) return `${key}|${index + 1}`;\n\n seenFieldAggregates.add(pairKey);\n return key;\n });\n}\n","import { createValueKeys } from './pivot-model';\nimport type { PivotConfig, PivotResult, PivotRow, PivotSortConfig, PivotSortDir, PivotValueField } from './types';\n\n/** @since 0.1.1 */\nexport type PivotDataRow = Record<string, unknown>;\n\n/**\n * Build a hierarchical pivot result from flat data.\n * Supports multiple row group fields for nested hierarchy.\n */\nexport function buildPivot(rows: PivotDataRow[], config: PivotConfig): PivotResult {\n const rowGroupFields = config.rowGroupFields ?? [];\n const columnGroupFields = config.columnGroupFields ?? [];\n const valueFields = config.valueFields ?? [];\n\n // Get unique column combinations\n const columnKeys = getUniqueColumnKeys(rows, columnGroupFields, config.sortColumns);\n\n // Build hierarchical pivot rows\n const pivotRows = buildHierarchicalPivotRows(\n rows,\n rowGroupFields,\n columnGroupFields,\n columnKeys,\n valueFields,\n 0, // starting depth\n '', // parent key prefix\n );\n\n // Sort row groups if configured\n if (config.sortRows) {\n sortPivotRows(pivotRows, config.sortRows, valueFields);\n }\n\n // Calculate grand totals\n const totals = calculateTotals(pivotRows, columnKeys, valueFields);\n const grandTotal = Object.values(totals).reduce((a, b) => a + b, 0);\n\n return {\n rows: pivotRows,\n columnKeys,\n totals,\n grandTotal,\n };\n}\n\n/**\n * Get unique column key combinations from the data.\n */\nexport function getUniqueColumnKeys(\n rows: PivotDataRow[],\n columnFields: string[],\n sortDir: PivotSortDir = 'asc',\n): string[] {\n if (columnFields.length === 0) return ['value'];\n\n const keys = new Set<string>();\n for (const row of rows) {\n const key = columnFields.map((f) => String(row[f] ?? '')).join('|');\n keys.add(key);\n }\n const sorted = [...keys].sort();\n return sortDir === 'desc' ? sorted.reverse() : sorted;\n}\n\n/**\n * Group rows by a single field.\n */\nexport function groupByField(rows: PivotDataRow[], field: string): Map<string, PivotDataRow[]> {\n const groups = new Map<string, PivotDataRow[]>();\n\n for (const row of rows) {\n const key = String(row[field] ?? '');\n const existing = groups.get(key);\n if (existing) {\n existing.push(row);\n } else {\n groups.set(key, [row]);\n }\n }\n\n return groups;\n}\n\n/**\n * Group rows by multiple fields (legacy flat grouping).\n */\nexport function groupByFields(rows: PivotDataRow[], fields: string[]): Map<string, PivotDataRow[]> {\n const groups = new Map<string, PivotDataRow[]>();\n\n for (const row of rows) {\n const key = fields.map((f) => String(row[f] ?? '')).join('|');\n const existing = groups.get(key);\n if (existing) {\n existing.push(row);\n } else {\n groups.set(key, [row]);\n }\n }\n\n return groups;\n}\n\n// Built-in branches must stay in sync with `builtInValueAggregators` in core/internal/aggregators.ts;\n// they are inlined here to aggregate without materializing a number[] per group.\nfunction aggregateValueField(rows: PivotDataRow[], valueField: PivotValueField): number | null {\n const { aggFunc, field } = valueField;\n if (typeof aggFunc === 'function') {\n const numbers: number[] = [];\n for (let i = 0; i < rows.length; i++) {\n const raw = rows[i][field];\n if (raw == null || raw === '') continue;\n const value = Number(raw);\n if (!isNaN(value)) numbers.push(value);\n }\n return numbers.length > 0 ? aggFunc(numbers) : null;\n }\n\n if (aggFunc === 'first') {\n for (let i = 0; i < rows.length; i++) {\n const raw = rows[i][field];\n if (raw == null || raw === '') continue;\n const value = Number(raw);\n if (!isNaN(value)) return value;\n }\n return null;\n }\n\n if (aggFunc === 'last') {\n for (let i = rows.length - 1; i >= 0; i--) {\n const raw = rows[i][field];\n if (raw == null || raw === '') continue;\n const value = Number(raw);\n if (!isNaN(value)) return value;\n }\n return null;\n }\n\n let result = aggFunc === 'min' ? Infinity : aggFunc === 'max' ? -Infinity : 0;\n let count = 0;\n for (let i = 0; i < rows.length; i++) {\n const raw = rows[i][field];\n if (raw == null || raw === '') continue;\n const value = Number(raw);\n if (isNaN(value)) continue;\n\n count++;\n if (aggFunc === 'min') {\n if (value < result) result = value;\n } else if (aggFunc === 'max') {\n if (value > result) result = value;\n } else if (aggFunc !== 'count') {\n result += value;\n }\n }\n\n if (count === 0) return null;\n if (aggFunc === 'avg') return result / count;\n if (aggFunc === 'count') return count;\n return result;\n}\n\nfunction aggregateSharedBuiltIns(\n rows: PivotDataRow[],\n field: string,\n valueFields: PivotValueField[],\n valueKeys: string[],\n values: Record<string, number | null>,\n): void {\n let sum = 0;\n let count = 0;\n let min = Infinity;\n let max = -Infinity;\n let first: number | null = null;\n let last: number | null = null;\n\n for (let i = 0; i < rows.length; i++) {\n const raw = rows[i][field];\n if (raw == null || raw === '') continue;\n const value = Number(raw);\n if (isNaN(value)) continue;\n\n if (count === 0) first = value;\n last = value;\n sum += value;\n count++;\n if (value < min) min = value;\n if (value > max) max = value;\n }\n\n for (let i = 0; i < valueFields.length; i++) {\n const valueField = valueFields[i];\n if (valueField.field !== field || typeof valueField.aggFunc === 'function') continue;\n\n let result: number | null;\n if (count === 0) {\n result = null;\n } else if (valueField.aggFunc === 'avg') {\n result = sum / count;\n } else if (valueField.aggFunc === 'count') {\n result = count;\n } else if (valueField.aggFunc === 'min') {\n result = min;\n } else if (valueField.aggFunc === 'max') {\n result = max;\n } else if (valueField.aggFunc === 'first') {\n result = first;\n } else if (valueField.aggFunc === 'last') {\n result = last;\n } else {\n result = sum;\n }\n values[valueKeys[i]] = result;\n }\n}\n\nfunction aggregateValueFields(\n rows: PivotDataRow[],\n valueFields: PivotValueField[],\n valueKeys: string[],\n values: Record<string, number | null>,\n): void {\n for (let i = 0; i < valueFields.length; i++) {\n const valueField = valueFields[i];\n let fieldHandled = false;\n let builtInCount = 0;\n for (let j = 0; j < valueFields.length; j++) {\n if (valueFields[j].field !== valueField.field) continue;\n if (j < i) fieldHandled = true;\n if (typeof valueFields[j].aggFunc !== 'function') builtInCount++;\n }\n if (fieldHandled) continue;\n\n if (builtInCount > 1) {\n aggregateSharedBuiltIns(rows, valueField.field, valueFields, valueKeys, values);\n }\n for (let j = i; j < valueFields.length; j++) {\n const candidate = valueFields[j];\n if (candidate.field !== valueField.field) continue;\n if (builtInCount > 1 && typeof candidate.aggFunc !== 'function') continue;\n values[valueKeys[j]] = aggregateValueField(rows, candidate);\n }\n }\n}\n\n/**\n * Build hierarchical pivot rows recursively.\n * Each level of rowGroupFields creates a new depth level.\n */\nexport function buildHierarchicalPivotRows(\n rows: PivotDataRow[],\n rowGroupFields: string[],\n columnFields: string[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n depth: number,\n parentKey: string,\n): PivotRow[] {\n const result: PivotRow[] = [];\n\n // If no more row group fields, we're at the leaf level - aggregate the data\n if (rowGroupFields.length === 0) {\n // This shouldn't normally happen as we need at least one grouping field\n // But handle it by creating a single aggregated row\n const values = aggregateValues(rows, columnFields, columnKeys, valueFields);\n const total = calculateRowTotal(values);\n result.push({\n rowKey: parentKey || 'all',\n rowLabel: parentKey || 'All',\n depth,\n values,\n total,\n isGroup: false,\n rowCount: rows.length,\n });\n return result;\n }\n\n // Get the current grouping field\n const currentField = rowGroupFields[0];\n const remainingFields = rowGroupFields.slice(1);\n const hasChildren = remainingFields.length > 0;\n\n // Group rows by current field\n const grouped = groupByField(rows, currentField);\n\n for (const [groupValue, groupRows] of grouped) {\n const rowKey = parentKey ? `${parentKey}|${groupValue}` : groupValue;\n\n // Aggregate values for this group (sum of all child rows)\n const values = aggregateValues(groupRows, columnFields, columnKeys, valueFields);\n const total = calculateRowTotal(values);\n\n // Build children if there are more grouping levels\n let children: PivotRow[] | undefined;\n if (hasChildren) {\n children = buildHierarchicalPivotRows(\n groupRows,\n remainingFields,\n columnFields,\n columnKeys,\n valueFields,\n depth + 1,\n rowKey,\n );\n }\n\n result.push({\n rowKey,\n rowLabel: groupValue || '(blank)',\n depth,\n values,\n total,\n isGroup: hasChildren,\n children,\n rowCount: groupRows.length,\n });\n }\n\n return result;\n}\n\n/**\n * Aggregate values for a set of rows across all column keys.\n * Pre-groups rows by column key in a single pass to avoid O(rows × colKeys) repeated filtering.\n */\nexport function aggregateValues(\n rows: PivotDataRow[],\n columnFields: string[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n): Record<string, number | null> {\n const values: Record<string, number | null> = {};\n\n if (columnFields.length === 0) {\n // No column grouping — all rows match every key\n const valueKeys = createValueKeys(['value'], valueFields);\n aggregateValueFields(rows, valueFields, valueKeys, values);\n return values;\n }\n\n // Pre-group rows by column key in a single O(rows) pass\n const rowsByColKey = new Map<string, PivotDataRow[]>();\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n let key = String(row[columnFields[0]] ?? '');\n for (let j = 1; j < columnFields.length; j++) {\n key += '|';\n key += String(row[columnFields[j]] ?? '');\n }\n const existing = rowsByColKey.get(key);\n if (existing) {\n existing.push(row);\n } else {\n rowsByColKey.set(key, [row]);\n }\n }\n\n for (const colKey of columnKeys) {\n const valueKeys = createValueKeys([colKey], valueFields);\n const matchingRows = rowsByColKey.get(colKey);\n if (!matchingRows || matchingRows.length === 0) {\n for (const valueKey of valueKeys) {\n values[valueKey] = null;\n }\n continue;\n }\n\n aggregateValueFields(matchingRows, valueFields, valueKeys, values);\n }\n\n return values;\n}\n\n/**\n * Calculate the total for a row's values.\n */\nexport function calculateRowTotal(values: Record<string, number | null>): number {\n let sum = 0;\n for (const val of Object.values(values)) {\n sum += val ?? 0;\n }\n return sum;\n}\n\n/**\n * Calculate grand totals across all pivot rows.\n */\nexport function calculateTotals(\n pivotRows: PivotRow[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n): Record<string, number> {\n const totals: Record<string, number> = {};\n const allValueKeys = columnKeys.flatMap((colKey) => createValueKeys([colKey], valueFields));\n\n // Recursively sum all rows (including nested children)\n function sumRows(rows: PivotRow[]) {\n for (const row of rows) {\n // Only count leaf rows to avoid double-counting\n if (!row.isGroup || !row.children?.length) {\n for (const valueKey of allValueKeys) {\n totals[valueKey] = (totals[valueKey] ?? 0) + (row.values[valueKey] ?? 0);\n }\n } else if (row.children) {\n sumRows(row.children);\n }\n }\n }\n\n sumRows(pivotRows);\n return totals;\n}\n\n/**\n * Flatten hierarchical pivot rows for rendering.\n * Respects expanded state - only includes children of expanded groups.\n */\nexport function flattenPivotRows(rows: PivotRow[], expandedKeys?: Set<string>, defaultExpanded = true): PivotRow[] {\n const result: PivotRow[] = [];\n\n function flatten(row: PivotRow) {\n result.push(row);\n\n // Check if this group is expanded\n const isExpanded = expandedKeys ? expandedKeys.has(row.rowKey) : defaultExpanded;\n\n // Only include children if expanded\n if (row.children && isExpanded) {\n for (const child of row.children) {\n flatten(child);\n }\n }\n }\n\n for (const row of rows) {\n flatten(row);\n }\n\n return result;\n}\n\n/**\n * Get all group keys from pivot rows (for expand all / collapse all).\n */\nexport function getAllGroupKeys(rows: PivotRow[]): string[] {\n const keys: string[] = [];\n\n function collectKeys(row: PivotRow) {\n if (row.isGroup) {\n keys.push(row.rowKey);\n }\n if (row.children) {\n for (const child of row.children) {\n collectKeys(child);\n }\n }\n }\n\n for (const row of rows) {\n collectKeys(row);\n }\n\n return keys;\n}\n\n/**\n * Recursively sort pivot rows at each level.\n */\nexport function sortPivotRows(rows: PivotRow[], sortConfig: PivotSortConfig, valueFields: PivotValueField[]): void {\n const dir = sortConfig.direction === 'desc' ? -1 : 1;\n\n rows.sort((a, b) => {\n if (sortConfig.by === 'value') {\n const field = sortConfig.valueField ?? valueFields[0]?.field;\n if (field) {\n const aVal = a.total ?? 0;\n const bVal = b.total ?? 0;\n return (aVal - bVal) * dir;\n }\n }\n // Default: sort by label\n return a.rowLabel.localeCompare(b.rowLabel) * dir;\n });\n\n for (const row of rows) {\n if (row.children?.length) {\n sortPivotRows(row.children, sortConfig, valueFields);\n }\n }\n}\n\n/**\n * Sort pivot rows by multiple criteria (from MultiSort's sort model).\n * Each criterion is a PivotSortConfig; earlier entries take precedence.\n * Maintains hierarchy by sorting children recursively at each level.\n */\nexport function sortPivotMulti(rows: PivotRow[], configs: PivotSortConfig[], valueFields: PivotValueField[]): void {\n if (configs.length === 0) return;\n\n const knownValueFields = new Set(valueFields.map((vf) => vf.field));\n\n /** Resolve the numeric sort value for a row given a valueField key. */\n const getSortValue = (row: PivotRow, valueField: string): number => {\n // Direct match (full value key like \"Q1|sales\")\n if (row.values[valueField] != null) {\n return typeof row.values[valueField] === 'number' ? (row.values[valueField] as number) : 0;\n }\n // Suffix match — sum all matching columns for a bare field name\n if (knownValueFields.has(valueField)) {\n const suffix = `|${valueField}`;\n let sum = 0;\n let found = false;\n for (const key of Object.keys(row.values)) {\n if (key.endsWith(suffix)) {\n sum += typeof row.values[key] === 'number' ? (row.values[key] as number) : 0;\n found = true;\n }\n }\n if (found) return sum;\n }\n return row.total ?? 0;\n };\n\n // Precompute accessors for deterministic ordering (same accessor for both a and b)\n const accessors = configs.map((cfg) => {\n if (cfg.by === 'value') {\n if (cfg.valueField) {\n return (row: PivotRow) => getSortValue(row, cfg.valueField!);\n }\n return (row: PivotRow) => row.total ?? 0;\n }\n return null; // label sort — uses localeCompare directly\n });\n\n rows.sort((a, b) => {\n for (let i = 0; i < configs.length; i++) {\n const cfg = configs[i];\n const dir = cfg.direction === 'desc' ? -1 : 1;\n let cmp = 0;\n\n if (cfg.by === 'value') {\n const accessor = accessors[i] as (row: PivotRow) => number;\n cmp = (accessor(a) - accessor(b)) * dir;\n } else {\n cmp = a.rowLabel.localeCompare(b.rowLabel) * dir;\n }\n\n if (cmp !== 0) return cmp;\n }\n return 0;\n });\n\n for (const row of rows) {\n if (row.children?.length) {\n sortPivotMulti(row.children, configs, valueFields);\n }\n }\n}\n\n/**\n * Resolve `defaultExpanded` config to a set of keys.\n *\n * Deliberately NOT the same function as `resolveDefaultExpanded` in\n * `plugins/grouping-rows/grouping-rows.ts`: pivot treats `undefined` as\n * expand-all (a pivot with everything collapsed shows nothing useful), whereas\n * grouping-rows treats it as collapse-all. Merging them would silently flip one\n * plugin's default.\n */\nexport function resolveDefaultExpanded(\n value: boolean | number | string | string[] | undefined,\n allGroupKeys: string[],\n): Set<string> {\n if (value === true || value === undefined) return new Set(allGroupKeys);\n if (value === false) return new Set();\n if (typeof value === 'number') {\n const key = allGroupKeys[value];\n return key ? new Set([key]) : new Set();\n }\n if (typeof value === 'string') return new Set([value]);\n if (Array.isArray(value)) return new Set(value);\n return new Set();\n}\n\n/**\n * Calculate column totals (sum of all leaf row values per column key).\n * Used for percentage-of-column calculations.\n */\nexport function getColumnTotals(\n rows: PivotRow[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n): Record<string, number> {\n const totals: Record<string, number> = {};\n const allValueKeys = columnKeys.flatMap((colKey) => createValueKeys([colKey], valueFields));\n\n function sumLeaves(rows: PivotRow[]) {\n for (const row of rows) {\n if (!row.isGroup || !row.children?.length) {\n for (const valueKey of allValueKeys) {\n totals[valueKey] = (totals[valueKey] ?? 0) + (row.values[valueKey] ?? 0);\n }\n } else if (row.children) {\n sumLeaves(row.children);\n }\n }\n }\n\n sumLeaves(rows);\n return totals;\n}\n","/**\n * Grid DOM Constants\n *\n * Centralized constants for CSS classes, data attributes, and selectors\n * used throughout the grid. Use these instead of magic strings.\n *\n * Note: Some constants here are used by plugins but defined in core because:\n * 1. The core CSS needs to style these elements (e.g., sticky positioning)\n * 2. Multiple plugins may share the same class names\n *\n * Plugins can define their own additional constants and export them.\n * See each plugin's index.ts for plugin-specific exports.\n */\n\n/**\n * CSS selector for focusable editor elements within a cell.\n * Used by EditingPlugin, UndoRedoPlugin, and keyboard navigation. Defined here\n * (a leaf module) rather than in `rows.ts` so `keyboard.ts` can consume it\n * without creating a rows ↔ keyboard import cycle.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// #region CSS Classes\n\n/**\n * CSS class names used in the grid's DOM.\n * Use these when adding/removing classes or querying elements.\n *\n * Classes are organized by:\n * - Core: Used by the grid core for structure and basic functionality\n * - Shared: Used by multiple features/plugins, styled by core CSS\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridClasses = {\n // ─── Core Structure ───────────────────────────────────────────────\n ROOT: 'tbw-grid-root',\n HEADER: 'header',\n HEADER_ROW: 'header-row',\n HEADER_CELL: 'header-cell',\n\n // Body structure\n ROWS_VIEWPORT: 'rows-viewport',\n ROWS_SPACER: 'rows-spacer',\n ROWS_CONTAINER: 'rows',\n\n // Row elements\n DATA_ROW: 'data-grid-row',\n GROUP_ROW: 'group-row',\n\n // Cell elements\n DATA_CELL: 'cell',\n\n // ─── Core States ──────────────────────────────────────────────────\n SELECTED: 'selected',\n FOCUSED: 'focused',\n EDITING: 'editing',\n EXPANDED: 'expanded',\n COLLAPSED: 'collapsed',\n DRAGGING: 'dragging',\n RESIZING: 'resizing',\n\n // Sorting (core feature)\n SORTABLE: 'sortable',\n SORTED_ASC: 'sorted-asc',\n SORTED_DESC: 'sorted-desc',\n\n // Visibility\n HIDDEN: 'hidden',\n\n // ─── Shared Classes (used by plugins, styled by core CSS) ────────\n // These are defined here because core CSS provides the base styling.\n // Plugins apply these classes; core CSS defines how they look.\n\n // Sticky positioning (PinnedColumnsPlugin applies, core CSS styles)\n STICKY_LEFT: 'sticky-left',\n STICKY_RIGHT: 'sticky-right',\n\n // Pinned rows (PinnedRowsPlugin applies, core CSS styles)\n PINNED_TOP: 'pinned-top',\n PINNED_BOTTOM: 'pinned-bottom',\n\n // Tree structure (TreePlugin applies, core CSS styles)\n TREE_TOGGLE: 'tree-toggle',\n TREE_INDENT: 'tree-indent',\n\n // Grouping (GroupingRowsPlugin applies, core CSS styles)\n GROUP_TOGGLE: 'group-toggle',\n GROUP_LABEL: 'group-label',\n GROUP_COUNT: 'group-count',\n\n // Selection (SelectionPlugin applies, core CSS styles)\n RANGE_SELECTION: 'range-selection',\n SELECTION_OVERLAY: 'selection-overlay',\n} as const;\n\n// #endregion\n\n// #region Data Attributes\n\n/**\n * Data attribute names used on grid elements.\n * Use these when getting/setting data attributes.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridDataAttrs = {\n // ─── Core Attributes ──────────────────────────────────────────────\n FIELD: 'data-field',\n\n /**\n * Marks a `title` the grid itself put on a cell to reveal ellipsised text\n * (WCAG SC 1.4.12). Anything that sets its own `title` on a cell must clear\n * this, or the grid will later mistake that title for its own and replace it.\n *\n * @since 3.6.0\n */\n TRUNCATED: 'data-tbw-truncated',\n\n // ─── Shared Attributes (used by plugins) ──────────────────────────\n GROUP_KEY: 'data-group-key', // GroupingRowsPlugin\n} as const;\n\n// #endregion\n\n// #region Selectors\n\n/**\n * Common CSS selectors for querying grid elements.\n * Built from the class constants for consistency.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridSelectors = {\n ROOT: `.${GridClasses.ROOT}`,\n HEADER: `.${GridClasses.HEADER}`,\n HEADER_ROW: `.${GridClasses.HEADER_ROW}`,\n HEADER_CELL: `.${GridClasses.HEADER_CELL}`,\n ROWS_VIEWPORT: `.${GridClasses.ROWS_VIEWPORT}`,\n ROWS_CONTAINER: `.${GridClasses.ROWS_CONTAINER}`,\n DATA_ROW: `.${GridClasses.DATA_ROW}`,\n DATA_CELL: `.${GridClasses.DATA_CELL}`,\n GROUP_ROW: `.${GridClasses.GROUP_ROW}`,\n\n // By data attribute\n CELL_BY_FIELD: (field: string) => `.${GridClasses.DATA_CELL}[${GridDataAttrs.FIELD}=\"${field}\"]`,\n\n // State selectors\n SELECTED_ROWS: `.${GridClasses.DATA_ROW}.${GridClasses.SELECTED}`,\n EDITING_CELL: `.${GridClasses.DATA_CELL}.${GridClasses.EDITING}`,\n} as const;\n\n// #endregion\n\n// #region CSS Custom Properties\n\n/**\n * CSS custom property names for theming.\n * Use these when programmatically setting styles.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridCSSVars = {\n // Colors\n COLOR_BG: '--tbw-color-bg',\n COLOR_FG: '--tbw-color-fg',\n COLOR_FG_MUTED: '--tbw-color-fg-muted',\n COLOR_BORDER: '--tbw-color-border',\n COLOR_ACCENT: '--tbw-color-accent',\n COLOR_HEADER_BG: '--tbw-color-header-bg',\n COLOR_HEADER_FG: '--tbw-color-header-fg',\n COLOR_SELECTION: '--tbw-color-selection',\n COLOR_ROW_HOVER: '--tbw-color-row-hover',\n COLOR_ROW_ALT: '--tbw-color-row-alt',\n\n // Sizing\n ROW_HEIGHT: '--tbw-row-height',\n HEADER_HEIGHT: '--tbw-header-height',\n CELL_PADDING: '--tbw-cell-padding',\n\n // Typography\n FONT_FAMILY: '--tbw-font-family',\n FONT_SIZE: '--tbw-font-size',\n\n // Borders\n BORDER_RADIUS: '--tbw-border-radius',\n FOCUS_OUTLINE: '--tbw-focus-outline',\n} as const;\n\n// #endregion\n\n// Type helpers\n\n/**\n * Union of all CSS class names used in the grid's DOM.\n * Use `GridClasses` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridClassName = (typeof GridClasses)[keyof typeof GridClasses];\n\n/**\n * Union of all `data-*` attribute names used on grid elements.\n * Use `GridDataAttrs` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridDataAttr = (typeof GridDataAttrs)[keyof typeof GridDataAttrs];\n\n/**\n * Union of all CSS custom property names (`--tbw-*`) available for theming.\n * Use `GridCSSVars` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridCSSVar = (typeof GridCSSVars)[keyof typeof GridCSSVars];\n","/**\n * Generic click-only action menu — the shared building block for WCAG 2.2\n * SC 2.5.7 \"Dragging Movements\" alternatives in plugins.\n *\n * Every drag affordance in the grid needs a path that works with plain clicks\n * or taps. A keyboard equivalent is **not** enough: SC 2.5.7 is specifically\n * about pointer input, and the W3C Understanding text calls out \"a pop-up menu\n * after tap\" as a conforming alternative. This module is that pop-up.\n *\n * Lives under `plugins/shared/` on purpose: each plugin bundle is built\n * self-contained, so this code is duplicated into the plugins that use it and\n * never lands in the core `index.js` budget.\n *\n * @since 3.6.0\n * @internal\n */\n\n/** Gap between the anchor element and the menu, in px. */\nconst ANCHOR_GAP_PX = 6;\n\n/** Runtime check — happy-dom and older browsers may lack the Popover API. */\nfunction supportsPopover(): boolean {\n return typeof HTMLElement.prototype?.showPopover === 'function';\n}\n\nlet hoverlessQuery: MediaQueryList | null | undefined;\n\n/** Whether the primary pointer cannot hover (touch, most switch devices). */\nfunction isHoverless(): boolean {\n if (hoverlessQuery === undefined) hoverlessQuery = globalThis.matchMedia?.('(hover: none)') ?? null;\n return hoverlessQuery?.matches ?? false;\n}\n\n/**\n * Whether a plugin should also expose its drag alternative as a dedicated\n * inline control, on top of the menu that is always available.\n *\n * Hover-less pointers get one unconditionally — a control revealed on hover is\n * unreachable when nothing can hover, and long-press is the wrong fallback for\n * the tremor and low-dexterity users SC 2.5.7 exists for.\n *\n * Driven by `a11y.dragAlternatives` on the grid config.\n */\nexport function prefersInlineDragAlternative(grid: {\n effectiveConfig?: { a11y?: { dragAlternatives?: 'menu' | 'inline' } };\n}): boolean {\n return grid.effectiveConfig?.a11y?.dragAlternatives === 'inline' || isHoverless();\n}\n\n/** One clickable entry in the menu. */\nexport interface DragAlternativeAction {\n /** Visible text, also the accessible name. */\n label: string;\n /** Render the entry but refuse the click (e.g. \"Move up\" on the first row). */\n disabled?: boolean;\n /** Invoked on click; the menu closes first so focus handling is predictable. */\n run(): void;\n}\n\nexport interface DragAlternativeMenu {\n /**\n * Show `actions` anchored to `anchor`, with `label` as the group's accessible\n * name. Mounted inside the anchor's grid host so it inherits theme tokens.\n */\n open(anchor: HTMLElement, label: string, actions: readonly DragAlternativeAction[]): void;\n /** Hide the menu. Safe to call when already closed. */\n close(): void;\n /** Remove the element and every document-level listener. */\n dispose(): void;\n /** The menu element, or `null` before the first {@link DragAlternativeMenu.open}. */\n readonly element: HTMLElement | null;\n}\n\n/**\n * Create a menu instance.\n *\n * @param id - DOM id for the menu element, unique per owning plugin.\n * @param className - Root class name, so each plugin can style its own menu.\n */\nexport function createDragAlternativeMenu(id: string, className: string): DragAlternativeMenu {\n let root: HTMLDivElement | null = null;\n let opened = false;\n let disposed = false;\n const abort = new AbortController();\n\n function close(): void {\n if (!opened || !root) return;\n opened = false;\n if (supportsPopover()) root.hidePopover();\n root.hidden = true;\n }\n\n function build(): HTMLDivElement {\n const el = document.createElement('div');\n el.id = id;\n el.className = className;\n el.setAttribute('popover', 'manual');\n el.setAttribute('role', 'group');\n // Without the Popover API there is no UA `display: none`, so hide it here.\n el.hidden = true;\n // Tabbing past either end dismisses instead of stranding the user — the\n // menu has no visible trigger to tab back to. A null `relatedTarget` (click\n // focus in Firefox/Safari, focus leaving the document) is ignored.\n el.addEventListener(\n 'focusout',\n (e) => {\n const next = (e as FocusEvent).relatedTarget;\n if (next instanceof Node && !el.contains(next)) close();\n },\n { signal: abort.signal },\n );\n return el;\n }\n\n /**\n * Position against the anchor in viewport coordinates. The menu lives in the\n * top layer, so its containing block is the viewport regardless of where the\n * anchor sits in the DOM.\n */\n function place(anchor: HTMLElement): void {\n if (!root) return;\n const a = anchor.getBoundingClientRect();\n const box = root.getBoundingClientRect();\n const left = Math.max(4, Math.min(a.left, window.innerWidth - box.width - 4));\n const below = a.bottom + ANCHOR_GAP_PX;\n const top = below + box.height <= window.innerHeight ? below : Math.max(4, a.top - ANCHOR_GAP_PX - box.height);\n root.style.left = `${Math.round(left)}px`;\n root.style.top = `${Math.round(top)}px`;\n }\n\n // Any pointer press outside the menu dismisses it. Capture phase so the press\n // that lands on a grid cell still reaches its own handler afterwards.\n document.addEventListener(\n 'pointerdown',\n (e) => {\n if (opened && root && !e.composedPath().includes(root)) close();\n },\n { capture: true, signal: abort.signal },\n );\n document.addEventListener(\n 'keydown',\n (e) => {\n if ((e as KeyboardEvent).key === 'Escape') close();\n },\n { capture: true, signal: abort.signal },\n );\n\n return {\n get element() {\n return root;\n },\n open(anchor, label, actions) {\n if (disposed || actions.length === 0) return;\n if (!root) root = build();\n // Mount inside the grid host (light DOM) so theme custom properties and\n // `color-scheme` cascade in; the top layer keeps it out of overflow.\n const host = anchor.closest<HTMLElement>('tbw-grid, [data-tbw-grid]') ?? document.body;\n if (root.parentNode !== host) host.appendChild(root);\n root.setAttribute('aria-label', label);\n root.replaceChildren();\n for (const action of actions) {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `${className}-item`;\n btn.textContent = action.label;\n btn.disabled = action.disabled === true;\n btn.addEventListener('click', () => {\n close();\n action.run();\n });\n root.appendChild(btn);\n }\n root.hidden = false;\n if (!opened && supportsPopover()) root.showPopover();\n opened = true;\n place(anchor);\n root.querySelector<HTMLButtonElement>('button:not([disabled])')?.focus();\n },\n close,\n dispose() {\n disposed = true;\n close();\n abort.abort();\n root?.remove();\n root = null;\n },\n };\n}\n","/**\n * Pivot Tool Panel Rendering\n *\n * Pure functions for rendering the pivot configuration panel.\n * Separated from PivotPlugin for better code organization.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport type { Translate } from '../../core/types';\nimport {\n createDragAlternativeMenu,\n type DragAlternativeAction,\n type DragAlternativeMenu,\n} from '../shared/drag-alternative-menu';\nimport type { AggFunc, CustomAggFunc, PivotConfig, PivotValueField } from './types';\n\n/** Built-in aggregation function names (excludes custom functions) */\ntype BuiltInAggFunc = Exclude<AggFunc, CustomAggFunc>;\n\n/** Zones a field can be dragged into. */\ntype GroupZone = 'rowGroups' | 'columnGroups';\n\n/**\n * One menu shared by every chip. Each action re-renders the whole panel, so a\n * menu owned by a chip would be torn out from under itself mid-click.\n */\nlet chipMenu: DragAlternativeMenu | null = null;\n\n/** All available built-in aggregation functions for the panel UI */\nexport const AGG_FUNCS: BuiltInAggFunc[] = ['sum', 'avg', 'count', 'min', 'max', 'first', 'last'];\n\n/** Field info for available fields */\nexport interface FieldInfo {\n field: string;\n header: string;\n}\n\n/** Callbacks for panel interactions */\nexport interface PanelCallbacks {\n onTogglePivot: (enabled: boolean) => void;\n onAddFieldToZone: (field: string, zone: 'rowGroups' | 'columnGroups') => void;\n onRemoveFieldFromZone: (field: string, zone: 'rowGroups' | 'columnGroups') => void;\n onReorderFieldInZone: (field: string, zone: 'rowGroups' | 'columnGroups', newIndex: number) => void;\n onMoveFieldBetweenZones: (\n field: string,\n fromZone: 'rowGroups' | 'columnGroups',\n toZone: 'rowGroups' | 'columnGroups',\n ) => void;\n onAddValueField: (field: string, aggFunc: AggFunc) => void;\n onRemoveValueField: (field: string) => void;\n onUpdateValueAggFunc: (field: string, aggFunc: AggFunc) => void;\n onOptionChange: (option: 'showTotals' | 'showGrandTotal', value: boolean) => void;\n getAvailableFields: () => FieldInfo[];\n}\n\n/** Internal context passed to rendering functions */\ninterface RenderContext {\n config: PivotConfig;\n callbacks: PanelCallbacks;\n signal: AbortSignal;\n t: Translate;\n}\n\n/**\n * Render the complete pivot panel content.\n * Returns a cleanup function that removes all event listeners and DOM elements.\n */\nexport function renderPivotPanel(\n container: HTMLElement,\n config: PivotConfig,\n isActive: boolean,\n callbacks: PanelCallbacks,\n t: Translate = (_key, fallback) => fallback,\n): () => void {\n // Create AbortController for automatic listener cleanup\n const controller = new AbortController();\n const ctx: RenderContext = { config, callbacks, signal: controller.signal, t };\n\n const wrapper = document.createElement('div');\n wrapper.className = 'tbw-pivot-panel';\n\n // Options section (at top, includes pivot toggle)\n wrapper.appendChild(createSection(t('pivot.options', 'Options'), () => createOptionsPanel(isActive, ctx)));\n\n // Row Groups section\n wrapper.appendChild(createSection(t('pivot.rowGroups', 'Row Groups'), () => createFieldZone('rowGroups', ctx)));\n\n // Column Groups section\n wrapper.appendChild(\n createSection(t('pivot.columnGroups', 'Column Groups'), () => createFieldZone('columnGroups', ctx)),\n );\n\n // Values section\n wrapper.appendChild(createSection(t('pivot.values', 'Values'), () => createValuesZone(ctx)));\n\n // Available fields section\n wrapper.appendChild(\n createSection(t('pivot.availableFields', 'Available Fields'), () => createAvailableFieldsZone(ctx)),\n );\n\n container.appendChild(wrapper);\n\n // Cleanup: abort all listeners, then remove DOM\n return () => {\n controller.abort();\n chipMenu?.dispose();\n chipMenu = null;\n wrapper.remove();\n };\n}\n\n/**\n * Show the click-only alternative to dragging `chip` (WCAG 2.2 SC 2.5.7).\n */\nfunction openChipMenu(chip: HTMLElement, label: string, actions: readonly DragAlternativeAction[]): void {\n chipMenu ??= createDragAlternativeMenu('tbw-pivot-chip-menu', 'tbw-pivot-chip-menu');\n chipMenu.open(chip, label, actions);\n}\n\n/**\n * Mark a chip as the pointer trigger for its move menu.\n *\n * The role goes on `target`, which for group chips is the label rather than the\n * chip itself — `role=\"button\"` makes descendants presentational, which would\n * hide the chip's own remove button from assistive technology.\n */\nfunction markAsMenuTrigger(target: HTMLElement, name: string, t: Translate): void {\n target.setAttribute('role', 'button');\n target.tabIndex = -1;\n target.setAttribute('aria-label', `${name} — ${t('pivot.chipHint', 'drag, or activate for move options')}`);\n}\n\n/** Menu entries mirroring every drag the chip supports, plus its remove button. */\nfunction fieldChipActions(field: string, zoneType: GroupZone, ctx: RenderContext): DragAlternativeAction[] {\n const { config, callbacks, t } = ctx;\n const fields = zoneType === 'rowGroups' ? (config.rowGroupFields ?? []) : (config.columnGroupFields ?? []);\n const index = fields.indexOf(field);\n const otherZone: GroupZone = zoneType === 'rowGroups' ? 'columnGroups' : 'rowGroups';\n\n return [\n {\n label: t('pivot.moveUp', 'Move up'),\n disabled: index <= 0,\n run: () => callbacks.onReorderFieldInZone(field, zoneType, index - 1),\n },\n {\n label: t('pivot.moveDown', 'Move down'),\n disabled: index < 0 || index >= fields.length - 1,\n // The callback takes an insert-before index in the pre-move list, so\n // stepping down one place means aiming past the field that follows.\n run: () => callbacks.onReorderFieldInZone(field, zoneType, index + 2),\n },\n {\n label:\n otherZone === 'columnGroups'\n ? t('pivot.moveToColumnGroups', 'Move to Column Groups')\n : t('pivot.moveToRowGroups', 'Move to Row Groups'),\n run: () => callbacks.onMoveFieldBetweenZones(field, zoneType, otherZone),\n },\n {\n // Matches dropping the chip on the values zone, which also only adds.\n label: t('pivot.moveToValues', 'Move to Values'),\n run: () => callbacks.onAddValueField(field, 'sum'),\n },\n {\n label: t('pivot.removeField', 'Remove field'),\n run: () => callbacks.onRemoveFieldFromZone(field, zoneType),\n },\n ];\n}\n\n/** Menu entries for an unused field, mirroring a drag into each zone. */\nfunction availableChipActions(field: string, ctx: RenderContext): DragAlternativeAction[] {\n const { callbacks, t } = ctx;\n return [\n {\n label: t('pivot.addToRowGroups', 'Add to Row Groups'),\n run: () => callbacks.onAddFieldToZone(field, 'rowGroups'),\n },\n {\n label: t('pivot.addToColumnGroups', 'Add to Column Groups'),\n run: () => callbacks.onAddFieldToZone(field, 'columnGroups'),\n },\n {\n label: t('pivot.addToValues', 'Add to Values'),\n run: () => callbacks.onAddValueField(field, 'sum'),\n },\n ];\n}\n\n/**\n * Create a collapsible section wrapper.\n */\nfunction createSection(title: string, contentFactory: () => HTMLElement): HTMLElement {\n const section = document.createElement('div');\n section.className = 'tbw-pivot-section';\n\n const header = document.createElement('div');\n header.className = 'tbw-pivot-section-header';\n header.textContent = title;\n\n const content = document.createElement('div');\n content.className = 'tbw-pivot-section-content';\n content.appendChild(contentFactory());\n\n section.appendChild(header);\n section.appendChild(content);\n\n return section;\n}\n\n/**\n * Create a drop zone for row/column group fields.\n */\nfunction createFieldZone(zoneType: 'rowGroups' | 'columnGroups', ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const zone = document.createElement('div');\n zone.className = 'tbw-pivot-drop-zone';\n zone.setAttribute('data-zone', zoneType);\n\n const currentFields = zoneType === 'rowGroups' ? (config.rowGroupFields ?? []) : (config.columnGroupFields ?? []);\n\n if (currentFields.length === 0) {\n const placeholder = document.createElement('div');\n placeholder.className = 'tbw-pivot-placeholder';\n placeholder.textContent = ctx.t('pivot.dropFields', 'Drag fields here');\n zone.appendChild(placeholder);\n } else {\n for (const field of currentFields) {\n zone.appendChild(createFieldChip(field, zoneType, ctx));\n }\n }\n\n // Drop handling — supports reorder within zone and cross-zone moves\n zone.addEventListener(\n 'dragover',\n (e) => {\n e.preventDefault();\n zone.classList.add('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'dragleave',\n () => {\n zone.classList.remove('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'drop',\n (e) => {\n e.preventDefault();\n zone.classList.remove('drag-over');\n\n const field = e.dataTransfer?.getData('text/plain');\n const sourceZone = e.dataTransfer?.getData('source-zone') as 'rowGroups' | 'columnGroups' | undefined;\n if (!field) return;\n\n // Determine drop position for reordering\n const chips = zone.querySelectorAll('.tbw-pivot-field-chip');\n let dropIndex = chips.length;\n for (let i = 0; i < chips.length; i++) {\n const rect = chips[i].getBoundingClientRect();\n if (e.clientY < rect.top + rect.height / 2) {\n dropIndex = i;\n break;\n }\n }\n\n if (sourceZone && sourceZone !== zoneType && (sourceZone === 'rowGroups' || sourceZone === 'columnGroups')) {\n // Cross-zone move\n callbacks.onMoveFieldBetweenZones(field, sourceZone, zoneType);\n } else if (sourceZone === zoneType) {\n // Reorder within same zone\n callbacks.onReorderFieldInZone(field, zoneType, dropIndex);\n } else {\n // New field from available fields\n callbacks.onAddFieldToZone(field, zoneType);\n }\n },\n { signal },\n );\n\n return zone;\n}\n\n/**\n * Create a field chip for row/column zones.\n */\nfunction createFieldChip(field: string, zoneType: GroupZone, ctx: RenderContext): HTMLElement {\n const { callbacks, signal } = ctx;\n const chip = document.createElement('div');\n chip.className = 'tbw-pivot-field-chip';\n chip.draggable = true;\n\n const fieldInfo = callbacks.getAvailableFields().find((f) => f.field === field);\n const name = fieldInfo?.header ?? field;\n const label = document.createElement('span');\n label.className = 'tbw-pivot-chip-label';\n label.textContent = name;\n markAsMenuTrigger(label, name, ctx.t);\n\n const removeBtn = document.createElement('button');\n removeBtn.type = 'button';\n removeBtn.className = 'tbw-pivot-chip-remove';\n removeBtn.innerHTML = '×';\n removeBtn.title = ctx.t('pivot.removeField', 'Remove field');\n removeBtn.addEventListener(\n 'click',\n (e) => {\n e.stopPropagation();\n callbacks.onRemoveFieldFromZone(field, zoneType);\n },\n { signal },\n );\n\n chip.appendChild(label);\n chip.appendChild(removeBtn);\n\n // Drag handling for reordering\n chip.addEventListener(\n 'dragstart',\n (e) => {\n e.dataTransfer?.setData('text/plain', field);\n e.dataTransfer?.setData('source-zone', zoneType);\n chip.classList.add(GridClasses.DRAGGING);\n },\n { signal },\n );\n\n chip.addEventListener(\n 'dragend',\n () => {\n chip.classList.remove(GridClasses.DRAGGING);\n },\n { signal },\n );\n\n // A press-and-release without movement never fires `dragstart`, so a plain\n // click is the tap that SC 2.5.7 asks us to honour.\n chip.addEventListener('click', () => openChipMenu(chip, name, fieldChipActions(field, zoneType, ctx)), { signal });\n\n return chip;\n}\n\n/**\n * Create the values zone with aggregation controls.\n */\nfunction createValuesZone(ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const zone = document.createElement('div');\n zone.className = 'tbw-pivot-drop-zone tbw-pivot-values-zone';\n zone.setAttribute('data-zone', 'values');\n\n const currentValues = config.valueFields ?? [];\n\n if (currentValues.length === 0) {\n const placeholder = document.createElement('div');\n placeholder.className = 'tbw-pivot-placeholder';\n placeholder.textContent = ctx.t('pivot.dropNumericFields', 'Drag numeric fields here');\n zone.appendChild(placeholder);\n } else {\n for (const valueField of currentValues) {\n zone.appendChild(createValueChip(valueField, ctx));\n }\n }\n\n // Drop handling with signal for cleanup\n zone.addEventListener(\n 'dragover',\n (e) => {\n e.preventDefault();\n zone.classList.add('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'dragleave',\n () => {\n zone.classList.remove('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'drop',\n (e) => {\n e.preventDefault();\n zone.classList.remove('drag-over');\n const field = e.dataTransfer?.getData('text/plain');\n if (field) {\n callbacks.onAddValueField(field, 'sum');\n }\n },\n { signal },\n );\n\n return zone;\n}\n\n/**\n * Create a value chip with aggregation selector.\n */\nfunction createValueChip(valueField: PivotValueField, ctx: RenderContext): HTMLElement {\n const { callbacks, signal } = ctx;\n const chip = document.createElement('div');\n chip.className = 'tbw-pivot-field-chip tbw-pivot-value-chip';\n\n const fieldInfo = callbacks.getAvailableFields().find((f) => f.field === valueField.field);\n\n const labelWrapper = document.createElement('div');\n labelWrapper.className = 'tbw-pivot-value-label-wrapper';\n\n const label = document.createElement('span');\n label.className = 'tbw-pivot-chip-label';\n label.textContent = fieldInfo?.header ?? valueField.field;\n\n const isCustomAgg = typeof valueField.aggFunc === 'function';\n\n const aggSelect = document.createElement('select');\n aggSelect.className = 'tbw-pivot-agg-select';\n aggSelect.title = ctx.t('pivot.aggFunction', 'Aggregation function');\n\n if (isCustomAgg) {\n const option = document.createElement('option');\n option.value = '__custom__';\n option.textContent = ctx.t('pivot.customAgg', 'CUSTOM');\n option.selected = true;\n aggSelect.appendChild(option);\n aggSelect.disabled = true;\n }\n\n for (const aggFunc of AGG_FUNCS) {\n const option = document.createElement('option');\n option.value = aggFunc;\n option.textContent = aggFunc.toUpperCase();\n if (!isCustomAgg) option.selected = aggFunc === valueField.aggFunc;\n aggSelect.appendChild(option);\n }\n\n aggSelect.addEventListener(\n 'change',\n () => {\n callbacks.onUpdateValueAggFunc(valueField.field, aggSelect.value as BuiltInAggFunc);\n },\n { signal },\n );\n\n const removeBtn = document.createElement('button');\n removeBtn.type = 'button';\n removeBtn.className = 'tbw-pivot-chip-remove';\n removeBtn.innerHTML = '×';\n removeBtn.title = ctx.t('pivot.removeValueField', 'Remove value field');\n removeBtn.addEventListener(\n 'click',\n (e) => {\n e.stopPropagation();\n callbacks.onRemoveValueField(valueField.field);\n },\n { signal },\n );\n\n labelWrapper.appendChild(label);\n labelWrapper.appendChild(aggSelect);\n\n chip.appendChild(labelWrapper);\n chip.appendChild(removeBtn);\n\n return chip;\n}\n\n/**\n * Create the available fields zone.\n */\nfunction createAvailableFieldsZone(ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const zone = document.createElement('div');\n zone.className = 'tbw-pivot-available-fields';\n\n const allFields = callbacks.getAvailableFields();\n const usedFields = new Set([\n ...(config.rowGroupFields ?? []),\n ...(config.columnGroupFields ?? []),\n ...(config.valueFields?.map((v) => v.field) ?? []),\n ]);\n\n // Filter to show only unused fields\n const availableFields = allFields.filter((f) => !usedFields.has(f.field));\n\n if (availableFields.length === 0) {\n const empty = document.createElement('div');\n empty.className = 'tbw-pivot-placeholder';\n empty.textContent = ctx.t('pivot.allFieldsUsed', 'All fields are in use');\n zone.appendChild(empty);\n } else {\n // Search filter for available fields (only when 6+ fields)\n const fieldsContainer = document.createElement('div');\n fieldsContainer.className = 'tbw-pivot-fields-list';\n\n if (availableFields.length >= 6) {\n const search = document.createElement('input');\n search.type = 'text';\n search.placeholder = ctx.t('pivot.filterFields', 'Filter fields\\u2026');\n search.className = 'tbw-pivot-field-search';\n search.addEventListener(\n 'input',\n () => {\n const query = search.value.toLowerCase();\n for (const chip of fieldsContainer.querySelectorAll('.tbw-pivot-field-chip')) {\n const el = chip as HTMLElement;\n el.style.display = el.textContent?.toLowerCase().includes(query) ? '' : 'none';\n }\n },\n { signal },\n );\n zone.appendChild(search);\n }\n\n for (const field of availableFields) {\n const chip = document.createElement('div');\n chip.className = 'tbw-pivot-field-chip available';\n chip.textContent = field.header;\n chip.draggable = true;\n chip.title = ctx.t('pivot.availableChipHint', 'Drag into a zone, or click to choose one');\n markAsMenuTrigger(chip, field.header, ctx.t);\n\n chip.addEventListener(\n 'dragstart',\n (e) => {\n e.dataTransfer?.setData('text/plain', field.field);\n chip.classList.add(GridClasses.DRAGGING);\n },\n { signal },\n );\n\n chip.addEventListener(\n 'dragend',\n () => {\n chip.classList.remove(GridClasses.DRAGGING);\n },\n { signal },\n );\n\n chip.addEventListener('click', () => openChipMenu(chip, field.header, availableChipActions(field.field, ctx)), {\n signal,\n });\n\n fieldsContainer.appendChild(chip);\n }\n\n zone.appendChild(fieldsContainer);\n }\n\n return zone;\n}\n\n/**\n * Create the options panel with pivot toggle and checkboxes for totals.\n */\nfunction createOptionsPanel(isActive: boolean, ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal, t } = ctx;\n const panel = document.createElement('div');\n panel.className = 'tbw-pivot-options';\n\n // Pivot Mode toggle\n panel.appendChild(\n createCheckbox(\n t('pivot.enable', 'Enable Pivot View'),\n isActive,\n (checked) => {\n callbacks.onTogglePivot(checked);\n },\n signal,\n ),\n );\n\n // Show Totals checkbox\n panel.appendChild(\n createCheckbox(\n t('pivot.showRowTotals', 'Show Row Totals'),\n config.showTotals ?? true,\n (checked) => {\n callbacks.onOptionChange('showTotals', checked);\n },\n signal,\n ),\n );\n\n // Show Grand Total checkbox\n panel.appendChild(\n createCheckbox(\n t('pivot.showGrandTotal', 'Show Grand Total'),\n config.showGrandTotal ?? true,\n (checked) => {\n callbacks.onOptionChange('showGrandTotal', checked);\n },\n signal,\n ),\n );\n\n return panel;\n}\n\n/**\n * Create a checkbox with label.\n */\nfunction createCheckbox(\n label: string,\n checked: boolean,\n onChange: (checked: boolean) => void,\n signal: AbortSignal,\n): HTMLElement {\n const wrapper = document.createElement('label');\n wrapper.className = 'tbw-pivot-checkbox';\n\n const input = document.createElement('input');\n input.type = 'checkbox';\n input.checked = checked;\n input.addEventListener('change', () => onChange(input.checked), { signal });\n\n const span = document.createElement('span');\n span.textContent = label;\n\n wrapper.appendChild(input);\n wrapper.appendChild(span);\n\n return wrapper;\n}\n","/**\n * Pivot Row Rendering\n *\n * Pure functions for rendering pivot rows (group rows, leaf rows, grand total).\n * Separated from PivotPlugin for better code organization.\n *\n * IMPORTANT: These functions bypass the grid's normal cell rendering pipeline.\n * Column-level features (format, cellRenderer, cellClass) must be applied manually.\n * When adding new cell rendering logic, always check for `col.format` on value columns.\n */\n\nimport type { ColumnConfig, GridIcons, Translate } from '../../core/types';\n\n/** Row data with pivot metadata */\nexport interface PivotRowData {\n __pivotRowKey?: string;\n __pivotLabel?: string;\n __pivotDepth?: number;\n __pivotIndent?: number;\n __pivotExpanded?: boolean;\n __pivotHasChildren?: boolean;\n __pivotRowCount?: number;\n __pivotIsGrandTotal?: boolean;\n [key: string]: unknown;\n}\n\n/** Context for row rendering */\nexport interface RowRenderContext {\n columns: ColumnConfig[];\n rowIndex: number;\n onToggle: (key: string) => void;\n setIcon: (element: HTMLElement, iconKey: keyof GridIcons) => void;\n}\n\n/**\n * Render a pivot group row (has children, can expand/collapse).\n */\nexport function renderPivotGroupRow(row: PivotRowData, rowEl: HTMLElement, ctx: RowRenderContext): boolean {\n rowEl.className = 'data-grid-row pivot-group-row';\n rowEl.setAttribute('data-pivot-depth', String(row.__pivotDepth ?? 0));\n rowEl.setAttribute('data-pivot-key', String(row.__pivotRowKey ?? ''));\n rowEl.setAttribute('role', 'row');\n // Note: aria-expanded is not set here because it's only valid in treegrid, not grid\n // The expand/collapse state is conveyed via the toggle button's aria-label\n rowEl.innerHTML = '';\n\n ctx.columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('data-row', String(ctx.rowIndex));\n cell.setAttribute('role', 'gridcell');\n\n if (colIdx === 0) {\n // First column: indent + toggle + label + count\n const indent = Number(row.__pivotIndent) || 0;\n cell.style.paddingLeft = `${indent}px`;\n\n // Toggle button\n const rowKey = String(row.__pivotRowKey);\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'pivot-toggle';\n btn.setAttribute('aria-label', row.__pivotExpanded ? 'Collapse group' : 'Expand group');\n ctx.setIcon(btn, row.__pivotExpanded ? 'collapse' : 'expand');\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n ctx.onToggle(rowKey);\n });\n cell.appendChild(btn);\n\n // Group label\n const label = document.createElement('span');\n label.className = 'pivot-label';\n label.textContent = String(row.__pivotLabel ?? '');\n cell.appendChild(label);\n\n // Row count\n const count = document.createElement('span');\n count.className = 'pivot-count';\n count.textContent = ` (${Number(row.__pivotRowCount) || 0})`;\n cell.appendChild(count);\n } else {\n // Other columns: render value, applying column format when available\n const value = row[col.field];\n cell.textContent = value != null ? (col.format ? col.format(value, row) : String(value)) : '';\n }\n\n rowEl.appendChild(cell);\n });\n\n return true;\n}\n\n/**\n * Render a pivot leaf row (no children, just indentation).\n */\nexport function renderPivotLeafRow(\n row: PivotRowData,\n rowEl: HTMLElement,\n columns: ColumnConfig[],\n rowIndex: number,\n): boolean {\n rowEl.className = 'data-grid-row pivot-leaf-row';\n rowEl.setAttribute('data-pivot-depth', String(row.__pivotDepth ?? 0));\n rowEl.setAttribute('data-pivot-key', String(row.__pivotRowKey ?? ''));\n rowEl.innerHTML = '';\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('data-row', String(rowIndex));\n cell.setAttribute('role', 'gridcell');\n\n if (colIdx === 0) {\n // First column: indent + label (no toggle for leaves)\n const indent = Number(row.__pivotIndent) || 0;\n // Add extra indent for alignment with toggle button\n cell.style.paddingLeft = `${indent + 20}px`;\n\n const label = document.createElement('span');\n label.className = 'pivot-label';\n label.textContent = String(row.__pivotLabel ?? '');\n cell.appendChild(label);\n } else {\n // Other columns: render value, applying column format when available\n const value = row[col.field];\n cell.textContent = value != null ? (col.format ? col.format(value, row) : String(value)) : '';\n }\n\n rowEl.appendChild(cell);\n });\n\n return true;\n}\n\n/**\n * Render the grand total row.\n * Used both for the sticky footer and for in-row-model rendering.\n */\nexport function renderPivotGrandTotalRow(\n row: PivotRowData,\n rowEl: HTMLElement,\n columns: ColumnConfig[],\n t: Translate = (_key, fallback) => fallback,\n): boolean {\n rowEl.className = 'data-grid-row pivot-grand-total-row';\n rowEl.innerHTML = '';\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.setAttribute('data-col', String(colIdx));\n // No role attribute - parent row has role=presentation so children don't need grid semantics\n\n if (colIdx === 0) {\n // First column: Grand Total label\n const label = document.createElement('span');\n label.className = 'pivot-label';\n label.textContent = t('pivot.grandTotal', 'Grand Total');\n cell.appendChild(label);\n } else {\n // Other columns: render totals, applying column format when available\n const value = row[col.field];\n cell.textContent = value != null ? (col.format ? col.format(value, row) : String(value)) : '';\n }\n\n rowEl.appendChild(cell);\n });\n\n return true;\n}\n","/**\n * Pivot Plugin Styles\n *\n * Uses CSS custom properties with plugin-specific overrides\n * falling back to grid-level variables.\n */\n\n@layer tbw-plugins {\n /* ===== Pivot Row Styles ===== */\n\n .pivot-group-row {\n display: grid;\n grid-template-columns: var(--tbw-column-template);\n font-weight: 600;\n background: var(--tbw-pivot-group-bg, var(--tbw-color-row-alt));\n min-height: var(--tbw-row-height);\n border-bottom: var(--tbw-row-divider);\n }\n\n @media (hover: hover) {\n .pivot-group-row:hover {\n background: var(--tbw-pivot-group-hover, var(--tbw-color-row-hover));\n }\n }\n\n .pivot-leaf-row {\n display: grid;\n grid-template-columns: var(--tbw-column-template);\n background: var(--tbw-pivot-leaf-bg, var(--tbw-color-bg));\n min-height: var(--tbw-row-height);\n border-bottom: var(--tbw-row-divider);\n }\n\n .pivot-grand-total-row {\n display: grid;\n grid-template-columns: var(--tbw-column-template);\n font-weight: 700;\n background: var(--tbw-pivot-grand-total-bg, var(--tbw-color-header-bg));\n min-height: var(--tbw-row-height);\n border-top: 2px solid var(--tbw-color-border-strong);\n }\n\n .pivot-grand-total-row > .cell {\n display: flex;\n align-items: center;\n padding: var(--tbw-cell-padding);\n border-right: 1px solid var(--tbw-color-border-cell);\n overflow: hidden;\n min-width: 0;\n }\n\n .pivot-grand-total-row > .cell:last-child {\n border-right: 0;\n }\n\n /* ===== Sticky Grand Total Footer ===== */\n\n .pivot-grand-total-footer {\n position: sticky;\n bottom: 0;\n z-index: var(--tbw-z-layer-pinned-rows, 20);\n background: var(--tbw-pivot-grand-total-bg, var(--tbw-color-header-bg));\n min-width: fit-content;\n }\n\n /* Cells within pivot rows */\n .pivot-group-row > .cell,\n .pivot-leaf-row > .cell {\n display: flex;\n align-items: center;\n padding: var(--tbw-cell-padding);\n border-right: 1px solid var(--tbw-color-border-cell);\n overflow: hidden;\n min-width: 0;\n }\n\n .pivot-group-row > .cell:last-child,\n .pivot-leaf-row > .cell:last-child {\n border-right: 0;\n }\n\n /* ===== Toggle Button ===== */\n\n .pivot-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--tbw-pivot-toggle-size, var(--tbw-toggle-size, 1.25em));\n height: var(--tbw-pivot-toggle-size, var(--tbw-toggle-size, 1.25em));\n margin-right: var(--tbw-spacing-sm, 0.375em);\n border: none;\n background: transparent;\n cursor: pointer;\n color: var(--tbw-pivot-toggle-color, var(--tbw-color-fg-muted));\n border-radius: var(--tbw-border-radius);\n transition:\n background var(--tbw-transition-duration, 120ms) var(--tbw-transition-ease, ease),\n color var(--tbw-transition-duration, 120ms) var(--tbw-transition-ease, ease);\n }\n\n .pivot-toggle:hover {\n background: var(--tbw-pivot-toggle-hover-bg, var(--tbw-color-row-hover));\n color: var(--tbw-pivot-toggle-hover-color, var(--tbw-color-fg));\n }\n\n /* SC 2.5.8: grow the target, not the box, so the pivot row keeps its height. */\n .pivot-toggle {\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset-block-start: 50%;\n inset-inline-start: 50%;\n translate: -50% -50%;\n inline-size: var(--tbw-touch-target-min, 24px);\n block-size: var(--tbw-touch-target-min, 24px);\n }\n }\n\n @media (pointer: coarse) {\n .pivot-toggle {\n min-width: var(--tbw-touch-target-min, 24px);\n min-height: var(--tbw-touch-target-min, 24px);\n }\n }\n\n .pivot-toggle:focus {\n outline: var(--tbw-focus-outline);\n outline-offset: var(--tbw-focus-outline-offset);\n }\n\n /* ===== Labels & Counts ===== */\n\n .pivot-label {\n font-weight: inherit;\n }\n\n .pivot-count {\n color: var(--tbw-pivot-count-color, var(--tbw-color-fg-muted));\n font-size: 0.9em;\n font-weight: normal;\n }\n\n .pivot-total-row {\n font-weight: bold;\n border-top: 2px solid var(--tbw-pivot-border, var(--tbw-color-border-strong));\n }\n\n /* Depth-based styling (for non-custom rendered rows) */\n [data-pivot-depth='1'] {\n --tbw-pivot-depth: 1;\n }\n [data-pivot-depth='2'] {\n --tbw-pivot-depth: 2;\n }\n [data-pivot-depth='3'] {\n --tbw-pivot-depth: 3;\n }\n [data-pivot-depth='4'] {\n --tbw-pivot-depth: 4;\n }\n\n /* ===== Tool Panel Styles ===== */\n\n .tbw-pivot-panel {\n display: flex;\n flex-direction: column;\n gap: var(--tbw-panel-padding, var(--tbw-spacing-lg, 0.75rem));\n padding: var(--tbw-panel-padding, var(--tbw-spacing-lg, 0.75rem));\n height: 100%;\n overflow-y: auto;\n font-size: var(--tbw-font-size-sm, 0.8125rem);\n }\n\n .tbw-pivot-section {\n border: 1px solid var(--tbw-pivot-border, var(--tbw-color-border));\n border-radius: var(--tbw-border-radius);\n background: var(--tbw-pivot-section-bg, var(--tbw-color-bg));\n }\n\n .tbw-pivot-section-header {\n padding: var(--tbw-button-padding, var(--tbw-spacing-md, 0.5rem) var(--tbw-spacing-lg, 0.75rem));\n font-weight: 600;\n background: var(--tbw-pivot-header-bg, var(--tbw-color-header-bg));\n border-bottom: 1px solid var(--tbw-pivot-border, var(--tbw-color-border));\n border-radius: var(--tbw-border-radius) var(--tbw-border-radius) 0 0;\n }\n\n .tbw-pivot-section-content {\n padding: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n }\n\n .tbw-pivot-toggle-wrapper {\n display: flex;\n align-items: center;\n }\n\n .tbw-pivot-toggle-label {\n display: flex;\n align-items: center;\n gap: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n cursor: pointer;\n }\n\n .tbw-pivot-toggle-label input {\n width: var(--tbw-icon-size, 1rem);\n height: var(--tbw-icon-size, 1rem);\n cursor: pointer;\n }\n\n /* ===== Drop Zones ===== */\n\n .tbw-pivot-drop-zone {\n min-height: 60px;\n padding: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n border: 2px dashed var(--tbw-pivot-drop-border, var(--tbw-color-border));\n border-radius: var(--tbw-border-radius);\n background: var(--tbw-pivot-drop-bg, var(--tbw-color-row-alt));\n display: flex;\n flex-wrap: wrap;\n gap: var(--tbw-spacing-sm, 0.375rem);\n align-content: flex-start;\n transition: all 0.15s ease;\n }\n\n .tbw-pivot-drop-zone.drag-over {\n border-color: var(--tbw-color-accent);\n background: var(--tbw-pivot-drop-active, var(--tbw-focus-background));\n }\n\n .tbw-pivot-placeholder {\n color: var(--tbw-color-fg-muted);\n font-style: italic;\n padding: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n text-align: center;\n width: 100%;\n }\n\n /* ===== Field Chips ===== */\n\n .tbw-pivot-field-chip {\n display: inline-flex;\n align-items: center;\n gap: var(--tbw-spacing-sm, 0.375rem);\n padding: var(--tbw-button-padding-sm, var(--tbw-spacing-xs, 0.25rem) var(--tbw-spacing-md, 0.5rem));\n background: var(--tbw-pivot-chip-bg, var(--tbw-color-header-bg));\n border: 1px solid var(--tbw-pivot-chip-border, var(--tbw-color-border));\n border-radius: var(--tbw-border-radius);\n cursor: grab;\n font-size: var(--tbw-font-size-xs, 0.75rem);\n transition: all 0.15s ease;\n }\n\n .tbw-pivot-field-chip:hover {\n background: var(--tbw-pivot-chip-hover, var(--tbw-color-row-hover));\n border-color: var(--tbw-color-accent);\n }\n\n .tbw-pivot-field-chip.available {\n background: var(--tbw-color-bg);\n }\n\n .tbw-pivot-field-chip.dragging {\n opacity: 0.5;\n cursor: grabbing;\n }\n\n .tbw-pivot-chip-label {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 120px;\n }\n\n .tbw-pivot-chip-remove {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--tbw-icon-size, 1rem);\n height: var(--tbw-icon-size, 1rem);\n padding: 0;\n border: none;\n background: transparent;\n color: var(--tbw-color-fg-muted);\n font-size: var(--tbw-font-size-sm, 0.875rem);\n font-weight: bold;\n cursor: pointer;\n border-radius: 50%;\n transition: all 0.15s ease;\n }\n\n .tbw-pivot-chip-remove:hover {\n background: var(--tbw-pivot-chip-remove-hover-bg, var(--tbw-color-accent));\n color: var(--tbw-pivot-chip-remove-hover-fg, var(--tbw-color-accent-fg));\n }\n\n /* ===== Chip move menu (WCAG 2.2 SC 2.5.7) ===== */\n\n .tbw-pivot-chip-menu {\n position: fixed;\n margin: 0;\n display: flex;\n flex-direction: column;\n min-width: 12rem;\n padding: var(--tbw-spacing-xs, 0.25rem);\n background: var(--tbw-color-panel-bg, light-dark(#ffffff, #1f1f1f));\n color: var(--tbw-color-fg, light-dark(#1a1a1a, #f0f0f0));\n border: 1px solid var(--tbw-color-border, light-dark(#d0d0d0, #3a3a3a));\n border-radius: var(--tbw-border-radius, 0.25rem);\n box-shadow: var(--tbw-shadow-md, 0 4px 12px rgb(0 0 0 / 25%));\n font: inherit;\n z-index: 10001;\n\n &[hidden] {\n display: none;\n }\n }\n\n .tbw-pivot-chip-menu-item {\n min-height: var(--tbw-touch-target-min, 24px);\n padding: var(--tbw-spacing-xs, 0.25rem) var(--tbw-spacing-sm, 0.5rem);\n text-align: start;\n background: none;\n border: none;\n /* Themes set radii for much larger surfaces (Material uses 12px), which\n turns a 24px row into a lozenge. */\n border-radius: min(var(--tbw-border-radius, 0.25rem), 6px);\n color: inherit;\n font: inherit;\n cursor: pointer;\n\n &:hover:not(:disabled) {\n background: var(--tbw-color-row-hover, light-dark(#f0f0f0, #2a2a2a));\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: default;\n }\n }\n\n /* ===== Value Chips ===== */\n\n .tbw-pivot-value-chip {\n /* Keep inline-flex from parent, just adjust padding */\n padding: var(--tbw-button-padding-sm, var(--tbw-spacing-xs, 0.25rem) var(--tbw-spacing-md, 0.5rem));\n }\n\n .tbw-pivot-value-label-wrapper {\n display: flex;\n align-items: center;\n gap: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n flex: 1;\n min-width: 0;\n }\n\n .tbw-pivot-agg-select {\n padding: var(--tbw-spacing-xs, 0.125rem) var(--tbw-spacing-xs, 0.25rem);\n font-size: var(--tbw-font-size-xs, 0.6875rem);\n border: 1px solid var(--tbw-color-border);\n border-radius: var(--tbw-border-radius);\n background: var(--tbw-color-bg);\n cursor: pointer;\n }\n\n /* ===== Available Fields ===== */\n\n .tbw-pivot-available-fields {\n display: flex;\n flex-direction: column;\n gap: var(--tbw-spacing-sm, 0.375rem);\n min-height: 40px;\n }\n\n .tbw-pivot-field-search {\n width: 100%;\n padding: 0.25rem 0.5rem;\n border: 1px solid var(--tbw-color-border, #d0d5dd);\n border-radius: var(--tbw-radius-sm, 0.25rem);\n font-size: var(--tbw-font-size-sm, 0.875rem);\n background: var(--tbw-color-bg, #fff);\n color: var(--tbw-color-fg, #1a1a2e);\n outline: none;\n }\n\n .tbw-pivot-field-search:focus {\n border-color: var(--tbw-color-primary, #3b82f6);\n }\n\n .tbw-pivot-fields-list {\n display: flex;\n flex-wrap: wrap;\n gap: var(--tbw-spacing-sm, 0.375rem);\n }\n\n /* ===== Options Panel ===== */\n\n .tbw-pivot-options {\n display: flex;\n flex-direction: column;\n gap: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n }\n\n .tbw-pivot-checkbox {\n display: flex;\n align-items: center;\n gap: var(--tbw-panel-gap, var(--tbw-spacing-md, 0.5rem));\n cursor: pointer;\n }\n\n .tbw-pivot-checkbox input {\n width: var(--tbw-icon-size-sm, 0.875rem);\n height: var(--tbw-icon-size-sm, 0.875rem);\n cursor: pointer;\n }\n\n /* ===== Expand/Collapse Animations ===== */\n\n /* Slide animation for rows appearing */\n .pivot-group-row.tbw-pivot-slide-in,\n .pivot-leaf-row.tbw-pivot-slide-in {\n animation: tbw-pivot-slide-in var(--tbw-animation-duration, 200ms) var(--tbw-animation-easing, ease-out) forwards;\n }\n\n @keyframes tbw-pivot-slide-in {\n from {\n opacity: 0;\n transform: translateX(-8px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n }\n\n /* Fade animation for rows appearing */\n .pivot-group-row.tbw-pivot-fade-in,\n .pivot-leaf-row.tbw-pivot-fade-in {\n animation: tbw-pivot-fade-in var(--tbw-animation-duration, 200ms) var(--tbw-animation-easing, ease-out) forwards;\n }\n\n @keyframes tbw-pivot-fade-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n }\n} /* End @layer tbw-plugins */\n","/**\n * Pivot Plugin (Class-based)\n *\n * Provides pivot table functionality for tbw-grid.\n * Transforms flat data into grouped, aggregated pivot views.\n * Includes a tool panel for interactive pivot configuration.\n */\n\nimport { announce, getA11yMessage } from '../../core/internal/aria';\nimport { FORMAT_ERROR } from '../../core/internal/diagnostics';\nimport {\n BaseGridPlugin,\n HeaderClickEvent,\n type PluginDependency,\n type PluginManifest,\n type PluginQuery,\n} from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { isFormFieldKeyTarget } from '../shared/form-field-target';\nimport { tryFormat } from '../shared/format-value';\nimport type { ToolPanelDefinition } from '../shell/types';\nimport {\n buildPivot,\n flattenPivotRows,\n getAllGroupKeys,\n getColumnTotals,\n resolveDefaultExpanded,\n sortPivotMulti,\n type PivotDataRow,\n} from './pivot-engine';\nimport { createValueKeys, validatePivotConfig } from './pivot-model';\nimport { renderPivotPanel, type FieldInfo, type PanelCallbacks } from './pivot-panel';\nimport { renderPivotGrandTotalRow, renderPivotGroupRow, renderPivotLeafRow, type PivotRowData } from './pivot-rows';\nimport type {\n AggFunc,\n ExpandCollapseAnimation,\n PivotConfig,\n PivotConfigChangeDetail,\n PivotResult,\n PivotSortConfig,\n PivotSortDir,\n PivotStateChangeDetail,\n PivotToggleDetail,\n PivotValueField,\n} from './types';\n\n// Import CSS as inline string (Vite handles this)\nimport styles from './pivot.css?inline';\n\n/**\n * Pivot Table Plugin for tbw-grid\n *\n * Transforms flat data into a pivot table view with grouped rows, grouped columns,\n * and aggregated values. Includes an interactive tool panel for configuring\n * row groups, column groups, and value aggregations at runtime.\n *\n * ## Installation\n *\n * ```ts\n * import { PivotPlugin } from '@toolbox-web/grid/plugins/pivot';\n * ```\n *\n * ## Aggregation Functions\n *\n * `sum`, `avg`, `count`, `min`, `max`, `first`, `last`\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pivot-group-bg` | `var(--tbw-color-row-alt)` | Group row background |\n * | `--tbw-pivot-grand-total-bg` | `var(--tbw-color-header-bg)` | Grand total row |\n *\n * @example Basic Pivot Table\n * ```ts\n * import '@toolbox-web/grid';\n * import { PivotPlugin } from '@toolbox-web/grid/plugins/pivot';\n *\n * grid.gridConfig = {\n * columns: [...],\n * plugins: [\n * new PivotPlugin({\n * rowGroupFields: ['region', 'product'],\n * columnGroupFields: ['quarter'],\n * valueFields: [{ field: 'sales', aggFunc: 'sum', header: 'Total' }],\n * }),\n * ],\n * };\n * ```\n *\n * @example Programmatic-Only (No Tool Panel)\n * ```ts\n * new PivotPlugin({\n * showToolPanel: false,\n * rowGroupFields: ['category'],\n * valueFields: [{ field: 'amount', aggFunc: 'sum' }],\n * })\n * ```\n *\n * @see {@link PivotConfig} for all configuration options\n * @see {@link PivotValueField} for value field structure\n *\n * @internal Extends BaseGridPlugin\n * @since 0.1.1\n */\nexport class PivotPlugin extends BaseGridPlugin<PivotConfig> {\n /**\n * Plugin manifest declaring incompatibilities with other plugins.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n modifiesRowStructure: true,\n hookPriority: {\n // Run before MultiSortPlugin so pivot columns are intercepted first\n // when MultiSort is NOT present; non-pivot columns fall through.\n onHeaderClick: -10,\n // Run AFTER MultiSortPlugin's processRows so MultiSort sorts raw rows\n // (a no-op for pivot fields) before Pivot builds and sorts its own rows.\n processRows: 100,\n },\n incompatibleWith: [\n {\n name: 'groupingRows',\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 name: 'tree',\n reason:\n 'PivotPlugin replaces the entire row and column structure with aggregated pivot data. ' +\n 'Tree hierarchy cannot coexist with pivot aggregation.',\n },\n {\n name: 'serverSide',\n reason:\n 'PivotPlugin requires the full dataset to compute aggregations. ' +\n 'ServerSidePlugin lazy-loads rows in blocks, so pivot aggregation cannot be performed client-side.',\n },\n ],\n queries: [{ type: 'sort:get-sort-config', description: 'Returns the current pivot sort configuration' }],\n };\n\n /**\n * Plugin dependencies.\n *\n * - `multiSort` (optional): when present, Pivot queries its sort model to\n * apply sorting to pivot columns.\n * - `shell` (optional, warn): only when the pivot tool panel is enabled\n * (`showToolPanel: true`, the default) does Pivot need a shell host for\n * its panel. Soft dependency — recommends but does not require. The shell\n * auto-registers today; this only surfaces once auto-register is removed\n * at v3 (#370).\n */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'multiSort', required: false, reason: 'Queries sort model for pivot column sorting' },\n {\n name: 'shell',\n required: false,\n severity: 'warn',\n when: (cfg) => (cfg as PivotConfig).showToolPanel === true,\n reason: 'PivotPlugin renders its tool panel in a shell host when showToolPanel is enabled',\n },\n ];\n\n /** @internal */\n readonly name = 'pivot';\n /** @internal */\n override readonly styles = styles;\n\n /** Tool panel ID for shell integration */\n static readonly PANEL_ID = 'pivot';\n\n /** @internal */\n protected override get defaultConfig(): Partial<PivotConfig> {\n return {\n active: true,\n showTotals: true,\n showGrandTotal: true,\n showToolPanel: true,\n animation: 'slide',\n };\n }\n\n // #region Internal State\n private isActive = false;\n private hasInitialized = false;\n private pivotResult: PivotResult | null = null;\n private fieldHeaderMap: Map<string, string> = new Map();\n private expandedKeys: Set<string> = new Set();\n private defaultExpanded: boolean | number | string | string[] = true;\n /** Tracks whether user has manually interacted with expand/collapse */\n private userHasToggledExpand = false;\n private originalColumns: Array<{ field: string; header: string }> = [];\n private panelContainer: HTMLElement | null = null;\n private grandTotalFooter: HTMLElement | null = null;\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n /** Cached value formatters keyed by value field name */\n private valueFormatters: Map<number, (value: number) => string> = new Map();\n /** Value fields whose column `format` already logged TBW062 (warn once per field, not per rebuild) */\n private formatErrorFields = new Set<string>();\n /** Column totals for percentage mode */\n private columnTotals: Record<string, number> = {};\n /** Current interactive sort state for pivot columns (managed by onHeaderClick) */\n private activeSortField: string | null = null;\n private activeSortDir: PivotSortDir | null = null;\n\n /**\n * Check if the plugin has valid pivot configuration (at least value fields).\n */\n private hasValidPivotConfig(): boolean {\n return (this.config.valueFields?.length ?? 0) > 0;\n }\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.isActive = false;\n this.hasInitialized = false;\n this.pivotResult = null;\n this.fieldHeaderMap.clear();\n this.originalColumns = [];\n this.panelContainer = null;\n this.cleanupGrandTotalFooter();\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.userHasToggledExpand = false;\n this.valueFormatters.clear();\n this.formatErrorFields.clear();\n this.columnTotals = {};\n this.activeSortField = null;\n this.activeSortDir = null;\n this.config.sortRows = undefined;\n }\n\n // #endregion\n\n // #region Shell Integration\n\n /** @internal */\n override getToolPanel(): ToolPanelDefinition | undefined {\n // Allow users to disable the tool panel for programmatic-only pivot\n // Check userConfig first (works before attach), then merged config\n const showToolPanel = this.config?.showToolPanel ?? this.userConfig?.showToolPanel ?? true;\n if (showToolPanel === false) {\n return undefined;\n }\n\n return {\n id: PivotPlugin.PANEL_ID,\n title: this.t('pivot.panelTitle', 'Pivot'),\n icon: '⊞',\n tooltip: 'Configure pivot table',\n order: 90,\n render: (container) => this.renderPanel(container),\n };\n }\n\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processRows(rows: readonly unknown[]): PivotDataRow[] {\n // Auto-enable pivot if config.active is true and we have valid pivot fields\n if (!this.hasInitialized && this.config.active !== false && this.hasValidPivotConfig()) {\n this.hasInitialized = true;\n this.isActive = true;\n }\n\n if (!this.isActive) {\n return [...rows] as PivotDataRow[];\n }\n\n const errors = validatePivotConfig(this.config);\n if (errors.length > 0) {\n this.warn(`Config errors: ${errors.join(', ')}`);\n return [...rows] as PivotDataRow[];\n }\n\n this.buildFieldHeaderMap();\n this.buildValueFormatters();\n this.defaultExpanded = this.config.defaultExpanded ?? true;\n\n // Build pivot first so we have the rows structure\n this.pivotResult = buildPivot(rows as PivotDataRow[], this.config);\n\n // When MultiSort is active, apply its sort model to pivot rows.\n // This overrides config.sortRows when MultiSort has active sorts.\n const multiSortConfigs = this.getMultiSortConfigs();\n if (multiSortConfigs) {\n sortPivotMulti(this.pivotResult.rows, multiSortConfigs, this.config.valueFields ?? []);\n }\n\n // Initialize expanded state with defaults if first build AND user hasn't manually toggled\n // This prevents re-expanding when user collapses all groups\n if (this.expandedKeys.size === 0 && !this.userHasToggledExpand) {\n const allKeys = getAllGroupKeys(this.pivotResult.rows);\n this.expandedKeys = resolveDefaultExpanded(this.defaultExpanded, allKeys);\n }\n\n // Cache column totals for percentage mode\n if (this.config.valueDisplayMode && this.config.valueDisplayMode !== 'raw') {\n this.columnTotals = getColumnTotals(\n this.pivotResult.rows,\n this.pivotResult.columnKeys,\n this.config.valueFields ?? [],\n );\n }\n\n // Return flattened pivot rows respecting expanded state\n const indentWidth = this.config.indentWidth ?? 20;\n const flattenedDefExpanded = this.defaultExpanded === true || this.defaultExpanded === undefined;\n const flatRows: PivotDataRow[] = flattenPivotRows(\n this.pivotResult.rows,\n this.expandedKeys,\n flattenedDefExpanded,\n ).map((pr) => ({\n __pivotRowKey: pr.rowKey,\n __pivotLabel: pr.rowLabel,\n __pivotDepth: pr.depth,\n __pivotIsGroup: pr.isGroup,\n __pivotHasChildren: Boolean(pr.children?.length),\n __pivotExpanded: this.expandedKeys.has(pr.rowKey),\n __pivotRowCount: pr.rowCount ?? 0,\n __pivotIndent: pr.depth * indentWidth,\n __pivotTotal: pr.total,\n ...pr.values,\n }));\n\n // Track which rows are newly visible (for animation)\n this.keysToAnimate.clear();\n const currentVisibleKeys = new Set<string>();\n for (const row of flatRows) {\n const key = row.__pivotRowKey as string;\n currentVisibleKeys.add(key);\n // Animate non-root rows that weren't previously visible\n if (!this.previousVisibleKeys.has(key) && (row.__pivotDepth as number) > 0) {\n this.keysToAnimate.add(key);\n }\n }\n this.previousVisibleKeys = currentVisibleKeys;\n\n // Grand total: include in row model when configured, otherwise rendered as sticky footer\n if (this.config.grandTotalInRowModel && this.config.showGrandTotal && this.pivotResult) {\n flatRows.push({\n __pivotRowKey: '__grandTotal',\n __pivotLabel: 'Grand Total',\n __pivotIsGrandTotal: true,\n __pivotDepth: 0,\n __pivotTotal: this.pivotResult.grandTotal,\n ...this.pivotResult.totals,\n });\n }\n\n return flatRows;\n }\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.isActive || !this.pivotResult) {\n return [...columns];\n }\n\n const pivotColumns: ColumnConfig[] = [];\n\n // Row label column\n const rowGroupHeaders = (this.config.rowGroupFields ?? []).map((f) => this.fieldHeaderMap.get(f) ?? f).join(' / ');\n pivotColumns.push({\n field: '__pivotLabel',\n header: rowGroupHeaders || 'Group',\n width: 200,\n sortable: true,\n });\n\n // Value columns for each column key\n const valueFields = this.config.valueFields ?? [];\n for (const colKey of this.pivotResult.columnKeys) {\n const valueKeys = createValueKeys([colKey], valueFields);\n for (let valueIndex = 0; valueIndex < valueFields.length; valueIndex++) {\n const vf = valueFields[valueIndex];\n const valueKey = valueKeys[valueIndex];\n const valueHeader = vf.header || this.fieldHeaderMap.get(vf.field) || vf.field;\n const aggLabel = typeof vf.aggFunc === 'function' ? 'custom' : vf.aggFunc;\n const formatter = this.valueFormatters.get(valueIndex);\n pivotColumns.push({\n field: valueKey,\n header: `${colKey} - ${valueHeader} (${aggLabel})`,\n width: 120,\n type: 'number',\n sortable: true,\n ...(formatter ? { format: (v: unknown) => (v != null ? formatter(Number(v)) : '') } : {}),\n } as ColumnConfig);\n }\n }\n\n // Totals column\n if (this.config.showTotals) {\n pivotColumns.push({\n field: '__pivotTotal',\n header: 'Total',\n width: 100,\n type: 'number',\n sortable: true,\n });\n }\n\n return pivotColumns;\n }\n\n /** @internal */\n override renderRow(row: Record<string, unknown>, rowEl: HTMLElement, rowIndex: number): boolean {\n const pivotRow = row as PivotRowData;\n\n // Handle grand total row in row model (when grandTotalInRowModel is true)\n if (pivotRow.__pivotIsGrandTotal) {\n return renderPivotGrandTotalRow(pivotRow, rowEl, this.gridColumns, this.translate);\n }\n\n // Handle pivot group row (has children)\n if (pivotRow.__pivotRowKey && pivotRow.__pivotHasChildren) {\n return renderPivotGroupRow(pivotRow, rowEl, {\n columns: this.gridColumns,\n rowIndex,\n onToggle: (key) => this.toggle(key),\n setIcon: (el, iconKey) => this.setIcon(el, iconKey),\n });\n }\n\n // Handle pivot leaf row (no children but in pivot mode)\n if (pivotRow.__pivotRowKey !== undefined && this.isActive) {\n return renderPivotLeafRow(pivotRow, rowEl, this.gridColumns, rowIndex);\n }\n\n // Clean up any leftover pivot styling from pooled row elements\n this.cleanupPivotStyling(rowEl);\n\n return false;\n }\n\n /**\n * Remove pivot-specific classes, attributes, and inline styles from a row element.\n * Called when pivot mode is disabled to clean up reused DOM elements.\n * Clears innerHTML so the grid's default renderer can rebuild the row.\n */\n private cleanupPivotStyling(rowEl: HTMLElement): void {\n // Check if this row was previously rendered by pivot (has pivot classes)\n const wasPivotRow =\n rowEl.classList.contains('pivot-group-row') ||\n rowEl.classList.contains('pivot-leaf-row') ||\n rowEl.classList.contains('pivot-grand-total-row');\n\n if (wasPivotRow) {\n // Remove pivot row classes and restore the default grid row class\n rowEl.classList.remove('pivot-group-row', 'pivot-leaf-row', 'pivot-grand-total-row');\n rowEl.classList.add('data-grid-row');\n\n // Remove pivot-specific attributes\n rowEl.removeAttribute('data-pivot-depth');\n\n // Clear the row content so the default renderer can rebuild it\n rowEl.innerHTML = '';\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion on pivot group rows\n if (event.key !== ' ') return;\n if (!this.isActive) return;\n // Inputs (e.g. the pivot tool panel or a custom renderer) own Space (#546).\n if (isFormFieldKeyTarget(event)) 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 pivot group rows with children\n if (!row?.__pivotIsGroup || !row.__pivotHasChildren) return;\n\n event.preventDefault();\n this.toggle(row.__pivotRowKey as string);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /** @internal */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'sort:get-sort-config') {\n // When MultiSort is driving sort state, return its translated config\n const multiConfigs = this.getMultiSortConfigs();\n if (multiConfigs) return multiConfigs;\n return this.config.sortRows ?? null;\n }\n return undefined;\n }\n\n /** @internal */\n override onHeaderClick(event: HeaderClickEvent): boolean {\n if (!this.isActive) return false;\n\n const field = event.field;\n\n // Only handle clicks on pivot-generated columns\n if (!this.isPivotField(field)) return false;\n\n // When MultiSort is present, let it handle the click (supports shift-click\n // for multi-column sort). PivotPlugin reads MultiSort's sort model in processRows.\n if (this.isMultiSortActive()) return false;\n\n // Map the clicked column to a PivotSortConfig\n const sortConfig = this.mapFieldToSortConfig(field);\n if (!sortConfig) return false;\n\n // Toggle direction: none → asc → desc → none\n const currentDir = this.activeSortDir;\n const currentField = this.activeSortField;\n let newDir: PivotSortDir | null;\n\n if (currentField !== field) {\n // Different column — start ascending\n newDir = 'asc';\n } else if (currentDir === 'asc') {\n newDir = 'desc';\n } else {\n newDir = null; // Clear sort\n }\n\n this.activeSortField = newDir ? field : null;\n this.activeSortDir = newDir;\n\n // Apply to pivot config\n if (newDir) {\n this.config.sortRows = { ...sortConfig, direction: newDir };\n } else {\n this.config.sortRows = undefined;\n }\n\n this.emit<PivotConfigChangeDetail>('pivot-config-change', {\n property: 'sortRows',\n });\n\n this.refresh();\n\n // Announce for screen readers\n const gridEl = this.gridElement;\n if (gridEl) {\n const colHeader = event.column.header ?? field;\n if (newDir) {\n announce(\n gridEl,\n getA11yMessage(gridEl, 'sortApplied', colHeader, newDir === 'asc' ? 'ascending' : 'descending'),\n );\n } else {\n announce(gridEl, getA11yMessage(gridEl, 'sortCleared'));\n }\n }\n\n return true;\n }\n\n /** Check if a field belongs to a pivot-generated column. */\n private isPivotField(field: string): boolean {\n return field === '__pivotLabel' || field === '__pivotTotal' || field.includes('|');\n }\n\n /** Check whether the MultiSort plugin is loaded alongside Pivot. */\n private isMultiSortActive(): boolean {\n const results = this.grid?.query?.('sort:get-model', null);\n return Array.isArray(results) && results.length > 0;\n }\n\n /**\n * Read MultiSort's sort model and translate entries to PivotSortConfig[].\n * Returns null when MultiSort is not present or has no active sorts.\n */\n private getMultiSortConfigs(): PivotSortConfig[] | null {\n const results = this.grid?.query?.('sort:get-model', null);\n if (!results || results.length === 0) return null;\n\n const sortModel = results[0] as Array<{ field: string; direction: 'asc' | 'desc' }>;\n if (!Array.isArray(sortModel) || sortModel.length === 0) return null;\n\n const configs: PivotSortConfig[] = [];\n for (const entry of sortModel) {\n if (!this.isPivotField(entry.field)) continue;\n const base = this.mapFieldToSortConfig(entry.field);\n if (base) {\n configs.push({ ...base, direction: entry.direction });\n }\n }\n return configs.length > 0 ? configs : null;\n }\n\n /** Map a pivot column field to the appropriate PivotSortConfig (without direction). */\n private mapFieldToSortConfig(field: string): Omit<PivotSortConfig, 'direction'> | null {\n if (field === '__pivotLabel') {\n return { by: 'label' };\n }\n if (field === '__pivotTotal') {\n return { by: 'value' };\n }\n // Value columns use format: colKey|field[|aggFunc[|n]] (from createValueKeys).\n // Preserve the full value key so sorting targets the exact pivot column\n // the user clicked, rather than collapsing distinct columns like\n // \"Q1|sales\" and \"Q2|sales\" into the same \"sales\" config.\n if (field.includes('|')) {\n return { by: 'value', valueField: field };\n }\n return null;\n }\n\n /** @internal */\n override afterRender(): void {\n // Render grand total as a sticky pinned footer when pivot is active\n // Skip when grandTotalInRowModel is true (grand total is already in the row model)\n if (this.isActive && this.config.showGrandTotal && this.pivotResult && !this.config.grandTotalInRowModel) {\n this.renderGrandTotalFooter();\n } else {\n this.cleanupGrandTotalFooter();\n }\n\n // Update sort indicators on pivot header cells.\n // When MultiSort is active it manages all indicators (including unsorted state).\n if (this.isActive && !this.isMultiSortActive()) {\n this.updateSortIndicators();\n }\n\n // Apply animations to newly visible rows\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-pivot-fade-in' : 'tbw-pivot-slide-in';\n for (const rowEl of body.querySelectorAll('.pivot-group-row, .pivot-leaf-row')) {\n const key = (rowEl as HTMLElement).dataset.pivotKey;\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 * Update sort indicator icons on pivot header cells.\n * Core's indicator rendering reads `_sortState`, which doesn't reflect pivot sort.\n * We manage indicators directly, following the same pattern as MultiSortPlugin.\n */\n private updateSortIndicators(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Derive effective sort state: interactive state takes priority,\n // then fall back to programmatic config.sortRows.\n const effectiveField = this.activeSortField;\n const effectiveDir = this.activeSortDir;\n const programmatic = !effectiveField ? this.config.sortRows : null;\n\n const headerCells = gridEl.querySelectorAll('.header-row .cell[data-field]');\n for (const cell of headerCells) {\n const field = cell.getAttribute('data-field');\n if (!field || !this.isPivotField(field)) continue;\n\n let sortDir: 'asc' | 'desc' | null = null;\n\n if (effectiveField === field) {\n // Interactive sort is active on this column\n sortDir = effectiveDir;\n } else if (programmatic) {\n // Programmatic sortRows — match by sort type\n if (programmatic.by === 'label' && field === '__pivotLabel') {\n sortDir = programmatic.direction ?? 'asc';\n } else if (programmatic.by === 'value' && field === '__pivotTotal' && !programmatic.valueField) {\n sortDir = programmatic.direction ?? 'asc';\n } else if (programmatic.by === 'value' && programmatic.valueField && field === programmatic.valueField) {\n sortDir = programmatic.direction ?? 'asc';\n }\n }\n\n this.updateSortIndicator(cell, sortDir);\n }\n }\n\n /**\n * Render the grand total row as a sticky footer pinned to the bottom.\n */\n private renderGrandTotalFooter(): void {\n if (!this.pivotResult) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Find the scroll container to append the footer\n const container =\n gridEl.querySelector('.tbw-scroll-area') ??\n gridEl.querySelector('.tbw-grid-content') ??\n gridEl.querySelector('.tbw-grid-root');\n if (!container) return;\n\n // Create footer if it doesn't exist\n if (!this.grandTotalFooter) {\n this.grandTotalFooter = document.createElement('div');\n this.grandTotalFooter.className = 'pivot-grand-total-footer';\n container.appendChild(this.grandTotalFooter);\n }\n\n // Build the row data for grand total\n const grandTotalRow: PivotRowData = {\n __pivotRowKey: '__grandTotal',\n __pivotLabel: 'Grand Total',\n __pivotIsGrandTotal: true,\n __pivotTotal: this.pivotResult.grandTotal,\n ...this.pivotResult.totals,\n };\n\n // Render the grand total row into the footer\n renderPivotGrandTotalRow(grandTotalRow, this.grandTotalFooter, this.gridColumns, this.translate);\n // Footer is outside the grid's role=grid element — use presentation role\n this.grandTotalFooter.setAttribute('role', 'presentation');\n }\n\n /**\n * Remove the grand total footer element.\n */\n private cleanupGrandTotalFooter(): void {\n if (this.grandTotalFooter) {\n this.grandTotalFooter.remove();\n this.grandTotalFooter = null;\n }\n }\n\n // #endregion\n\n // #region Expand/Collapse API\n\n toggle(key: string): void {\n this.userHasToggledExpand = true;\n const wasExpanded = this.expandedKeys.has(key);\n if (wasExpanded) {\n this.expandedKeys.delete(key);\n } else {\n this.expandedKeys.add(key);\n }\n this.emitToggle(key, !wasExpanded);\n this.requestRender();\n }\n\n /**\n * Expand a specific pivot group row, revealing its children.\n *\n * @param key - The pivot row key (hierarchical path, e.g. `'Engineering'` or `'Engineering||Frontend'`)\n */\n expand(key: string): void {\n this.userHasToggledExpand = true;\n this.expandedKeys.add(key);\n this.emitToggle(key, true);\n this.requestRender();\n }\n\n /**\n * Collapse a specific pivot group row, hiding its children.\n *\n * @param key - The pivot row key to collapse\n */\n collapse(key: string): void {\n this.userHasToggledExpand = true;\n this.expandedKeys.delete(key);\n this.emitToggle(key, false);\n this.requestRender();\n }\n\n /**\n * Expand all pivot group rows, revealing the full hierarchy.\n */\n expandAll(): void {\n this.userHasToggledExpand = true;\n this.expandAllKeys();\n this.requestRender();\n }\n\n /**\n * Collapse all pivot group rows, showing only top-level groups.\n */\n collapseAll(): void {\n this.userHasToggledExpand = true;\n this.expandedKeys.clear();\n this.requestRender();\n }\n\n /**\n * Add all group keys from the current pivot result to expandedKeys.\n */\n private expandAllKeys(): void {\n if (!this.pivotResult) return;\n const allKeys = getAllGroupKeys(this.pivotResult.rows);\n for (const key of allKeys) {\n this.expandedKeys.add(key);\n }\n }\n\n /**\n * Check whether a specific pivot group row is currently expanded.\n *\n * @param key - The pivot row key to check\n * @returns `true` if the group is expanded\n */\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n /**\n * Get all currently expanded group keys.\n *\n * @returns Array of expanded pivot row keys\n */\n getExpandedGroups(): string[] {\n return [...this.expandedKeys];\n }\n\n // #endregion\n\n // #region Public API\n\n /**\n * Enable pivot mode.\n *\n * Captures the original column set (if not already captured) and activates\n * the pivot transformation. The grid will re-render with pivot columns and rows.\n *\n * @example\n * ```ts\n * const pivot = grid.getPluginByName('pivot');\n * pivot.enablePivot();\n * ```\n */\n enablePivot(): void {\n if (this.originalColumns.length === 0) {\n this.captureOriginalColumns();\n }\n this.isActive = true;\n this.emit<PivotStateChangeDetail>('pivot-state-change', { active: true });\n this.requestRender();\n }\n\n /**\n * Disable pivot mode and restore the original grid columns.\n *\n * The grid reverts to its normal tabular layout with the original column definitions.\n */\n disablePivot(): void {\n this.isActive = false;\n this.pivotResult = null;\n this.emit<PivotStateChangeDetail>('pivot-state-change', { active: false });\n this.requestRender();\n }\n\n /**\n * Check whether pivot mode is currently active.\n *\n * @returns `true` if the grid is in pivot mode\n */\n isPivotActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Get the current pivot computation result.\n *\n * Returns the full {@link PivotResult} with rows, column keys, totals,\n * and grand total. Returns `null` if pivot is inactive or not yet computed.\n *\n * @returns The computed pivot result, or `null`\n */\n getPivotResult(): PivotResult | null {\n return this.pivotResult;\n }\n\n /**\n * Set the fields used for row grouping (vertical axis).\n *\n * Triggers a re-render with the new pivot structure.\n *\n * @param fields - Array of field names to group rows by\n *\n * @example\n * ```ts\n * const pivot = grid.getPluginByName('pivot');\n * pivot.setRowGroupFields(['region', 'department']);\n * ```\n */\n setRowGroupFields(fields: string[]): void {\n this.config.rowGroupFields = fields;\n this.emitConfigChange('rowGroupFields');\n this.refresh();\n }\n\n /**\n * Set the fields whose unique values become column headers (horizontal axis).\n *\n * Triggers a re-render with the new pivot column structure.\n *\n * @param fields - Array of field names for column grouping\n *\n * @example\n * ```ts\n * const pivot = grid.getPluginByName('pivot');\n * pivot.setColumnGroupFields(['quarter']);\n * ```\n */\n setColumnGroupFields(fields: string[]): void {\n this.config.columnGroupFields = fields;\n this.emitConfigChange('columnGroupFields');\n this.refresh();\n }\n\n /**\n * Set the value fields and their aggregation functions.\n *\n * Each value field defines which data field to aggregate and how.\n * Triggers a re-render with the new aggregation.\n *\n * @param fields - Array of {@link PivotValueField} definitions\n *\n * @example\n * ```ts\n * const pivot = grid.getPluginByName('pivot');\n * pivot.setValueFields([\n * { field: 'revenue', aggFunc: 'sum', header: 'Total Revenue' },\n * { field: 'orders', aggFunc: 'count', header: '# Orders' },\n * ]);\n * ```\n */\n setValueFields(fields: PivotValueField[]): void {\n this.config.valueFields = fields;\n this.emitConfigChange('valueFields');\n this.refresh();\n }\n\n /**\n * Force re-computation of the pivot result.\n *\n * Clears the cached pivot result and triggers a full re-render.\n * Call this after changing the underlying row data.\n */\n refresh(): void {\n this.pivotResult = null;\n this.requestRender();\n }\n\n // #endregion\n\n // #region Tool Panel API\n\n /**\n * Resolve the shell plugin (#370). The pivot tool panel lives in the shell,\n * so panel open/close/toggle is delegated to the ShellPlugin instance rather\n * than core `grid.*` shell delegates (removed in v3).\n */\n #shell() {\n return this.grid.getPluginByName('shell');\n }\n\n /**\n * Show the pivot tool panel.\n * Opens the tool panel and ensures this section is expanded.\n */\n showPanel(): void {\n const shell = this.#shell();\n shell?.openToolPanel();\n // Ensure our section is expanded\n if (!shell?.expandedToolPanelSections.includes(PivotPlugin.PANEL_ID)) {\n shell?.toggleToolPanelSection(PivotPlugin.PANEL_ID);\n }\n }\n\n /**\n * Hide the tool panel.\n */\n hidePanel(): void {\n this.#shell()?.closeToolPanel();\n }\n\n /**\n * Toggle the pivot tool panel section.\n */\n togglePanel(): void {\n const shell = this.#shell();\n // If tool panel is closed, open it first\n if (!shell?.isToolPanelOpen) {\n shell?.openToolPanel();\n }\n shell?.toggleToolPanelSection(PivotPlugin.PANEL_ID);\n }\n\n /**\n * Check if the pivot panel section is currently expanded.\n */\n isPanelVisible(): boolean {\n const shell = this.#shell();\n return !!shell?.isToolPanelOpen && shell.expandedToolPanelSections.includes(PivotPlugin.PANEL_ID);\n }\n\n // #endregion\n\n // #region Private Helpers\n\n private get gridColumns(): ColumnConfig[] {\n return (this.grid.columns ?? []) as ColumnConfig[];\n }\n\n /**\n * Refresh pivot and update tool panel if active.\n */\n private refreshIfActive(): void {\n if (this.isActive) this.refresh();\n this.refreshPanel();\n }\n\n private buildFieldHeaderMap(): void {\n const availableFields = this.getAvailableFields();\n this.fieldHeaderMap.clear();\n for (const field of availableFields) {\n this.fieldHeaderMap.set(field.field, field.header);\n }\n }\n\n private getAvailableFields(): FieldInfo[] {\n if (this.originalColumns.length > 0) {\n return this.originalColumns;\n }\n return this.captureOriginalColumns();\n }\n\n private captureOriginalColumns(): FieldInfo[] {\n try {\n const columns = this.grid.getAllColumns?.() ?? this.grid.columns ?? [];\n this.originalColumns = columns\n .filter((col: { field: string }) => !col.field.startsWith('__pivot'))\n .map((col: { field: string; header?: string }) => ({\n field: col.field,\n header: col.header ?? col.field,\n }));\n return this.originalColumns;\n } catch {\n return [];\n }\n }\n\n private renderPanel(container: HTMLElement): (() => void) | void {\n this.panelContainer = container;\n\n if (this.originalColumns.length === 0) {\n this.captureOriginalColumns();\n }\n\n const callbacks: PanelCallbacks = {\n onTogglePivot: (enabled) => {\n if (enabled) {\n this.enablePivot();\n } else {\n this.disablePivot();\n }\n this.refreshPanel();\n },\n onAddFieldToZone: (field, zone) => {\n this.addFieldToZone(field, zone);\n this.emitConfigChange(zone === 'rowGroups' ? 'rowGroupFields' : 'columnGroupFields', field, zone);\n },\n onRemoveFieldFromZone: (field, zone) => {\n this.removeFieldFromZone(field, zone);\n this.emitConfigChange(zone === 'rowGroups' ? 'rowGroupFields' : 'columnGroupFields', field, zone);\n },\n onReorderFieldInZone: (field, zone, newIndex) => {\n this.reorderFieldInZone(field, zone, newIndex);\n this.emitConfigChange(zone === 'rowGroups' ? 'rowGroupFields' : 'columnGroupFields', field, zone);\n },\n onMoveFieldBetweenZones: (field, fromZone, toZone) => {\n this.moveFieldBetweenZones(field, fromZone, toZone);\n this.emitConfigChange(toZone === 'rowGroups' ? 'rowGroupFields' : 'columnGroupFields', field, toZone);\n },\n onAddValueField: (field, aggFunc) => {\n this.addValueField(field, aggFunc);\n this.emitConfigChange('valueFields', field, 'values');\n },\n onRemoveValueField: (field) => {\n this.removeValueField(field);\n this.emitConfigChange('valueFields', field, 'values');\n },\n onUpdateValueAggFunc: (field, aggFunc) => {\n this.updateValueAggFunc(field, aggFunc);\n this.emitConfigChange('valueFields', field, 'values');\n },\n onOptionChange: (option, value) => {\n this.config[option] = value;\n this.emitConfigChange(option);\n if (this.isActive) this.refresh();\n },\n getAvailableFields: () => this.getAvailableFields(),\n };\n\n return renderPivotPanel(container, this.config, this.isActive, callbacks, this.translate);\n }\n\n private refreshPanel(): void {\n if (!this.panelContainer) return;\n this.panelContainer.innerHTML = '';\n this.renderPanel(this.panelContainer);\n }\n\n private addFieldToZone(field: string, zoneType: 'rowGroups' | 'columnGroups'): void {\n if (zoneType === 'rowGroups') {\n const current = this.config.rowGroupFields ?? [];\n if (!current.includes(field)) {\n this.config.rowGroupFields = [...current, field];\n }\n } else {\n const current = this.config.columnGroupFields ?? [];\n if (!current.includes(field)) {\n this.config.columnGroupFields = [...current, field];\n }\n }\n\n this.removeFromOtherZones(field, zoneType);\n this.refreshIfActive();\n }\n\n private removeFieldFromZone(field: string, zoneType: 'rowGroups' | 'columnGroups'): void {\n if (zoneType === 'rowGroups') {\n this.config.rowGroupFields = (this.config.rowGroupFields ?? []).filter((f) => f !== field);\n } else {\n this.config.columnGroupFields = (this.config.columnGroupFields ?? []).filter((f) => f !== field);\n }\n\n this.refreshIfActive();\n }\n\n private reorderFieldInZone(field: string, zoneType: 'rowGroups' | 'columnGroups', newIndex: number): void {\n const fields =\n zoneType === 'rowGroups' ? [...(this.config.rowGroupFields ?? [])] : [...(this.config.columnGroupFields ?? [])];\n const oldIndex = fields.indexOf(field);\n if (oldIndex === -1 || oldIndex === newIndex) return;\n fields.splice(oldIndex, 1);\n fields.splice(newIndex > oldIndex ? newIndex - 1 : newIndex, 0, field);\n if (zoneType === 'rowGroups') {\n this.config.rowGroupFields = fields;\n } else {\n this.config.columnGroupFields = fields;\n }\n this.refreshIfActive();\n }\n\n private moveFieldBetweenZones(\n field: string,\n fromZone: 'rowGroups' | 'columnGroups',\n toZone: 'rowGroups' | 'columnGroups',\n ): void {\n // Remove from source\n if (fromZone === 'rowGroups') {\n this.config.rowGroupFields = (this.config.rowGroupFields ?? []).filter((f) => f !== field);\n } else {\n this.config.columnGroupFields = (this.config.columnGroupFields ?? []).filter((f) => f !== field);\n }\n // Add to target\n if (toZone === 'rowGroups') {\n this.config.rowGroupFields = [...(this.config.rowGroupFields ?? []), field];\n } else {\n this.config.columnGroupFields = [...(this.config.columnGroupFields ?? []), field];\n }\n this.refreshIfActive();\n }\n\n private removeFromOtherZones(field: string, targetZone: 'rowGroups' | 'columnGroups' | 'values'): void {\n if (targetZone !== 'rowGroups') {\n this.config.rowGroupFields = (this.config.rowGroupFields ?? []).filter((f) => f !== field);\n }\n if (targetZone !== 'columnGroups') {\n this.config.columnGroupFields = (this.config.columnGroupFields ?? []).filter((f) => f !== field);\n }\n if (targetZone !== 'values') {\n this.config.valueFields = (this.config.valueFields ?? []).filter((v) => v.field !== field);\n }\n }\n\n private addValueField(field: string, aggFunc: AggFunc): void {\n const current = this.config.valueFields ?? [];\n if (!current.some((v) => v.field === field)) {\n this.config.valueFields = [...current, { field, aggFunc }];\n }\n\n this.removeFromOtherZones(field, 'values');\n this.refreshIfActive();\n }\n\n private removeValueField(field: string): void {\n this.config.valueFields = (this.config.valueFields ?? []).filter((v) => v.field !== field);\n this.refreshIfActive();\n }\n\n private updateValueAggFunc(field: string, aggFunc: AggFunc): void {\n const valueFields = this.config.valueFields ?? [];\n const fieldIndex = valueFields.findIndex((v) => v.field === field);\n if (fieldIndex >= 0) {\n valueFields[fieldIndex] = { ...valueFields[fieldIndex], aggFunc };\n this.config.valueFields = [...valueFields];\n }\n if (this.isActive) this.refresh();\n }\n\n // #region Event Helpers\n\n private emitToggle(key: string, expanded: boolean): void {\n const flatRow = this.rows.find((r) => (r as PivotDataRow).__pivotRowKey === key) as PivotDataRow | undefined;\n this.emit<PivotToggleDetail>('pivot-toggle', {\n key,\n expanded,\n label: (flatRow?.__pivotLabel as string) ?? key,\n depth: (flatRow?.__pivotDepth as number) ?? 0,\n });\n }\n\n private emitConfigChange(property: string, field?: string, zone?: 'rowGroups' | 'columnGroups' | 'values'): void {\n this.emit<PivotConfigChangeDetail>('pivot-config-change', { property, field, zone });\n }\n\n // #endregion\n\n // #region Value Formatting\n\n /**\n * Build value formatters from PivotValueField.format or original column format.\n * Keyed by `valueFields` index — the same field may appear multiple times with\n * different aggregators and formatters.\n */\n private buildValueFormatters(): void {\n this.valueFormatters.clear();\n const valueFields = this.config.valueFields ?? [];\n for (let index = 0; index < valueFields.length; index++) {\n const vf = valueFields[index];\n if (vf.format) {\n this.valueFormatters.set(index, vf.format);\n } else {\n // Check if the original column had a format function\n const origCol = this.originalColumns.find((c) => c.field === vf.field);\n if (origCol) {\n const allCols = this.grid.getAllColumns?.() ?? this.grid.columns ?? [];\n const col = allCols.find((c: { field: string }) => c.field === vf.field) as\n { field: string; format?: (value: unknown, row: unknown) => string } | undefined;\n if (col?.format) {\n const fmt = col.format;\n // Aggregates have no source row, so `row` is undefined; a row-reading\n // formatter falls back to the raw number (warned once per value field).\n const onError = (error: unknown) => {\n if (this.formatErrorFields.has(vf.field)) return;\n this.formatErrorFields.add(vf.field);\n this.warn(FORMAT_ERROR, `Format error in column '${vf.field}' while formatting pivot values: ${error}`);\n };\n this.valueFormatters.set(index, (v: number) => tryFormat(fmt, v, undefined, onError) ?? String(v));\n }\n }\n }\n }\n }\n\n // #endregion\n}\n"],"mappings":"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"}