@toolbox-web/grid 1.28.1 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/all.js +2 -2
  2. package/all.js.map +1 -1
  3. package/custom-elements.json +43 -1
  4. package/index.js +1 -1
  5. package/index.js.map +1 -1
  6. package/lib/core/constants.d.ts +1 -7
  7. package/lib/core/grid.d.ts +40 -3
  8. package/lib/core/internal/render-scheduler.d.ts +3 -1
  9. package/lib/core/plugin/base-plugin.d.ts +1 -1
  10. package/lib/core/plugin/plugin-manager.d.ts +2 -0
  11. package/lib/core/types.d.ts +59 -0
  12. package/lib/features/clipboard.d.ts +2 -0
  13. package/lib/features/clipboard.js.map +1 -1
  14. package/lib/features/column-virtualization.d.ts +2 -0
  15. package/lib/features/column-virtualization.js.map +1 -1
  16. package/lib/features/context-menu.d.ts +2 -0
  17. package/lib/features/context-menu.js.map +1 -1
  18. package/lib/features/editing.d.ts +2 -0
  19. package/lib/features/editing.js.map +1 -1
  20. package/lib/features/export.d.ts +2 -0
  21. package/lib/features/export.js.map +1 -1
  22. package/lib/features/filtering.d.ts +2 -0
  23. package/lib/features/filtering.js.map +1 -1
  24. package/lib/features/grouping-columns.d.ts +2 -0
  25. package/lib/features/grouping-columns.js.map +1 -1
  26. package/lib/features/grouping-rows.d.ts +2 -0
  27. package/lib/features/grouping-rows.js.map +1 -1
  28. package/lib/features/master-detail.d.ts +2 -0
  29. package/lib/features/master-detail.js.map +1 -1
  30. package/lib/features/multi-sort.d.ts +2 -0
  31. package/lib/features/multi-sort.js.map +1 -1
  32. package/lib/features/pinned-columns.d.ts +2 -1
  33. package/lib/features/pinned-columns.js.map +1 -1
  34. package/lib/features/pinned-rows.d.ts +2 -0
  35. package/lib/features/pinned-rows.js.map +1 -1
  36. package/lib/features/pivot.d.ts +2 -0
  37. package/lib/features/pivot.js.map +1 -1
  38. package/lib/features/print.d.ts +2 -0
  39. package/lib/features/print.js.map +1 -1
  40. package/lib/features/reorder-columns.d.ts +2 -0
  41. package/lib/features/reorder-columns.js.map +1 -1
  42. package/lib/features/reorder-rows.d.ts +2 -0
  43. package/lib/features/reorder-rows.js.map +1 -1
  44. package/lib/features/responsive.d.ts +2 -0
  45. package/lib/features/responsive.js.map +1 -1
  46. package/lib/features/selection.d.ts +2 -0
  47. package/lib/features/selection.js.map +1 -1
  48. package/lib/features/server-side.d.ts +2 -0
  49. package/lib/features/server-side.js.map +1 -1
  50. package/lib/features/tooltip.d.ts +2 -0
  51. package/lib/features/tooltip.js.map +1 -1
  52. package/lib/features/tree.d.ts +2 -0
  53. package/lib/features/tree.js.map +1 -1
  54. package/lib/features/undo-redo.d.ts +2 -0
  55. package/lib/features/undo-redo.js.map +1 -1
  56. package/lib/features/visibility.d.ts +2 -0
  57. package/lib/features/visibility.js.map +1 -1
  58. package/lib/plugins/clipboard/ClipboardPlugin.d.ts +0 -21
  59. package/lib/plugins/clipboard/index.js.map +1 -1
  60. package/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.d.ts +0 -16
  61. package/lib/plugins/column-virtualization/index.js.map +1 -1
  62. package/lib/plugins/context-menu/index.js.map +1 -1
  63. package/lib/plugins/editing/index.js +1 -1
  64. package/lib/plugins/editing/index.js.map +1 -1
  65. package/lib/plugins/export/ExportPlugin.d.ts +0 -18
  66. package/lib/plugins/export/index.js.map +1 -1
  67. package/lib/plugins/filtering/FilteringPlugin.d.ts +0 -21
  68. package/lib/plugins/filtering/filter-model.d.ts +8 -1
  69. package/lib/plugins/filtering/index.js +1 -1
  70. package/lib/plugins/filtering/index.js.map +1 -1
  71. package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts +0 -16
  72. package/lib/plugins/grouping-columns/grouping-columns.d.ts +23 -3
  73. package/lib/plugins/grouping-columns/index.js +1 -1
  74. package/lib/plugins/grouping-columns/index.js.map +1 -1
  75. package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts +0 -23
  76. package/lib/plugins/grouping-rows/index.js +2 -2
  77. package/lib/plugins/grouping-rows/index.js.map +1 -1
  78. package/lib/plugins/master-detail/MasterDetailPlugin.d.ts +0 -22
  79. package/lib/plugins/master-detail/index.js.map +1 -1
  80. package/lib/plugins/multi-sort/MultiSortPlugin.d.ts +0 -18
  81. package/lib/plugins/multi-sort/index.js +1 -1
  82. package/lib/plugins/multi-sort/index.js.map +1 -1
  83. package/lib/plugins/multi-sort/multi-sort.d.ts +7 -0
  84. package/lib/plugins/pinned-columns/index.js +1 -1
  85. package/lib/plugins/pinned-columns/index.js.map +1 -1
  86. package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts +0 -11
  87. package/lib/plugins/pinned-rows/index.js.map +1 -1
  88. package/lib/plugins/pivot/PivotPlugin.d.ts +0 -24
  89. package/lib/plugins/pivot/index.js +1 -1
  90. package/lib/plugins/pivot/index.js.map +1 -1
  91. package/lib/plugins/print/PrintPlugin.d.ts +2 -21
  92. package/lib/plugins/print/index.js.map +1 -1
  93. package/lib/plugins/reorder-columns/ReorderPlugin.d.ts +0 -7
  94. package/lib/plugins/reorder-columns/index.js +1 -1
  95. package/lib/plugins/reorder-columns/index.js.map +1 -1
  96. package/lib/plugins/reorder-rows/RowReorderPlugin.d.ts +1 -13
  97. package/lib/plugins/reorder-rows/index.js +1 -1
  98. package/lib/plugins/reorder-rows/index.js.map +1 -1
  99. package/lib/plugins/responsive/index.js +1 -1
  100. package/lib/plugins/responsive/index.js.map +1 -1
  101. package/lib/plugins/selection/index.js +1 -1
  102. package/lib/plugins/selection/index.js.map +1 -1
  103. package/lib/plugins/server-side/ServerSidePlugin.d.ts +0 -16
  104. package/lib/plugins/server-side/index.js.map +1 -1
  105. package/lib/plugins/tooltip/index.js.map +1 -1
  106. package/lib/plugins/tree/TreePlugin.d.ts +0 -22
  107. package/lib/plugins/tree/index.js +1 -1
  108. package/lib/plugins/tree/index.js.map +1 -1
  109. package/lib/plugins/undo-redo/UndoRedoPlugin.d.ts +0 -19
  110. package/lib/plugins/undo-redo/index.js +1 -1
  111. package/lib/plugins/undo-redo/index.js.map +1 -1
  112. package/lib/plugins/visibility/VisibilityPlugin.d.ts +0 -16
  113. package/lib/plugins/visibility/index.js +1 -1
  114. package/lib/plugins/visibility/index.js.map +1 -1
  115. package/package.json +1 -1
  116. package/themes/dg-theme-material.css +5 -0
  117. package/umd/grid.all.umd.js +1 -1
  118. package/umd/grid.all.umd.js.map +1 -1
  119. package/umd/grid.umd.js +1 -1
  120. package/umd/grid.umd.js.map +1 -1
  121. package/umd/plugins/clipboard.umd.js.map +1 -1
  122. package/umd/plugins/column-virtualization.umd.js.map +1 -1
  123. package/umd/plugins/export.umd.js.map +1 -1
  124. package/umd/plugins/filtering.umd.js +1 -1
  125. package/umd/plugins/filtering.umd.js.map +1 -1
  126. package/umd/plugins/grouping-columns.umd.js +1 -1
  127. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  128. package/umd/plugins/grouping-rows.umd.js +1 -1
  129. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  130. package/umd/plugins/master-detail.umd.js.map +1 -1
  131. package/umd/plugins/multi-sort.umd.js +1 -1
  132. package/umd/plugins/multi-sort.umd.js.map +1 -1
  133. package/umd/plugins/pinned-columns.umd.js +1 -1
  134. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  135. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  136. package/umd/plugins/pivot.umd.js +1 -1
  137. package/umd/plugins/pivot.umd.js.map +1 -1
  138. package/umd/plugins/print.umd.js.map +1 -1
  139. package/umd/plugins/reorder-columns.umd.js +1 -1
  140. package/umd/plugins/reorder-columns.umd.js.map +1 -1
  141. package/umd/plugins/reorder-rows.umd.js +1 -1
  142. package/umd/plugins/reorder-rows.umd.js.map +1 -1
  143. package/umd/plugins/selection.umd.js +1 -1
  144. package/umd/plugins/selection.umd.js.map +1 -1
  145. package/umd/plugins/server-side.umd.js.map +1 -1
  146. package/umd/plugins/tree.umd.js +1 -1
  147. package/umd/plugins/tree.umd.js.map +1 -1
  148. package/umd/plugins/undo-redo.umd.js +1 -1
  149. package/umd/plugins/undo-redo.umd.js.map +1 -1
  150. package/umd/plugins/visibility.umd.js +1 -1
  151. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"pinned-rows.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/pinned-rows/pinned-rows.ts","../../../../../libs/grid/src/lib/plugins/pinned-rows/PinnedRowsPlugin.ts"],"sourcesContent":["/**\n * Status Bar Rendering Logic\n *\n * Pure functions for creating and updating the status bar UI.\n * Includes both info bar and aggregation row rendering.\n */\n\nimport { getAggregator } from '../../core/internal/aggregators';\nimport type { ColumnConfig } from '../../core/types';\nimport type {\n AggregationRowConfig,\n AggregatorConfig,\n AggregatorDefinition,\n PinnedRowsConfig,\n PinnedRowsContext,\n PinnedRowsPanel,\n} from './types';\n\n/**\n * Check if an aggregator definition is a full config object (with aggFunc and optional formatter).\n */\nfunction isAggregatorConfig(def: AggregatorDefinition): def is AggregatorConfig {\n return typeof def === 'object' && def !== null && 'aggFunc' in def;\n}\n\n/**\n * Creates the info bar DOM element with all configured panels.\n *\n * @param config - The status bar configuration\n * @param context - The current grid context for rendering\n * @returns The complete info bar element\n */\nexport function createInfoBarElement(config: PinnedRowsConfig, context: PinnedRowsContext): HTMLElement {\n const pinnedRows = document.createElement('div');\n pinnedRows.className = 'tbw-pinned-rows';\n pinnedRows.setAttribute('role', 'presentation');\n pinnedRows.setAttribute('aria-live', 'polite');\n\n const left = document.createElement('div');\n left.className = 'tbw-pinned-rows-left';\n\n const center = document.createElement('div');\n center.className = 'tbw-pinned-rows-center';\n\n const right = document.createElement('div');\n right.className = 'tbw-pinned-rows-right';\n\n // Default panels - row count\n if (config.showRowCount !== false) {\n const rowCount = document.createElement('span');\n rowCount.className = 'tbw-status-panel tbw-status-panel-row-count';\n rowCount.textContent = `Total: ${context.totalRows} rows`;\n left.appendChild(rowCount);\n }\n\n // Filtered count panel (only shows when filter is active)\n if (config.showFilteredCount && context.filteredRows !== context.totalRows) {\n const filteredCount = document.createElement('span');\n filteredCount.className = 'tbw-status-panel tbw-status-panel-filtered-count';\n filteredCount.textContent = `Filtered: ${context.filteredRows}`;\n left.appendChild(filteredCount);\n }\n\n // Selected count panel (only shows when rows are selected)\n if (config.showSelectedCount && context.selectedRows > 0) {\n const selectedCount = document.createElement('span');\n selectedCount.className = 'tbw-status-panel tbw-status-panel-selected-count';\n selectedCount.textContent = `Selected: ${context.selectedRows}`;\n right.appendChild(selectedCount);\n }\n\n // Render custom panels\n if (config.customPanels) {\n for (const panel of config.customPanels) {\n const panelEl = renderCustomPanel(panel, context);\n switch (panel.position) {\n case 'left':\n left.appendChild(panelEl);\n break;\n case 'center':\n center.appendChild(panelEl);\n break;\n case 'right':\n right.appendChild(panelEl);\n break;\n }\n }\n }\n\n pinnedRows.appendChild(left);\n pinnedRows.appendChild(center);\n pinnedRows.appendChild(right);\n\n return pinnedRows;\n}\n\n/**\n * Creates a container for aggregation rows at top or bottom.\n *\n * @param position - 'top' or 'bottom'\n * @returns The container element\n */\nexport function createAggregationContainer(position: 'top' | 'bottom'): HTMLElement {\n const container = document.createElement('div');\n container.className = `tbw-aggregation-rows tbw-aggregation-rows-${position}`;\n // Use presentation role since aggregation rows are outside the role=\"grid\" element for layout reasons\n container.setAttribute('role', 'presentation');\n return container;\n}\n\n/**\n * Renders aggregation rows into a container.\n *\n * @param container - The container to render into\n * @param rows - Aggregation row configurations\n * @param columns - Current column configuration\n * @param dataRows - Current row data for aggregation calculations\n * @param globalFullWidth - Global fullWidth default from PinnedRowsConfig (default: false)\n */\nexport function renderAggregationRows(\n container: HTMLElement,\n rows: AggregationRowConfig[],\n columns: ColumnConfig[],\n dataRows: unknown[],\n globalFullWidth = false,\n): void {\n container.innerHTML = '';\n\n for (const rowConfig of rows) {\n const rowEl = document.createElement('div');\n rowEl.className = 'tbw-aggregation-row';\n // Use presentation role since aggregation rows are outside the role=\"grid\" element\n rowEl.setAttribute('role', 'presentation');\n if (rowConfig.id) {\n rowEl.setAttribute('data-aggregation-id', rowConfig.id);\n }\n\n // Per-row fullWidth overrides global default\n const isFullWidth = rowConfig.fullWidth ?? globalFullWidth;\n\n if (isFullWidth) {\n renderFullWidthAggregationRow(rowEl, rowConfig, columns, dataRows);\n } else {\n renderPerColumnAggregationRow(rowEl, rowConfig, columns, dataRows);\n }\n\n container.appendChild(rowEl);\n }\n}\n\n/**\n * Renders a full-width aggregation row: single spanning cell with label and inline aggregated values.\n */\nfunction renderFullWidthAggregationRow(\n rowEl: HTMLElement,\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell tbw-aggregation-cell-full';\n cell.style.gridColumn = '1 / -1';\n\n // Label (static string or dynamic function)\n const labelValue = typeof rowConfig.label === 'function' ? rowConfig.label(dataRows, columns) : rowConfig.label;\n if (labelValue) {\n const labelSpan = document.createElement('span');\n labelSpan.className = 'tbw-aggregation-label';\n labelSpan.textContent = labelValue;\n cell.appendChild(labelSpan);\n }\n\n // Inline aggregated values\n const aggregatesContainer = renderInlineAggregates(rowConfig, columns, dataRows);\n if (aggregatesContainer) {\n cell.appendChild(aggregatesContainer);\n }\n\n // If nothing was added (no label, no aggregates), ensure cell is empty but present\n rowEl.appendChild(cell);\n}\n\n/**\n * Renders per-column aggregation cells aligned to the grid template.\n */\nfunction renderPerColumnAggregationRow(\n rowEl: HTMLElement,\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\n for (const col of columns) {\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell';\n cell.setAttribute('data-field', col.field);\n\n const { value, formatter } = resolveAggregatedValue(rowConfig, col, dataRows);\n\n if (value != null) {\n cell.textContent = formatter ? formatter(value, col.field, col) : String(value);\n } else {\n cell.textContent = '';\n }\n rowEl.appendChild(cell);\n }\n\n // Overlay label: positioned at the left edge, independent of column alignment\n const labelValue = typeof rowConfig.label === 'function' ? rowConfig.label(dataRows, columns) : rowConfig.label;\n if (labelValue) {\n const labelEl = document.createElement('span');\n labelEl.className = 'tbw-aggregation-label';\n labelEl.textContent = labelValue;\n rowEl.appendChild(labelEl);\n }\n}\n\n/**\n * Resolves the aggregated value for a single column in an aggregation row.\n * Returns the computed value and an optional formatter function.\n */\nfunction resolveAggregatedValue(\n rowConfig: AggregationRowConfig,\n col: ColumnConfig,\n dataRows: unknown[],\n): { value: unknown; formatter?: (value: unknown, field: string, column?: ColumnConfig) => string } {\n let value: unknown;\n let formatter: ((value: unknown, field: string, column?: ColumnConfig) => string) | undefined;\n\n // Check for aggregator first\n const aggDef = rowConfig.aggregators?.[col.field];\n if (aggDef) {\n if (isAggregatorConfig(aggDef)) {\n const aggFn = getAggregator(aggDef.aggFunc);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n formatter = aggDef.formatter;\n } else {\n const aggFn = getAggregator(aggDef);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n }\n } else if (rowConfig.cells && Object.prototype.hasOwnProperty.call(rowConfig.cells, col.field)) {\n const staticVal = rowConfig.cells[col.field];\n if (typeof staticVal === 'function') {\n value = staticVal(dataRows, col.field, col);\n } else {\n value = staticVal;\n }\n }\n\n return { value, formatter };\n}\n\n/**\n * Renders inline aggregate values for a full-width aggregation row.\n * Returns a container element with aggregate spans, or null if no aggregates are defined.\n */\nfunction renderInlineAggregates(\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): HTMLElement | null {\n // Collect fields that have aggregators or cell values\n const hasAggregators = rowConfig.aggregators && Object.keys(rowConfig.aggregators).length > 0;\n const hasCells = rowConfig.cells && Object.keys(rowConfig.cells).length > 0;\n if (!hasAggregators && !hasCells) return null;\n\n const container = document.createElement('span');\n container.className = 'tbw-aggregation-aggregates';\n\n for (const col of columns) {\n const { value, formatter } = resolveAggregatedValue(rowConfig, col, dataRows);\n if (value != null) {\n const span = document.createElement('span');\n span.className = 'tbw-aggregation-aggregate';\n span.setAttribute('data-field', col.field);\n const header = col.header ?? col.field;\n const displayValue = formatter ? formatter(value, col.field, col) : String(value);\n span.textContent = `${header}: ${displayValue}`;\n container.appendChild(span);\n }\n }\n\n return container.children.length > 0 ? container : null;\n}\n\n/**\n * Renders a custom panel element.\n *\n * @param panel - The panel definition\n * @param context - The current grid context\n * @returns The panel DOM element\n */\nfunction renderCustomPanel(panel: PinnedRowsPanel, context: PinnedRowsContext): HTMLElement {\n const panelEl = document.createElement('div');\n panelEl.className = 'tbw-status-panel tbw-status-panel-custom';\n panelEl.id = `status-panel-${panel.id}`;\n\n const content = panel.render(context);\n\n if (typeof content === 'string') {\n panelEl.innerHTML = content;\n } else {\n panelEl.appendChild(content);\n }\n\n return panelEl;\n}\n\n/**\n * Builds the status bar context from grid state and plugin states.\n *\n * @param rows - Current row data\n * @param columns - Current column configuration\n * @param grid - Grid element reference\n * @param selectionState - Optional selection plugin state\n * @param filterState - Optional filtering plugin state\n * @returns The status bar context\n */\nexport function buildContext(\n rows: unknown[],\n columns: unknown[],\n grid: HTMLElement,\n selectionState?: { selected: Set<number> } | null,\n filterState?: { cachedResult: unknown[] | null } | null,\n): PinnedRowsContext {\n return {\n totalRows: rows.length,\n filteredRows: filterState?.cachedResult?.length ?? rows.length,\n selectedRows: selectionState?.selected?.size ?? 0,\n columns: columns as PinnedRowsContext['columns'],\n rows,\n grid,\n };\n}\n\n// Keep old name as alias for backwards compatibility\nexport const createPinnedRowsElement = createInfoBarElement;\n","/**\n * Pinned Rows Plugin (Class-based)\n *\n * Adds info bars and aggregation rows to the grid.\n * - Info bar: Shows row counts, selection info, and custom panels\n * - Aggregation rows: Footer/header rows with computed values (sum, avg, etc.)\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { buildContext, createAggregationContainer, createInfoBarElement, renderAggregationRows } from './pinned-rows';\nimport styles from './pinned-rows.css?inline';\nimport type { AggregationRowConfig, PinnedRowsConfig, PinnedRowsContext, PinnedRowsPanel } from './types';\n\n/**\n * Pinned Rows (Status Bar) Plugin for tbw-grid\n *\n * Creates fixed status bars at the top or bottom of the grid for displaying aggregations,\n * row counts, or custom content. Think of it as the \"totals row\" you'd see in a spreadsheet—\n * always visible regardless of scroll position.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedRowsPlugin } from '@toolbox-web/grid/plugins/pinned-rows';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `position` | `'top' \\| 'bottom'` | `'bottom'` | Status bar position |\n * | `showRowCount` | `boolean` | `true` | Show total row count |\n * | `showSelectedCount` | `boolean` | `true` | Show selected row count |\n * | `showFilteredCount` | `boolean` | `true` | Show filtered row count |\n * | `fullWidth` | `boolean` | `false` | Default fullWidth for aggregation rows |\n * | `aggregationRows` | `AggregationRowConfig[]` | - | Aggregation row configs |\n *\n * ## Built-in Aggregation Functions\n *\n * | Function | Description |\n * |----------|-------------|\n * | `sum` | Sum of values |\n * | `avg` | Average of values |\n * | `count` | Count of rows |\n * | `min` | Minimum value |\n * | `max` | Maximum value |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-rows-bg` | `var(--tbw-color-panel-bg)` | Status bar background |\n * | `--tbw-pinned-rows-border` | `var(--tbw-color-border)` | Status bar border |\n *\n * @example Status Bar with Aggregation\n * ```ts\n * import '@toolbox-web/grid';\n * import { PinnedRowsPlugin } from '@toolbox-web/grid/plugins/pinned-rows';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'product', header: 'Product' },\n * { field: 'quantity', header: 'Qty', type: 'number' },\n * { field: 'price', header: 'Price', type: 'currency' },\n * ],\n * plugins: [\n * new PinnedRowsPlugin({\n * position: 'bottom',\n * showRowCount: true,\n * aggregationRows: [\n * {\n * id: 'totals',\n * aggregators: { quantity: 'sum', price: 'sum' },\n * cells: { product: 'Totals:' },\n * },\n * ],\n * }),\n * ],\n * };\n * ```\n *\n * @see {@link PinnedRowsConfig} for all configuration options\n * @see {@link AggregationRowConfig} for aggregation row structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedRowsPlugin extends BaseGridPlugin<PinnedRowsConfig> {\n /** @internal */\n readonly name = 'pinnedRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<PinnedRowsConfig> {\n return {\n position: 'bottom',\n showRowCount: true,\n showSelectedCount: true,\n showFilteredCount: true,\n };\n }\n\n // #region Internal State\n private infoBarElement: HTMLElement | null = null;\n private topAggregationContainer: HTMLElement | null = null;\n private bottomAggregationContainer: HTMLElement | null = null;\n private footerWrapper: HTMLElement | null = null;\n // #endregion\n\n // #region Lifecycle\n /** @internal */\n override detach(): void {\n if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n // #endregion\n\n // #region Hooks\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Use .tbw-scroll-area so footer is inside the horizontal scroll area,\n // otherwise fall back to .tbw-grid-content or root container\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 // Clear orphaned element references if they were removed from the DOM\n // (e.g., by buildGridDOMIntoShadow calling replaceChildren())\n // We check if the element is still inside the container rather than isConnected,\n // because in unit tests the mock grid may not be attached to document.body\n if (this.footerWrapper && !container.contains(this.footerWrapper)) {\n this.footerWrapper = null;\n this.bottomAggregationContainer = null;\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer && !container.contains(this.topAggregationContainer)) {\n this.topAggregationContainer = null;\n }\n if (this.infoBarElement && !container.contains(this.infoBarElement)) {\n this.infoBarElement = null;\n }\n\n // Build context with plugin states\n const selectionState = this.getSelectionState();\n const filterState = this.getFilterState();\n\n const context = buildContext(\n this.sourceRows as unknown[],\n this.columns as unknown[],\n this.gridElement,\n selectionState,\n filterState,\n );\n\n // #region Handle Aggregation Rows\n const aggregationRows = this.config.aggregationRows || [];\n const topRows = aggregationRows.filter((r) => r.position === 'top');\n const bottomRows = aggregationRows.filter((r) => r.position !== 'top');\n\n // Top aggregation rows\n if (topRows.length > 0) {\n if (!this.topAggregationContainer) {\n this.topAggregationContainer = createAggregationContainer('top');\n const header = gridEl.querySelector('.header');\n if (header && header.nextSibling) {\n container.insertBefore(this.topAggregationContainer, header.nextSibling);\n } else {\n container.appendChild(this.topAggregationContainer);\n }\n }\n renderAggregationRows(\n this.topAggregationContainer,\n topRows,\n this.visibleColumns as ColumnConfig[],\n this.sourceRows as unknown[],\n this.config.fullWidth,\n );\n } else if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n\n // Handle footer\n const hasInfoContent =\n this.config.showRowCount !== false ||\n (this.config.showSelectedCount && context.selectedRows > 0) ||\n (this.config.showFilteredCount && context.filteredRows !== context.totalRows) ||\n (this.config.customPanels && this.config.customPanels.length > 0);\n const hasBottomInfoBar = hasInfoContent && this.config.position !== 'top';\n const needsFooter = bottomRows.length > 0 || hasBottomInfoBar;\n\n // Handle top info bar\n if (hasInfoContent && this.config.position === 'top') {\n if (!this.infoBarElement) {\n this.infoBarElement = createInfoBarElement(this.config, context);\n container.insertBefore(this.infoBarElement, container.firstChild);\n } else {\n const newInfoBar = createInfoBarElement(this.config, context);\n this.infoBarElement.replaceWith(newInfoBar);\n this.infoBarElement = newInfoBar;\n }\n } else if (this.config.position === 'top' && this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n\n // Create/manage footer wrapper\n if (needsFooter) {\n if (!this.footerWrapper) {\n this.footerWrapper = document.createElement('div');\n this.footerWrapper.className = 'tbw-footer';\n container.appendChild(this.footerWrapper);\n }\n\n this.footerWrapper.innerHTML = '';\n\n if (bottomRows.length > 0) {\n if (!this.bottomAggregationContainer) {\n this.bottomAggregationContainer = createAggregationContainer('bottom');\n }\n this.footerWrapper.appendChild(this.bottomAggregationContainer);\n renderAggregationRows(\n this.bottomAggregationContainer,\n bottomRows,\n this.visibleColumns as ColumnConfig[],\n this.sourceRows as unknown[],\n this.config.fullWidth,\n );\n }\n\n if (hasBottomInfoBar) {\n this.infoBarElement = createInfoBarElement(this.config, context);\n this.footerWrapper.appendChild(this.infoBarElement);\n }\n } else {\n this.cleanupFooter();\n }\n // #endregion\n }\n // #endregion\n\n // #region Private Methods\n private cleanup(): void {\n if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n\n private cleanupFooter(): void {\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.infoBarElement && this.config.position !== 'top') {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n }\n\n private getSelectionState(): { selected: Set<number> } | null {\n // Try to get selection plugin state\n try {\n return (this.grid?.getPluginState?.('selection') as { selected: Set<number> } | null) ?? null;\n } catch {\n return null;\n }\n }\n\n private getFilterState(): { cachedResult: unknown[] | null } | null {\n try {\n return (this.grid?.getPluginState?.('filtering') as { cachedResult: unknown[] | null } | null) ?? null;\n } catch {\n return null;\n }\n }\n // #endregion\n\n // #region Public API\n /**\n * Refresh the status bar to reflect current grid state.\n */\n refresh(): void {\n this.requestRender();\n }\n\n /**\n * Get the current status bar context.\n * @returns The context with row counts and other info\n */\n getContext(): PinnedRowsContext {\n const selectionState = this.getSelectionState();\n const filterState = this.getFilterState();\n\n return buildContext(\n this.rows as unknown[],\n this.columns as unknown[],\n this.gridElement,\n selectionState,\n filterState,\n );\n }\n\n /**\n * Add a custom panel to the info bar.\n * @param panel - The panel configuration to add\n */\n addPanel(panel: PinnedRowsPanel): void {\n if (!this.config.customPanels) {\n this.config.customPanels = [];\n }\n this.config.customPanels.push(panel);\n this.requestRender();\n }\n\n /**\n * Remove a custom panel by ID.\n * @param id - The panel ID to remove\n */\n removePanel(id: string): void {\n if (this.config.customPanels) {\n this.config.customPanels = this.config.customPanels.filter((p) => p.id !== id);\n this.requestRender();\n }\n }\n\n /**\n * Add an aggregation row.\n * @param row - The aggregation row configuration\n */\n addAggregationRow(row: AggregationRowConfig): void {\n if (!this.config.aggregationRows) {\n this.config.aggregationRows = [];\n }\n this.config.aggregationRows.push(row);\n this.requestRender();\n }\n\n /**\n * Remove an aggregation row by ID.\n * @param id - The aggregation row ID to remove\n */\n removeAggregationRow(id: string): void {\n if (this.config.aggregationRows) {\n this.config.aggregationRows = this.config.aggregationRows.filter((r) => r.id !== id);\n this.requestRender();\n }\n }\n // #endregion\n}\n"],"names":["createInfoBarElement","config","context","pinnedRows","document","createElement","className","setAttribute","left","center","right","showRowCount","rowCount","textContent","totalRows","appendChild","showFilteredCount","filteredRows","filteredCount","showSelectedCount","selectedRows","selectedCount","customPanels","panel","panelEl","renderCustomPanel","position","createAggregationContainer","container","renderAggregationRows","rows","columns","dataRows","globalFullWidth","innerHTML","rowConfig","rowEl","id","fullWidth","renderFullWidthAggregationRow","renderPerColumnAggregationRow","cell","style","gridColumn","labelValue","label","labelSpan","aggregatesContainer","hasAggregators","aggregators","Object","keys","length","hasCells","cells","col","value","formatter","resolveAggregatedValue","span","field","header","displayValue","String","children","renderInlineAggregates","labelEl","aggDef","def","aggFn","getAggregator","aggFunc","prototype","hasOwnProperty","call","staticVal","content","render","buildContext","grid","selectionState","filterState","cachedResult","selected","size","PinnedRowsPlugin","BaseGridPlugin","name","styles","defaultConfig","infoBarElement","topAggregationContainer","bottomAggregationContainer","footerWrapper","detach","this","remove","afterRender","gridEl","gridElement","querySelector","contains","getSelectionState","getFilterState","sourceRows","aggregationRows","topRows","filter","r","bottomRows","nextSibling","insertBefore","visibleColumns","hasInfoContent","hasBottomInfoBar","needsFooter","newInfoBar","replaceWith","firstChild","cleanupFooter","cleanup","getPluginState","refresh","requestRender","getContext","addPanel","push","removePanel","p","addAggregationRow","row","removeAggregationRow"],"mappings":"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"}
1
+ {"version":3,"file":"pinned-rows.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/pinned-rows/pinned-rows.ts","../../../../../libs/grid/src/lib/plugins/pinned-rows/PinnedRowsPlugin.ts"],"sourcesContent":["/**\n * Status Bar Rendering Logic\n *\n * Pure functions for creating and updating the status bar UI.\n * Includes both info bar and aggregation row rendering.\n */\n\nimport { getAggregator } from '../../core/internal/aggregators';\nimport type { ColumnConfig } from '../../core/types';\nimport type {\n AggregationRowConfig,\n AggregatorConfig,\n AggregatorDefinition,\n PinnedRowsConfig,\n PinnedRowsContext,\n PinnedRowsPanel,\n} from './types';\n\n/**\n * Check if an aggregator definition is a full config object (with aggFunc and optional formatter).\n */\nfunction isAggregatorConfig(def: AggregatorDefinition): def is AggregatorConfig {\n return typeof def === 'object' && def !== null && 'aggFunc' in def;\n}\n\n/**\n * Creates the info bar DOM element with all configured panels.\n *\n * @param config - The status bar configuration\n * @param context - The current grid context for rendering\n * @returns The complete info bar element\n */\nexport function createInfoBarElement(config: PinnedRowsConfig, context: PinnedRowsContext): HTMLElement {\n const pinnedRows = document.createElement('div');\n pinnedRows.className = 'tbw-pinned-rows';\n pinnedRows.setAttribute('role', 'presentation');\n pinnedRows.setAttribute('aria-live', 'polite');\n\n const left = document.createElement('div');\n left.className = 'tbw-pinned-rows-left';\n\n const center = document.createElement('div');\n center.className = 'tbw-pinned-rows-center';\n\n const right = document.createElement('div');\n right.className = 'tbw-pinned-rows-right';\n\n // Default panels - row count\n if (config.showRowCount !== false) {\n const rowCount = document.createElement('span');\n rowCount.className = 'tbw-status-panel tbw-status-panel-row-count';\n rowCount.textContent = `Total: ${context.totalRows} rows`;\n left.appendChild(rowCount);\n }\n\n // Filtered count panel (only shows when filter is active)\n if (config.showFilteredCount && context.filteredRows !== context.totalRows) {\n const filteredCount = document.createElement('span');\n filteredCount.className = 'tbw-status-panel tbw-status-panel-filtered-count';\n filteredCount.textContent = `Filtered: ${context.filteredRows}`;\n left.appendChild(filteredCount);\n }\n\n // Selected count panel (only shows when rows are selected)\n if (config.showSelectedCount && context.selectedRows > 0) {\n const selectedCount = document.createElement('span');\n selectedCount.className = 'tbw-status-panel tbw-status-panel-selected-count';\n selectedCount.textContent = `Selected: ${context.selectedRows}`;\n right.appendChild(selectedCount);\n }\n\n // Render custom panels\n if (config.customPanels) {\n for (const panel of config.customPanels) {\n const panelEl = renderCustomPanel(panel, context);\n switch (panel.position) {\n case 'left':\n left.appendChild(panelEl);\n break;\n case 'center':\n center.appendChild(panelEl);\n break;\n case 'right':\n right.appendChild(panelEl);\n break;\n }\n }\n }\n\n pinnedRows.appendChild(left);\n pinnedRows.appendChild(center);\n pinnedRows.appendChild(right);\n\n return pinnedRows;\n}\n\n/**\n * Creates a container for aggregation rows at top or bottom.\n *\n * @param position - 'top' or 'bottom'\n * @returns The container element\n */\nexport function createAggregationContainer(position: 'top' | 'bottom'): HTMLElement {\n const container = document.createElement('div');\n container.className = `tbw-aggregation-rows tbw-aggregation-rows-${position}`;\n // Use presentation role since aggregation rows are outside the role=\"grid\" element for layout reasons\n container.setAttribute('role', 'presentation');\n return container;\n}\n\n/**\n * Renders aggregation rows into a container.\n *\n * @param container - The container to render into\n * @param rows - Aggregation row configurations\n * @param columns - Current column configuration\n * @param dataRows - Current row data for aggregation calculations\n * @param globalFullWidth - Global fullWidth default from PinnedRowsConfig (default: false)\n */\nexport function renderAggregationRows(\n container: HTMLElement,\n rows: AggregationRowConfig[],\n columns: ColumnConfig[],\n dataRows: unknown[],\n globalFullWidth = false,\n): void {\n container.innerHTML = '';\n\n for (const rowConfig of rows) {\n const rowEl = document.createElement('div');\n rowEl.className = 'tbw-aggregation-row';\n // Use presentation role since aggregation rows are outside the role=\"grid\" element\n rowEl.setAttribute('role', 'presentation');\n if (rowConfig.id) {\n rowEl.setAttribute('data-aggregation-id', rowConfig.id);\n }\n\n // Per-row fullWidth overrides global default\n const isFullWidth = rowConfig.fullWidth ?? globalFullWidth;\n\n if (isFullWidth) {\n renderFullWidthAggregationRow(rowEl, rowConfig, columns, dataRows);\n } else {\n renderPerColumnAggregationRow(rowEl, rowConfig, columns, dataRows);\n }\n\n container.appendChild(rowEl);\n }\n}\n\n/**\n * Renders a full-width aggregation row: single spanning cell with label and inline aggregated values.\n */\nfunction renderFullWidthAggregationRow(\n rowEl: HTMLElement,\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell tbw-aggregation-cell-full';\n cell.style.gridColumn = '1 / -1';\n\n // Label (static string or dynamic function)\n const labelValue = typeof rowConfig.label === 'function' ? rowConfig.label(dataRows, columns) : rowConfig.label;\n if (labelValue) {\n const labelSpan = document.createElement('span');\n labelSpan.className = 'tbw-aggregation-label';\n labelSpan.textContent = labelValue;\n cell.appendChild(labelSpan);\n }\n\n // Inline aggregated values\n const aggregatesContainer = renderInlineAggregates(rowConfig, columns, dataRows);\n if (aggregatesContainer) {\n cell.appendChild(aggregatesContainer);\n }\n\n // If nothing was added (no label, no aggregates), ensure cell is empty but present\n rowEl.appendChild(cell);\n}\n\n/**\n * Renders per-column aggregation cells aligned to the grid template.\n */\nfunction renderPerColumnAggregationRow(\n rowEl: HTMLElement,\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\n for (const col of columns) {\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell';\n cell.setAttribute('data-field', col.field);\n\n const { value, formatter } = resolveAggregatedValue(rowConfig, col, dataRows);\n\n if (value != null) {\n cell.textContent = formatter ? formatter(value, col.field, col) : String(value);\n } else {\n cell.textContent = '';\n }\n rowEl.appendChild(cell);\n }\n\n // Overlay label: positioned at the left edge, independent of column alignment\n const labelValue = typeof rowConfig.label === 'function' ? rowConfig.label(dataRows, columns) : rowConfig.label;\n if (labelValue) {\n const labelEl = document.createElement('span');\n labelEl.className = 'tbw-aggregation-label';\n labelEl.textContent = labelValue;\n rowEl.appendChild(labelEl);\n }\n}\n\n/**\n * Resolves the aggregated value for a single column in an aggregation row.\n * Returns the computed value and an optional formatter function.\n */\nfunction resolveAggregatedValue(\n rowConfig: AggregationRowConfig,\n col: ColumnConfig,\n dataRows: unknown[],\n): { value: unknown; formatter?: (value: unknown, field: string, column?: ColumnConfig) => string } {\n let value: unknown;\n let formatter: ((value: unknown, field: string, column?: ColumnConfig) => string) | undefined;\n\n // Check for aggregator first\n const aggDef = rowConfig.aggregators?.[col.field];\n if (aggDef) {\n if (isAggregatorConfig(aggDef)) {\n const aggFn = getAggregator(aggDef.aggFunc);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n formatter = aggDef.formatter;\n } else {\n const aggFn = getAggregator(aggDef);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n }\n } else if (rowConfig.cells && Object.prototype.hasOwnProperty.call(rowConfig.cells, col.field)) {\n const staticVal = rowConfig.cells[col.field];\n if (typeof staticVal === 'function') {\n value = staticVal(dataRows, col.field, col);\n } else {\n value = staticVal;\n }\n }\n\n return { value, formatter };\n}\n\n/**\n * Renders inline aggregate values for a full-width aggregation row.\n * Returns a container element with aggregate spans, or null if no aggregates are defined.\n */\nfunction renderInlineAggregates(\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): HTMLElement | null {\n // Collect fields that have aggregators or cell values\n const hasAggregators = rowConfig.aggregators && Object.keys(rowConfig.aggregators).length > 0;\n const hasCells = rowConfig.cells && Object.keys(rowConfig.cells).length > 0;\n if (!hasAggregators && !hasCells) return null;\n\n const container = document.createElement('span');\n container.className = 'tbw-aggregation-aggregates';\n\n for (const col of columns) {\n const { value, formatter } = resolveAggregatedValue(rowConfig, col, dataRows);\n if (value != null) {\n const span = document.createElement('span');\n span.className = 'tbw-aggregation-aggregate';\n span.setAttribute('data-field', col.field);\n const header = col.header ?? col.field;\n const displayValue = formatter ? formatter(value, col.field, col) : String(value);\n span.textContent = `${header}: ${displayValue}`;\n container.appendChild(span);\n }\n }\n\n return container.children.length > 0 ? container : null;\n}\n\n/**\n * Renders a custom panel element.\n *\n * @param panel - The panel definition\n * @param context - The current grid context\n * @returns The panel DOM element\n */\nfunction renderCustomPanel(panel: PinnedRowsPanel, context: PinnedRowsContext): HTMLElement {\n const panelEl = document.createElement('div');\n panelEl.className = 'tbw-status-panel tbw-status-panel-custom';\n panelEl.id = `status-panel-${panel.id}`;\n\n const content = panel.render(context);\n\n if (typeof content === 'string') {\n panelEl.innerHTML = content;\n } else {\n panelEl.appendChild(content);\n }\n\n return panelEl;\n}\n\n/**\n * Builds the status bar context from grid state and plugin states.\n *\n * @param rows - Current row data\n * @param columns - Current column configuration\n * @param grid - Grid element reference\n * @param selectionState - Optional selection plugin state\n * @param filterState - Optional filtering plugin state\n * @returns The status bar context\n */\nexport function buildContext(\n rows: unknown[],\n columns: unknown[],\n grid: HTMLElement,\n selectionState?: { selected: Set<number> } | null,\n filterState?: { cachedResult: unknown[] | null } | null,\n): PinnedRowsContext {\n return {\n totalRows: rows.length,\n filteredRows: filterState?.cachedResult?.length ?? rows.length,\n selectedRows: selectionState?.selected?.size ?? 0,\n columns: columns as PinnedRowsContext['columns'],\n rows,\n grid,\n };\n}\n\n// Keep old name as alias for backwards compatibility\nexport const createPinnedRowsElement = createInfoBarElement;\n","/**\n * Pinned Rows Plugin (Class-based)\n *\n * Adds info bars and aggregation rows to the grid.\n * - Info bar: Shows row counts, selection info, and custom panels\n * - Aggregation rows: Footer/header rows with computed values (sum, avg, etc.)\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { buildContext, createAggregationContainer, createInfoBarElement, renderAggregationRows } from './pinned-rows';\nimport styles from './pinned-rows.css?inline';\nimport type { AggregationRowConfig, PinnedRowsConfig, PinnedRowsContext, PinnedRowsPanel } from './types';\n\n/**\n * Pinned Rows (Status Bar) Plugin for tbw-grid\n *\n * Creates fixed status bars at the top or bottom of the grid for displaying aggregations,\n * row counts, or custom content. Think of it as the \"totals row\" you'd see in a spreadsheet—\n * always visible regardless of scroll position.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedRowsPlugin } from '@toolbox-web/grid/plugins/pinned-rows';\n * ```\n *\n * ## Built-in Aggregation Functions\n *\n * | Function | Description |\n * |----------|-------------|\n * | `sum` | Sum of values |\n * | `avg` | Average of values |\n * | `count` | Count of rows |\n * | `min` | Minimum value |\n * | `max` | Maximum value |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-rows-bg` | `var(--tbw-color-panel-bg)` | Status bar background |\n * | `--tbw-pinned-rows-border` | `var(--tbw-color-border)` | Status bar border |\n *\n * @example Status Bar with Aggregation\n * ```ts\n * import '@toolbox-web/grid';\n * import { PinnedRowsPlugin } from '@toolbox-web/grid/plugins/pinned-rows';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'product', header: 'Product' },\n * { field: 'quantity', header: 'Qty', type: 'number' },\n * { field: 'price', header: 'Price', type: 'currency' },\n * ],\n * plugins: [\n * new PinnedRowsPlugin({\n * position: 'bottom',\n * showRowCount: true,\n * aggregationRows: [\n * {\n * id: 'totals',\n * aggregators: { quantity: 'sum', price: 'sum' },\n * cells: { product: 'Totals:' },\n * },\n * ],\n * }),\n * ],\n * };\n * ```\n *\n * @see {@link PinnedRowsConfig} for all configuration options\n * @see {@link AggregationRowConfig} for aggregation row structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedRowsPlugin extends BaseGridPlugin<PinnedRowsConfig> {\n /** @internal */\n readonly name = 'pinnedRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<PinnedRowsConfig> {\n return {\n position: 'bottom',\n showRowCount: true,\n showSelectedCount: true,\n showFilteredCount: true,\n };\n }\n\n // #region Internal State\n private infoBarElement: HTMLElement | null = null;\n private topAggregationContainer: HTMLElement | null = null;\n private bottomAggregationContainer: HTMLElement | null = null;\n private footerWrapper: HTMLElement | null = null;\n // #endregion\n\n // #region Lifecycle\n /** @internal */\n override detach(): void {\n if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n // #endregion\n\n // #region Hooks\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Use .tbw-scroll-area so footer is inside the horizontal scroll area,\n // otherwise fall back to .tbw-grid-content or root container\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 // Clear orphaned element references if they were removed from the DOM\n // (e.g., by buildGridDOMIntoShadow calling replaceChildren())\n // We check if the element is still inside the container rather than isConnected,\n // because in unit tests the mock grid may not be attached to document.body\n if (this.footerWrapper && !container.contains(this.footerWrapper)) {\n this.footerWrapper = null;\n this.bottomAggregationContainer = null;\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer && !container.contains(this.topAggregationContainer)) {\n this.topAggregationContainer = null;\n }\n if (this.infoBarElement && !container.contains(this.infoBarElement)) {\n this.infoBarElement = null;\n }\n\n // Build context with plugin states\n const selectionState = this.getSelectionState();\n const filterState = this.getFilterState();\n\n const context = buildContext(\n this.sourceRows as unknown[],\n this.columns as unknown[],\n this.gridElement,\n selectionState,\n filterState,\n );\n\n // #region Handle Aggregation Rows\n const aggregationRows = this.config.aggregationRows || [];\n const topRows = aggregationRows.filter((r) => r.position === 'top');\n const bottomRows = aggregationRows.filter((r) => r.position !== 'top');\n\n // Top aggregation rows\n if (topRows.length > 0) {\n if (!this.topAggregationContainer) {\n this.topAggregationContainer = createAggregationContainer('top');\n const header = gridEl.querySelector('.header');\n if (header && header.nextSibling) {\n container.insertBefore(this.topAggregationContainer, header.nextSibling);\n } else {\n container.appendChild(this.topAggregationContainer);\n }\n }\n renderAggregationRows(\n this.topAggregationContainer,\n topRows,\n this.visibleColumns as ColumnConfig[],\n this.sourceRows as unknown[],\n this.config.fullWidth,\n );\n } else if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n\n // Handle footer\n const hasInfoContent =\n this.config.showRowCount !== false ||\n (this.config.showSelectedCount && context.selectedRows > 0) ||\n (this.config.showFilteredCount && context.filteredRows !== context.totalRows) ||\n (this.config.customPanels && this.config.customPanels.length > 0);\n const hasBottomInfoBar = hasInfoContent && this.config.position !== 'top';\n const needsFooter = bottomRows.length > 0 || hasBottomInfoBar;\n\n // Handle top info bar\n if (hasInfoContent && this.config.position === 'top') {\n if (!this.infoBarElement) {\n this.infoBarElement = createInfoBarElement(this.config, context);\n container.insertBefore(this.infoBarElement, container.firstChild);\n } else {\n const newInfoBar = createInfoBarElement(this.config, context);\n this.infoBarElement.replaceWith(newInfoBar);\n this.infoBarElement = newInfoBar;\n }\n } else if (this.config.position === 'top' && this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n\n // Create/manage footer wrapper\n if (needsFooter) {\n if (!this.footerWrapper) {\n this.footerWrapper = document.createElement('div');\n this.footerWrapper.className = 'tbw-footer';\n container.appendChild(this.footerWrapper);\n }\n\n this.footerWrapper.innerHTML = '';\n\n if (bottomRows.length > 0) {\n if (!this.bottomAggregationContainer) {\n this.bottomAggregationContainer = createAggregationContainer('bottom');\n }\n this.footerWrapper.appendChild(this.bottomAggregationContainer);\n renderAggregationRows(\n this.bottomAggregationContainer,\n bottomRows,\n this.visibleColumns as ColumnConfig[],\n this.sourceRows as unknown[],\n this.config.fullWidth,\n );\n }\n\n if (hasBottomInfoBar) {\n this.infoBarElement = createInfoBarElement(this.config, context);\n this.footerWrapper.appendChild(this.infoBarElement);\n }\n } else {\n this.cleanupFooter();\n }\n // #endregion\n }\n // #endregion\n\n // #region Private Methods\n private cleanup(): void {\n if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n\n private cleanupFooter(): void {\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.infoBarElement && this.config.position !== 'top') {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n }\n\n private getSelectionState(): { selected: Set<number> } | null {\n // Try to get selection plugin state\n try {\n return (this.grid?.getPluginState?.('selection') as { selected: Set<number> } | null) ?? null;\n } catch {\n return null;\n }\n }\n\n private getFilterState(): { cachedResult: unknown[] | null } | null {\n try {\n return (this.grid?.getPluginState?.('filtering') as { cachedResult: unknown[] | null } | null) ?? null;\n } catch {\n return null;\n }\n }\n // #endregion\n\n // #region Public API\n /**\n * Refresh the status bar to reflect current grid state.\n */\n refresh(): void {\n this.requestRender();\n }\n\n /**\n * Get the current status bar context.\n * @returns The context with row counts and other info\n */\n getContext(): PinnedRowsContext {\n const selectionState = this.getSelectionState();\n const filterState = this.getFilterState();\n\n return buildContext(\n this.rows as unknown[],\n this.columns as unknown[],\n this.gridElement,\n selectionState,\n filterState,\n );\n }\n\n /**\n * Add a custom panel to the info bar.\n * @param panel - The panel configuration to add\n */\n addPanel(panel: PinnedRowsPanel): void {\n if (!this.config.customPanels) {\n this.config.customPanels = [];\n }\n this.config.customPanels.push(panel);\n this.requestRender();\n }\n\n /**\n * Remove a custom panel by ID.\n * @param id - The panel ID to remove\n */\n removePanel(id: string): void {\n if (this.config.customPanels) {\n this.config.customPanels = this.config.customPanels.filter((p) => p.id !== id);\n this.requestRender();\n }\n }\n\n /**\n * Add an aggregation row.\n * @param row - The aggregation row configuration\n */\n addAggregationRow(row: AggregationRowConfig): void {\n if (!this.config.aggregationRows) {\n this.config.aggregationRows = [];\n }\n this.config.aggregationRows.push(row);\n this.requestRender();\n }\n\n /**\n * Remove an aggregation row by ID.\n * @param id - The aggregation row ID to remove\n */\n removeAggregationRow(id: string): void {\n if (this.config.aggregationRows) {\n this.config.aggregationRows = this.config.aggregationRows.filter((r) => r.id !== id);\n this.requestRender();\n }\n }\n // #endregion\n}\n"],"names":["createInfoBarElement","config","context","pinnedRows","document","createElement","className","setAttribute","left","center","right","showRowCount","rowCount","textContent","totalRows","appendChild","showFilteredCount","filteredRows","filteredCount","showSelectedCount","selectedRows","selectedCount","customPanels","panel","panelEl","renderCustomPanel","position","createAggregationContainer","container","renderAggregationRows","rows","columns","dataRows","globalFullWidth","innerHTML","rowConfig","rowEl","id","fullWidth","renderFullWidthAggregationRow","renderPerColumnAggregationRow","cell","style","gridColumn","labelValue","label","labelSpan","aggregatesContainer","hasAggregators","aggregators","Object","keys","length","hasCells","cells","col","value","formatter","resolveAggregatedValue","span","field","header","displayValue","String","children","renderInlineAggregates","labelEl","aggDef","def","aggFn","getAggregator","aggFunc","prototype","hasOwnProperty","call","staticVal","content","render","buildContext","grid","selectionState","filterState","cachedResult","selected","size","PinnedRowsPlugin","BaseGridPlugin","name","styles","defaultConfig","infoBarElement","topAggregationContainer","bottomAggregationContainer","footerWrapper","detach","this","remove","afterRender","gridEl","gridElement","querySelector","contains","getSelectionState","getFilterState","sourceRows","aggregationRows","topRows","filter","r","bottomRows","nextSibling","insertBefore","visibleColumns","hasInfoContent","hasBottomInfoBar","needsFooter","newInfoBar","replaceWith","firstChild","cleanupFooter","cleanup","getPluginState","refresh","requestRender","getContext","addPanel","push","removePanel","p","addAggregationRow","row","removeAggregationRow"],"mappings":"6aAgCO,SAASA,EAAqBC,EAA0BC,GAC7D,MAAMC,EAAaC,SAASC,cAAc,OAC1CF,EAAWG,UAAY,kBACvBH,EAAWI,aAAa,OAAQ,gBAChCJ,EAAWI,aAAa,YAAa,UAErC,MAAMC,EAAOJ,SAASC,cAAc,OACpCG,EAAKF,UAAY,uBAEjB,MAAMG,EAASL,SAASC,cAAc,OACtCI,EAAOH,UAAY,yBAEnB,MAAMI,EAAQN,SAASC,cAAc,OAIrC,GAHAK,EAAMJ,UAAY,yBAGU,IAAxBL,EAAOU,aAAwB,CACjC,MAAMC,EAAWR,SAASC,cAAc,QACxCO,EAASN,UAAY,8CACrBM,EAASC,YAAc,UAAUX,EAAQY,iBACzCN,EAAKO,YAAYH,EACnB,CAGA,GAAIX,EAAOe,mBAAqBd,EAAQe,eAAiBf,EAAQY,UAAW,CAC1E,MAAMI,EAAgBd,SAASC,cAAc,QAC7Ca,EAAcZ,UAAY,mDAC1BY,EAAcL,YAAc,aAAaX,EAAQe,eACjDT,EAAKO,YAAYG,EACnB,CAGA,GAAIjB,EAAOkB,mBAAqBjB,EAAQkB,aAAe,EAAG,CACxD,MAAMC,EAAgBjB,SAASC,cAAc,QAC7CgB,EAAcf,UAAY,mDAC1Be,EAAcR,YAAc,aAAaX,EAAQkB,eACjDV,EAAMK,YAAYM,EACpB,CAGA,GAAIpB,EAAOqB,aACT,IAAA,MAAWC,KAAStB,EAAOqB,aAAc,CACvC,MAAME,EAAUC,EAAkBF,EAAOrB,GACzC,OAAQqB,EAAMG,UACZ,IAAK,OACHlB,EAAKO,YAAYS,GACjB,MACF,IAAK,SACHf,EAAOM,YAAYS,GACnB,MACF,IAAK,QACHd,EAAMK,YAAYS,GAGxB,CAOF,OAJArB,EAAWY,YAAYP,GACvBL,EAAWY,YAAYN,GACvBN,EAAWY,YAAYL,GAEhBP,CACT,CAQO,SAASwB,EAA2BD,GACzC,MAAME,EAAYxB,SAASC,cAAc,OAIzC,OAHAuB,EAAUtB,UAAY,6CAA6CoB,IAEnEE,EAAUrB,aAAa,OAAQ,gBACxBqB,CACT,CAWO,SAASC,EACdD,EACAE,EACAC,EACAC,EACAC,GAAkB,GAElBL,EAAUM,UAAY,GAEtB,IAAA,MAAWC,KAAaL,EAAM,CAC5B,MAAMM,EAAQhC,SAASC,cAAc,OACrC+B,EAAM9B,UAAY,sBAElB8B,EAAM7B,aAAa,OAAQ,gBACvB4B,EAAUE,IACZD,EAAM7B,aAAa,sBAAuB4B,EAAUE,IAIlCF,EAAUG,WAAaL,EAGzCM,EAA8BH,EAAOD,EAAWJ,EAASC,GAEzDQ,EAA8BJ,EAAOD,EAAWJ,EAASC,GAG3DJ,EAAUb,YAAYqB,EACxB,CACF,CAKA,SAASG,EACPH,EACAD,EACAJ,EACAC,GAEA,MAAMS,EAAOrC,SAASC,cAAc,OACpCoC,EAAKnC,UAAY,iDACjBmC,EAAKC,MAAMC,WAAa,SAGxB,MAAMC,EAAwC,mBAApBT,EAAUU,MAAuBV,EAAUU,MAAMb,EAAUD,GAAWI,EAAUU,MAC1G,GAAID,EAAY,CACd,MAAME,EAAY1C,SAASC,cAAc,QACzCyC,EAAUxC,UAAY,wBACtBwC,EAAUjC,YAAc+B,EACxBH,EAAK1B,YAAY+B,EACnB,CAGA,MAAMC,EAsFR,SACEZ,EACAJ,EACAC,GAGA,MAAMgB,EAAiBb,EAAUc,aAAeC,OAAOC,KAAKhB,EAAUc,aAAaG,OAAS,EACtFC,EAAWlB,EAAUmB,OAASJ,OAAOC,KAAKhB,EAAUmB,OAAOF,OAAS,EAC1E,IAAKJ,IAAmBK,EAAU,OAAO,KAEzC,MAAMzB,EAAYxB,SAASC,cAAc,QACzCuB,EAAUtB,UAAY,6BAEtB,IAAA,MAAWiD,KAAOxB,EAAS,CACzB,MAAMyB,MAAEA,EAAAC,UAAOA,GAAcC,EAAuBvB,EAAWoB,EAAKvB,GACpE,GAAa,MAATwB,EAAe,CACjB,MAAMG,EAAOvD,SAASC,cAAc,QACpCsD,EAAKrD,UAAY,4BACjBqD,EAAKpD,aAAa,aAAcgD,EAAIK,OACpC,MAAMC,EAASN,EAAIM,QAAUN,EAAIK,MAC3BE,EAAeL,EAAYA,EAAUD,EAAOD,EAAIK,MAAOL,GAAOQ,OAAOP,GAC3EG,EAAK9C,YAAc,GAAGgD,MAAWC,IACjClC,EAAUb,YAAY4C,EACxB,CACF,CAEA,OAAO/B,EAAUoC,SAASZ,OAAS,EAAIxB,EAAY,IACrD,CAjH8BqC,CAAuB9B,EAAWJ,EAASC,GACnEe,GACFN,EAAK1B,YAAYgC,GAInBX,EAAMrB,YAAY0B,EACpB,CAKA,SAASD,EACPJ,EACAD,EACAJ,EACAC,GAEA,IAAA,MAAWuB,KAAOxB,EAAS,CACzB,MAAMU,EAAOrC,SAASC,cAAc,OACpCoC,EAAKnC,UAAY,uBACjBmC,EAAKlC,aAAa,aAAcgD,EAAIK,OAEpC,MAAMJ,MAAEA,EAAAC,UAAOA,GAAcC,EAAuBvB,EAAWoB,EAAKvB,GAGlES,EAAK5B,YADM,MAAT2C,EACiBC,EAAYA,EAAUD,EAAOD,EAAIK,MAAOL,GAAOQ,OAAOP,GAEtD,GAErBpB,EAAMrB,YAAY0B,EACpB,CAGA,MAAMG,EAAwC,mBAApBT,EAAUU,MAAuBV,EAAUU,MAAMb,EAAUD,GAAWI,EAAUU,MAC1G,GAAID,EAAY,CACd,MAAMsB,EAAU9D,SAASC,cAAc,QACvC6D,EAAQ5D,UAAY,wBACpB4D,EAAQrD,YAAc+B,EACtBR,EAAMrB,YAAYmD,EACpB,CACF,CAMA,SAASR,EACPvB,EACAoB,EACAvB,GAEA,IAAIwB,EACAC,EAGJ,MAAMU,EAAShC,EAAUc,cAAcM,EAAIK,OAC3C,GAAIO,EACF,GAjNoB,iBADIC,EAkNDD,IAjNiB,OAARC,GAAgB,YAAaA,EAiN7B,CAC9B,MAAMC,EAAQC,EAAAA,cAAcH,EAAOI,SAC/BF,IACFb,EAAQa,EAAMrC,EAAUuB,EAAIK,MAAOL,IAErCE,EAAYU,EAAOV,SACrB,KAAO,CACL,MAAMY,EAAQC,EAAAA,cAAcH,GACxBE,IACFb,EAAQa,EAAMrC,EAAUuB,EAAIK,MAAOL,GAEvC,MACF,GAAWpB,EAAUmB,OAASJ,OAAOsB,UAAUC,eAAeC,KAAKvC,EAAUmB,MAAOC,EAAIK,OAAQ,CAC9F,MAAMe,EAAYxC,EAAUmB,MAAMC,EAAIK,OAEpCJ,EADuB,mBAAdmB,EACDA,EAAU3C,EAAUuB,EAAIK,MAAOL,GAE/BoB,CAEZ,CArOF,IAA4BP,EAuO1B,MAAO,CAAEZ,QAAOC,YAClB,CA0CA,SAAShC,EAAkBF,EAAwBrB,GACjD,MAAMsB,EAAUpB,SAASC,cAAc,OACvCmB,EAAQlB,UAAY,2CACpBkB,EAAQa,GAAK,gBAAgBd,EAAMc,KAEnC,MAAMuC,EAAUrD,EAAMsD,OAAO3E,GAQ7B,MANuB,iBAAZ0E,EACTpD,EAAQU,UAAY0C,EAEpBpD,EAAQT,YAAY6D,GAGfpD,CACT,CAYO,SAASsD,EACdhD,EACAC,EACAgD,EACAC,EACAC,GAEA,MAAO,CACLnE,UAAWgB,EAAKsB,OAChBnC,aAAcgE,GAAaC,cAAc9B,QAAUtB,EAAKsB,OACxDhC,aAAc4D,GAAgBG,UAAUC,MAAQ,EAChDrD,UACAD,OACAiD,OAEJ,CCpQO,MAAMM,UAAyBC,EAAAA,eAE3BC,KAAO,aAEEC,4kFAGlB,iBAAuBC,GACrB,MAAO,CACL/D,SAAU,SACVf,cAAc,EACdQ,mBAAmB,EACnBH,mBAAmB,EAEvB,CAGQ0E,eAAqC,KACrCC,wBAA8C,KAC9CC,2BAAiD,KACjDC,cAAoC,KAKnC,MAAAC,GACHC,KAAKL,iBACPK,KAAKL,eAAeM,SACpBD,KAAKL,eAAiB,MAEpBK,KAAKJ,0BACPI,KAAKJ,wBAAwBK,SAC7BD,KAAKJ,wBAA0B,MAE7BI,KAAKH,6BACPG,KAAKH,2BAA2BI,SAChCD,KAAKH,2BAA6B,MAEhCG,KAAKF,gBACPE,KAAKF,cAAcG,SACnBD,KAAKF,cAAgB,KAEzB,CAKS,WAAAI,GACP,MAAMC,EAASH,KAAKI,YACpB,IAAKD,EAAQ,OAIb,MAAMtE,EACJsE,EAAOE,cAAc,qBACrBF,EAAOE,cAAc,sBACrBF,EAAOE,cAAc,kBACvB,IAAKxE,EAAW,OAMZmE,KAAKF,gBAAkBjE,EAAUyE,SAASN,KAAKF,iBACjDE,KAAKF,cAAgB,KACrBE,KAAKH,2BAA6B,KAClCG,KAAKL,eAAiB,MAEpBK,KAAKJ,0BAA4B/D,EAAUyE,SAASN,KAAKJ,2BAC3DI,KAAKJ,wBAA0B,MAE7BI,KAAKL,iBAAmB9D,EAAUyE,SAASN,KAAKL,kBAClDK,KAAKL,eAAiB,MAIxB,MAAMV,EAAiBe,KAAKO,oBACtBrB,EAAcc,KAAKQ,iBAEnBrG,EAAU4E,EACdiB,KAAKS,WACLT,KAAKhE,QACLgE,KAAKI,YACLnB,EACAC,GAIIwB,EAAkBV,KAAK9F,OAAOwG,iBAAmB,GACjDC,EAAUD,EAAgBE,OAAQC,GAAqB,QAAfA,EAAElF,UAC1CmF,EAAaJ,EAAgBE,OAAQC,GAAqB,QAAfA,EAAElF,UAGnD,GAAIgF,EAAQtD,OAAS,EAAG,CACtB,IAAK2C,KAAKJ,wBAAyB,CACjCI,KAAKJ,wBAA0BhE,EAA2B,OAC1D,MAAMkC,EAASqC,EAAOE,cAAc,WAChCvC,GAAUA,EAAOiD,YACnBlF,EAAUmF,aAAahB,KAAKJ,wBAAyB9B,EAAOiD,aAE5DlF,EAAUb,YAAYgF,KAAKJ,wBAE/B,CACA9D,EACEkE,KAAKJ,wBACLe,EACAX,KAAKiB,eACLjB,KAAKS,WACLT,KAAK9F,OAAOqC,UAEhB,MAAWyD,KAAKJ,0BACdI,KAAKJ,wBAAwBK,SAC7BD,KAAKJ,wBAA0B,MAIjC,MAAMsB,GACyB,IAA7BlB,KAAK9F,OAAOU,cACXoF,KAAK9F,OAAOkB,mBAAqBjB,EAAQkB,aAAe,GACxD2E,KAAK9F,OAAOe,mBAAqBd,EAAQe,eAAiBf,EAAQY,WAClEiF,KAAK9F,OAAOqB,cAAgByE,KAAK9F,OAAOqB,aAAa8B,OAAS,EAC3D8D,EAAmBD,GAA2C,QAAzBlB,KAAK9F,OAAOyB,SACjDyF,EAAcN,EAAWzD,OAAS,GAAK8D,EAG7C,GAAID,GAA2C,QAAzBlB,KAAK9F,OAAOyB,SAChC,GAAKqE,KAAKL,eAGH,CACL,MAAM0B,EAAapH,EAAqB+F,KAAK9F,OAAQC,GACrD6F,KAAKL,eAAe2B,YAAYD,GAChCrB,KAAKL,eAAiB0B,CACxB,MANErB,KAAKL,eAAiB1F,EAAqB+F,KAAK9F,OAAQC,GACxD0B,EAAUmF,aAAahB,KAAKL,eAAgB9D,EAAU0F,gBAMtB,QAAzBvB,KAAK9F,OAAOyB,UAAsBqE,KAAKL,iBAChDK,KAAKL,eAAeM,SACpBD,KAAKL,eAAiB,MAIpByB,GACGpB,KAAKF,gBACRE,KAAKF,cAAgBzF,SAASC,cAAc,OAC5C0F,KAAKF,cAAcvF,UAAY,aAC/BsB,EAAUb,YAAYgF,KAAKF,gBAG7BE,KAAKF,cAAc3D,UAAY,GAE3B2E,EAAWzD,OAAS,IACjB2C,KAAKH,6BACRG,KAAKH,2BAA6BjE,EAA2B,WAE/DoE,KAAKF,cAAc9E,YAAYgF,KAAKH,4BACpC/D,EACEkE,KAAKH,2BACLiB,EACAd,KAAKiB,eACLjB,KAAKS,WACLT,KAAK9F,OAAOqC,YAIZ4E,IACFnB,KAAKL,eAAiB1F,EAAqB+F,KAAK9F,OAAQC,GACxD6F,KAAKF,cAAc9E,YAAYgF,KAAKL,kBAGtCK,KAAKwB,eAGT,CAIQ,OAAAC,GACFzB,KAAKL,iBACPK,KAAKL,eAAeM,SACpBD,KAAKL,eAAiB,MAEpBK,KAAKJ,0BACPI,KAAKJ,wBAAwBK,SAC7BD,KAAKJ,wBAA0B,MAE7BI,KAAKH,6BACPG,KAAKH,2BAA2BI,SAChCD,KAAKH,2BAA6B,MAEhCG,KAAKF,gBACPE,KAAKF,cAAcG,SACnBD,KAAKF,cAAgB,KAEzB,CAEQ,aAAA0B,GACFxB,KAAKF,gBACPE,KAAKF,cAAcG,SACnBD,KAAKF,cAAgB,MAEnBE,KAAKH,6BACPG,KAAKH,2BAA2BI,SAChCD,KAAKH,2BAA6B,MAEhCG,KAAKL,gBAA2C,QAAzBK,KAAK9F,OAAOyB,WACrCqE,KAAKL,eAAeM,SACpBD,KAAKL,eAAiB,KAE1B,CAEQ,iBAAAY,GAEN,IACE,OAAQP,KAAKhB,MAAM0C,iBAAiB,cAAqD,IAC3F,CAAA,MACE,OAAO,IACT,CACF,CAEQ,cAAAlB,GACN,IACE,OAAQR,KAAKhB,MAAM0C,iBAAiB,cAA8D,IACpG,CAAA,MACE,OAAO,IACT,CACF,CAOA,OAAAC,GACE3B,KAAK4B,eACP,CAMA,UAAAC,GACE,MAAM5C,EAAiBe,KAAKO,oBACtBrB,EAAcc,KAAKQ,iBAEzB,OAAOzB,EACLiB,KAAKjE,KACLiE,KAAKhE,QACLgE,KAAKI,YACLnB,EACAC,EAEJ,CAMA,QAAA4C,CAAStG,GACFwE,KAAK9F,OAAOqB,eACfyE,KAAK9F,OAAOqB,aAAe,IAE7ByE,KAAK9F,OAAOqB,aAAawG,KAAKvG,GAC9BwE,KAAK4B,eACP,CAMA,WAAAI,CAAY1F,GACN0D,KAAK9F,OAAOqB,eACdyE,KAAK9F,OAAOqB,aAAeyE,KAAK9F,OAAOqB,aAAaqF,OAAQqB,GAAMA,EAAE3F,KAAOA,GAC3E0D,KAAK4B,gBAET,CAMA,iBAAAM,CAAkBC,GACXnC,KAAK9F,OAAOwG,kBACfV,KAAK9F,OAAOwG,gBAAkB,IAEhCV,KAAK9F,OAAOwG,gBAAgBqB,KAAKI,GACjCnC,KAAK4B,eACP,CAMA,oBAAAQ,CAAqB9F,GACf0D,KAAK9F,OAAOwG,kBACdV,KAAK9F,OAAOwG,gBAAkBV,KAAK9F,OAAOwG,gBAAgBE,OAAQC,GAAMA,EAAEvE,KAAOA,GACjF0D,KAAK4B,gBAET"}
@@ -1,2 +1,2 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/plugin/base-plugin"),require("../../core/internal/aggregators")):"function"==typeof define&&define.amd?define(["exports","../../core/plugin/base-plugin","../../core/internal/aggregators"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_pivot={},e.TbwGrid,e.TbwGrid)}(this,function(e,t,o){"use strict";const i=o.getValueAggregator;function r(e,t){return[...e,t].join("|")}function n(e,t){const o=t.rowGroupFields??[],i=t.columnGroupFields??[],n=t.valueFields??[],s=function(e,t){if(0===t.length)return["value"];const o=new Set;for(const i of e){const e=t.map(e=>String(i[e]??"")).join("|");o.add(e)}return[...o].sort()}(e,i),l=a(e,o,i,s,n,0,""),d=function(e,t,o){const i={};function n(e){for(const a of e)if(a.isGroup&&a.children?.length)a.children&&n(a.children);else for(const e of t)for(const t of o){const o=r([e],t.field);i[o]=(i[o]??0)+(a.values[o]??0)}}return n(e),i}(l,s,n);return{rows:l,columnKeys:s,totals:d,grandTotal:Object.values(d).reduce((e,t)=>e+t,0)}}function a(e,t,o,i,r,n,d){const c=[];if(0===t.length){const t=s(e,o,i,r),a=l(t);return c.push({rowKey:d||"all",rowLabel:d||"All",depth:n,values:t,total:a,isGroup:!1,rowCount:e.length}),c}const p=t[0],g=t.slice(1),v=g.length>0,u=function(e,t){const o=new Map;for(const i of e){const e=String(i[t]??""),r=o.get(e);r?r.push(i):o.set(e,[i])}return o}(e,p);for(const[h,b]of u){const e=d?`${d}|${h}`:h,t=s(b,o,i,r),p=l(t);let u;v&&(u=a(b,g,o,i,r,n+1,e)),c.push({rowKey:e,rowLabel:h||"(blank)",depth:n,values:t,total:p,isGroup:v,children:u,rowCount:b.length})}return c}function s(e,t,o,n){const a={};for(const s of o)for(const o of n){const n=(t.length>0?e.filter(e=>t.map(t=>String(e[t]??"")).join("|")===s):e).map(e=>Number(e[o.field])||0),l=i(o.aggFunc),d=n.length>0?l(n):null;a[r([s],o.field)]=d}return a}function l(e){let t=0;for(const o of Object.values(e))t+=o??0;return t}const d=["sum","avg","count","min","max","first","last"];function c(e,t,o,i){const r=new AbortController,n={config:t,callbacks:i,signal:r.signal},a=document.createElement("div");return a.className="tbw-pivot-panel",a.appendChild(p("Options",()=>function(e,t){const{config:o,callbacks:i,signal:r}=t,n=document.createElement("div");return n.className="tbw-pivot-options",n.appendChild(h("Enable Pivot View",e,e=>{i.onTogglePivot(e)},r)),n.appendChild(h("Show Row Totals",o.showTotals??!0,e=>{i.onOptionChange("showTotals",e)},r)),n.appendChild(h("Show Grand Total",o.showGrandTotal??!0,e=>{i.onOptionChange("showGrandTotal",e)},r)),n}(o,n))),a.appendChild(p("Row Groups",()=>g("rowGroups",n))),a.appendChild(p("Column Groups",()=>g("columnGroups",n))),a.appendChild(p("Values",()=>function(e){const{config:t,callbacks:o,signal:i}=e,r=document.createElement("div");r.className="tbw-pivot-drop-zone tbw-pivot-values-zone",r.setAttribute("data-zone","values");const n=t.valueFields??[];if(0===n.length){const e=document.createElement("div");e.className="tbw-pivot-placeholder",e.textContent="Drag numeric fields here for aggregation",r.appendChild(e)}else for(const a of n)r.appendChild(u(a,e));return r.addEventListener("dragover",e=>{e.preventDefault(),r.classList.add("drag-over")},{signal:i}),r.addEventListener("dragleave",()=>{r.classList.remove("drag-over")},{signal:i}),r.addEventListener("drop",e=>{e.preventDefault(),r.classList.remove("drag-over");const t=e.dataTransfer?.getData("text/plain");t&&o.onAddValueField(t,"sum")},{signal:i}),r}(n))),a.appendChild(p("Available Fields",()=>function(e){const{config:t,callbacks:o,signal:i}=e,r=document.createElement("div");r.className="tbw-pivot-available-fields";const n=o.getAvailableFields(),a=new Set([...t.rowGroupFields??[],...t.columnGroupFields??[],...t.valueFields?.map(e=>e.field)??[]]),s=n.filter(e=>!a.has(e.field));if(0===s.length){const e=document.createElement("div");e.className="tbw-pivot-placeholder",e.textContent="All fields are in use",r.appendChild(e)}else for(const l of s){const e=document.createElement("div");e.className="tbw-pivot-field-chip available",e.textContent=l.header,e.draggable=!0,e.title=`Drag to add "${l.field}" to a zone`,e.addEventListener("dragstart",t=>{t.dataTransfer?.setData("text/plain",l.field),e.classList.add("dragging")},{signal:i}),e.addEventListener("dragend",()=>{e.classList.remove("dragging")},{signal:i}),r.appendChild(e)}return r}(n))),e.appendChild(a),()=>{r.abort(),a.remove()}}function p(e,t){const o=document.createElement("div");o.className="tbw-pivot-section";const i=document.createElement("div");i.className="tbw-pivot-section-header",i.textContent=e;const r=document.createElement("div");return r.className="tbw-pivot-section-content",r.appendChild(t()),o.appendChild(i),o.appendChild(r),o}function g(e,t){const{config:o,callbacks:i,signal:r}=t,n=document.createElement("div");n.className="tbw-pivot-drop-zone",n.setAttribute("data-zone",e);const a="rowGroups"===e?o.rowGroupFields??[]:o.columnGroupFields??[];if(0===a.length){const e=document.createElement("div");e.className="tbw-pivot-placeholder",e.textContent="Drag fields here or click to add",n.appendChild(e)}else for(const s of a)n.appendChild(v(s,e,t));return n.addEventListener("dragover",e=>{e.preventDefault(),n.classList.add("drag-over")},{signal:r}),n.addEventListener("dragleave",()=>{n.classList.remove("drag-over")},{signal:r}),n.addEventListener("drop",t=>{t.preventDefault(),n.classList.remove("drag-over");const o=t.dataTransfer?.getData("text/plain");o&&i.onAddFieldToZone(o,e)},{signal:r}),n}function v(e,t,o){const{callbacks:i,signal:r}=o,n=document.createElement("div");n.className="tbw-pivot-field-chip",n.draggable=!0;const a=i.getAvailableFields().find(t=>t.field===e),s=document.createElement("span");s.className="tbw-pivot-chip-label",s.textContent=a?.header??e;const l=document.createElement("button");return l.className="tbw-pivot-chip-remove",l.innerHTML="×",l.title="Remove field",l.addEventListener("click",o=>{o.stopPropagation(),i.onRemoveFieldFromZone(e,t)},{signal:r}),n.appendChild(s),n.appendChild(l),n.addEventListener("dragstart",o=>{o.dataTransfer?.setData("text/plain",e),o.dataTransfer?.setData("source-zone",t),n.classList.add("dragging")},{signal:r}),n.addEventListener("dragend",()=>{n.classList.remove("dragging")},{signal:r}),n}function u(e,t){const{callbacks:o,signal:i}=t,r=document.createElement("div");r.className="tbw-pivot-field-chip tbw-pivot-value-chip";const n=o.getAvailableFields().find(t=>t.field===e.field),a=document.createElement("div");a.className="tbw-pivot-value-label-wrapper";const s=document.createElement("span");s.className="tbw-pivot-chip-label",s.textContent=n?.header??e.field;const l=document.createElement("select");l.className="tbw-pivot-agg-select",l.title="Aggregation function";for(const p of d){const t=document.createElement("option");t.value=p,t.textContent=p.toUpperCase(),t.selected=p===e.aggFunc,l.appendChild(t)}l.addEventListener("change",()=>{o.onUpdateValueAggFunc(e.field,l.value)},{signal:i});const c=document.createElement("button");return c.className="tbw-pivot-chip-remove",c.innerHTML="×",c.title="Remove value field",c.addEventListener("click",t=>{t.stopPropagation(),o.onRemoveValueField(e.field)},{signal:i}),a.appendChild(s),a.appendChild(l),r.appendChild(a),r.appendChild(c),r}function h(e,t,o,i){const r=document.createElement("label");r.className="tbw-pivot-checkbox";const n=document.createElement("input");n.type="checkbox",n.checked=t,n.addEventListener("change",()=>o(n.checked),{signal:i});const a=document.createElement("span");return a.textContent=e,r.appendChild(n),r.appendChild(a),r}class b extends t.BaseGridPlugin{static manifest={incompatibleWith:[{name:"groupingRows",reason:"PivotPlugin creates its own aggregated row and column structure. Row grouping cannot be applied on top of pivot-generated rows."},{name:"tree",reason:"PivotPlugin replaces the entire row and column structure with aggregated pivot data. Tree hierarchy cannot coexist with pivot aggregation."},{name:"serverSide",reason:"PivotPlugin requires the full dataset to compute aggregations. ServerSidePlugin lazy-loads rows in blocks, so pivot aggregation cannot be performed client-side."}]};name="pivot";styles='@layer tbw-plugins{.pivot-group-row{display:grid;grid-template-columns:var(--tbw-column-template);font-weight:600;background:var(--tbw-pivot-group-bg, var(--tbw-color-row-alt));min-height:var(--tbw-row-height);border-bottom:var(--tbw-row-divider)}.pivot-group-row:hover{background:var(--tbw-pivot-group-hover, var(--tbw-color-row-hover))}.pivot-leaf-row{display:grid;grid-template-columns:var(--tbw-column-template);background:var(--tbw-pivot-leaf-bg, var(--tbw-color-bg));min-height:var(--tbw-row-height);border-bottom:var(--tbw-row-divider)}.pivot-grand-total-row{display:grid;grid-template-columns:var(--tbw-column-template);font-weight:700;background:var(--tbw-pivot-grand-total-bg, var(--tbw-color-header-bg));min-height:var(--tbw-row-height);border-top:2px solid var(--tbw-color-border-strong)}.pivot-grand-total-row>.cell{display:flex;align-items:center;padding:var(--tbw-cell-padding);border-right:1px solid var(--tbw-color-border-cell);overflow:hidden;min-width:0}.pivot-grand-total-row>.cell:last-child{border-right:0}.pivot-grand-total-footer{position:sticky;bottom:0;z-index:var(--tbw-z-layer-pinned-rows, 20);background:var(--tbw-pivot-grand-total-bg, var(--tbw-color-header-bg));min-width:fit-content}.pivot-group-row>.cell,.pivot-leaf-row>.cell{display:flex;align-items:center;padding:var(--tbw-cell-padding);border-right:1px solid var(--tbw-color-border-cell);overflow:hidden;min-width:0}.pivot-group-row>.cell:last-child,.pivot-leaf-row>.cell:last-child{border-right:0}.pivot-toggle{display:inline-flex;align-items:center;justify-content:center;width:var(--tbw-pivot-toggle-size, var(--tbw-toggle-size, 1.25em));height:var(--tbw-pivot-toggle-size, var(--tbw-toggle-size, 1.25em));margin-right:var(--tbw-spacing-sm, .375em);border:none;background:transparent;cursor:pointer;color:var(--tbw-pivot-toggle-color, var(--tbw-color-fg-muted));border-radius:var(--tbw-border-radius);transition:background var(--tbw-transition-duration, .12s) var(--tbw-transition-ease, ease),color var(--tbw-transition-duration, .12s) var(--tbw-transition-ease, ease)}.pivot-toggle:hover{background:var(--tbw-pivot-toggle-hover-bg, var(--tbw-color-row-hover));color:var(--tbw-pivot-toggle-hover-color, var(--tbw-color-fg))}.pivot-toggle:focus{outline:var(--tbw-focus-outline);outline-offset:var(--tbw-focus-outline-offset)}.pivot-label{font-weight:inherit}.pivot-count{color:var(--tbw-pivot-count-color, var(--tbw-color-fg-muted));font-size:.9em;font-weight:400}.pivot-total-row{font-weight:700;border-top:2px solid var(--tbw-pivot-border, var(--tbw-color-border-strong))}[data-pivot-depth="1"]{--tbw-pivot-depth: 1}[data-pivot-depth="2"]{--tbw-pivot-depth: 2}[data-pivot-depth="3"]{--tbw-pivot-depth: 3}[data-pivot-depth="4"]{--tbw-pivot-depth: 4}.tbw-pivot-panel{display:flex;flex-direction:column;gap:var(--tbw-panel-padding, var(--tbw-spacing-lg, .75rem));padding:var(--tbw-panel-padding, var(--tbw-spacing-lg, .75rem));height:100%;overflow-y:auto;font-size:var(--tbw-font-size-sm, .8125rem)}.tbw-pivot-section{border:1px solid var(--tbw-pivot-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius);background:var(--tbw-pivot-section-bg, var(--tbw-color-bg))}.tbw-pivot-section-header{padding:var(--tbw-button-padding, var(--tbw-spacing-md, .5rem) var(--tbw-spacing-lg, .75rem));font-weight:600;background:var(--tbw-pivot-header-bg, var(--tbw-color-header-bg));border-bottom:1px solid var(--tbw-pivot-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius) var(--tbw-border-radius) 0 0}.tbw-pivot-section-content{padding:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.tbw-pivot-toggle-wrapper{display:flex;align-items:center}.tbw-pivot-toggle-label{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));cursor:pointer}.tbw-pivot-toggle-label input{width:var(--tbw-icon-size, 1rem);height:var(--tbw-icon-size, 1rem);cursor:pointer}.tbw-pivot-drop-zone{min-height:60px;padding:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));border:2px dashed var(--tbw-pivot-drop-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius);background:var(--tbw-pivot-drop-bg, var(--tbw-color-row-alt));display:flex;flex-wrap:wrap;gap:var(--tbw-spacing-sm, .375rem);align-content:flex-start;transition:all .15s ease}.tbw-pivot-drop-zone.drag-over{border-color:var(--tbw-color-accent);background:var(--tbw-pivot-drop-active, var(--tbw-focus-background))}.tbw-pivot-placeholder{color:var(--tbw-color-fg-muted);font-style:italic;padding:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));text-align:center;width:100%}.tbw-pivot-field-chip{display:inline-flex;align-items:center;gap:var(--tbw-spacing-sm, .375rem);padding:var(--tbw-button-padding-sm, var(--tbw-spacing-xs, .25rem) var(--tbw-spacing-md, .5rem));background:var(--tbw-pivot-chip-bg, var(--tbw-color-header-bg));border:1px solid var(--tbw-pivot-chip-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius);cursor:grab;font-size:var(--tbw-font-size-xs, .75rem);transition:all .15s ease}.tbw-pivot-field-chip:hover{background:var(--tbw-pivot-chip-hover, var(--tbw-color-row-hover));border-color:var(--tbw-color-accent)}.tbw-pivot-field-chip.available{background:var(--tbw-color-bg)}.tbw-pivot-field-chip.dragging{opacity:.5;cursor:grabbing}.tbw-pivot-chip-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:120px}.tbw-pivot-chip-remove{display:flex;align-items:center;justify-content:center;width:var(--tbw-icon-size, 1rem);height:var(--tbw-icon-size, 1rem);padding:0;border:none;background:transparent;color:var(--tbw-color-fg-muted);font-size:var(--tbw-font-size-sm, .875rem);font-weight:700;cursor:pointer;border-radius:50%;transition:all .15s ease}.tbw-pivot-chip-remove:hover{background:var(--tbw-pivot-chip-remove-hover-bg, var(--tbw-color-accent));color:var(--tbw-pivot-chip-remove-hover-fg, var(--tbw-color-accent-fg))}.tbw-pivot-value-chip{padding:var(--tbw-button-padding-sm, var(--tbw-spacing-xs, .25rem) var(--tbw-spacing-md, .5rem))}.tbw-pivot-value-label-wrapper{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));flex:1;min-width:0}.tbw-pivot-agg-select{padding:var(--tbw-spacing-xs, .125rem) var(--tbw-spacing-xs, .25rem);font-size:var(--tbw-font-size-xs, .6875rem);border:1px solid var(--tbw-color-border);border-radius:var(--tbw-border-radius);background:var(--tbw-color-bg);cursor:pointer}.tbw-pivot-available-fields{display:flex;flex-wrap:wrap;gap:var(--tbw-spacing-sm, .375rem);min-height:40px}.tbw-pivot-options{display:flex;flex-direction:column;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.tbw-pivot-checkbox{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));cursor:pointer}.tbw-pivot-checkbox input{width:var(--tbw-icon-size-sm, .875rem);height:var(--tbw-icon-size-sm, .875rem);cursor:pointer}.pivot-group-row.tbw-pivot-slide-in,.pivot-leaf-row.tbw-pivot-slide-in{animation:tbw-pivot-slide-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-pivot-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}.pivot-group-row.tbw-pivot-fade-in,.pivot-leaf-row.tbw-pivot-fade-in{animation:tbw-pivot-fade-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-pivot-fade-in{0%{opacity:0}to{opacity:1}}}';static PANEL_ID="pivot";get defaultConfig(){return{active:!0,showTotals:!0,showGrandTotal:!0,showToolPanel:!0,animation:"slide"}}isActive=!1;hasInitialized=!1;pivotResult=null;fieldHeaderMap=new Map;expandedKeys=new Set;defaultExpanded=!0;userHasToggledExpand=!1;originalColumns=[];panelContainer=null;grandTotalFooter=null;previousVisibleKeys=new Set;keysToAnimate=new Set;hasValidPivotConfig(){return(this.config.valueFields?.length??0)>0}get animationStyle(){return!!this.isAnimationEnabled&&(this.config.animation??"slide")}detach(){this.isActive=!1,this.hasInitialized=!1,this.pivotResult=null,this.fieldHeaderMap.clear(),this.originalColumns=[],this.panelContainer=null,this.cleanupGrandTotalFooter(),this.previousVisibleKeys.clear(),this.keysToAnimate.clear(),this.userHasToggledExpand=!1}getToolPanel(){if(!1!==(this.config?.showToolPanel??this.userConfig?.showToolPanel??!0))return{id:b.PANEL_ID,title:"Pivot",icon:"⊞",tooltip:"Configure pivot table",order:90,render:e=>this.renderPanel(e)}}processRows(e){if(!this.hasInitialized&&!1!==this.config.active&&this.hasValidPivotConfig()&&(this.hasInitialized=!0,this.isActive=!0),!this.isActive)return[...e];const t=function(e){const t=[];return e.rowGroupFields?.length||e.columnGroupFields?.length||t.push("At least one row or column group field is required"),e.valueFields?.length||t.push("At least one value field is required"),t}(this.config);if(t.length>0)return this.warn(`Config errors: ${t.join(", ")}`),[...e];this.buildFieldHeaderMap(),this.defaultExpanded=this.config.defaultExpanded??!0,this.pivotResult=n(e,this.config),0===this.expandedKeys.size&&this.defaultExpanded&&!this.userHasToggledExpand&&this.expandAllKeys();const o=this.config.indentWidth??20,i=function(e,t,o=!0){const i=[];function r(e){i.push(e);const n=t?t.has(e.rowKey):o;if(e.children&&n)for(const t of e.children)r(t)}for(const n of e)r(n);return i}(this.pivotResult.rows,this.expandedKeys,this.defaultExpanded).map(e=>({__pivotRowKey:e.rowKey,__pivotLabel:e.rowLabel,__pivotDepth:e.depth,__pivotIsGroup:e.isGroup,__pivotHasChildren:Boolean(e.children?.length),__pivotExpanded:this.expandedKeys.has(e.rowKey),__pivotRowCount:e.rowCount??0,__pivotIndent:e.depth*o,__pivotTotal:e.total,...e.values}));this.keysToAnimate.clear();const r=new Set;for(const n of i){const e=n.__pivotRowKey;r.add(e),!this.previousVisibleKeys.has(e)&&n.__pivotDepth>0&&this.keysToAnimate.add(e)}return this.previousVisibleKeys=r,i}processColumns(e){if(!this.isActive||!this.pivotResult)return[...e];const t=[],o=(this.config.rowGroupFields??[]).map(e=>this.fieldHeaderMap.get(e)??e).join(" / ");t.push({field:"__pivotLabel",header:o||"Group",width:200});for(const i of this.pivotResult.columnKeys)for(const e of this.config.valueFields??[]){const o=r([i],e.field),n=e.header||this.fieldHeaderMap.get(e.field)||e.field;t.push({field:o,header:`${i} - ${n} (${e.aggFunc})`,width:120,type:"number"})}return this.config.showTotals&&t.push({field:"__pivotTotal",header:"Total",width:100,type:"number"}),t}renderRow(e,t,o){const i=e;return i.__pivotRowKey&&i.__pivotHasChildren?function(e,t,o){return t.className="data-grid-row pivot-group-row",t.setAttribute("data-pivot-depth",String(e.__pivotDepth??0)),t.setAttribute("data-pivot-key",String(e.__pivotRowKey??"")),t.setAttribute("role","row"),t.innerHTML="",o.columns.forEach((i,r)=>{const n=document.createElement("div");if(n.className="cell",n.setAttribute("data-col",String(r)),n.setAttribute("data-row",String(o.rowIndex)),n.setAttribute("role","gridcell"),0===r){const t=Number(e.__pivotIndent)||0;n.style.paddingLeft=`${t}px`;const i=String(e.__pivotRowKey),r=document.createElement("button");r.type="button",r.className="pivot-toggle",r.setAttribute("aria-label",e.__pivotExpanded?"Collapse group":"Expand group"),o.setIcon(r,o.resolveIcon(e.__pivotExpanded?"collapse":"expand")),r.addEventListener("click",e=>{e.stopPropagation(),o.onToggle(i)}),n.appendChild(r);const a=document.createElement("span");a.className="pivot-label",a.textContent=String(e.__pivotLabel??""),n.appendChild(a);const s=document.createElement("span");s.className="pivot-count",s.textContent=` (${Number(e.__pivotRowCount)||0})`,n.appendChild(s)}else{const t=e[i.field];n.textContent=null!=t?String(t):""}t.appendChild(n)}),!0}(i,t,{columns:this.gridColumns,rowIndex:o,onToggle:e=>this.toggle(e),resolveIcon:e=>this.resolveIcon(e),setIcon:(e,t)=>this.setIcon(e,t)}):void 0!==i.__pivotRowKey&&this.isActive?function(e,t,o,i){return t.className="data-grid-row pivot-leaf-row",t.setAttribute("data-pivot-depth",String(e.__pivotDepth??0)),t.setAttribute("data-pivot-key",String(e.__pivotRowKey??"")),t.innerHTML="",o.forEach((o,r)=>{const n=document.createElement("div");if(n.className="cell",n.setAttribute("data-col",String(r)),n.setAttribute("data-row",String(i)),n.setAttribute("role","gridcell"),0===r){const t=Number(e.__pivotIndent)||0;n.style.paddingLeft=`${t+20}px`;const o=document.createElement("span");o.className="pivot-label",o.textContent=String(e.__pivotLabel??""),n.appendChild(o)}else{const t=e[o.field];n.textContent=null!=t?String(t):""}t.appendChild(n)}),!0}(i,t,this.gridColumns,o):(this.cleanupPivotStyling(t),!1)}cleanupPivotStyling(e){(e.classList.contains("pivot-group-row")||e.classList.contains("pivot-leaf-row")||e.classList.contains("pivot-grand-total-row"))&&(e.classList.remove("pivot-group-row","pivot-leaf-row","pivot-grand-total-row"),e.classList.add("data-grid-row"),e.removeAttribute("data-pivot-depth"),e.innerHTML="")}onKeyDown(e){if(" "!==e.key)return;if(!this.isActive)return;const t=this.grid._focusRow,o=this.rows[t];return o?.__pivotIsGroup&&o.__pivotHasChildren?(e.preventDefault(),this.toggle(o.__pivotRowKey),this.requestRenderWithFocus(),!0):void 0}afterRender(){this.isActive&&this.config.showGrandTotal&&this.pivotResult?this.renderGrandTotalFooter():this.cleanupGrandTotalFooter();const e=this.animationStyle;if(!1===e||0===this.keysToAnimate.size)return;const t=this.gridElement?.querySelector(".rows");if(!t)return;const o="fade"===e?"tbw-pivot-fade-in":"tbw-pivot-slide-in";for(const i of t.querySelectorAll(".pivot-group-row, .pivot-leaf-row")){const e=i.dataset.pivotKey;e&&this.keysToAnimate.has(e)&&(i.classList.add(o),i.addEventListener("animationend",()=>i.classList.remove(o),{once:!0}))}this.keysToAnimate.clear()}renderGrandTotalFooter(){if(!this.pivotResult)return;const e=this.gridElement;if(!e)return;const t=e.querySelector(".tbw-scroll-area")??e.querySelector(".tbw-grid-content")??e.querySelector(".tbw-grid-root");if(!t)return;this.grandTotalFooter||(this.grandTotalFooter=document.createElement("div"),this.grandTotalFooter.className="pivot-grand-total-footer",t.appendChild(this.grandTotalFooter));const o={__pivotRowKey:"__grandTotal",__pivotLabel:"Grand Total",__pivotIsGrandTotal:!0,__pivotTotal:this.pivotResult.grandTotal,...this.pivotResult.totals};var i,r,n;i=o,r=this.grandTotalFooter,n=this.gridColumns,r.className="pivot-grand-total-row",r.setAttribute("role","presentation"),r.innerHTML="",n.forEach((e,t)=>{const o=document.createElement("div");if(o.className="cell",o.setAttribute("data-col",String(t)),0===t){const e=document.createElement("span");e.className="pivot-label",e.textContent="Grand Total",o.appendChild(e)}else{const t=i[e.field];o.textContent=null!=t?String(t):""}r.appendChild(o)})}cleanupGrandTotalFooter(){this.grandTotalFooter&&(this.grandTotalFooter.remove(),this.grandTotalFooter=null)}toggle(e){this.userHasToggledExpand=!0,this.expandedKeys.has(e)?this.expandedKeys.delete(e):this.expandedKeys.add(e),this.requestRender()}expand(e){this.userHasToggledExpand=!0,this.expandedKeys.add(e),this.requestRender()}collapse(e){this.userHasToggledExpand=!0,this.expandedKeys.delete(e),this.requestRender()}expandAll(){this.userHasToggledExpand=!0,this.expandAllKeys(),this.requestRender()}collapseAll(){this.userHasToggledExpand=!0,this.expandedKeys.clear(),this.requestRender()}expandAllKeys(){if(!this.pivotResult)return;const e=function(e){const t=[];function o(e){if(e.isGroup&&t.push(e.rowKey),e.children)for(const t of e.children)o(t)}for(const i of e)o(i);return t}(this.pivotResult.rows);for(const t of e)this.expandedKeys.add(t)}isExpanded(e){return this.expandedKeys.has(e)}enablePivot(){0===this.originalColumns.length&&this.captureOriginalColumns(),this.isActive=!0,this.requestRender()}disablePivot(){this.isActive=!1,this.pivotResult=null,this.requestRender()}isPivotActive(){return this.isActive}getPivotResult(){return this.pivotResult}setRowGroupFields(e){this.config.rowGroupFields=e,this.requestRender()}setColumnGroupFields(e){this.config.columnGroupFields=e,this.requestRender()}setValueFields(e){this.config.valueFields=e,this.requestRender()}refresh(){this.pivotResult=null,this.requestRender()}showPanel(){this.grid.openToolPanel(),this.grid.expandedToolPanelSections.includes(b.PANEL_ID)||this.grid.toggleToolPanelSection(b.PANEL_ID)}hidePanel(){this.grid.closeToolPanel()}togglePanel(){this.grid.isToolPanelOpen||this.grid.openToolPanel(),this.grid.toggleToolPanelSection(b.PANEL_ID)}isPanelVisible(){return this.grid.isToolPanelOpen&&this.grid.expandedToolPanelSections.includes(b.PANEL_ID)}get gridColumns(){return this.grid.columns??[]}refreshIfActive(){this.isActive&&this.refresh(),this.refreshPanel()}buildFieldHeaderMap(){const e=this.getAvailableFields();this.fieldHeaderMap.clear();for(const t of e)this.fieldHeaderMap.set(t.field,t.header)}getAvailableFields(){return this.originalColumns.length>0?this.originalColumns:this.captureOriginalColumns()}captureOriginalColumns(){try{const e=this.grid.getAllColumns?.()??this.grid.columns??[];return this.originalColumns=e.filter(e=>!e.field.startsWith("__pivot")).map(e=>({field:e.field,header:e.header??e.field})),this.originalColumns}catch{return[]}}renderPanel(e){this.panelContainer=e,0===this.originalColumns.length&&this.captureOriginalColumns();const t={onTogglePivot:e=>{e?this.enablePivot():this.disablePivot(),this.refreshPanel()},onAddFieldToZone:(e,t)=>this.addFieldToZone(e,t),onRemoveFieldFromZone:(e,t)=>this.removeFieldFromZone(e,t),onAddValueField:(e,t)=>this.addValueField(e,t),onRemoveValueField:e=>this.removeValueField(e),onUpdateValueAggFunc:(e,t)=>this.updateValueAggFunc(e,t),onOptionChange:(e,t)=>{this.config[e]=t,this.isActive&&this.refresh()},getAvailableFields:()=>this.getAvailableFields()};return c(e,this.config,this.isActive,t)}refreshPanel(){this.panelContainer&&(this.panelContainer.innerHTML="",this.renderPanel(this.panelContainer))}addFieldToZone(e,t){if("rowGroups"===t){const t=this.config.rowGroupFields??[];t.includes(e)||(this.config.rowGroupFields=[...t,e])}else{const t=this.config.columnGroupFields??[];t.includes(e)||(this.config.columnGroupFields=[...t,e])}this.removeFromOtherZones(e,t),this.refreshIfActive()}removeFieldFromZone(e,t){"rowGroups"===t?this.config.rowGroupFields=(this.config.rowGroupFields??[]).filter(t=>t!==e):this.config.columnGroupFields=(this.config.columnGroupFields??[]).filter(t=>t!==e),this.refreshIfActive()}removeFromOtherZones(e,t){"rowGroups"!==t&&(this.config.rowGroupFields=(this.config.rowGroupFields??[]).filter(t=>t!==e)),"columnGroups"!==t&&(this.config.columnGroupFields=(this.config.columnGroupFields??[]).filter(t=>t!==e)),"values"!==t&&(this.config.valueFields=(this.config.valueFields??[]).filter(t=>t.field!==e))}addValueField(e,t){const o=this.config.valueFields??[];o.some(t=>t.field===e)||(this.config.valueFields=[...o,{field:e,aggFunc:t}]),this.removeFromOtherZones(e,"values"),this.refreshIfActive()}removeValueField(e){this.config.valueFields=(this.config.valueFields??[]).filter(t=>t.field!==e),this.refreshIfActive()}updateValueAggFunc(e,t){const o=this.config.valueFields??[],i=o.findIndex(t=>t.field===e);i>=0&&(o[i]={...o[i],aggFunc:t},this.config.valueFields=[...o]),this.isActive&&this.refresh()}}e.PivotPlugin=b,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/plugin/base-plugin"),require("../../core/internal/aggregators"),require("../../core/constants")):"function"==typeof define&&define.amd?define(["exports","../../core/plugin/base-plugin","../../core/internal/aggregators","../../core/constants"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_pivot={},e.TbwGrid,e.TbwGrid,e.TbwGrid)}(this,function(e,t,o,i){"use strict";const r=o.getValueAggregator;function n(e,t){return[...e,t].join("|")}function a(e,t){const o=t.rowGroupFields??[],i=t.columnGroupFields??[],r=t.valueFields??[],a=function(e,t){if(0===t.length)return["value"];const o=new Set;for(const i of e){const e=t.map(e=>String(i[e]??"")).join("|");o.add(e)}return[...o].sort()}(e,i),l=s(e,o,i,a,r,0,""),d=function(e,t,o){const i={};function r(e){for(const a of e)if(a.isGroup&&a.children?.length)a.children&&r(a.children);else for(const e of t)for(const t of o){const o=n([e],t.field);i[o]=(i[o]??0)+(a.values[o]??0)}}return r(e),i}(l,a,r);return{rows:l,columnKeys:a,totals:d,grandTotal:Object.values(d).reduce((e,t)=>e+t,0)}}function s(e,t,o,i,r,n,a){const c=[];if(0===t.length){const t=l(e,o,i,r),s=d(t);return c.push({rowKey:a||"all",rowLabel:a||"All",depth:n,values:t,total:s,isGroup:!1,rowCount:e.length}),c}const p=t[0],g=t.slice(1),v=g.length>0,u=function(e,t){const o=new Map;for(const i of e){const e=String(i[t]??""),r=o.get(e);r?r.push(i):o.set(e,[i])}return o}(e,p);for(const[h,b]of u){const e=a?`${a}|${h}`:h,t=l(b,o,i,r),p=d(t);let u;v&&(u=s(b,g,o,i,r,n+1,e)),c.push({rowKey:e,rowLabel:h||"(blank)",depth:n,values:t,total:p,isGroup:v,children:u,rowCount:b.length})}return c}function l(e,t,o,i){const a={};for(const s of o)for(const o of i){const i=(t.length>0?e.filter(e=>t.map(t=>String(e[t]??"")).join("|")===s):e).map(e=>Number(e[o.field])||0),l=r(o.aggFunc),d=i.length>0?l(i):null;a[n([s],o.field)]=d}return a}function d(e){let t=0;for(const o of Object.values(e))t+=o??0;return t}const c=["sum","avg","count","min","max","first","last"];function p(e,t,o,r){const n=new AbortController,a={config:t,callbacks:r,signal:n.signal},s=document.createElement("div");return s.className="tbw-pivot-panel",s.appendChild(g("Options",()=>function(e,t){const{config:o,callbacks:i,signal:r}=t,n=document.createElement("div");return n.className="tbw-pivot-options",n.appendChild(b("Enable Pivot View",e,e=>{i.onTogglePivot(e)},r)),n.appendChild(b("Show Row Totals",o.showTotals??!0,e=>{i.onOptionChange("showTotals",e)},r)),n.appendChild(b("Show Grand Total",o.showGrandTotal??!0,e=>{i.onOptionChange("showGrandTotal",e)},r)),n}(o,a))),s.appendChild(g("Row Groups",()=>v("rowGroups",a))),s.appendChild(g("Column Groups",()=>v("columnGroups",a))),s.appendChild(g("Values",()=>function(e){const{config:t,callbacks:o,signal:i}=e,r=document.createElement("div");r.className="tbw-pivot-drop-zone tbw-pivot-values-zone",r.setAttribute("data-zone","values");const n=t.valueFields??[];if(0===n.length){const e=document.createElement("div");e.className="tbw-pivot-placeholder",e.textContent="Drag numeric fields here for aggregation",r.appendChild(e)}else for(const a of n)r.appendChild(h(a,e));return r.addEventListener("dragover",e=>{e.preventDefault(),r.classList.add("drag-over")},{signal:i}),r.addEventListener("dragleave",()=>{r.classList.remove("drag-over")},{signal:i}),r.addEventListener("drop",e=>{e.preventDefault(),r.classList.remove("drag-over");const t=e.dataTransfer?.getData("text/plain");t&&o.onAddValueField(t,"sum")},{signal:i}),r}(a))),s.appendChild(g("Available Fields",()=>function(e){const{config:t,callbacks:o,signal:r}=e,n=document.createElement("div");n.className="tbw-pivot-available-fields";const a=o.getAvailableFields(),s=new Set([...t.rowGroupFields??[],...t.columnGroupFields??[],...t.valueFields?.map(e=>e.field)??[]]),l=a.filter(e=>!s.has(e.field));if(0===l.length){const e=document.createElement("div");e.className="tbw-pivot-placeholder",e.textContent="All fields are in use",n.appendChild(e)}else for(const d of l){const e=document.createElement("div");e.className="tbw-pivot-field-chip available",e.textContent=d.header,e.draggable=!0,e.title=`Drag to add "${d.field}" to a zone`,e.addEventListener("dragstart",t=>{t.dataTransfer?.setData("text/plain",d.field),e.classList.add(i.GridClasses.DRAGGING)},{signal:r}),e.addEventListener("dragend",()=>{e.classList.remove(i.GridClasses.DRAGGING)},{signal:r}),n.appendChild(e)}return n}(a))),e.appendChild(s),()=>{n.abort(),s.remove()}}function g(e,t){const o=document.createElement("div");o.className="tbw-pivot-section";const i=document.createElement("div");i.className="tbw-pivot-section-header",i.textContent=e;const r=document.createElement("div");return r.className="tbw-pivot-section-content",r.appendChild(t()),o.appendChild(i),o.appendChild(r),o}function v(e,t){const{config:o,callbacks:i,signal:r}=t,n=document.createElement("div");n.className="tbw-pivot-drop-zone",n.setAttribute("data-zone",e);const a="rowGroups"===e?o.rowGroupFields??[]:o.columnGroupFields??[];if(0===a.length){const e=document.createElement("div");e.className="tbw-pivot-placeholder",e.textContent="Drag fields here or click to add",n.appendChild(e)}else for(const s of a)n.appendChild(u(s,e,t));return n.addEventListener("dragover",e=>{e.preventDefault(),n.classList.add("drag-over")},{signal:r}),n.addEventListener("dragleave",()=>{n.classList.remove("drag-over")},{signal:r}),n.addEventListener("drop",t=>{t.preventDefault(),n.classList.remove("drag-over");const o=t.dataTransfer?.getData("text/plain");o&&i.onAddFieldToZone(o,e)},{signal:r}),n}function u(e,t,o){const{callbacks:r,signal:n}=o,a=document.createElement("div");a.className="tbw-pivot-field-chip",a.draggable=!0;const s=r.getAvailableFields().find(t=>t.field===e),l=document.createElement("span");l.className="tbw-pivot-chip-label",l.textContent=s?.header??e;const d=document.createElement("button");return d.className="tbw-pivot-chip-remove",d.innerHTML="×",d.title="Remove field",d.addEventListener("click",o=>{o.stopPropagation(),r.onRemoveFieldFromZone(e,t)},{signal:n}),a.appendChild(l),a.appendChild(d),a.addEventListener("dragstart",o=>{o.dataTransfer?.setData("text/plain",e),o.dataTransfer?.setData("source-zone",t),a.classList.add(i.GridClasses.DRAGGING)},{signal:n}),a.addEventListener("dragend",()=>{a.classList.remove(i.GridClasses.DRAGGING)},{signal:n}),a}function h(e,t){const{callbacks:o,signal:i}=t,r=document.createElement("div");r.className="tbw-pivot-field-chip tbw-pivot-value-chip";const n=o.getAvailableFields().find(t=>t.field===e.field),a=document.createElement("div");a.className="tbw-pivot-value-label-wrapper";const s=document.createElement("span");s.className="tbw-pivot-chip-label",s.textContent=n?.header??e.field;const l=document.createElement("select");l.className="tbw-pivot-agg-select",l.title="Aggregation function";for(const p of c){const t=document.createElement("option");t.value=p,t.textContent=p.toUpperCase(),t.selected=p===e.aggFunc,l.appendChild(t)}l.addEventListener("change",()=>{o.onUpdateValueAggFunc(e.field,l.value)},{signal:i});const d=document.createElement("button");return d.className="tbw-pivot-chip-remove",d.innerHTML="×",d.title="Remove value field",d.addEventListener("click",t=>{t.stopPropagation(),o.onRemoveValueField(e.field)},{signal:i}),a.appendChild(s),a.appendChild(l),r.appendChild(a),r.appendChild(d),r}function b(e,t,o,i){const r=document.createElement("label");r.className="tbw-pivot-checkbox";const n=document.createElement("input");n.type="checkbox",n.checked=t,n.addEventListener("change",()=>o(n.checked),{signal:i});const a=document.createElement("span");return a.textContent=e,r.appendChild(n),r.appendChild(a),r}class w extends t.BaseGridPlugin{static manifest={incompatibleWith:[{name:"groupingRows",reason:"PivotPlugin creates its own aggregated row and column structure. Row grouping cannot be applied on top of pivot-generated rows."},{name:"tree",reason:"PivotPlugin replaces the entire row and column structure with aggregated pivot data. Tree hierarchy cannot coexist with pivot aggregation."},{name:"serverSide",reason:"PivotPlugin requires the full dataset to compute aggregations. ServerSidePlugin lazy-loads rows in blocks, so pivot aggregation cannot be performed client-side."}]};name="pivot";styles='@layer tbw-plugins{.pivot-group-row{display:grid;grid-template-columns:var(--tbw-column-template);font-weight:600;background:var(--tbw-pivot-group-bg, var(--tbw-color-row-alt));min-height:var(--tbw-row-height);border-bottom:var(--tbw-row-divider)}@media(hover:hover){.pivot-group-row:hover{background:var(--tbw-pivot-group-hover, var(--tbw-color-row-hover))}}.pivot-leaf-row{display:grid;grid-template-columns:var(--tbw-column-template);background:var(--tbw-pivot-leaf-bg, var(--tbw-color-bg));min-height:var(--tbw-row-height);border-bottom:var(--tbw-row-divider)}.pivot-grand-total-row{display:grid;grid-template-columns:var(--tbw-column-template);font-weight:700;background:var(--tbw-pivot-grand-total-bg, var(--tbw-color-header-bg));min-height:var(--tbw-row-height);border-top:2px solid var(--tbw-color-border-strong)}.pivot-grand-total-row>.cell{display:flex;align-items:center;padding:var(--tbw-cell-padding);border-right:1px solid var(--tbw-color-border-cell);overflow:hidden;min-width:0}.pivot-grand-total-row>.cell:last-child{border-right:0}.pivot-grand-total-footer{position:sticky;bottom:0;z-index:var(--tbw-z-layer-pinned-rows, 20);background:var(--tbw-pivot-grand-total-bg, var(--tbw-color-header-bg));min-width:fit-content}.pivot-group-row>.cell,.pivot-leaf-row>.cell{display:flex;align-items:center;padding:var(--tbw-cell-padding);border-right:1px solid var(--tbw-color-border-cell);overflow:hidden;min-width:0}.pivot-group-row>.cell:last-child,.pivot-leaf-row>.cell:last-child{border-right:0}.pivot-toggle{display:inline-flex;align-items:center;justify-content:center;width:var(--tbw-pivot-toggle-size, var(--tbw-toggle-size, 1.25em));height:var(--tbw-pivot-toggle-size, var(--tbw-toggle-size, 1.25em));margin-right:var(--tbw-spacing-sm, .375em);border:none;background:transparent;cursor:pointer;color:var(--tbw-pivot-toggle-color, var(--tbw-color-fg-muted));border-radius:var(--tbw-border-radius);transition:background var(--tbw-transition-duration, .12s) var(--tbw-transition-ease, ease),color var(--tbw-transition-duration, .12s) var(--tbw-transition-ease, ease)}.pivot-toggle:hover{background:var(--tbw-pivot-toggle-hover-bg, var(--tbw-color-row-hover));color:var(--tbw-pivot-toggle-hover-color, var(--tbw-color-fg))}.pivot-toggle:focus{outline:var(--tbw-focus-outline);outline-offset:var(--tbw-focus-outline-offset)}.pivot-label{font-weight:inherit}.pivot-count{color:var(--tbw-pivot-count-color, var(--tbw-color-fg-muted));font-size:.9em;font-weight:400}.pivot-total-row{font-weight:700;border-top:2px solid var(--tbw-pivot-border, var(--tbw-color-border-strong))}[data-pivot-depth="1"]{--tbw-pivot-depth: 1}[data-pivot-depth="2"]{--tbw-pivot-depth: 2}[data-pivot-depth="3"]{--tbw-pivot-depth: 3}[data-pivot-depth="4"]{--tbw-pivot-depth: 4}.tbw-pivot-panel{display:flex;flex-direction:column;gap:var(--tbw-panel-padding, var(--tbw-spacing-lg, .75rem));padding:var(--tbw-panel-padding, var(--tbw-spacing-lg, .75rem));height:100%;overflow-y:auto;font-size:var(--tbw-font-size-sm, .8125rem)}.tbw-pivot-section{border:1px solid var(--tbw-pivot-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius);background:var(--tbw-pivot-section-bg, var(--tbw-color-bg))}.tbw-pivot-section-header{padding:var(--tbw-button-padding, var(--tbw-spacing-md, .5rem) var(--tbw-spacing-lg, .75rem));font-weight:600;background:var(--tbw-pivot-header-bg, var(--tbw-color-header-bg));border-bottom:1px solid var(--tbw-pivot-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius) var(--tbw-border-radius) 0 0}.tbw-pivot-section-content{padding:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.tbw-pivot-toggle-wrapper{display:flex;align-items:center}.tbw-pivot-toggle-label{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));cursor:pointer}.tbw-pivot-toggle-label input{width:var(--tbw-icon-size, 1rem);height:var(--tbw-icon-size, 1rem);cursor:pointer}.tbw-pivot-drop-zone{min-height:60px;padding:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));border:2px dashed var(--tbw-pivot-drop-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius);background:var(--tbw-pivot-drop-bg, var(--tbw-color-row-alt));display:flex;flex-wrap:wrap;gap:var(--tbw-spacing-sm, .375rem);align-content:flex-start;transition:all .15s ease}.tbw-pivot-drop-zone.drag-over{border-color:var(--tbw-color-accent);background:var(--tbw-pivot-drop-active, var(--tbw-focus-background))}.tbw-pivot-placeholder{color:var(--tbw-color-fg-muted);font-style:italic;padding:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));text-align:center;width:100%}.tbw-pivot-field-chip{display:inline-flex;align-items:center;gap:var(--tbw-spacing-sm, .375rem);padding:var(--tbw-button-padding-sm, var(--tbw-spacing-xs, .25rem) var(--tbw-spacing-md, .5rem));background:var(--tbw-pivot-chip-bg, var(--tbw-color-header-bg));border:1px solid var(--tbw-pivot-chip-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius);cursor:grab;font-size:var(--tbw-font-size-xs, .75rem);transition:all .15s ease}.tbw-pivot-field-chip:hover{background:var(--tbw-pivot-chip-hover, var(--tbw-color-row-hover));border-color:var(--tbw-color-accent)}.tbw-pivot-field-chip.available{background:var(--tbw-color-bg)}.tbw-pivot-field-chip.dragging{opacity:.5;cursor:grabbing}.tbw-pivot-chip-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:120px}.tbw-pivot-chip-remove{display:flex;align-items:center;justify-content:center;width:var(--tbw-icon-size, 1rem);height:var(--tbw-icon-size, 1rem);padding:0;border:none;background:transparent;color:var(--tbw-color-fg-muted);font-size:var(--tbw-font-size-sm, .875rem);font-weight:700;cursor:pointer;border-radius:50%;transition:all .15s ease}.tbw-pivot-chip-remove:hover{background:var(--tbw-pivot-chip-remove-hover-bg, var(--tbw-color-accent));color:var(--tbw-pivot-chip-remove-hover-fg, var(--tbw-color-accent-fg))}.tbw-pivot-value-chip{padding:var(--tbw-button-padding-sm, var(--tbw-spacing-xs, .25rem) var(--tbw-spacing-md, .5rem))}.tbw-pivot-value-label-wrapper{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));flex:1;min-width:0}.tbw-pivot-agg-select{padding:var(--tbw-spacing-xs, .125rem) var(--tbw-spacing-xs, .25rem);font-size:var(--tbw-font-size-xs, .6875rem);border:1px solid var(--tbw-color-border);border-radius:var(--tbw-border-radius);background:var(--tbw-color-bg);cursor:pointer}.tbw-pivot-available-fields{display:flex;flex-wrap:wrap;gap:var(--tbw-spacing-sm, .375rem);min-height:40px}.tbw-pivot-options{display:flex;flex-direction:column;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.tbw-pivot-checkbox{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));cursor:pointer}.tbw-pivot-checkbox input{width:var(--tbw-icon-size-sm, .875rem);height:var(--tbw-icon-size-sm, .875rem);cursor:pointer}.pivot-group-row.tbw-pivot-slide-in,.pivot-leaf-row.tbw-pivot-slide-in{animation:tbw-pivot-slide-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-pivot-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}.pivot-group-row.tbw-pivot-fade-in,.pivot-leaf-row.tbw-pivot-fade-in{animation:tbw-pivot-fade-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-pivot-fade-in{0%{opacity:0}to{opacity:1}}}';static PANEL_ID="pivot";get defaultConfig(){return{active:!0,showTotals:!0,showGrandTotal:!0,showToolPanel:!0,animation:"slide"}}isActive=!1;hasInitialized=!1;pivotResult=null;fieldHeaderMap=new Map;expandedKeys=new Set;defaultExpanded=!0;userHasToggledExpand=!1;originalColumns=[];panelContainer=null;grandTotalFooter=null;previousVisibleKeys=new Set;keysToAnimate=new Set;hasValidPivotConfig(){return(this.config.valueFields?.length??0)>0}get animationStyle(){return!!this.isAnimationEnabled&&(this.config.animation??"slide")}detach(){this.isActive=!1,this.hasInitialized=!1,this.pivotResult=null,this.fieldHeaderMap.clear(),this.originalColumns=[],this.panelContainer=null,this.cleanupGrandTotalFooter(),this.previousVisibleKeys.clear(),this.keysToAnimate.clear(),this.userHasToggledExpand=!1}getToolPanel(){if(!1!==(this.config?.showToolPanel??this.userConfig?.showToolPanel??!0))return{id:w.PANEL_ID,title:"Pivot",icon:"⊞",tooltip:"Configure pivot table",order:90,render:e=>this.renderPanel(e)}}processRows(e){if(!this.hasInitialized&&!1!==this.config.active&&this.hasValidPivotConfig()&&(this.hasInitialized=!0,this.isActive=!0),!this.isActive)return[...e];const t=function(e){const t=[];return e.rowGroupFields?.length||e.columnGroupFields?.length||t.push("At least one row or column group field is required"),e.valueFields?.length||t.push("At least one value field is required"),t}(this.config);if(t.length>0)return this.warn(`Config errors: ${t.join(", ")}`),[...e];this.buildFieldHeaderMap(),this.defaultExpanded=this.config.defaultExpanded??!0,this.pivotResult=a(e,this.config),0===this.expandedKeys.size&&this.defaultExpanded&&!this.userHasToggledExpand&&this.expandAllKeys();const o=this.config.indentWidth??20,i=function(e,t,o=!0){const i=[];function r(e){i.push(e);const n=t?t.has(e.rowKey):o;if(e.children&&n)for(const t of e.children)r(t)}for(const n of e)r(n);return i}(this.pivotResult.rows,this.expandedKeys,this.defaultExpanded).map(e=>({__pivotRowKey:e.rowKey,__pivotLabel:e.rowLabel,__pivotDepth:e.depth,__pivotIsGroup:e.isGroup,__pivotHasChildren:Boolean(e.children?.length),__pivotExpanded:this.expandedKeys.has(e.rowKey),__pivotRowCount:e.rowCount??0,__pivotIndent:e.depth*o,__pivotTotal:e.total,...e.values}));this.keysToAnimate.clear();const r=new Set;for(const n of i){const e=n.__pivotRowKey;r.add(e),!this.previousVisibleKeys.has(e)&&n.__pivotDepth>0&&this.keysToAnimate.add(e)}return this.previousVisibleKeys=r,i}processColumns(e){if(!this.isActive||!this.pivotResult)return[...e];const t=[],o=(this.config.rowGroupFields??[]).map(e=>this.fieldHeaderMap.get(e)??e).join(" / ");t.push({field:"__pivotLabel",header:o||"Group",width:200});for(const i of this.pivotResult.columnKeys)for(const e of this.config.valueFields??[]){const o=n([i],e.field),r=e.header||this.fieldHeaderMap.get(e.field)||e.field;t.push({field:o,header:`${i} - ${r} (${e.aggFunc})`,width:120,type:"number"})}return this.config.showTotals&&t.push({field:"__pivotTotal",header:"Total",width:100,type:"number"}),t}renderRow(e,t,o){const i=e;return i.__pivotRowKey&&i.__pivotHasChildren?function(e,t,o){return t.className="data-grid-row pivot-group-row",t.setAttribute("data-pivot-depth",String(e.__pivotDepth??0)),t.setAttribute("data-pivot-key",String(e.__pivotRowKey??"")),t.setAttribute("role","row"),t.innerHTML="",o.columns.forEach((i,r)=>{const n=document.createElement("div");if(n.className="cell",n.setAttribute("data-col",String(r)),n.setAttribute("data-row",String(o.rowIndex)),n.setAttribute("role","gridcell"),0===r){const t=Number(e.__pivotIndent)||0;n.style.paddingLeft=`${t}px`;const i=String(e.__pivotRowKey),r=document.createElement("button");r.type="button",r.className="pivot-toggle",r.setAttribute("aria-label",e.__pivotExpanded?"Collapse group":"Expand group"),o.setIcon(r,o.resolveIcon(e.__pivotExpanded?"collapse":"expand")),r.addEventListener("click",e=>{e.stopPropagation(),o.onToggle(i)}),n.appendChild(r);const a=document.createElement("span");a.className="pivot-label",a.textContent=String(e.__pivotLabel??""),n.appendChild(a);const s=document.createElement("span");s.className="pivot-count",s.textContent=` (${Number(e.__pivotRowCount)||0})`,n.appendChild(s)}else{const t=e[i.field];n.textContent=null!=t?String(t):""}t.appendChild(n)}),!0}(i,t,{columns:this.gridColumns,rowIndex:o,onToggle:e=>this.toggle(e),resolveIcon:e=>this.resolveIcon(e),setIcon:(e,t)=>this.setIcon(e,t)}):void 0!==i.__pivotRowKey&&this.isActive?function(e,t,o,i){return t.className="data-grid-row pivot-leaf-row",t.setAttribute("data-pivot-depth",String(e.__pivotDepth??0)),t.setAttribute("data-pivot-key",String(e.__pivotRowKey??"")),t.innerHTML="",o.forEach((o,r)=>{const n=document.createElement("div");if(n.className="cell",n.setAttribute("data-col",String(r)),n.setAttribute("data-row",String(i)),n.setAttribute("role","gridcell"),0===r){const t=Number(e.__pivotIndent)||0;n.style.paddingLeft=`${t+20}px`;const o=document.createElement("span");o.className="pivot-label",o.textContent=String(e.__pivotLabel??""),n.appendChild(o)}else{const t=e[o.field];n.textContent=null!=t?String(t):""}t.appendChild(n)}),!0}(i,t,this.gridColumns,o):(this.cleanupPivotStyling(t),!1)}cleanupPivotStyling(e){(e.classList.contains("pivot-group-row")||e.classList.contains("pivot-leaf-row")||e.classList.contains("pivot-grand-total-row"))&&(e.classList.remove("pivot-group-row","pivot-leaf-row","pivot-grand-total-row"),e.classList.add("data-grid-row"),e.removeAttribute("data-pivot-depth"),e.innerHTML="")}onKeyDown(e){if(" "!==e.key)return;if(!this.isActive)return;const t=this.grid._focusRow,o=this.rows[t];return o?.__pivotIsGroup&&o.__pivotHasChildren?(e.preventDefault(),this.toggle(o.__pivotRowKey),this.requestRenderWithFocus(),!0):void 0}afterRender(){this.isActive&&this.config.showGrandTotal&&this.pivotResult?this.renderGrandTotalFooter():this.cleanupGrandTotalFooter();const e=this.animationStyle;if(!1===e||0===this.keysToAnimate.size)return;const t=this.gridElement?.querySelector(".rows");if(!t)return;const o="fade"===e?"tbw-pivot-fade-in":"tbw-pivot-slide-in";for(const i of t.querySelectorAll(".pivot-group-row, .pivot-leaf-row")){const e=i.dataset.pivotKey;e&&this.keysToAnimate.has(e)&&(i.classList.add(o),i.addEventListener("animationend",()=>i.classList.remove(o),{once:!0}))}this.keysToAnimate.clear()}renderGrandTotalFooter(){if(!this.pivotResult)return;const e=this.gridElement;if(!e)return;const t=e.querySelector(".tbw-scroll-area")??e.querySelector(".tbw-grid-content")??e.querySelector(".tbw-grid-root");if(!t)return;this.grandTotalFooter||(this.grandTotalFooter=document.createElement("div"),this.grandTotalFooter.className="pivot-grand-total-footer",t.appendChild(this.grandTotalFooter));const o={__pivotRowKey:"__grandTotal",__pivotLabel:"Grand Total",__pivotIsGrandTotal:!0,__pivotTotal:this.pivotResult.grandTotal,...this.pivotResult.totals};var i,r,n;i=o,r=this.grandTotalFooter,n=this.gridColumns,r.className="pivot-grand-total-row",r.setAttribute("role","presentation"),r.innerHTML="",n.forEach((e,t)=>{const o=document.createElement("div");if(o.className="cell",o.setAttribute("data-col",String(t)),0===t){const e=document.createElement("span");e.className="pivot-label",e.textContent="Grand Total",o.appendChild(e)}else{const t=i[e.field];o.textContent=null!=t?String(t):""}r.appendChild(o)})}cleanupGrandTotalFooter(){this.grandTotalFooter&&(this.grandTotalFooter.remove(),this.grandTotalFooter=null)}toggle(e){this.userHasToggledExpand=!0,this.expandedKeys.has(e)?this.expandedKeys.delete(e):this.expandedKeys.add(e),this.requestRender()}expand(e){this.userHasToggledExpand=!0,this.expandedKeys.add(e),this.requestRender()}collapse(e){this.userHasToggledExpand=!0,this.expandedKeys.delete(e),this.requestRender()}expandAll(){this.userHasToggledExpand=!0,this.expandAllKeys(),this.requestRender()}collapseAll(){this.userHasToggledExpand=!0,this.expandedKeys.clear(),this.requestRender()}expandAllKeys(){if(!this.pivotResult)return;const e=function(e){const t=[];function o(e){if(e.isGroup&&t.push(e.rowKey),e.children)for(const t of e.children)o(t)}for(const i of e)o(i);return t}(this.pivotResult.rows);for(const t of e)this.expandedKeys.add(t)}isExpanded(e){return this.expandedKeys.has(e)}enablePivot(){0===this.originalColumns.length&&this.captureOriginalColumns(),this.isActive=!0,this.requestRender()}disablePivot(){this.isActive=!1,this.pivotResult=null,this.requestRender()}isPivotActive(){return this.isActive}getPivotResult(){return this.pivotResult}setRowGroupFields(e){this.config.rowGroupFields=e,this.requestRender()}setColumnGroupFields(e){this.config.columnGroupFields=e,this.requestRender()}setValueFields(e){this.config.valueFields=e,this.requestRender()}refresh(){this.pivotResult=null,this.requestRender()}showPanel(){this.grid.openToolPanel(),this.grid.expandedToolPanelSections.includes(w.PANEL_ID)||this.grid.toggleToolPanelSection(w.PANEL_ID)}hidePanel(){this.grid.closeToolPanel()}togglePanel(){this.grid.isToolPanelOpen||this.grid.openToolPanel(),this.grid.toggleToolPanelSection(w.PANEL_ID)}isPanelVisible(){return this.grid.isToolPanelOpen&&this.grid.expandedToolPanelSections.includes(w.PANEL_ID)}get gridColumns(){return this.grid.columns??[]}refreshIfActive(){this.isActive&&this.refresh(),this.refreshPanel()}buildFieldHeaderMap(){const e=this.getAvailableFields();this.fieldHeaderMap.clear();for(const t of e)this.fieldHeaderMap.set(t.field,t.header)}getAvailableFields(){return this.originalColumns.length>0?this.originalColumns:this.captureOriginalColumns()}captureOriginalColumns(){try{const e=this.grid.getAllColumns?.()??this.grid.columns??[];return this.originalColumns=e.filter(e=>!e.field.startsWith("__pivot")).map(e=>({field:e.field,header:e.header??e.field})),this.originalColumns}catch{return[]}}renderPanel(e){this.panelContainer=e,0===this.originalColumns.length&&this.captureOriginalColumns();const t={onTogglePivot:e=>{e?this.enablePivot():this.disablePivot(),this.refreshPanel()},onAddFieldToZone:(e,t)=>this.addFieldToZone(e,t),onRemoveFieldFromZone:(e,t)=>this.removeFieldFromZone(e,t),onAddValueField:(e,t)=>this.addValueField(e,t),onRemoveValueField:e=>this.removeValueField(e),onUpdateValueAggFunc:(e,t)=>this.updateValueAggFunc(e,t),onOptionChange:(e,t)=>{this.config[e]=t,this.isActive&&this.refresh()},getAvailableFields:()=>this.getAvailableFields()};return p(e,this.config,this.isActive,t)}refreshPanel(){this.panelContainer&&(this.panelContainer.innerHTML="",this.renderPanel(this.panelContainer))}addFieldToZone(e,t){if("rowGroups"===t){const t=this.config.rowGroupFields??[];t.includes(e)||(this.config.rowGroupFields=[...t,e])}else{const t=this.config.columnGroupFields??[];t.includes(e)||(this.config.columnGroupFields=[...t,e])}this.removeFromOtherZones(e,t),this.refreshIfActive()}removeFieldFromZone(e,t){"rowGroups"===t?this.config.rowGroupFields=(this.config.rowGroupFields??[]).filter(t=>t!==e):this.config.columnGroupFields=(this.config.columnGroupFields??[]).filter(t=>t!==e),this.refreshIfActive()}removeFromOtherZones(e,t){"rowGroups"!==t&&(this.config.rowGroupFields=(this.config.rowGroupFields??[]).filter(t=>t!==e)),"columnGroups"!==t&&(this.config.columnGroupFields=(this.config.columnGroupFields??[]).filter(t=>t!==e)),"values"!==t&&(this.config.valueFields=(this.config.valueFields??[]).filter(t=>t.field!==e))}addValueField(e,t){const o=this.config.valueFields??[];o.some(t=>t.field===e)||(this.config.valueFields=[...o,{field:e,aggFunc:t}]),this.removeFromOtherZones(e,"values"),this.refreshIfActive()}removeValueField(e){this.config.valueFields=(this.config.valueFields??[]).filter(t=>t.field!==e),this.refreshIfActive()}updateValueAggFunc(e,t){const o=this.config.valueFields??[],i=o.findIndex(t=>t.field===e);i>=0&&(o[i]={...o[i],aggFunc:t},this.config.valueFields=[...o]),this.isActive&&this.refresh()}}e.PivotPlugin=w,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
2
2
  //# sourceMappingURL=pivot.umd.js.map