@toolbox-web/grid 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/all.d.ts +3518 -0
  2. package/all.js +3762 -0
  3. package/all.js.map +1 -0
  4. package/index.d.ts +2367 -0
  5. package/index.js +3105 -0
  6. package/index.js.map +1 -0
  7. package/lib/plugins/clipboard/index.js +365 -0
  8. package/lib/plugins/clipboard/index.js.map +1 -0
  9. package/lib/plugins/column-virtualization/index.js +255 -0
  10. package/lib/plugins/column-virtualization/index.js.map +1 -0
  11. package/lib/plugins/context-menu/index.js +341 -0
  12. package/lib/plugins/context-menu/index.js.map +1 -0
  13. package/lib/plugins/export/index.js +305 -0
  14. package/lib/plugins/export/index.js.map +1 -0
  15. package/lib/plugins/filtering/index.js +759 -0
  16. package/lib/plugins/filtering/index.js.map +1 -0
  17. package/lib/plugins/grouping-columns/index.js +283 -0
  18. package/lib/plugins/grouping-columns/index.js.map +1 -0
  19. package/lib/plugins/grouping-rows/index.js +494 -0
  20. package/lib/plugins/grouping-rows/index.js.map +1 -0
  21. package/lib/plugins/master-detail/index.js +303 -0
  22. package/lib/plugins/master-detail/index.js.map +1 -0
  23. package/lib/plugins/multi-sort/index.js +270 -0
  24. package/lib/plugins/multi-sort/index.js.map +1 -0
  25. package/lib/plugins/pinned-columns/index.js +221 -0
  26. package/lib/plugins/pinned-columns/index.js.map +1 -0
  27. package/lib/plugins/pinned-rows/index.js +459 -0
  28. package/lib/plugins/pinned-rows/index.js.map +1 -0
  29. package/lib/plugins/pivot/index.js +326 -0
  30. package/lib/plugins/pivot/index.js.map +1 -0
  31. package/lib/plugins/reorder/index.js +260 -0
  32. package/lib/plugins/reorder/index.js.map +1 -0
  33. package/lib/plugins/selection/index.js +426 -0
  34. package/lib/plugins/selection/index.js.map +1 -0
  35. package/lib/plugins/server-side/index.js +241 -0
  36. package/lib/plugins/server-side/index.js.map +1 -0
  37. package/lib/plugins/tree/index.js +383 -0
  38. package/lib/plugins/tree/index.js.map +1 -0
  39. package/lib/plugins/undo-redo/index.js +289 -0
  40. package/lib/plugins/undo-redo/index.js.map +1 -0
  41. package/lib/plugins/visibility/index.js +430 -0
  42. package/lib/plugins/visibility/index.js.map +1 -0
  43. package/package.json +53 -0
  44. package/themes/dg-theme-contrast.css +43 -0
  45. package/themes/dg-theme-large.css +54 -0
  46. package/themes/dg-theme-standard.css +19 -0
  47. package/themes/dg-theme-vibrant.css +16 -0
  48. package/umd/grid.all.umd.js +660 -0
  49. package/umd/grid.all.umd.js.map +1 -0
  50. package/umd/grid.umd.js +105 -0
  51. package/umd/grid.umd.js.map +1 -0
  52. package/umd/plugins/clipboard.umd.js +9 -0
  53. package/umd/plugins/clipboard.umd.js.map +1 -0
  54. package/umd/plugins/column-virtualization.umd.js +2 -0
  55. package/umd/plugins/column-virtualization.umd.js.map +1 -0
  56. package/umd/plugins/context-menu.umd.js +53 -0
  57. package/umd/plugins/context-menu.umd.js.map +1 -0
  58. package/umd/plugins/export.umd.js +14 -0
  59. package/umd/plugins/export.umd.js.map +1 -0
  60. package/umd/plugins/filtering.umd.js +175 -0
  61. package/umd/plugins/filtering.umd.js.map +1 -0
  62. package/umd/plugins/grouping-columns.umd.js +29 -0
  63. package/umd/plugins/grouping-columns.umd.js.map +1 -0
  64. package/umd/plugins/grouping-rows.umd.js +40 -0
  65. package/umd/plugins/grouping-rows.umd.js.map +1 -0
  66. package/umd/plugins/master-detail.umd.js +27 -0
  67. package/umd/plugins/master-detail.umd.js.map +1 -0
  68. package/umd/plugins/multi-sort.umd.js +26 -0
  69. package/umd/plugins/multi-sort.umd.js.map +1 -0
  70. package/umd/plugins/pinned-columns.umd.js +2 -0
  71. package/umd/plugins/pinned-columns.umd.js.map +1 -0
  72. package/umd/plugins/pinned-rows.umd.js +73 -0
  73. package/umd/plugins/pinned-rows.umd.js.map +1 -0
  74. package/umd/plugins/pivot.umd.js +8 -0
  75. package/umd/plugins/pivot.umd.js.map +1 -0
  76. package/umd/plugins/reorder.umd.js +31 -0
  77. package/umd/plugins/reorder.umd.js.map +1 -0
  78. package/umd/plugins/selection.umd.js +34 -0
  79. package/umd/plugins/selection.umd.js.map +1 -0
  80. package/umd/plugins/server-side.umd.js +2 -0
  81. package/umd/plugins/server-side.umd.js.map +1 -0
  82. package/umd/plugins/tree.umd.js +11 -0
  83. package/umd/plugins/tree.umd.js.map +1 -0
  84. package/umd/plugins/undo-redo.umd.js +2 -0
  85. package/umd/plugins/undo-redo.umd.js.map +1 -0
  86. package/umd/plugins/visibility.umd.js +94 -0
  87. package/umd/plugins/visibility.umd.js.map +1 -0
@@ -0,0 +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 type { PinnedRowsPanel, PinnedRowsContext, PinnedRowsConfig, AggregationRowConfig } from './types';\nimport type { ColumnConfig } from '../../core/types';\nimport { getAggregator } from '../../core/internal/aggregators';\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', 'status');\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 container.setAttribute('role', 'rowgroup');\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 */\nexport function renderAggregationRows(\n container: HTMLElement,\n rows: AggregationRowConfig[],\n columns: ColumnConfig[],\n dataRows: unknown[]\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 rowEl.setAttribute('role', 'row');\n if (rowConfig.id) {\n rowEl.setAttribute('data-aggregation-id', rowConfig.id);\n }\n\n if (rowConfig.fullWidth) {\n // Full-width mode: single cell spanning all columns\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell tbw-aggregation-cell-full';\n cell.style.gridColumn = '1 / -1';\n cell.textContent = rowConfig.label || '';\n rowEl.appendChild(cell);\n } else {\n // Per-column mode: one cell per column with aggregated/static values\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 let value: unknown;\n\n // Check for aggregator first\n const aggRef = rowConfig.aggregators?.[col.field];\n if (aggRef) {\n const aggFn = getAggregator(aggRef);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n } else if (rowConfig.cells && Object.prototype.hasOwnProperty.call(rowConfig.cells, col.field)) {\n // Static or computed cell value\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 cell.textContent = value != null ? String(value) : '';\n rowEl.appendChild(cell);\n }\n }\n\n container.appendChild(rowEl);\n }\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 type { AggregationRowConfig, PinnedRowsConfig, PinnedRowsContext, PinnedRowsPanel } from './types';\n\n/**\n * Pinned Rows Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new PinnedRowsPlugin({\n * enabled: true,\n * position: 'bottom',\n * showRowCount: true,\n * showSelectedCount: true,\n * aggregationRows: [\n * { id: 'totals', position: 'bottom', values: { amount: 'sum' } },\n * ],\n * })\n * ```\n */\nexport class PinnedRowsPlugin extends BaseGridPlugin<PinnedRowsConfig> {\n readonly name = 'pinnedRows';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<PinnedRowsConfig> {\n return {\n enabled: true,\n position: 'bottom',\n showRowCount: true,\n showSelectedCount: true,\n showFilteredCount: true,\n };\n }\n\n // ===== 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\n // ===== Lifecycle =====\n\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\n // ===== Hooks =====\n\n override afterRender(): void {\n if (!this.config.enabled) {\n this.cleanup();\n return;\n }\n\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) 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 shadowRoot.querySelector('.tbw-scroll-area') ??\n shadowRoot.querySelector('.tbw-grid-content') ??\n shadowRoot.children[0];\n if (!container) return;\n\n // Build context with plugin states\n const selectionState = this.getSelectionState();\n const filterState = this.getFilterState();\n\n const context = buildContext(\n this.rows as unknown[],\n this.columns as unknown[],\n this.grid as unknown as HTMLElement,\n selectionState,\n filterState\n );\n\n // ===== 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 = shadowRoot.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.rows as unknown[]\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.rows as unknown[]\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 }\n\n // ===== Private Methods =====\n\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 const grid = this.grid as any;\n return grid?.getPluginState?.('selection') ?? null;\n } catch {\n return null;\n }\n }\n\n private getFilterState(): { cachedResult: unknown[] | null } | null {\n try {\n const grid = this.grid as any;\n return grid?.getPluginState?.('filtering') ?? null;\n } catch {\n return null;\n }\n }\n\n // ===== Public API =====\n\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.grid as unknown as HTMLElement,\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\n // ===== Styles =====\n\n override readonly styles = `\n .tbw-footer {\n position: sticky;\n bottom: 0;\n z-index: var(--tbw-z-layer-pinned-rows, 20);\n background: var(--tbw-color-panel-bg);\n }\n\n .tbw-pinned-rows {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n background: var(--tbw-pinned-rows-bg, var(--tbw-color-panel-bg));\n border-top: 1px solid var(--tbw-pinned-rows-border, var(--tbw-color-border));\n font-size: 12px;\n color: var(--tbw-pinned-rows-color, var(--tbw-color-fg-muted));\n min-height: 32px;\n box-sizing: border-box;\n min-width: fit-content;\n }\n .tbw-pinned-rows-left,\n .tbw-pinned-rows-center,\n .tbw-pinned-rows-right {\n display: flex;\n align-items: center;\n gap: 16px;\n }\n .tbw-pinned-rows-left {\n justify-content: flex-start;\n }\n .tbw-pinned-rows-center {\n justify-content: center;\n flex: 1;\n }\n .tbw-pinned-rows-right {\n justify-content: flex-end;\n }\n .tbw-status-panel {\n white-space: nowrap;\n }\n\n .tbw-aggregation-rows {\n min-width: fit-content;\n background: var(--tbw-aggregation-bg, var(--tbw-color-header-bg));\n }\n .tbw-aggregation-rows-top {\n border-bottom: 1px solid var(--tbw-aggregation-border, var(--tbw-color-border));\n }\n .tbw-aggregation-rows-bottom {\n border-top: 1px solid var(--tbw-aggregation-border, var(--tbw-color-border));\n }\n .tbw-aggregation-row {\n display: grid;\n grid-template-columns: var(--tbw-column-template);\n font-weight: var(--tbw-aggregation-font-weight, 600);\n }\n .tbw-aggregation-cell {\n padding: var(--tbw-cell-padding, 2px 8px);\n min-height: var(--tbw-row-height, 28px);\n display: flex;\n align-items: center;\n border-right: 1px solid var(--tbw-color-border-cell);\n }\n .tbw-aggregation-cell:last-child {\n border-right: 0;\n }\n .tbw-aggregation-cell-full {\n grid-column: 1 / -1;\n border-right: 0;\n }\n `;\n}\n"],"names":["createInfoBarElement","config","context","pinnedRows","left","center","right","rowCount","filteredCount","selectedCount","panel","panelEl","renderCustomPanel","createAggregationContainer","position","container","renderAggregationRows","rows","columns","dataRows","rowConfig","rowEl","cell","col","value","aggRef","aggFn","getAggregator","staticVal","content","buildContext","grid","selectionState","filterState","PinnedRowsPlugin","BaseGridPlugin","shadowRoot","aggregationRows","topRows","r","bottomRows","header","hasInfoContent","hasBottomInfoBar","needsFooter","newInfoBar","id","p","row"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ (function(u,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],c):(u=typeof globalThis<"u"?globalThis:u||self,c(u.TbwGridPlugin_pivot={},u.TbwGrid))})(this,(function(u,c){"use strict";function b(i){const e=[];return!i.rowGroupFields?.length&&!i.columnGroupFields?.length&&e.push("At least one row or column group field is required"),i.valueFields?.length||e.push("At least one value field is required"),e}function m(i){switch(i){case"sum":return e=>e.reduce((o,t)=>o+t,0);case"avg":return e=>e.length?e.reduce((o,t)=>o+t,0)/e.length:0;case"count":return e=>e.length;case"min":return e=>e.length?Math.min(...e):0;case"max":return e=>e.length?Math.max(...e):0;case"first":return e=>e[0]??0;case"last":return e=>e[e.length-1]??0;default:return e=>e.reduce((o,t)=>o+t,0)}}function h(i,e){return[...i,e].join("|")}function R(i,e){const o=e.rowGroupFields??[],t=e.columnGroupFields??[],s=e.valueFields??[],n=y(i,t),r=G(i,o),l=F(r,t,n,s,0),a=P(l,n,s),f=Object.values(a).reduce((p,d)=>p+d,0);return{rows:l,columnKeys:n,totals:a,grandTotal:f}}function y(i,e){if(e.length===0)return["value"];const o=new Set;for(const t of i){const s=e.map(n=>String(t[n]??"")).join("|");o.add(s)}return[...o].sort()}function G(i,e){const o=new Map;for(const t of i){const s=e.map(r=>String(t[r]??"")).join("|");o.has(s)||o.set(s,[]);const n=o.get(s);n&&n.push(t)}return o}function F(i,e,o,t,s){const n=[];for(const[r,l]of i){const a={};let f=0;for(const p of o)for(const d of t){const w=(e.length>0?l.filter(v=>e.map(q=>String(v[q]??"")).join("|")===p):l).map(v=>Number(v[d.field])||0),T=m(d.aggFunc),g=w.length>0?T(w):null,A=h([p],d.field);a[A]=g,g!==null&&(f+=g)}n.push({rowKey:r,rowLabel:r||"(blank)",depth:s,values:a,total:f,isGroup:!1})}return n}function P(i,e,o){const t={};for(const s of e)for(const n of o){const r=h([s],n.field);t[r]=i.reduce((l,a)=>l+(a.values[r]??0),0)}return t}function _(i){const e=[];function o(t){if(e.push(t),t.children)for(const s of t.children)o(s)}for(const t of i)o(t);return e}class K extends c.BaseGridPlugin{name="pivot";version="1.0.0";get defaultConfig(){return{enabled:!0,showTotals:!0,showGrandTotal:!0}}isActive=!1;pivotResult=null;columnHeaders=[];rowHeaders=[];detach(){this.isActive=!1,this.pivotResult=null,this.columnHeaders=[],this.rowHeaders=[]}processRows(e){if(!this.config.enabled||!this.isActive)return[...e];const o=b(this.config);return o.length>0?(this.warn(`Config errors: ${o.join(", ")}`),[...e]):(this.pivotResult=R(e,this.config),_(this.pivotResult.rows).map(t=>({__pivotRowKey:t.rowKey,__pivotLabel:t.rowLabel,__pivotDepth:t.depth,__pivotIsGroup:t.isGroup,__pivotTotal:t.total,...t.values})))}processColumns(e){if(!this.config.enabled||!this.isActive||!this.pivotResult)return[...e];const o=[];o.push({field:"__pivotLabel",header:this.config.rowGroupFields?.join(" / ")??"Group",width:200});for(const t of this.pivotResult.columnKeys)for(const s of this.config.valueFields??[]){const n=h([t],s.field);o.push({field:n,header:`${t} - ${s.header||s.field} (${s.aggFunc})`,width:120,type:"number"})}return this.config.showTotals&&o.push({field:"__pivotTotal",header:"Total",width:100,type:"number"}),o}enablePivot(){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()}styles=`
2
+ [data-pivot-depth="1"] { padding-left: 20px; }
3
+ [data-pivot-depth="2"] { padding-left: 40px; }
4
+ [data-pivot-depth="3"] { padding-left: 60px; }
5
+ .pivot-group-row { font-weight: bold; background: var(--tbw-pivot-group-bg, var(--tbw-color-panel-bg)); }
6
+ .pivot-total-row { font-weight: bold; border-top: 2px solid var(--tbw-pivot-border, var(--tbw-color-border-strong)); }
7
+ `}u.PivotPlugin=K,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
8
+ //# sourceMappingURL=pivot.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pivot.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/pivot/pivot-model.ts","../../../../../libs/grid/src/lib/plugins/pivot/pivot-engine.ts","../../../../../libs/grid/src/lib/plugins/pivot/PivotPlugin.ts"],"sourcesContent":["import type { PivotConfig } from './types';\n\nexport function validatePivotConfig(config: PivotConfig): string[] {\n const errors: string[] = [];\n\n if (!config.rowGroupFields?.length && !config.columnGroupFields?.length) {\n errors.push('At least one row or column group field is required');\n }\n\n if (!config.valueFields?.length) {\n errors.push('At least one value field is required');\n }\n\n return errors;\n}\n\n/**\n * Get a value-based aggregator function for pivot operations.\n * This operates on pre-extracted numeric values (different from core row-based aggregators).\n */\nexport function getPivotAggregator(aggFunc: string): (values: number[]) => number {\n switch (aggFunc) {\n case 'sum':\n return (vals) => vals.reduce((a, b) => a + b, 0);\n case 'avg':\n return (vals) => (vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : 0);\n case 'count':\n return (vals) => vals.length;\n case 'min':\n return (vals) => (vals.length ? Math.min(...vals) : 0);\n case 'max':\n return (vals) => (vals.length ? Math.max(...vals) : 0);\n case 'first':\n return (vals) => vals[0] ?? 0;\n case 'last':\n return (vals) => vals[vals.length - 1] ?? 0;\n default:\n return (vals) => vals.reduce((a, b) => a + b, 0);\n }\n}\n\nexport function createValueKey(columnValues: string[], valueField: string): string {\n return [...columnValues, valueField].join('|');\n}\n","import type { PivotConfig, PivotResult, PivotRow, PivotValueField } from './types';\nimport { getPivotAggregator, createValueKey } from './pivot-model';\n\nexport type PivotDataRow = Record<string, unknown>;\n\nexport function buildPivot(rows: PivotDataRow[], config: PivotConfig): PivotResult {\n const rowGroupFields = config.rowGroupFields ?? [];\n const columnGroupFields = config.columnGroupFields ?? [];\n const valueFields = config.valueFields ?? [];\n\n // Get unique column combinations\n const columnKeys = getUniqueColumnKeys(rows, columnGroupFields);\n\n // Group rows by row group fields\n const groupedData = groupByFields(rows, rowGroupFields);\n\n // Build pivot rows\n const pivotRows = buildPivotRows(groupedData, columnGroupFields, columnKeys, valueFields, 0);\n\n // Calculate totals\n const totals = calculateTotals(pivotRows, columnKeys, valueFields);\n const grandTotal = Object.values(totals).reduce((a, b) => a + b, 0);\n\n return {\n rows: pivotRows,\n columnKeys,\n totals,\n grandTotal,\n };\n}\n\nexport function getUniqueColumnKeys(rows: PivotDataRow[], columnFields: string[]): string[] {\n if (columnFields.length === 0) return ['value'];\n\n const keys = new Set<string>();\n for (const row of rows) {\n const key = columnFields.map((f) => String(row[f] ?? '')).join('|');\n keys.add(key);\n }\n return [...keys].sort();\n}\n\nexport function groupByFields(rows: PivotDataRow[], fields: string[]): Map<string, PivotDataRow[]> {\n const groups = new Map<string, PivotDataRow[]>();\n\n for (const row of rows) {\n const key = fields.map((f) => String(row[f] ?? '')).join('|');\n if (!groups.has(key)) {\n groups.set(key, []);\n }\n const group = groups.get(key);\n if (group) group.push(row);\n }\n\n return groups;\n}\n\nexport function buildPivotRows(\n groupedData: Map<string, PivotDataRow[]>,\n columnFields: string[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n depth: number\n): PivotRow[] {\n const result: PivotRow[] = [];\n\n for (const [rowKey, groupRows] of groupedData) {\n const values: Record<string, number | null> = {};\n let total = 0;\n\n for (const colKey of columnKeys) {\n for (const vf of valueFields) {\n const matchingRows =\n columnFields.length > 0\n ? groupRows.filter((r) => columnFields.map((f) => String(r[f] ?? '')).join('|') === colKey)\n : groupRows;\n\n const nums = matchingRows.map((r) => Number(r[vf.field]) || 0);\n const aggregator = getPivotAggregator(vf.aggFunc);\n const aggregatedResult = nums.length > 0 ? aggregator(nums) : null;\n\n const valueKey = createValueKey([colKey], vf.field);\n values[valueKey] = aggregatedResult;\n\n if (aggregatedResult !== null) total += aggregatedResult;\n }\n }\n\n result.push({\n rowKey,\n rowLabel: rowKey || '(blank)',\n depth,\n values,\n total,\n isGroup: false,\n });\n }\n\n return result;\n}\n\nexport function calculateTotals(\n pivotRows: PivotRow[],\n columnKeys: string[],\n valueFields: PivotValueField[]\n): Record<string, number> {\n const totals: Record<string, number> = {};\n\n for (const colKey of columnKeys) {\n for (const vf of valueFields) {\n const valueKey = createValueKey([colKey], vf.field);\n totals[valueKey] = pivotRows.reduce((sum, row) => {\n return sum + (row.values[valueKey] ?? 0);\n }, 0);\n }\n }\n\n return totals;\n}\n\nexport function flattenPivotRows(rows: PivotRow[]): PivotRow[] {\n const result: PivotRow[] = [];\n\n function flatten(row: PivotRow) {\n result.push(row);\n if (row.children) {\n for (const child of row.children) {\n flatten(child);\n }\n }\n }\n\n for (const row of rows) {\n flatten(row);\n }\n\n return result;\n}\n","/**\n * Pivot Plugin (Class-based)\n *\n * Provides pivot table functionality for tbw-grid.\n * Transforms flat data into grouped, aggregated pivot views.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { buildPivot, flattenPivotRows, type PivotDataRow } from './pivot-engine';\nimport { createValueKey, validatePivotConfig } from './pivot-model';\nimport type { PivotConfig, PivotResult, PivotValueField } from './types';\n\n/**\n * Pivot Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new PivotPlugin({\n * rowGroupFields: ['category'],\n * columnGroupFields: ['region'],\n * valueFields: [{ field: 'sales', aggFunc: 'sum' }]\n * })\n * ```\n */\nexport class PivotPlugin extends BaseGridPlugin<PivotConfig> {\n readonly name = 'pivot';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<PivotConfig> {\n return {\n enabled: true,\n showTotals: true,\n showGrandTotal: true,\n };\n }\n\n // ===== Internal State =====\n private isActive = false;\n private pivotResult: PivotResult | null = null;\n private columnHeaders: string[] = [];\n private rowHeaders: string[] = [];\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.isActive = false;\n this.pivotResult = null;\n this.columnHeaders = [];\n this.rowHeaders = [];\n }\n\n // ===== Hooks =====\n\n override processRows(rows: readonly unknown[]): PivotDataRow[] {\n if (!this.config.enabled || !this.isActive) {\n return [...rows] as PivotDataRow[];\n }\n\n const errors = validatePivotConfig(this.config);\n if (errors.length > 0) {\n this.warn(`Config errors: ${errors.join(', ')}`);\n return [...rows] as PivotDataRow[];\n }\n\n // Build pivot\n this.pivotResult = buildPivot(rows as PivotDataRow[], this.config);\n\n // Return flattened pivot rows for rendering\n return flattenPivotRows(this.pivotResult.rows).map((pr) => ({\n __pivotRowKey: pr.rowKey,\n __pivotLabel: pr.rowLabel,\n __pivotDepth: pr.depth,\n __pivotIsGroup: pr.isGroup,\n __pivotTotal: pr.total,\n ...pr.values,\n }));\n }\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.config.enabled || !this.isActive || !this.pivotResult) {\n return [...columns];\n }\n\n const pivotColumns: ColumnConfig[] = [];\n\n // Row label column\n pivotColumns.push({\n field: '__pivotLabel',\n header: this.config.rowGroupFields?.join(' / ') ?? 'Group',\n width: 200,\n });\n\n // Value columns for each column key\n for (const colKey of this.pivotResult.columnKeys) {\n for (const vf of this.config.valueFields ?? []) {\n const valueKey = createValueKey([colKey], vf.field);\n pivotColumns.push({\n field: valueKey,\n header: `${colKey} - ${vf.header || vf.field} (${vf.aggFunc})`,\n width: 120,\n type: 'number',\n });\n }\n }\n\n // Totals column\n if (this.config.showTotals) {\n pivotColumns.push({\n field: '__pivotTotal',\n header: 'Total',\n width: 100,\n type: 'number',\n });\n }\n\n return pivotColumns;\n }\n\n // ===== Public API =====\n\n /**\n * Enable pivot mode.\n */\n enablePivot(): void {\n this.isActive = true;\n this.requestRender();\n }\n\n /**\n * Disable pivot mode and return to normal grid view.\n */\n disablePivot(): void {\n this.isActive = false;\n this.pivotResult = null;\n this.requestRender();\n }\n\n /**\n * Check if pivot mode is currently active.\n */\n isPivotActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Get the current pivot result.\n */\n getPivotResult(): PivotResult | null {\n return this.pivotResult;\n }\n\n /**\n * Set the row group fields for pivoting.\n * @param fields - Array of field names to group rows by\n */\n setRowGroupFields(fields: string[]): void {\n this.config.rowGroupFields = fields;\n this.requestRender();\n }\n\n /**\n * Set the column group fields for pivoting.\n * @param fields - Array of field names to create columns from\n */\n setColumnGroupFields(fields: string[]): void {\n this.config.columnGroupFields = fields;\n this.requestRender();\n }\n\n /**\n * Set the value fields with aggregation functions.\n * @param fields - Array of value field configurations\n */\n setValueFields(fields: PivotValueField[]): void {\n this.config.valueFields = fields;\n this.requestRender();\n }\n\n /**\n * Refresh the pivot by clearing cached results.\n */\n refresh(): void {\n this.pivotResult = null;\n this.requestRender();\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n [data-pivot-depth=\"1\"] { padding-left: 20px; }\n [data-pivot-depth=\"2\"] { padding-left: 40px; }\n [data-pivot-depth=\"3\"] { padding-left: 60px; }\n .pivot-group-row { font-weight: bold; background: var(--tbw-pivot-group-bg, var(--tbw-color-panel-bg)); }\n .pivot-total-row { font-weight: bold; border-top: 2px solid var(--tbw-pivot-border, var(--tbw-color-border-strong)); }\n `;\n}\n"],"names":["validatePivotConfig","config","errors","getPivotAggregator","aggFunc","vals","a","b","createValueKey","columnValues","valueField","buildPivot","rows","rowGroupFields","columnGroupFields","valueFields","columnKeys","getUniqueColumnKeys","groupedData","groupByFields","pivotRows","buildPivotRows","totals","calculateTotals","grandTotal","columnFields","keys","row","key","f","fields","groups","group","depth","result","rowKey","groupRows","values","total","colKey","vf","nums","r","aggregator","aggregatedResult","valueKey","sum","flattenPivotRows","flatten","child","PivotPlugin","BaseGridPlugin","pr","columns","pivotColumns"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ (function(a,u){typeof exports=="object"&&typeof module<"u"?u(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],u):(a=typeof globalThis<"u"?globalThis:a||self,u(a.TbwGridPlugin_reorder={},a.TbwGrid))})(this,(function(a,u){"use strict";function m(s){const e=s.meta??{},t=e.sticky;return t==="left"||t==="right"?!1:e.lockPosition!==!0&&e.suppressMovable!==!0}function h(s,e,t){if(e===t||e<0||e>=s.length||t<0||t>s.length)return s;const n=[...s],[r]=n.splice(e,1);return n.splice(t,0,r),n}class b extends u.BaseGridPlugin{name="reorder";version="1.0.0";get defaultConfig(){return{enabled:!0,animation:!0,animationDuration:200}}isDragging=!1;draggedField=null;draggedIndex=null;dropIndex=null;boundReorderRequestHandler=null;attach(e){super.attach(e),this.boundReorderRequestHandler=t=>{const n=t.detail;n?.field&&typeof n.toIndex=="number"&&this.moveColumn(n.field,n.toIndex)},e.addEventListener("column-reorder-request",this.boundReorderRequestHandler)}detach(){this.boundReorderRequestHandler&&this.grid&&(this.grid.removeEventListener("column-reorder-request",this.boundReorderRequestHandler),this.boundReorderRequestHandler=null),this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null}afterRender(){if(!this.config.enabled)return;const e=this.shadowRoot;if(!e)return;e.querySelectorAll(".header-row > .cell").forEach(n=>{const r=n,i=r.getAttribute("data-field");if(!i)return;const f=this.columns.find(d=>d.field===i);if(!f||!m(f)){r.draggable=!1;return}r.draggable=!0,!r.getAttribute("data-dragstart-bound")&&(r.setAttribute("data-dragstart-bound","true"),r.addEventListener("dragstart",d=>{const o=this.getColumnOrder().indexOf(i);this.isDragging=!0,this.draggedField=i,this.draggedIndex=o,d.dataTransfer&&(d.dataTransfer.effectAllowed="move",d.dataTransfer.setData("text/plain",i)),r.classList.add("dragging")}),r.addEventListener("dragend",()=>{this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null,e.querySelectorAll(".header-row > .cell").forEach(d=>{d.classList.remove("dragging","drop-target","drop-before","drop-after")})}),r.addEventListener("dragover",d=>{if(d.preventDefault(),!this.isDragging||this.draggedField===i)return;const l=r.getBoundingClientRect(),o=l.left+l.width/2,c=this.getColumnOrder().indexOf(i);this.dropIndex=d.clientX<o?c:c+1,r.classList.add("drop-target"),r.classList.toggle("drop-before",d.clientX<o),r.classList.toggle("drop-after",d.clientX>=o)}),r.addEventListener("dragleave",()=>{r.classList.remove("drop-target","drop-before","drop-after")}),r.addEventListener("drop",d=>{d.preventDefault();const l=this.draggedField,o=this.draggedIndex,g=this.dropIndex;if(!this.isDragging||l===null||o===null||g===null)return;const c=g>o?g-1:g,v=this.getColumnOrder(),p=h(v,o,c),O={field:l,fromIndex:o,toIndex:c,columnOrder:p};this.grid.setColumnOrder(p),this.emit("column-move",O),this.grid.requestStateChange?.()}))})}getColumnOrder(){return this.grid.getColumnOrder()}moveColumn(e,t){const n=this.getColumnOrder(),r=n.indexOf(e);if(r===-1)return;const i=h(n,r,t);this.grid.setColumnOrder(i),this.emit("column-move",{field:e,fromIndex:r,toIndex:t,columnOrder:i}),this.grid.requestStateChange?.()}setColumnOrder(e){this.grid.setColumnOrder(e),this.grid.requestStateChange?.()}resetColumnOrder(){const e=this.columns.map(t=>t.field);this.grid.setColumnOrder(e),this.grid.requestStateChange?.()}styles=`
2
+ .header-row > .cell[draggable="true"] {
3
+ cursor: grab;
4
+ position: relative;
5
+ }
6
+ .header-row > .cell.dragging {
7
+ opacity: 0.5;
8
+ cursor: grabbing;
9
+ }
10
+ .header-row > .cell.drop-before::before {
11
+ content: '';
12
+ position: absolute;
13
+ left: 0;
14
+ top: 0;
15
+ bottom: 0;
16
+ width: 2px;
17
+ background: var(--tbw-reorder-indicator, var(--tbw-color-accent));
18
+ z-index: 1;
19
+ }
20
+ .header-row > .cell.drop-after::after {
21
+ content: '';
22
+ position: absolute;
23
+ right: 0;
24
+ top: 0;
25
+ bottom: 0;
26
+ width: 2px;
27
+ background: var(--tbw-reorder-indicator, var(--tbw-color-accent));
28
+ z-index: 1;
29
+ }
30
+ `}a.ReorderPlugin=b,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}));
31
+ //# sourceMappingURL=reorder.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reorder.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/reorder/column-drag.ts","../../../../../libs/grid/src/lib/plugins/reorder/ReorderPlugin.ts"],"sourcesContent":["/**\n * Column Reordering Core Logic\n *\n * Pure functions for column drag and reordering operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Check if a column can be moved.\n * Respects lockPosition, suppressMovable, and sticky properties.\n * Sticky (pinned) columns cannot be reordered as they have fixed positions.\n *\n * @param column - The column configuration to check\n * @returns True if the column can be moved\n */\nexport function canMoveColumn<TRow = unknown>(column: ColumnConfig<TRow>): boolean {\n // Sticky columns cannot be reordered - they have fixed left/right positions\n // Check via meta since sticky is added by PinnedColumnsPlugin\n const meta = column.meta ?? {};\n const sticky = (meta as { sticky?: 'left' | 'right' }).sticky;\n if (sticky === 'left' || sticky === 'right') {\n return false;\n }\n // Check for lockPosition or suppressMovable properties in the column config\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Move a column from one position to another in the order array.\n *\n * @param columns - Array of field names in current order\n * @param fromIndex - The current index of the column to move\n * @param toIndex - The target index to move the column to\n * @returns New array with updated order\n */\nexport function moveColumn(columns: string[], fromIndex: number, toIndex: number): string[] {\n if (fromIndex === toIndex) return columns;\n if (fromIndex < 0 || fromIndex >= columns.length) return columns;\n if (toIndex < 0 || toIndex > columns.length) return columns;\n\n const result = [...columns];\n const [removed] = result.splice(fromIndex, 1);\n result.splice(toIndex, 0, removed);\n return result;\n}\n\n/**\n * Calculate the drop index based on the current drag position.\n *\n * @param dragX - The current X position of the drag\n * @param headerRect - The bounding rect of the header container\n * @param columnWidths - Array of column widths in order\n * @returns The index where the column should be dropped\n */\nexport function getDropIndex(dragX: number, headerRect: DOMRect, columnWidths: number[]): number {\n let x = headerRect.left;\n\n for (let i = 0; i < columnWidths.length; i++) {\n const mid = x + columnWidths[i] / 2;\n if (dragX < mid) return i;\n x += columnWidths[i];\n }\n\n return columnWidths.length;\n}\n\n/**\n * Reorder columns according to a specified order.\n * Columns not in the order array are appended at the end.\n *\n * @param columns - Array of column configurations\n * @param order - Array of field names specifying the desired order\n * @returns New array of columns in the specified order\n */\nexport function reorderColumns<TRow = unknown>(columns: ColumnConfig<TRow>[], order: string[]): ColumnConfig<TRow>[] {\n const columnMap = new Map<string, ColumnConfig<TRow>>(columns.map((c) => [c.field as string, c]));\n const reordered: ColumnConfig<TRow>[] = [];\n\n // Add columns in specified order\n for (const field of order) {\n const col = columnMap.get(field);\n if (col) {\n reordered.push(col);\n columnMap.delete(field);\n }\n }\n\n // Add any remaining columns not in order\n for (const col of columnMap.values()) {\n reordered.push(col);\n }\n\n return reordered;\n}\n","/**\r\n * Column Reordering Plugin (Class-based)\r\n *\r\n * Provides drag-and-drop column reordering functionality for tbw-grid.\r\n * Supports keyboard and mouse interactions with visual feedback.\r\n */\r\n\r\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\r\nimport { canMoveColumn, moveColumn } from './column-drag';\r\nimport type { ColumnMoveDetail, ReorderConfig } from './types';\r\n\r\n/** Extended grid interface with column order methods */\r\ninterface GridWithColumnOrder {\r\n setColumnOrder(order: string[]): void;\r\n getColumnOrder(): string[];\r\n requestStateChange?: () => void;\r\n}\r\n\r\n/**\r\n * Column Reordering Plugin for tbw-grid\r\n *\r\n * @example\r\n * ```ts\r\n * new ReorderPlugin({\r\n * enabled: true,\r\n * animation: true,\r\n * animationDuration: 200,\r\n * })\r\n * ```\r\n */\r\nexport class ReorderPlugin extends BaseGridPlugin<ReorderConfig> {\r\n readonly name = 'reorder';\r\n override readonly version = '1.0.0';\r\n\r\n protected override get defaultConfig(): Partial<ReorderConfig> {\r\n return {\r\n enabled: true,\r\n animation: true,\r\n animationDuration: 200,\r\n };\r\n }\r\n\r\n // ===== Internal State =====\r\n private isDragging = false;\r\n private draggedField: string | null = null;\r\n private draggedIndex: number | null = null;\r\n private dropIndex: number | null = null;\r\n private boundReorderRequestHandler: ((e: Event) => void) | null = null;\r\n\r\n // ===== Lifecycle =====\r\n\r\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\r\n super.attach(grid);\r\n\r\n // Listen for reorder requests from other plugins (e.g., VisibilityPlugin)\r\n this.boundReorderRequestHandler = (e: Event) => {\r\n const detail = (e as CustomEvent).detail;\r\n if (detail?.field && typeof detail.toIndex === 'number') {\r\n this.moveColumn(detail.field, detail.toIndex);\r\n }\r\n };\r\n (grid as unknown as HTMLElement).addEventListener('column-reorder-request', this.boundReorderRequestHandler);\r\n }\r\n\r\n override detach(): void {\r\n // Remove event listener\r\n if (this.boundReorderRequestHandler && this.grid) {\r\n (this.grid as unknown as HTMLElement).removeEventListener(\r\n 'column-reorder-request',\r\n this.boundReorderRequestHandler\r\n );\r\n this.boundReorderRequestHandler = null;\r\n }\r\n\r\n this.isDragging = false;\r\n this.draggedField = null;\r\n this.draggedIndex = null;\r\n this.dropIndex = null;\r\n }\r\n\r\n // ===== Hooks =====\r\n // Note: No processColumns hook needed - we directly update the grid's column order\r\n\r\n override afterRender(): void {\r\n if (!this.config.enabled) return;\r\n\r\n const shadowRoot = this.shadowRoot;\r\n if (!shadowRoot) return;\r\n\r\n const headers = shadowRoot.querySelectorAll('.header-row > .cell');\r\n\r\n headers.forEach((header) => {\r\n const headerEl = header as HTMLElement;\r\n const field = headerEl.getAttribute('data-field');\r\n if (!field) return;\r\n\r\n const column = this.columns.find((c) => c.field === field);\r\n if (!column || !canMoveColumn(column)) {\r\n headerEl.draggable = false;\r\n return;\r\n }\r\n\r\n headerEl.draggable = true;\r\n\r\n // Remove existing listeners to prevent duplicates\r\n if (headerEl.getAttribute('data-dragstart-bound')) return;\r\n headerEl.setAttribute('data-dragstart-bound', 'true');\r\n\r\n headerEl.addEventListener('dragstart', (e: DragEvent) => {\r\n const currentOrder = this.getColumnOrder();\r\n const orderIndex = currentOrder.indexOf(field);\r\n this.isDragging = true;\r\n this.draggedField = field;\r\n this.draggedIndex = orderIndex;\r\n\r\n if (e.dataTransfer) {\r\n e.dataTransfer.effectAllowed = 'move';\r\n e.dataTransfer.setData('text/plain', field);\r\n }\r\n\r\n headerEl.classList.add('dragging');\r\n });\r\n\r\n headerEl.addEventListener('dragend', () => {\r\n this.isDragging = false;\r\n this.draggedField = null;\r\n this.draggedIndex = null;\r\n this.dropIndex = null;\r\n\r\n shadowRoot.querySelectorAll('.header-row > .cell').forEach((h) => {\r\n h.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\r\n });\r\n });\r\n\r\n headerEl.addEventListener('dragover', (e: DragEvent) => {\r\n e.preventDefault();\r\n if (!this.isDragging || this.draggedField === field) return;\r\n\r\n const rect = headerEl.getBoundingClientRect();\r\n const midX = rect.left + rect.width / 2;\r\n\r\n const currentOrder = this.getColumnOrder();\r\n const orderIndex = currentOrder.indexOf(field);\r\n this.dropIndex = e.clientX < midX ? orderIndex : orderIndex + 1;\r\n\r\n headerEl.classList.add('drop-target');\r\n headerEl.classList.toggle('drop-before', e.clientX < midX);\r\n headerEl.classList.toggle('drop-after', e.clientX >= midX);\r\n });\r\n\r\n headerEl.addEventListener('dragleave', () => {\r\n headerEl.classList.remove('drop-target', 'drop-before', 'drop-after');\r\n });\r\n\r\n headerEl.addEventListener('drop', (e: DragEvent) => {\r\n e.preventDefault();\r\n const draggedField = this.draggedField;\r\n const draggedIndex = this.draggedIndex;\r\n const dropIndex = this.dropIndex;\r\n\r\n if (!this.isDragging || draggedField === null || draggedIndex === null || dropIndex === null) {\r\n return;\r\n }\r\n\r\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\r\n const currentOrder = this.getColumnOrder();\r\n const newOrder = moveColumn(currentOrder, draggedIndex, effectiveToIndex);\r\n\r\n const detail: ColumnMoveDetail = {\r\n field: draggedField,\r\n fromIndex: draggedIndex,\r\n toIndex: effectiveToIndex,\r\n columnOrder: newOrder,\r\n };\r\n\r\n // Directly update the grid's column order\r\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(newOrder);\r\n\r\n this.emit('column-move', detail);\r\n // Trigger state change after reorder\r\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\r\n });\r\n });\r\n }\r\n\r\n // ===== Public API =====\r\n\r\n /**\r\n * Get the current column order from the grid.\r\n * @returns Array of field names in display order\r\n */\r\n getColumnOrder(): string[] {\r\n return (this.grid as unknown as GridWithColumnOrder).getColumnOrder();\r\n }\r\n\r\n /**\r\n * Move a column to a new position.\r\n * @param field - The field name of the column to move\r\n * @param toIndex - The target index\r\n */\r\n moveColumn(field: string, toIndex: number): void {\r\n const currentOrder = this.getColumnOrder();\r\n const fromIndex = currentOrder.indexOf(field);\r\n if (fromIndex === -1) return;\r\n\r\n const newOrder = moveColumn(currentOrder, fromIndex, toIndex);\r\n\r\n // Directly update the grid's column order\r\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(newOrder);\r\n\r\n this.emit<ColumnMoveDetail>('column-move', {\r\n field,\r\n fromIndex,\r\n toIndex,\r\n columnOrder: newOrder,\r\n });\r\n\r\n // Trigger state change after reorder\r\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\r\n }\r\n\r\n /**\r\n * Set a specific column order.\r\n * @param order - Array of field names in desired order\r\n */\r\n setColumnOrder(order: string[]): void {\r\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(order);\r\n // Trigger state change after reorder\r\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\r\n }\r\n\r\n /**\r\n * Reset column order to the original configuration order.\r\n */\r\n resetColumnOrder(): void {\r\n const originalOrder = this.columns.map((c) => c.field);\r\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(originalOrder);\r\n // Trigger state change after reset\r\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\r\n }\r\n\r\n // ===== Styles =====\r\n\r\n override readonly styles = `\r\n .header-row > .cell[draggable=\"true\"] {\r\n cursor: grab;\r\n position: relative;\r\n }\r\n .header-row > .cell.dragging {\r\n opacity: 0.5;\r\n cursor: grabbing;\r\n }\r\n .header-row > .cell.drop-before::before {\r\n content: '';\r\n position: absolute;\r\n left: 0;\r\n top: 0;\r\n bottom: 0;\r\n width: 2px;\r\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\r\n z-index: 1;\r\n }\r\n .header-row > .cell.drop-after::after {\r\n content: '';\r\n position: absolute;\r\n right: 0;\r\n top: 0;\r\n bottom: 0;\r\n width: 2px;\r\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\r\n z-index: 1;\r\n }\r\n `;\r\n}\r\n"],"names":["canMoveColumn","column","meta","sticky","moveColumn","columns","fromIndex","toIndex","result","removed","ReorderPlugin","BaseGridPlugin","grid","e","detail","shadowRoot","header","headerEl","field","c","orderIndex","h","rect","midX","draggedField","draggedIndex","dropIndex","effectiveToIndex","currentOrder","newOrder","order","originalOrder"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ (function(c,d){typeof exports=="object"&&typeof module<"u"?d(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],d):(c=typeof globalThis<"u"?globalThis:c||self,d(c.TbwGridPlugin_selection={},c.TbwGrid))})(this,(function(c,d){"use strict";function g(s){return{startRow:Math.min(s.startRow,s.endRow),startCol:Math.min(s.startCol,s.endCol),endRow:Math.max(s.startRow,s.endRow),endCol:Math.max(s.startCol,s.endCol)}}function m(s){const e=g(s);return{from:{row:e.startRow,col:e.startCol},to:{row:e.endRow,col:e.endCol}}}function f(s){return s.map(m)}function C(s,e,t){const l=g(t);return s>=l.startRow&&s<=l.endRow&&e>=l.startCol&&e<=l.endCol}function w(s,e,t){return t.some(l=>C(s,e,l))}function b(s){const e=[],t=g(s);for(let l=t.startRow;l<=t.endRow;l++)for(let n=t.startCol;n<=t.endCol;n++)e.push({row:l,col:n});return e}function p(s){const e=new Map;for(const t of s)for(const l of b(t))e.set(`${l.row},${l.col}`,l);return[...e.values()]}function R(s,e){return{startRow:s.row,startCol:s.col,endRow:e.row,endCol:e.col}}function A(s,e,t){if(s==="cell"&&e.selectedCell)return{mode:s,ranges:[{from:{row:e.selectedCell.row,col:e.selectedCell.col},to:{row:e.selectedCell.row,col:e.selectedCell.col}}]};if(s==="row"&&e.selected.size>0){const l=[...e.selected].map(n=>({from:{row:n,col:0},to:{row:n,col:t-1}}));return{mode:s,ranges:l}}return s==="range"&&e.ranges.length>0?{mode:s,ranges:f(e.ranges)}:{mode:s,ranges:[]}}class y extends d.BaseGridPlugin{name="selection";version="1.0.0";get defaultConfig(){return{mode:"cell"}}selected=new Set;lastSelected=null;anchor=null;ranges=[];activeRange=null;cellAnchor=null;isDragging=!1;selectedCell=null;detach(){this.selected.clear(),this.ranges=[],this.activeRange=null,this.cellAnchor=null,this.isDragging=!1,this.selectedCell=null}onCellClick(e){const{rowIndex:t,colIndex:l,originalEvent:n}=e,{mode:r}=this.config;if(r==="cell")return this.selectedCell={row:t,col:l},this.emit("selection-change",this.#e()),this.requestAfterRender(),!1;if(r==="row")return this.selected.clear(),this.selected.add(t),this.lastSelected=t,this.emit("selection-change",this.#e()),this.requestAfterRender(),!1;if(r==="range"){const i=n.shiftKey,h=n.ctrlKey||n.metaKey;if(i&&this.cellAnchor){const o=R(this.cellAnchor,{row:t,col:l});h?this.ranges.length>0?this.ranges[this.ranges.length-1]=o:this.ranges.push(o):this.ranges=[o],this.activeRange=o}else if(h){const o={startRow:t,startCol:l,endRow:t,endCol:l};this.ranges.push(o),this.activeRange=o,this.cellAnchor={row:t,col:l}}else{const o={startRow:t,startCol:l,endRow:t,endCol:l};this.ranges=[o],this.activeRange=o,this.cellAnchor={row:t,col:l}}return this.emit("selection-change",this.#e()),this.requestAfterRender(),!1}return!1}onKeyDown(e){const{mode:t}=this.config;if(e.key==="Escape")return t==="cell"?this.selectedCell=null:t==="row"?(this.selected.clear(),this.anchor=null):t==="range"&&(this.ranges=[],this.activeRange=null,this.cellAnchor=null),this.emit("selection-change",this.#e()),this.requestAfterRender(),!0;if(t==="range"&&e.key==="a"&&(e.ctrlKey||e.metaKey)){const l=this.rows.length,n=this.columns.length;if(l>0&&n>0){const r={startRow:0,startCol:0,endRow:l-1,endCol:n-1};return this.ranges=[r],this.activeRange=r,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0}}return!1}onCellMouseDown(e){if(this.config.mode!=="range"||e.rowIndex===void 0||e.colIndex===void 0||e.rowIndex<0||e.originalEvent.shiftKey&&this.cellAnchor)return;this.isDragging=!0;const t=e.rowIndex,l=e.colIndex;this.cellAnchor={row:t,col:l},e.originalEvent.ctrlKey||e.originalEvent.metaKey||(this.ranges=[]);const r={startRow:t,startCol:l,endRow:t,endCol:l};return this.ranges.push(r),this.activeRange=r,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0}onCellMouseMove(e){if(this.config.mode!=="range"||!this.isDragging||!this.cellAnchor||e.rowIndex===void 0||e.colIndex===void 0||e.rowIndex<0)return;const t=R(this.cellAnchor,{row:e.rowIndex,col:e.colIndex});return this.ranges.length>0?this.ranges[this.ranges.length-1]=t:this.ranges.push(t),this.activeRange=t,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0}onCellMouseUp(e){if(this.config.mode==="range"&&this.isDragging)return this.isDragging=!1,!0}afterRender(){const e=this.shadowRoot;if(!e)return;const t=e.children[0],{mode:l}=this.config;this.grid.setAttribute("data-selection-mode",l),t&&t.classList.toggle("selecting",this.isDragging),e.querySelectorAll(".cell").forEach(i=>{i.classList.remove("selected","top","bottom","first","last")});const r=e.querySelectorAll(".data-grid-row");if(r.forEach(i=>{i.classList.remove("selected")}),l==="row"&&(r.forEach(i=>i.classList.remove("row-focus")),r.forEach(i=>{const h=i.querySelector(".cell[data-row]"),o=parseInt(h?.getAttribute("data-row")??"-1",10);o>=0&&this.selected.has(o)&&i.classList.add("selected","row-focus")})),l==="range"&&this.ranges.length>0){const i=this.activeRange?g(this.activeRange):null;e.querySelectorAll(".cell[data-row][data-col]").forEach(o=>{const a=parseInt(o.getAttribute("data-row")??"-1",10),u=parseInt(o.getAttribute("data-col")??"-1",10);a>=0&&u>=0&&w(a,u,this.ranges)&&(o.classList.add("selected"),i&&(a===i.startRow&&o.classList.add("top"),a===i.endRow&&o.classList.add("bottom"),u===i.startCol&&o.classList.add("first"),u===i.endCol&&o.classList.add("last")))})}}getSelectedCell(){return this.selectedCell}getSelectedRows(){return[...this.selected]}getRanges(){return f(this.ranges)}getSelectedCells(){return p(this.ranges)}isCellSelected(e,t){return w(e,t,this.ranges)}clearSelection(){this.selectedCell=null,this.selected.clear(),this.anchor=null,this.ranges=[],this.activeRange=null,this.cellAnchor=null,this.emit("selection-change",{mode:this.config.mode,ranges:[]}),this.requestAfterRender()}setRanges(e){this.ranges=e.map(t=>({startRow:t.from.row,startCol:t.from.col,endRow:t.to.row,endCol:t.to.col})),this.activeRange=this.ranges.length>0?this.ranges[this.ranges.length-1]:null,this.emit("selection-change",{mode:this.config.mode,ranges:f(this.ranges)}),this.requestAfterRender()}#e(){return A(this.config.mode,{selectedCell:this.selectedCell,selected:this.selected,ranges:this.ranges},this.columns.length)}styles=`
2
+ /* Prevent text selection during range drag */
3
+ :host .selecting .data-grid-row > .cell {
4
+ user-select: none;
5
+ }
6
+
7
+ /* Row selection - use accent color for row focus */
8
+ :host .data-grid-row.row-focus {
9
+ background-color: var(--tbw-focus-background, rgba(from var(--tbw-color-accent) r g b / 12%));
10
+ }
11
+
12
+ /* Disable cell-focus outline in row mode - row is the focus unit */
13
+ :host([data-selection-mode="row"]) .cell-focus {
14
+ outline: none;
15
+ }
16
+
17
+ /* Selection cell styles - for range mode */
18
+ :host .data-grid-row > .cell.selected {
19
+ background-color: var(--tbw-range-selection-bg);
20
+ }
21
+ :host .data-grid-row > .cell.selected.top {
22
+ border-top: 2px solid var(--tbw-range-border-color);
23
+ }
24
+ :host .data-grid-row > .cell.selected.bottom {
25
+ border-bottom: 2px solid var(--tbw-range-border-color);
26
+ }
27
+ :host .data-grid-row > .cell.selected.first {
28
+ border-left: 2px solid var(--tbw-range-border-color);
29
+ }
30
+ :host .data-grid-row > .cell.selected.last {
31
+ border-right: 2px solid var(--tbw-range-border-color);
32
+ }
33
+ `}function v(s,e,t,l){const n=new Set(s.selected);let r=s.anchor;if(t==="single")n.clear(),n.add(e),r=e;else if(t==="multiple"){const i=l.ctrlKey||l.metaKey;if(l.shiftKey&&s.anchor!==null){const h=Math.min(s.anchor,e),o=Math.max(s.anchor,e);for(let a=h;a<=o;a++)n.add(a)}else i?(n.has(e)?n.delete(e):n.add(e),r=e):(n.clear(),n.add(e),r=e)}return{selected:n,lastSelected:e,anchor:r}}function S(s){const e=new Set;for(let t=0;t<s;t++)e.add(t);return e}function x(s,e){const t=[],l=[];for(const n of e)s.has(n)||t.push(n);for(const n of s)e.has(n)||l.push(n);return{added:t,removed:l}}c.SelectionPlugin=y,c.computeSelectionDiff=x,c.handleRowClick=v,c.selectAll=S,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})}));
34
+ //# sourceMappingURL=selection.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/selection/range-selection.ts","../../../../../libs/grid/src/lib/plugins/selection/SelectionPlugin.ts","../../../../../libs/grid/src/lib/plugins/selection/row-selection.ts"],"sourcesContent":["/**\n * Cell Range Selection Core Logic\n *\n * Pure functions for cell range selection operations.\n */\n\nimport type { InternalCellRange, CellRange } from './types';\n\n/**\n * Normalize a range so startRow/startCol are always <= endRow/endCol.\n * This handles cases where user drags from bottom-right to top-left.\n *\n * @param range - The range to normalize\n * @returns Normalized range with start <= end for both dimensions\n */\nexport function normalizeRange(range: InternalCellRange): InternalCellRange {\n return {\n startRow: Math.min(range.startRow, range.endRow),\n startCol: Math.min(range.startCol, range.endCol),\n endRow: Math.max(range.startRow, range.endRow),\n endCol: Math.max(range.startCol, range.endCol),\n };\n}\n\n/**\n * Convert an internal range to the public event format.\n *\n * @param range - The internal range to convert\n * @returns Public CellRange format with from/to coordinates\n */\nexport function toPublicRange(range: InternalCellRange): CellRange {\n const normalized = normalizeRange(range);\n return {\n from: { row: normalized.startRow, col: normalized.startCol },\n to: { row: normalized.endRow, col: normalized.endCol },\n };\n}\n\n/**\n * Convert multiple internal ranges to public format.\n *\n * @param ranges - Array of internal ranges\n * @returns Array of public CellRange format\n */\nexport function toPublicRanges(ranges: InternalCellRange[]): CellRange[] {\n return ranges.map(toPublicRange);\n}\n\n/**\n * Check if a cell is within a specific range.\n *\n * @param row - The row index to check\n * @param col - The column index to check\n * @param range - The range to check against\n * @returns True if the cell is within the range\n */\nexport function isCellInRange(row: number, col: number, range: InternalCellRange): boolean {\n const normalized = normalizeRange(range);\n return (\n row >= normalized.startRow && row <= normalized.endRow && col >= normalized.startCol && col <= normalized.endCol\n );\n}\n\n/**\n * Check if a cell is within any of the provided ranges.\n *\n * @param row - The row index to check\n * @param col - The column index to check\n * @param ranges - Array of ranges to check against\n * @returns True if the cell is within any range\n */\nexport function isCellInAnyRange(row: number, col: number, ranges: InternalCellRange[]): boolean {\n return ranges.some((range) => isCellInRange(row, col, range));\n}\n\n/**\n * Get all cells within a range as an array of {row, col} objects.\n *\n * @param range - The range to enumerate\n * @returns Array of all cell coordinates in the range\n */\nexport function getCellsInRange(range: InternalCellRange): Array<{ row: number; col: number }> {\n const cells: Array<{ row: number; col: number }> = [];\n const normalized = normalizeRange(range);\n\n for (let row = normalized.startRow; row <= normalized.endRow; row++) {\n for (let col = normalized.startCol; col <= normalized.endCol; col++) {\n cells.push({ row, col });\n }\n }\n\n return cells;\n}\n\n/**\n * Get all unique cells across multiple ranges.\n * Deduplicates cells that appear in overlapping ranges.\n *\n * @param ranges - Array of ranges to enumerate\n * @returns Array of unique cell coordinates\n */\nexport function getAllCellsInRanges(ranges: InternalCellRange[]): Array<{ row: number; col: number }> {\n const cellMap = new Map<string, { row: number; col: number }>();\n\n for (const range of ranges) {\n for (const cell of getCellsInRange(range)) {\n cellMap.set(`${cell.row},${cell.col}`, cell);\n }\n }\n\n return [...cellMap.values()];\n}\n\n/**\n * Merge overlapping or adjacent ranges into fewer ranges.\n * Simple implementation - returns ranges as-is for now.\n * More complex merging logic can be added later for optimization.\n *\n * @param ranges - Array of ranges to merge\n * @returns Merged array of ranges\n */\nexport function mergeRanges(ranges: InternalCellRange[]): InternalCellRange[] {\n // Simple implementation - more complex merging can be added later\n return ranges;\n}\n\n/**\n * Create a range from an anchor cell to a current cell position.\n * The range is not normalized - it preserves the direction of selection.\n *\n * @param anchor - The anchor cell (where selection started)\n * @param current - The current cell (where selection ends)\n * @returns An InternalCellRange from anchor to current\n */\nexport function createRangeFromAnchor(\n anchor: { row: number; col: number },\n current: { row: number; col: number }\n): InternalCellRange {\n return {\n startRow: anchor.row,\n startCol: anchor.col,\n endRow: current.row,\n endCol: current.col,\n };\n}\n\n/**\n * Calculate the number of cells in a range.\n *\n * @param range - The range to measure\n * @returns Total number of cells in the range\n */\nexport function getRangeCellCount(range: InternalCellRange): number {\n const normalized = normalizeRange(range);\n const rowCount = normalized.endRow - normalized.startRow + 1;\n const colCount = normalized.endCol - normalized.startCol + 1;\n return rowCount * colCount;\n}\n\n/**\n * Check if two ranges are equal (same boundaries).\n *\n * @param a - First range\n * @param b - Second range\n * @returns True if ranges have same boundaries after normalization\n */\nexport function rangesEqual(a: InternalCellRange, b: InternalCellRange): boolean {\n const normA = normalizeRange(a);\n const normB = normalizeRange(b);\n return (\n normA.startRow === normB.startRow &&\n normA.startCol === normB.startCol &&\n normA.endRow === normB.endRow &&\n normA.endCol === normB.endCol\n );\n}\n\n/**\n * Check if a range is a single cell (1x1).\n *\n * @param range - The range to check\n * @returns True if the range is exactly one cell\n */\nexport function isSingleCell(range: InternalCellRange): boolean {\n const normalized = normalizeRange(range);\n return normalized.startRow === normalized.endRow && normalized.startCol === normalized.endCol;\n}\n","/**\n * Selection Plugin (Class-based)\n *\n * Provides selection functionality for tbw-grid.\n * Supports three modes:\n * - 'cell': Single cell selection (default). No border, just focus highlight.\n * - 'row': Row selection. Clicking a cell selects the entire row.\n * - 'range': Range selection. Shift+click or drag to select rectangular cell ranges.\n */\n\nimport { BaseGridPlugin, CellClickEvent, CellMouseEvent } from '../../core/plugin/base-plugin';\nimport {\n createRangeFromAnchor,\n getAllCellsInRanges,\n isCellInAnyRange,\n normalizeRange,\n toPublicRanges,\n} from './range-selection';\nimport type { CellRange, InternalCellRange, SelectionChangeDetail, SelectionConfig, SelectionMode } from './types';\n\n/**\n * Build the selection change event detail for the current state.\n */\nfunction buildSelectionEvent(\n mode: SelectionMode,\n state: {\n selectedCell: { row: number; col: number } | null;\n selected: Set<number>;\n ranges: InternalCellRange[];\n },\n colCount: number\n): SelectionChangeDetail {\n if (mode === 'cell' && state.selectedCell) {\n return {\n mode,\n ranges: [\n {\n from: { row: state.selectedCell.row, col: state.selectedCell.col },\n to: { row: state.selectedCell.row, col: state.selectedCell.col },\n },\n ],\n };\n }\n\n if (mode === 'row' && state.selected.size > 0) {\n const ranges = [...state.selected].map((rowIndex) => ({\n from: { row: rowIndex, col: 0 },\n to: { row: rowIndex, col: colCount - 1 },\n }));\n return { mode, ranges };\n }\n\n if (mode === 'range' && state.ranges.length > 0) {\n return { mode, ranges: toPublicRanges(state.ranges) };\n }\n\n return { mode, ranges: [] };\n}\n\n/**\n * Selection Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new SelectionPlugin({ mode: 'range' })\n * ```\n */\nexport class SelectionPlugin extends BaseGridPlugin<SelectionConfig> {\n readonly name = 'selection';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<SelectionConfig> {\n return {\n mode: 'cell',\n };\n }\n\n // ===== Internal State =====\n /** Row selection state (row mode) */\n private selected = new Set<number>();\n private lastSelected: number | null = null;\n private anchor: number | null = null;\n\n /** Range selection state (range mode) */\n private ranges: InternalCellRange[] = [];\n private activeRange: InternalCellRange | null = null;\n private cellAnchor: { row: number; col: number } | null = null;\n private isDragging = false;\n\n /** Cell selection state (cell mode) */\n private selectedCell: { row: number; col: number } | null = null;\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.selected.clear();\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.isDragging = false;\n this.selectedCell = null;\n }\n\n // ===== Event Handlers =====\n\n override onCellClick(event: CellClickEvent): boolean {\n const { rowIndex, colIndex, originalEvent } = event;\n const { mode } = this.config;\n\n // ===== CELL MODE: Single cell selection =====\n if (mode === 'cell') {\n this.selectedCell = { row: rowIndex, col: colIndex };\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return false;\n }\n\n // ===== ROW MODE: Select entire row =====\n if (mode === 'row') {\n this.selected.clear();\n this.selected.add(rowIndex);\n this.lastSelected = rowIndex;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return false;\n }\n\n // ===== RANGE MODE: Shift+click extends selection, click starts new =====\n if (mode === 'range') {\n const shiftKey = originalEvent.shiftKey;\n const ctrlKey = originalEvent.ctrlKey || originalEvent.metaKey;\n\n if (shiftKey && this.cellAnchor) {\n // Extend selection from anchor\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: rowIndex, col: colIndex });\n\n if (ctrlKey) {\n if (this.ranges.length > 0) {\n this.ranges[this.ranges.length - 1] = newRange;\n } else {\n this.ranges.push(newRange);\n }\n } else {\n this.ranges = [newRange];\n }\n this.activeRange = newRange;\n } else if (ctrlKey) {\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges.push(newRange);\n this.activeRange = newRange;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n } else {\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges = [newRange];\n this.activeRange = newRange;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n }\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n\n this.requestAfterRender();\n return false;\n }\n\n return false;\n }\n\n override onKeyDown(event: KeyboardEvent): boolean {\n const { mode } = this.config;\n\n // Escape clears selection in all modes\n if (event.key === 'Escape') {\n if (mode === 'cell') {\n this.selectedCell = null;\n } else if (mode === 'row') {\n this.selected.clear();\n this.anchor = null;\n } else if (mode === 'range') {\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n }\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n // Ctrl+A selects all in range mode\n if (mode === 'range' && event.key === 'a' && (event.ctrlKey || event.metaKey)) {\n const rowCount = this.rows.length;\n const colCount = this.columns.length;\n if (rowCount > 0 && colCount > 0) {\n const allRange: InternalCellRange = {\n startRow: 0,\n startCol: 0,\n endRow: rowCount - 1,\n endCol: colCount - 1,\n };\n this.ranges = [allRange];\n this.activeRange = allRange;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n }\n\n return false;\n }\n\n override onCellMouseDown(event: CellMouseEvent): boolean | void {\n if (this.config.mode !== 'range') return;\n if (event.rowIndex === undefined || event.colIndex === undefined) return;\n if (event.rowIndex < 0) return; // Header\n\n // Let onCellClick handle shift+click for range extension\n if (event.originalEvent.shiftKey && this.cellAnchor) {\n return;\n }\n\n // Start drag selection\n this.isDragging = true;\n const rowIndex = event.rowIndex;\n const colIndex = event.colIndex;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n\n const ctrlKey = event.originalEvent.ctrlKey || event.originalEvent.metaKey;\n if (!ctrlKey) {\n this.ranges = [];\n }\n\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges.push(newRange);\n this.activeRange = newRange;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n override onCellMouseMove(event: CellMouseEvent): boolean | void {\n if (this.config.mode !== 'range') return;\n if (!this.isDragging || !this.cellAnchor) return;\n if (event.rowIndex === undefined || event.colIndex === undefined) return;\n if (event.rowIndex < 0) return;\n\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: event.rowIndex, col: event.colIndex });\n\n if (this.ranges.length > 0) {\n this.ranges[this.ranges.length - 1] = newRange;\n } else {\n this.ranges.push(newRange);\n }\n this.activeRange = newRange;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n override onCellMouseUp(_event: CellMouseEvent): boolean | void {\n if (this.config.mode !== 'range') return;\n if (this.isDragging) {\n this.isDragging = false;\n return true;\n }\n }\n\n override afterRender(): void {\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n const container = shadowRoot.children[0];\n const { mode } = this.config;\n\n // Set data attribute on host for CSS variable scoping\n (this.grid as unknown as Element).setAttribute('data-selection-mode', mode);\n\n // Toggle .selecting class during drag to prevent text selection\n if (container) {\n container.classList.toggle('selecting', this.isDragging);\n }\n\n // Clear all selection classes first\n const allCells = shadowRoot.querySelectorAll('.cell');\n allCells.forEach((cell) => {\n cell.classList.remove('selected', 'top', 'bottom', 'first', 'last');\n });\n\n const allRows = shadowRoot.querySelectorAll('.data-grid-row');\n allRows.forEach((row) => {\n row.classList.remove('selected');\n });\n\n // ===== ROW MODE: Add row-focus class to selected rows =====\n if (mode === 'row') {\n allRows.forEach((row) => row.classList.remove('row-focus'));\n\n allRows.forEach((row) => {\n const firstCell = row.querySelector('.cell[data-row]');\n const rowIndex = parseInt(firstCell?.getAttribute('data-row') ?? '-1', 10);\n if (rowIndex >= 0 && this.selected.has(rowIndex)) {\n row.classList.add('selected', 'row-focus');\n }\n });\n }\n\n // ===== RANGE MODE: Add selected and edge classes to cells =====\n if (mode === 'range' && this.ranges.length > 0) {\n const normalized = this.activeRange ? normalizeRange(this.activeRange) : null;\n\n const cells = shadowRoot.querySelectorAll('.cell[data-row][data-col]');\n cells.forEach((cell) => {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n if (rowIndex >= 0 && colIndex >= 0) {\n const inRange = isCellInAnyRange(rowIndex, colIndex, this.ranges);\n\n if (inRange) {\n cell.classList.add('selected');\n\n if (normalized) {\n if (rowIndex === normalized.startRow) cell.classList.add('top');\n if (rowIndex === normalized.endRow) cell.classList.add('bottom');\n if (colIndex === normalized.startCol) cell.classList.add('first');\n if (colIndex === normalized.endCol) cell.classList.add('last');\n }\n }\n }\n });\n }\n }\n\n // ===== Public API =====\n\n /**\n * Get the selected cell (cell mode only).\n */\n getSelectedCell(): { row: number; col: number } | null {\n return this.selectedCell;\n }\n\n /**\n * Get all selected row indices (row mode).\n */\n getSelectedRows(): number[] {\n return [...this.selected];\n }\n\n /**\n * Get all selected cell ranges in public format.\n */\n getRanges(): CellRange[] {\n return toPublicRanges(this.ranges);\n }\n\n /**\n * Get all selected cells across all ranges.\n */\n getSelectedCells(): Array<{ row: number; col: number }> {\n return getAllCellsInRanges(this.ranges);\n }\n\n /**\n * Check if a specific cell is in range selection.\n */\n isCellSelected(row: number, col: number): boolean {\n return isCellInAnyRange(row, col, this.ranges);\n }\n\n /**\n * Clear all selection.\n */\n clearSelection(): void {\n this.selectedCell = null;\n this.selected.clear();\n this.anchor = null;\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.emit<SelectionChangeDetail>('selection-change', { mode: this.config.mode, ranges: [] });\n this.requestAfterRender();\n }\n\n /**\n * Set selected ranges programmatically.\n */\n setRanges(ranges: CellRange[]): void {\n this.ranges = ranges.map((r) => ({\n startRow: r.from.row,\n startCol: r.from.col,\n endRow: r.to.row,\n endCol: r.to.col,\n }));\n this.activeRange = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null;\n this.emit<SelectionChangeDetail>('selection-change', {\n mode: this.config.mode,\n ranges: toPublicRanges(this.ranges),\n });\n this.requestAfterRender();\n }\n\n // ===== Private Helpers =====\n\n #buildEvent(): SelectionChangeDetail {\n return buildSelectionEvent(\n this.config.mode,\n {\n selectedCell: this.selectedCell,\n selected: this.selected,\n ranges: this.ranges,\n },\n this.columns.length\n );\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n /* Prevent text selection during range drag */\n :host .selecting .data-grid-row > .cell {\n user-select: none;\n }\n\n /* Row selection - use accent color for row focus */\n :host .data-grid-row.row-focus {\n background-color: var(--tbw-focus-background, rgba(from var(--tbw-color-accent) r g b / 12%));\n }\n\n /* Disable cell-focus outline in row mode - row is the focus unit */\n :host([data-selection-mode=\"row\"]) .cell-focus {\n outline: none;\n }\n\n /* Selection cell styles - for range mode */\n :host .data-grid-row > .cell.selected {\n background-color: var(--tbw-range-selection-bg);\n }\n :host .data-grid-row > .cell.selected.top {\n border-top: 2px solid var(--tbw-range-border-color);\n }\n :host .data-grid-row > .cell.selected.bottom {\n border-bottom: 2px solid var(--tbw-range-border-color);\n }\n :host .data-grid-row > .cell.selected.first {\n border-left: 2px solid var(--tbw-range-border-color);\n }\n :host .data-grid-row > .cell.selected.last {\n border-right: 2px solid var(--tbw-range-border-color);\n }\n `;\n}\n","/**\n * Row Selection Core Logic\n *\n * Pure functions for row selection operations.\n */\n\nimport type { SelectionMode, SelectionState } from './types';\n\n/** Click modifier keys state */\nexport interface ClickModifiers {\n shiftKey: boolean;\n ctrlKey: boolean;\n metaKey: boolean;\n}\n\n/** Result from handling a row click for selection */\nexport interface RowClickResult {\n selected: Set<number>;\n lastSelected: number;\n anchor: number | null;\n}\n\n/**\n * Handle a row click event for selection purposes.\n *\n * In single mode: always selects the clicked row, clearing others.\n * In multiple mode:\n * - Plain click: clears selection and selects clicked row\n * - Ctrl/Cmd+click: toggles the clicked row\n * - Shift+click: range select from anchor to clicked row\n *\n * @param state - Current selection state\n * @param rowIndex - The clicked row index\n * @param mode - Selection mode ('single' or 'multiple')\n * @param modifiers - Keyboard modifiers held during click\n * @returns Updated selection state values\n */\nexport function handleRowClick(\n state: SelectionState,\n rowIndex: number,\n mode: SelectionMode,\n modifiers: ClickModifiers\n): RowClickResult {\n const newSelected = new Set(state.selected);\n let anchor = state.anchor;\n\n if ((mode as string) === 'single') {\n newSelected.clear();\n newSelected.add(rowIndex);\n anchor = rowIndex;\n } else if ((mode as string) === 'multiple') {\n const ctrlOrMeta = modifiers.ctrlKey || modifiers.metaKey;\n\n if (modifiers.shiftKey && state.anchor !== null) {\n // Range selection from anchor\n const start = Math.min(state.anchor, rowIndex);\n const end = Math.max(state.anchor, rowIndex);\n for (let i = start; i <= end; i++) {\n newSelected.add(i);\n }\n } else if (ctrlOrMeta) {\n // Toggle selection\n if (newSelected.has(rowIndex)) {\n newSelected.delete(rowIndex);\n } else {\n newSelected.add(rowIndex);\n }\n anchor = rowIndex;\n } else {\n // Clear and select single\n newSelected.clear();\n newSelected.add(rowIndex);\n anchor = rowIndex;\n }\n }\n\n return { selected: newSelected, lastSelected: rowIndex, anchor };\n}\n\n/**\n * Create a set containing all row indices (for select all).\n *\n * @param rowCount - Total number of rows\n * @returns Set containing indices 0 to rowCount-1\n */\nexport function selectAll(rowCount: number): Set<number> {\n const selected = new Set<number>();\n for (let i = 0; i < rowCount; i++) {\n selected.add(i);\n }\n return selected;\n}\n\n/**\n * Compute the difference between two selection states.\n *\n * @param oldSelected - Previous selection set\n * @param newSelected - New selection set\n * @returns Object with added and removed row indices\n */\nexport function computeSelectionDiff(\n oldSelected: Set<number>,\n newSelected: Set<number>\n): { added: number[]; removed: number[] } {\n const added: number[] = [];\n const removed: number[] = [];\n\n for (const idx of newSelected) {\n if (!oldSelected.has(idx)) added.push(idx);\n }\n for (const idx of oldSelected) {\n if (!newSelected.has(idx)) removed.push(idx);\n }\n\n return { added, removed };\n}\n"],"names":["normalizeRange","range","toPublicRange","normalized","toPublicRanges","ranges","isCellInRange","row","col","isCellInAnyRange","getCellsInRange","cells","getAllCellsInRanges","cellMap","cell","createRangeFromAnchor","anchor","current","buildSelectionEvent","mode","state","colCount","rowIndex","SelectionPlugin","BaseGridPlugin","event","colIndex","originalEvent","#buildEvent","shiftKey","ctrlKey","newRange","rowCount","allRange","_event","shadowRoot","container","allRows","firstCell","r","handleRowClick","modifiers","newSelected","ctrlOrMeta","start","end","i","selectAll","selected","computeSelectionDiff","oldSelected","added","removed","idx"],"mappings":"qUAeO,SAASA,EAAeC,EAA6C,CAC1E,MAAO,CACL,SAAU,KAAK,IAAIA,EAAM,SAAUA,EAAM,MAAM,EAC/C,SAAU,KAAK,IAAIA,EAAM,SAAUA,EAAM,MAAM,EAC/C,OAAQ,KAAK,IAAIA,EAAM,SAAUA,EAAM,MAAM,EAC7C,OAAQ,KAAK,IAAIA,EAAM,SAAUA,EAAM,MAAM,CAAA,CAEjD,CAQO,SAASC,EAAcD,EAAqC,CACjE,MAAME,EAAaH,EAAeC,CAAK,EACvC,MAAO,CACL,KAAM,CAAE,IAAKE,EAAW,SAAU,IAAKA,EAAW,QAAA,EAClD,GAAI,CAAE,IAAKA,EAAW,OAAQ,IAAKA,EAAW,MAAA,CAAO,CAEzD,CAQO,SAASC,EAAeC,EAA0C,CACvE,OAAOA,EAAO,IAAIH,CAAa,CACjC,CAUO,SAASI,EAAcC,EAAaC,EAAaP,EAAmC,CACzF,MAAME,EAAaH,EAAeC,CAAK,EACvC,OACEM,GAAOJ,EAAW,UAAYI,GAAOJ,EAAW,QAAUK,GAAOL,EAAW,UAAYK,GAAOL,EAAW,MAE9G,CAUO,SAASM,EAAiBF,EAAaC,EAAaH,EAAsC,CAC/F,OAAOA,EAAO,KAAMJ,GAAUK,EAAcC,EAAKC,EAAKP,CAAK,CAAC,CAC9D,CAQO,SAASS,EAAgBT,EAA+D,CAC7F,MAAMU,EAA6C,CAAA,EAC7CR,EAAaH,EAAeC,CAAK,EAEvC,QAASM,EAAMJ,EAAW,SAAUI,GAAOJ,EAAW,OAAQI,IAC5D,QAASC,EAAML,EAAW,SAAUK,GAAOL,EAAW,OAAQK,IAC5DG,EAAM,KAAK,CAAE,IAAAJ,EAAK,IAAAC,CAAA,CAAK,EAI3B,OAAOG,CACT,CASO,SAASC,EAAoBP,EAAkE,CACpG,MAAMQ,MAAc,IAEpB,UAAWZ,KAASI,EAClB,UAAWS,KAAQJ,EAAgBT,CAAK,EACtCY,EAAQ,IAAI,GAAGC,EAAK,GAAG,IAAIA,EAAK,GAAG,GAAIA,CAAI,EAI/C,MAAO,CAAC,GAAGD,EAAQ,QAAQ,CAC7B,CAuBO,SAASE,EACdC,EACAC,EACmB,CACnB,MAAO,CACL,SAAUD,EAAO,IACjB,SAAUA,EAAO,IACjB,OAAQC,EAAQ,IAChB,OAAQA,EAAQ,GAAA,CAEpB,CCzHA,SAASC,EACPC,EACAC,EAKAC,EACuB,CACvB,GAAIF,IAAS,QAAUC,EAAM,aAC3B,MAAO,CACL,KAAAD,EACA,OAAQ,CACN,CACE,KAAM,CAAE,IAAKC,EAAM,aAAa,IAAK,IAAKA,EAAM,aAAa,GAAA,EAC7D,GAAI,CAAE,IAAKA,EAAM,aAAa,IAAK,IAAKA,EAAM,aAAa,GAAA,CAAI,CACjE,CACF,EAIJ,GAAID,IAAS,OAASC,EAAM,SAAS,KAAO,EAAG,CAC7C,MAAMf,EAAS,CAAC,GAAGe,EAAM,QAAQ,EAAE,IAAKE,IAAc,CACpD,KAAM,CAAE,IAAKA,EAAU,IAAK,CAAA,EAC5B,GAAI,CAAE,IAAKA,EAAU,IAAKD,EAAW,CAAA,CAAE,EACvC,EACF,MAAO,CAAE,KAAAF,EAAM,OAAAd,CAAA,CACjB,CAEA,OAAIc,IAAS,SAAWC,EAAM,OAAO,OAAS,EACrC,CAAE,KAAAD,EAAM,OAAQf,EAAegB,EAAM,MAAM,CAAA,EAG7C,CAAE,KAAAD,EAAM,OAAQ,EAAC,CAC1B,CAUO,MAAMI,UAAwBC,EAAAA,cAAgC,CAC1D,KAAO,YACE,QAAU,QAE5B,IAAuB,eAA0C,CAC/D,MAAO,CACL,KAAM,MAAA,CAEV,CAIQ,aAAe,IACf,aAA8B,KAC9B,OAAwB,KAGxB,OAA8B,CAAA,EAC9B,YAAwC,KACxC,WAAkD,KAClD,WAAa,GAGb,aAAoD,KAInD,QAAe,CACtB,KAAK,SAAS,MAAA,EACd,KAAK,OAAS,CAAA,EACd,KAAK,YAAc,KACnB,KAAK,WAAa,KAClB,KAAK,WAAa,GAClB,KAAK,aAAe,IACtB,CAIS,YAAYC,EAAgC,CACnD,KAAM,CAAE,SAAAH,EAAU,SAAAI,EAAU,cAAAC,CAAA,EAAkBF,EACxC,CAAE,KAAAN,GAAS,KAAK,OAGtB,GAAIA,IAAS,OACX,YAAK,aAAe,CAAE,IAAKG,EAAU,IAAKI,CAAA,EAC1C,KAAK,KAA4B,mBAAoB,KAAKE,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,GAIT,GAAIT,IAAS,MACX,YAAK,SAAS,MAAA,EACd,KAAK,SAAS,IAAIG,CAAQ,EAC1B,KAAK,aAAeA,EAEpB,KAAK,KAA4B,mBAAoB,KAAKM,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,GAIT,GAAIT,IAAS,QAAS,CACpB,MAAMU,EAAWF,EAAc,SACzBG,EAAUH,EAAc,SAAWA,EAAc,QAEvD,GAAIE,GAAY,KAAK,WAAY,CAE/B,MAAME,EAAWhB,EAAsB,KAAK,WAAY,CAAE,IAAKO,EAAU,IAAKI,EAAU,EAEpFI,EACE,KAAK,OAAO,OAAS,EACvB,KAAK,OAAO,KAAK,OAAO,OAAS,CAAC,EAAIC,EAEtC,KAAK,OAAO,KAAKA,CAAQ,EAG3B,KAAK,OAAS,CAACA,CAAQ,EAEzB,KAAK,YAAcA,CACrB,SAAWD,EAAS,CAClB,MAAMC,EAA8B,CAClC,SAAUT,EACV,SAAUI,EACV,OAAQJ,EACR,OAAQI,CAAA,EAEV,KAAK,OAAO,KAAKK,CAAQ,EACzB,KAAK,YAAcA,EACnB,KAAK,WAAa,CAAE,IAAKT,EAAU,IAAKI,CAAA,CAC1C,KAAO,CACL,MAAMK,EAA8B,CAClC,SAAUT,EACV,SAAUI,EACV,OAAQJ,EACR,OAAQI,CAAA,EAEV,KAAK,OAAS,CAACK,CAAQ,EACvB,KAAK,YAAcA,EACnB,KAAK,WAAa,CAAE,IAAKT,EAAU,IAAKI,CAAA,CAC1C,CAEA,YAAK,KAA4B,mBAAoB,KAAKE,GAAA,CAAa,EAEvE,KAAK,mBAAA,EACE,EACT,CAEA,MAAO,EACT,CAES,UAAUH,EAA+B,CAChD,KAAM,CAAE,KAAAN,GAAS,KAAK,OAGtB,GAAIM,EAAM,MAAQ,SAChB,OAAIN,IAAS,OACX,KAAK,aAAe,KACXA,IAAS,OAClB,KAAK,SAAS,MAAA,EACd,KAAK,OAAS,MACLA,IAAS,UAClB,KAAK,OAAS,CAAA,EACd,KAAK,YAAc,KACnB,KAAK,WAAa,MAEpB,KAAK,KAA4B,mBAAoB,KAAKS,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,GAIT,GAAIT,IAAS,SAAWM,EAAM,MAAQ,MAAQA,EAAM,SAAWA,EAAM,SAAU,CAC7E,MAAMO,EAAW,KAAK,KAAK,OACrBX,EAAW,KAAK,QAAQ,OAC9B,GAAIW,EAAW,GAAKX,EAAW,EAAG,CAChC,MAAMY,EAA8B,CAClC,SAAU,EACV,SAAU,EACV,OAAQD,EAAW,EACnB,OAAQX,EAAW,CAAA,EAErB,YAAK,OAAS,CAACY,CAAQ,EACvB,KAAK,YAAcA,EACnB,KAAK,KAA4B,mBAAoB,KAAKL,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,EACT,CACF,CAEA,MAAO,EACT,CAES,gBAAgBH,EAAuC,CAM9D,GALI,KAAK,OAAO,OAAS,SACrBA,EAAM,WAAa,QAAaA,EAAM,WAAa,QACnDA,EAAM,SAAW,GAGjBA,EAAM,cAAc,UAAY,KAAK,WACvC,OAIF,KAAK,WAAa,GAClB,MAAMH,EAAWG,EAAM,SACjBC,EAAWD,EAAM,SACvB,KAAK,WAAa,CAAE,IAAKH,EAAU,IAAKI,CAAA,EAExBD,EAAM,cAAc,SAAWA,EAAM,cAAc,UAEjE,KAAK,OAAS,CAAA,GAGhB,MAAMM,EAA8B,CAClC,SAAUT,EACV,SAAUI,EACV,OAAQJ,EACR,OAAQI,CAAA,EAEV,YAAK,OAAO,KAAKK,CAAQ,EACzB,KAAK,YAAcA,EAEnB,KAAK,KAA4B,mBAAoB,KAAKH,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,EACT,CAES,gBAAgBH,EAAuC,CAI9D,GAHI,KAAK,OAAO,OAAS,SACrB,CAAC,KAAK,YAAc,CAAC,KAAK,YAC1BA,EAAM,WAAa,QAAaA,EAAM,WAAa,QACnDA,EAAM,SAAW,EAAG,OAExB,MAAMM,EAAWhB,EAAsB,KAAK,WAAY,CAAE,IAAKU,EAAM,SAAU,IAAKA,EAAM,QAAA,CAAU,EAEpG,OAAI,KAAK,OAAO,OAAS,EACvB,KAAK,OAAO,KAAK,OAAO,OAAS,CAAC,EAAIM,EAEtC,KAAK,OAAO,KAAKA,CAAQ,EAE3B,KAAK,YAAcA,EAEnB,KAAK,KAA4B,mBAAoB,KAAKH,GAAA,CAAa,EACvE,KAAK,mBAAA,EACE,EACT,CAES,cAAcM,EAAwC,CAC7D,GAAI,KAAK,OAAO,OAAS,SACrB,KAAK,WACP,YAAK,WAAa,GACX,EAEX,CAES,aAAoB,CAC3B,MAAMC,EAAa,KAAK,WACxB,GAAI,CAACA,EAAY,OAEjB,MAAMC,EAAYD,EAAW,SAAS,CAAC,EACjC,CAAE,KAAAhB,GAAS,KAAK,OAGrB,KAAK,KAA4B,aAAa,sBAAuBA,CAAI,EAGtEiB,GACFA,EAAU,UAAU,OAAO,YAAa,KAAK,UAAU,EAIxCD,EAAW,iBAAiB,OAAO,EAC3C,QAASrB,GAAS,CACzBA,EAAK,UAAU,OAAO,WAAY,MAAO,SAAU,QAAS,MAAM,CACpE,CAAC,EAED,MAAMuB,EAAUF,EAAW,iBAAiB,gBAAgB,EAmB5D,GAlBAE,EAAQ,QAAS9B,GAAQ,CACvBA,EAAI,UAAU,OAAO,UAAU,CACjC,CAAC,EAGGY,IAAS,QACXkB,EAAQ,QAAS9B,GAAQA,EAAI,UAAU,OAAO,WAAW,CAAC,EAE1D8B,EAAQ,QAAS9B,GAAQ,CACvB,MAAM+B,EAAY/B,EAAI,cAAc,iBAAiB,EAC/Ce,EAAW,SAASgB,GAAW,aAAa,UAAU,GAAK,KAAM,EAAE,EACrEhB,GAAY,GAAK,KAAK,SAAS,IAAIA,CAAQ,GAC7Cf,EAAI,UAAU,IAAI,WAAY,WAAW,CAE7C,CAAC,GAICY,IAAS,SAAW,KAAK,OAAO,OAAS,EAAG,CAC9C,MAAMhB,EAAa,KAAK,YAAcH,EAAe,KAAK,WAAW,EAAI,KAE3DmC,EAAW,iBAAiB,2BAA2B,EAC/D,QAASrB,GAAS,CACtB,MAAMQ,EAAW,SAASR,EAAK,aAAa,UAAU,GAAK,KAAM,EAAE,EAC7DY,EAAW,SAASZ,EAAK,aAAa,UAAU,GAAK,KAAM,EAAE,EAC/DQ,GAAY,GAAKI,GAAY,GACfjB,EAAiBa,EAAUI,EAAU,KAAK,MAAM,IAG9DZ,EAAK,UAAU,IAAI,UAAU,EAEzBX,IACEmB,IAAanB,EAAW,UAAUW,EAAK,UAAU,IAAI,KAAK,EAC1DQ,IAAanB,EAAW,QAAQW,EAAK,UAAU,IAAI,QAAQ,EAC3DY,IAAavB,EAAW,UAAUW,EAAK,UAAU,IAAI,OAAO,EAC5DY,IAAavB,EAAW,QAAQW,EAAK,UAAU,IAAI,MAAM,GAIrE,CAAC,CACH,CACF,CAOA,iBAAuD,CACrD,OAAO,KAAK,YACd,CAKA,iBAA4B,CAC1B,MAAO,CAAC,GAAG,KAAK,QAAQ,CAC1B,CAKA,WAAyB,CACvB,OAAOV,EAAe,KAAK,MAAM,CACnC,CAKA,kBAAwD,CACtD,OAAOQ,EAAoB,KAAK,MAAM,CACxC,CAKA,eAAeL,EAAaC,EAAsB,CAChD,OAAOC,EAAiBF,EAAKC,EAAK,KAAK,MAAM,CAC/C,CAKA,gBAAuB,CACrB,KAAK,aAAe,KACpB,KAAK,SAAS,MAAA,EACd,KAAK,OAAS,KACd,KAAK,OAAS,CAAA,EACd,KAAK,YAAc,KACnB,KAAK,WAAa,KAClB,KAAK,KAA4B,mBAAoB,CAAE,KAAM,KAAK,OAAO,KAAM,OAAQ,CAAA,EAAI,EAC3F,KAAK,mBAAA,CACP,CAKA,UAAUH,EAA2B,CACnC,KAAK,OAASA,EAAO,IAAKkC,IAAO,CAC/B,SAAUA,EAAE,KAAK,IACjB,SAAUA,EAAE,KAAK,IACjB,OAAQA,EAAE,GAAG,IACb,OAAQA,EAAE,GAAG,GAAA,EACb,EACF,KAAK,YAAc,KAAK,OAAO,OAAS,EAAI,KAAK,OAAO,KAAK,OAAO,OAAS,CAAC,EAAI,KAClF,KAAK,KAA4B,mBAAoB,CACnD,KAAM,KAAK,OAAO,KAClB,OAAQnC,EAAe,KAAK,MAAM,CAAA,CACnC,EACD,KAAK,mBAAA,CACP,CAIAwB,IAAqC,CACnC,OAAOV,EACL,KAAK,OAAO,KACZ,CACE,aAAc,KAAK,aACnB,SAAU,KAAK,SACf,OAAQ,KAAK,MAAA,EAEf,KAAK,QAAQ,MAAA,CAEjB,CAIkB,OAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAiC7B,CC7aO,SAASsB,EACdpB,EACAE,EACAH,EACAsB,EACgB,CAChB,MAAMC,EAAc,IAAI,IAAItB,EAAM,QAAQ,EAC1C,IAAIJ,EAASI,EAAM,OAEnB,GAAKD,IAAoB,SACvBuB,EAAY,MAAA,EACZA,EAAY,IAAIpB,CAAQ,EACxBN,EAASM,UACCH,IAAoB,WAAY,CAC1C,MAAMwB,EAAaF,EAAU,SAAWA,EAAU,QAElD,GAAIA,EAAU,UAAYrB,EAAM,SAAW,KAAM,CAE/C,MAAMwB,EAAQ,KAAK,IAAIxB,EAAM,OAAQE,CAAQ,EACvCuB,EAAM,KAAK,IAAIzB,EAAM,OAAQE,CAAQ,EAC3C,QAASwB,EAAIF,EAAOE,GAAKD,EAAKC,IAC5BJ,EAAY,IAAII,CAAC,CAErB,MAAWH,GAELD,EAAY,IAAIpB,CAAQ,EAC1BoB,EAAY,OAAOpB,CAAQ,EAE3BoB,EAAY,IAAIpB,CAAQ,EAE1BN,EAASM,IAGToB,EAAY,MAAA,EACZA,EAAY,IAAIpB,CAAQ,EACxBN,EAASM,EAEb,CAEA,MAAO,CAAE,SAAUoB,EAAa,aAAcpB,EAAU,OAAAN,CAAA,CAC1D,CAQO,SAAS+B,EAAUf,EAA+B,CACvD,MAAMgB,MAAe,IACrB,QAASF,EAAI,EAAGA,EAAId,EAAUc,IAC5BE,EAAS,IAAIF,CAAC,EAEhB,OAAOE,CACT,CASO,SAASC,EACdC,EACAR,EACwC,CACxC,MAAMS,EAAkB,CAAA,EAClBC,EAAoB,CAAA,EAE1B,UAAWC,KAAOX,EACXQ,EAAY,IAAIG,CAAG,GAAGF,EAAM,KAAKE,CAAG,EAE3C,UAAWA,KAAOH,EACXR,EAAY,IAAIW,CAAG,GAAGD,EAAQ,KAAKC,CAAG,EAG7C,MAAO,CAAE,MAAAF,EAAO,QAAAC,CAAA,CAClB"}
@@ -0,0 +1,2 @@
1
+ (function(n,r){typeof exports=="object"&&typeof module<"u"?r(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],r):(n=typeof globalThis<"u"?globalThis:n||self,r(n.TbwGridPlugin_serverSide={},n.TbwGrid))})(this,(function(n,r){"use strict";function a(s,e){return Math.floor(s/e)}function u(s,e){return{start:s*e,end:(s+1)*e}}function h(s,e,t){const o=a(s,t),l=a(e-1,t),i=[];for(let c=o;c<=l;c++)i.push(c);return i}async function d(s,e,t,o){const l=u(e,t);return s.getRows({startRow:l.start,endRow:l.end,sortModel:o.sortModel,filterModel:o.filterModel})}function k(s,e,t){const o=a(s,e),l=t.get(o);if(!l)return;const i=s%e;return l[i]}const f=100;class g extends r.BaseGridPlugin{name="serverSide";version="1.0.0";get defaultConfig(){return{enabled:!0,pageSize:100,cacheBlockSize:100,maxConcurrentRequests:2}}dataSource=null;totalRowCount=0;loadedBlocks=new Map;loadingBlocks=new Set;lastRequestId=0;scrollDebounceTimer;detach(){this.dataSource=null,this.totalRowCount=0,this.loadedBlocks.clear(),this.loadingBlocks.clear(),this.lastRequestId=0,this.scrollDebounceTimer&&(clearTimeout(this.scrollDebounceTimer),this.scrollDebounceTimer=void 0)}loadRequiredBlocks(){if(!this.dataSource)return;const e=this.grid,t=this.config.cacheBlockSize??100,o={startRow:e.virtualization.start,endRow:e.virtualization.end},l=h(o.startRow,o.endRow,t);for(const i of l)if(!(this.loadedBlocks.has(i)||this.loadingBlocks.has(i))){if(this.loadingBlocks.size>=(this.config.maxConcurrentRequests??2))break;this.loadingBlocks.add(i),d(this.dataSource,i,t,{}).then(c=>{this.loadedBlocks.set(i,c.rows),this.totalRowCount=c.totalRowCount,this.loadingBlocks.delete(i),this.requestRender(),this.loadRequiredBlocks()}).catch(()=>{this.loadingBlocks.delete(i)})}}processRows(e){if(!this.dataSource)return[...e];const t=[];for(let o=0;o<this.totalRowCount;o++){const l=k(o,this.config.cacheBlockSize??100,this.loadedBlocks);t.push(l??{__loading:!0,__index:o})}return t}onScroll(e){this.dataSource&&(this.loadRequiredBlocks(),this.scrollDebounceTimer&&clearTimeout(this.scrollDebounceTimer),this.scrollDebounceTimer=setTimeout(()=>{this.loadRequiredBlocks()},f))}setDataSource(e){this.dataSource=e,this.loadedBlocks.clear(),this.loadingBlocks.clear();const t=this.config.cacheBlockSize??100;d(e,0,t,{}).then(o=>{this.loadedBlocks.set(0,o.rows),this.totalRowCount=o.totalRowCount,this.requestRender()})}refresh(){this.dataSource&&(this.loadedBlocks.clear(),this.loadingBlocks.clear(),this.requestRender())}purgeCache(){this.loadedBlocks.clear()}getTotalRowCount(){return this.totalRowCount}isRowLoaded(e){const t=this.config.cacheBlockSize??100,o=a(e,t);return this.loadedBlocks.has(o)}getLoadedBlockCount(){return this.loadedBlocks.size}}n.ServerSidePlugin=g,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})}));
2
+ //# sourceMappingURL=server-side.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-side.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/server-side/datasource.ts","../../../../../libs/grid/src/lib/plugins/server-side/ServerSidePlugin.ts"],"sourcesContent":["import type { ServerSideDataSource, GetRowsParams, GetRowsResult } from './types';\n\nexport function getBlockNumber(rowIndex: number, blockSize: number): number {\n return Math.floor(rowIndex / blockSize);\n}\n\nexport function getBlockRange(blockNumber: number, blockSize: number): { start: number; end: number } {\n return {\n start: blockNumber * blockSize,\n end: (blockNumber + 1) * blockSize,\n };\n}\n\nexport function getRequiredBlocks(startRow: number, endRow: number, blockSize: number): number[] {\n const startBlock = getBlockNumber(startRow, blockSize);\n const endBlock = getBlockNumber(endRow - 1, blockSize);\n\n const blocks: number[] = [];\n for (let i = startBlock; i <= endBlock; i++) {\n blocks.push(i);\n }\n return blocks;\n}\n\nexport async function loadBlock(\n dataSource: ServerSideDataSource,\n blockNumber: number,\n blockSize: number,\n params: Partial<GetRowsParams>\n): Promise<GetRowsResult> {\n const range = getBlockRange(blockNumber, blockSize);\n\n return dataSource.getRows({\n startRow: range.start,\n endRow: range.end,\n sortModel: params.sortModel,\n filterModel: params.filterModel,\n });\n}\n\nexport function getRowFromCache(\n rowIndex: number,\n blockSize: number,\n loadedBlocks: Map<number, any[]>\n): any | undefined {\n const blockNumber = getBlockNumber(rowIndex, blockSize);\n const block = loadedBlocks.get(blockNumber);\n if (!block) return undefined;\n\n const indexInBlock = rowIndex % blockSize;\n return block[indexInBlock];\n}\n\nexport function isBlockLoaded(blockNumber: number, loadedBlocks: Map<number, any[]>): boolean {\n return loadedBlocks.has(blockNumber);\n}\n\nexport function isBlockLoading(blockNumber: number, loadingBlocks: Set<number>): boolean {\n return loadingBlocks.has(blockNumber);\n}\n","/**\n * Server-Side Data Plugin (Class-based)\n *\n * Provides server-side data loading with caching and lazy loading.\n */\n\nimport { BaseGridPlugin, ScrollEvent } from '../../core/plugin/base-plugin';\nimport { getBlockNumber, getRequiredBlocks, getRowFromCache, loadBlock } from './datasource';\nimport type { ServerSideConfig, ServerSideDataSource } from './types';\n\n/** Scroll debounce delay in ms */\nconst SCROLL_DEBOUNCE_MS = 100;\n\n/**\n * Server-Side Plugin for tbw-grid\n *\n * @example\n * ```ts\n * const plugin = new ServerSidePlugin({ cacheBlockSize: 100 });\n * plugin.setDataSource(myDataSource);\n * ```\n */\nexport class ServerSidePlugin extends BaseGridPlugin<ServerSideConfig> {\n readonly name = 'serverSide';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<ServerSideConfig> {\n return {\n enabled: true,\n pageSize: 100,\n cacheBlockSize: 100,\n maxConcurrentRequests: 2,\n };\n }\n\n // ===== Internal State =====\n private dataSource: ServerSideDataSource | null = null;\n private totalRowCount = 0;\n private loadedBlocks = new Map<number, unknown[]>();\n private loadingBlocks = new Set<number>();\n private lastRequestId = 0;\n private scrollDebounceTimer?: ReturnType<typeof setTimeout>;\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.dataSource = null;\n this.totalRowCount = 0;\n this.loadedBlocks.clear();\n this.loadingBlocks.clear();\n this.lastRequestId = 0;\n if (this.scrollDebounceTimer) {\n clearTimeout(this.scrollDebounceTimer);\n this.scrollDebounceTimer = undefined;\n }\n }\n\n // ===== Private Methods =====\n\n /**\n * Check current viewport and load any missing blocks.\n */\n private loadRequiredBlocks(): void {\n if (!this.dataSource) return;\n\n // Get fresh viewport from grid's virtualization state\n const gridRef = this.grid as unknown as { virtualization: { start: number; end: number } };\n const blockSize = this.config.cacheBlockSize ?? 100;\n const viewport = { startRow: gridRef.virtualization.start, endRow: gridRef.virtualization.end };\n\n // Determine which blocks are needed for current viewport\n const requiredBlocks = getRequiredBlocks(viewport.startRow, viewport.endRow, blockSize);\n\n // Load missing blocks\n for (const blockNum of requiredBlocks) {\n if (this.loadedBlocks.has(blockNum) || this.loadingBlocks.has(blockNum)) {\n continue;\n }\n\n // Check concurrent request limit\n if (this.loadingBlocks.size >= (this.config.maxConcurrentRequests ?? 2)) {\n break;\n }\n\n this.loadingBlocks.add(blockNum);\n\n loadBlock(this.dataSource, blockNum, blockSize, {})\n .then((result) => {\n this.loadedBlocks.set(blockNum, result.rows);\n this.totalRowCount = result.totalRowCount;\n this.loadingBlocks.delete(blockNum);\n this.requestRender();\n // Re-check with fresh viewport: user may have scrolled further\n this.loadRequiredBlocks();\n })\n .catch(() => {\n this.loadingBlocks.delete(blockNum);\n });\n }\n }\n\n // ===== Hooks =====\n\n override processRows(rows: readonly unknown[]): unknown[] {\n if (!this.dataSource) return [...rows];\n\n // Create placeholder rows for total count\n const result: unknown[] = [];\n for (let i = 0; i < this.totalRowCount; i++) {\n const cached = getRowFromCache(i, this.config.cacheBlockSize ?? 100, this.loadedBlocks);\n result.push(cached ?? { __loading: true, __index: i });\n }\n\n return result;\n }\n\n override onScroll(event: ScrollEvent): void {\n if (!this.dataSource) return;\n\n // Immediate check for blocks\n this.loadRequiredBlocks();\n\n // Debounce: when scrolling stops, do a final check with fresh viewport\n if (this.scrollDebounceTimer) {\n clearTimeout(this.scrollDebounceTimer);\n }\n this.scrollDebounceTimer = setTimeout(() => {\n this.loadRequiredBlocks();\n }, SCROLL_DEBOUNCE_MS);\n }\n\n // ===== Public API =====\n\n /**\n * Set the data source for server-side loading.\n * @param dataSource - Data source implementing the getRows method\n */\n setDataSource(dataSource: ServerSideDataSource): void {\n this.dataSource = dataSource;\n this.loadedBlocks.clear();\n this.loadingBlocks.clear();\n\n // Load first block\n const blockSize = this.config.cacheBlockSize ?? 100;\n loadBlock(dataSource, 0, blockSize, {}).then((result) => {\n this.loadedBlocks.set(0, result.rows);\n this.totalRowCount = result.totalRowCount;\n this.requestRender();\n });\n }\n\n /**\n * Refresh all data from the server.\n */\n refresh(): void {\n if (!this.dataSource) return;\n this.loadedBlocks.clear();\n this.loadingBlocks.clear();\n this.requestRender();\n }\n\n /**\n * Clear all cached data without refreshing.\n */\n purgeCache(): void {\n this.loadedBlocks.clear();\n }\n\n /**\n * Get the total row count from the server.\n */\n getTotalRowCount(): number {\n return this.totalRowCount;\n }\n\n /**\n * Check if a specific row is loaded in the cache.\n * @param rowIndex - Row index to check\n */\n isRowLoaded(rowIndex: number): boolean {\n const blockSize = this.config.cacheBlockSize ?? 100;\n const blockNum = getBlockNumber(rowIndex, blockSize);\n return this.loadedBlocks.has(blockNum);\n }\n\n /**\n * Get the number of loaded cache blocks.\n */\n getLoadedBlockCount(): number {\n return this.loadedBlocks.size;\n }\n}\n"],"names":["getBlockNumber","rowIndex","blockSize","getBlockRange","blockNumber","getRequiredBlocks","startRow","endRow","startBlock","endBlock","blocks","i","loadBlock","dataSource","params","range","getRowFromCache","loadedBlocks","block","indexInBlock","SCROLL_DEBOUNCE_MS","ServerSidePlugin","BaseGridPlugin","gridRef","viewport","requiredBlocks","blockNum","result","rows","cached","event"],"mappings":"sUAEO,SAASA,EAAeC,EAAkBC,EAA2B,CAC1E,OAAO,KAAK,MAAMD,EAAWC,CAAS,CACxC,CAEO,SAASC,EAAcC,EAAqBF,EAAmD,CACpG,MAAO,CACL,MAAOE,EAAcF,EACrB,KAAME,EAAc,GAAKF,CAAA,CAE7B,CAEO,SAASG,EAAkBC,EAAkBC,EAAgBL,EAA6B,CAC/F,MAAMM,EAAaR,EAAeM,EAAUJ,CAAS,EAC/CO,EAAWT,EAAeO,EAAS,EAAGL,CAAS,EAE/CQ,EAAmB,CAAA,EACzB,QAASC,EAAIH,EAAYG,GAAKF,EAAUE,IACtCD,EAAO,KAAKC,CAAC,EAEf,OAAOD,CACT,CAEA,eAAsBE,EACpBC,EACAT,EACAF,EACAY,EACwB,CACxB,MAAMC,EAAQZ,EAAcC,EAAaF,CAAS,EAElD,OAAOW,EAAW,QAAQ,CACxB,SAAUE,EAAM,MAChB,OAAQA,EAAM,IACd,UAAWD,EAAO,UAClB,YAAaA,EAAO,WAAA,CACrB,CACH,CAEO,SAASE,EACdf,EACAC,EACAe,EACiB,CACjB,MAAMb,EAAcJ,EAAeC,EAAUC,CAAS,EAChDgB,EAAQD,EAAa,IAAIb,CAAW,EAC1C,GAAI,CAACc,EAAO,OAEZ,MAAMC,EAAelB,EAAWC,EAChC,OAAOgB,EAAMC,CAAY,CAC3B,CCxCA,MAAMC,EAAqB,IAWpB,MAAMC,UAAyBC,EAAAA,cAAiC,CAC5D,KAAO,aACE,QAAU,QAE5B,IAAuB,eAA2C,CAChE,MAAO,CACL,QAAS,GACT,SAAU,IACV,eAAgB,IAChB,sBAAuB,CAAA,CAE3B,CAGQ,WAA0C,KAC1C,cAAgB,EAChB,iBAAmB,IACnB,kBAAoB,IACpB,cAAgB,EAChB,oBAIC,QAAe,CACtB,KAAK,WAAa,KAClB,KAAK,cAAgB,EACrB,KAAK,aAAa,MAAA,EAClB,KAAK,cAAc,MAAA,EACnB,KAAK,cAAgB,EACjB,KAAK,sBACP,aAAa,KAAK,mBAAmB,EACrC,KAAK,oBAAsB,OAE/B,CAOQ,oBAA2B,CACjC,GAAI,CAAC,KAAK,WAAY,OAGtB,MAAMC,EAAU,KAAK,KACfrB,EAAY,KAAK,OAAO,gBAAkB,IAC1CsB,EAAW,CAAE,SAAUD,EAAQ,eAAe,MAAO,OAAQA,EAAQ,eAAe,GAAA,EAGpFE,EAAiBpB,EAAkBmB,EAAS,SAAUA,EAAS,OAAQtB,CAAS,EAGtF,UAAWwB,KAAYD,EACrB,GAAI,OAAK,aAAa,IAAIC,CAAQ,GAAK,KAAK,cAAc,IAAIA,CAAQ,GAKtE,IAAI,KAAK,cAAc,OAAS,KAAK,OAAO,uBAAyB,GACnE,MAGF,KAAK,cAAc,IAAIA,CAAQ,EAE/Bd,EAAU,KAAK,WAAYc,EAAUxB,EAAW,EAAE,EAC/C,KAAMyB,GAAW,CAChB,KAAK,aAAa,IAAID,EAAUC,EAAO,IAAI,EAC3C,KAAK,cAAgBA,EAAO,cAC5B,KAAK,cAAc,OAAOD,CAAQ,EAClC,KAAK,cAAA,EAEL,KAAK,mBAAA,CACP,CAAC,EACA,MAAM,IAAM,CACX,KAAK,cAAc,OAAOA,CAAQ,CACpC,CAAC,EAEP,CAIS,YAAYE,EAAqC,CACxD,GAAI,CAAC,KAAK,WAAY,MAAO,CAAC,GAAGA,CAAI,EAGrC,MAAMD,EAAoB,CAAA,EAC1B,QAAShB,EAAI,EAAGA,EAAI,KAAK,cAAeA,IAAK,CAC3C,MAAMkB,EAASb,EAAgBL,EAAG,KAAK,OAAO,gBAAkB,IAAK,KAAK,YAAY,EACtFgB,EAAO,KAAKE,GAAU,CAAE,UAAW,GAAM,QAASlB,EAAG,CACvD,CAEA,OAAOgB,CACT,CAES,SAASG,EAA0B,CACrC,KAAK,aAGV,KAAK,mBAAA,EAGD,KAAK,qBACP,aAAa,KAAK,mBAAmB,EAEvC,KAAK,oBAAsB,WAAW,IAAM,CAC1C,KAAK,mBAAA,CACP,EAAGV,CAAkB,EACvB,CAQA,cAAcP,EAAwC,CACpD,KAAK,WAAaA,EAClB,KAAK,aAAa,MAAA,EAClB,KAAK,cAAc,MAAA,EAGnB,MAAMX,EAAY,KAAK,OAAO,gBAAkB,IAChDU,EAAUC,EAAY,EAAGX,EAAW,CAAA,CAAE,EAAE,KAAMyB,GAAW,CACvD,KAAK,aAAa,IAAI,EAAGA,EAAO,IAAI,EACpC,KAAK,cAAgBA,EAAO,cAC5B,KAAK,cAAA,CACP,CAAC,CACH,CAKA,SAAgB,CACT,KAAK,aACV,KAAK,aAAa,MAAA,EAClB,KAAK,cAAc,MAAA,EACnB,KAAK,cAAA,EACP,CAKA,YAAmB,CACjB,KAAK,aAAa,MAAA,CACpB,CAKA,kBAA2B,CACzB,OAAO,KAAK,aACd,CAMA,YAAY1B,EAA2B,CACrC,MAAMC,EAAY,KAAK,OAAO,gBAAkB,IAC1CwB,EAAW1B,EAAeC,EAAUC,CAAS,EACnD,OAAO,KAAK,aAAa,IAAIwB,CAAQ,CACvC,CAKA,qBAA8B,CAC5B,OAAO,KAAK,aAAa,IAC3B,CACF"}
@@ -0,0 +1,11 @@
1
+ (function(h,y){typeof exports=="object"&&typeof module<"u"?y(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],y):(h=typeof globalThis<"u"?globalThis:h||self,y(h.TbwGridPlugin_tree={},h.TbwGrid))})(this,(function(h,y){"use strict";function w(i,e,t){return i.id!==void 0?String(i.id):t?`${t}-${e}`:String(e)}function m(i,e,t,n=null,r=0){const d=e.childrenField??"children",s=[];for(let c=0;c<i.length;c++){const a=i[c],o=w(a,c,n),u=a[d],p=Array.isArray(u)&&u.length>0,g=t.has(o);if(s.push({key:o,data:a,depth:r,hasChildren:p,isExpanded:g,parentKey:n}),p&&g){const R=m(u,e,t,o,r+1);s.push(...R)}}return s}function E(i,e){const t=new Set(i);return t.has(e)?t.delete(e):t.add(e),t}function A(i,e,t=null,n=0){const r=e.childrenField??"children",d=new Set;for(let s=0;s<i.length;s++){const c=i[s],a=w(c,s,t),o=c[r];if(Array.isArray(o)&&o.length>0){d.add(a);const u=A(o,e,a,n+1);for(const p of u)d.add(p)}}return d}function T(){return new Set}function C(i,e,t,n=null,r=0){const d=t.childrenField??"children";for(let s=0;s<i.length;s++){const c=i[s],a=w(c,s,n);if(a===e)return[a];const o=c[d];if(Array.isArray(o)&&o.length>0){const u=C(o,e,t,a,r+1);if(u)return[a,...u]}}return null}function F(i,e,t,n){const r=C(i,e,t);if(!r)return n;const d=new Set(n);for(let s=0;s<r.length-1;s++)d.add(r[s]);return d}function K(i,e="children"){if(!Array.isArray(i)||i.length===0)return!1;for(const t of i)if(t&&Array.isArray(t[e])&&t[e].length>0)return!0;return!1}function _(i){if(!Array.isArray(i)||i.length===0)return null;const e=["children","items","nodes","subRows","nested"];for(const t of i)if(!(!t||typeof t!="object")){for(const n of e)if(Array.isArray(t[n])&&t[n].length>0)return n}return null}function b(i,e="children",t=0){if(!Array.isArray(i)||i.length===0)return t;let n=t;for(const r of i){if(!r)continue;const d=r[e];if(Array.isArray(d)&&d.length>0){const s=b(d,e,t+1);s>n&&(n=s)}}return n}function S(i,e="children"){if(!Array.isArray(i))return 0;let t=0;for(const n of i){if(!n)continue;t++;const r=n[e];Array.isArray(r)&&(t+=S(r,e))}return t}class M extends y.BaseGridPlugin{name="tree";version="1.0.0";get defaultConfig(){return{enabled:!0,childrenField:"children",autoDetect:!0,defaultExpanded:!1,indentWidth:20,showExpandIcons:!0}}expandedKeys=new Set;initialExpansionDone=!1;flattenedRows=[];rowKeyMap=new Map;detach(){this.expandedKeys.clear(),this.initialExpansionDone=!1,this.flattenedRows=[],this.rowKeyMap.clear()}detect(e){if(!this.config.autoDetect)return!1;const t=this.config.childrenField??_(e)??"children";return K(e,t)}processRows(e){const t=this.config.childrenField??"children";if(!K(e,t))return this.flattenedRows=[],this.rowKeyMap.clear(),[...e];this.config.defaultExpanded&&!this.initialExpansionDone&&(this.expandedKeys=A(e,this.config),this.initialExpansionDone=!0),this.flattenedRows=m(e,this.config,this.expandedKeys),this.rowKeyMap.clear();for(const n of this.flattenedRows)this.rowKeyMap.set(n.key,n);return this.flattenedRows.map(n=>({...n.data,__treeKey:n.key,__treeDepth:n.depth,__treeHasChildren:n.hasChildren,__treeExpanded:n.isExpanded}))}processColumns(e){if(this.flattenedRows.length===0)return[...e];const t=this.config.indentWidth??20,n=this.config.showExpandIcons??!0,r=[...e];if(r.length>0){const d={...r[0]},s=d.viewRenderer;d.viewRenderer=c=>{const{value:a,row:o,column:u}=c,p=o.__treeDepth??0,g=o.__treeHasChildren??!1,R=o.__treeExpanded??!1,f=document.createElement("span");if(f.style.display="flex",f.style.alignItems="center",f.style.paddingLeft=`${p*t}px`,g&&n){const l=document.createElement("span");l.className="tree-toggle",l.textContent=R?"▼":"▶",l.style.cursor="pointer",l.style.marginRight="4px",l.style.fontSize="10px",l.setAttribute("data-tree-key",o.__treeKey),f.appendChild(l)}else if(n){const l=document.createElement("span");l.style.width="14px",l.style.display="inline-block",f.appendChild(l)}const x=document.createElement("span");if(s){const l=s(c);l instanceof Node?x.appendChild(l):x.textContent=String(l??a??"")}else x.textContent=String(a??"");return f.appendChild(x),f},r[0]=d}return r}onCellClick(e){const t=e.originalEvent?.target;if(!t?.classList.contains("tree-toggle"))return!1;const n=t.getAttribute("data-tree-key");if(!n)return!1;const r=this.rowKeyMap.get(n);return r?(this.expandedKeys=E(this.expandedKeys,n),this.emit("tree-expand",{key:n,row:r.data,expanded:this.expandedKeys.has(n),depth:r.depth}),this.requestRender(),!0):!1}expand(e){this.expandedKeys.add(e),this.requestRender()}collapse(e){this.expandedKeys.delete(e),this.requestRender()}toggle(e){this.expandedKeys=E(this.expandedKeys,e),this.requestRender()}expandAll(){this.expandedKeys=A(this.rows,this.config),this.requestRender()}collapseAll(){this.expandedKeys=T(),this.requestRender()}isExpanded(e){return this.expandedKeys.has(e)}getExpandedKeys(){return[...this.expandedKeys]}getFlattenedRows(){return[...this.flattenedRows]}getRowByKey(e){return this.rowKeyMap.get(e)?.data}expandToKey(e){this.expandedKeys=F(this.rows,e,this.config,this.expandedKeys),this.requestRender()}styles=`
2
+ .tree-toggle {
3
+ cursor: pointer;
4
+ user-select: none;
5
+ transition: transform 0.2s;
6
+ }
7
+ .tree-toggle:hover {
8
+ color: var(--tbw-tree-accent, var(--tbw-color-accent));
9
+ }
10
+ `}h.TreePlugin=M,h.countNodes=S,h.detectTreeStructure=K,h.getMaxDepth=b,h.inferChildrenField=_,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
11
+ //# sourceMappingURL=tree.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/tree/tree-data.ts","../../../../../libs/grid/src/lib/plugins/tree/tree-detect.ts","../../../../../libs/grid/src/lib/plugins/tree/TreePlugin.ts"],"sourcesContent":["/**\n * Core Tree Data Logic\n *\n * Pure functions for tree flattening, expansion, and traversal.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// The tree plugin intentionally uses `any` for maximum flexibility with user-defined row types.\n\nimport type { FlattenedTreeRow, TreeConfig } from './types';\n\n/**\n * Generates a unique key for a row.\n * Uses row.id if available, otherwise generates from path.\n */\nexport function generateRowKey(row: any, index: number, parentKey: string | null): string {\n if (row.id !== undefined) return String(row.id);\n return parentKey ? `${parentKey}-${index}` : String(index);\n}\n\n/**\n * Flattens a hierarchical tree into a flat array of rows with metadata.\n * Only includes children of expanded nodes.\n */\nexport function flattenTree(\n rows: any[],\n config: TreeConfig,\n expandedKeys: Set<string>,\n parentKey: string | null = null,\n depth = 0\n): FlattenedTreeRow[] {\n const childrenField = config.childrenField ?? 'children';\n const result: FlattenedTreeRow[] = [];\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n const children = row[childrenField];\n const hasChildren = Array.isArray(children) && children.length > 0;\n const isExpanded = expandedKeys.has(key);\n\n result.push({\n key,\n data: row,\n depth,\n hasChildren,\n isExpanded,\n parentKey,\n });\n\n // Recursively add children if expanded\n if (hasChildren && isExpanded) {\n const childRows = flattenTree(children, config, expandedKeys, key, depth + 1);\n result.push(...childRows);\n }\n }\n\n return result;\n}\n\n/**\n * Toggles the expansion state of a row.\n * Returns a new Set with the toggled state.\n */\nexport function toggleExpand(expandedKeys: Set<string>, key: string): Set<string> {\n const newExpanded = new Set(expandedKeys);\n if (newExpanded.has(key)) {\n newExpanded.delete(key);\n } else {\n newExpanded.add(key);\n }\n return newExpanded;\n}\n\n/**\n * Expands all nodes in the tree.\n * Returns a Set of all parent row keys.\n */\nexport function expandAll(rows: any[], config: TreeConfig, parentKey: string | null = null, depth = 0): Set<string> {\n const childrenField = config.childrenField ?? 'children';\n const keys = new Set<string>();\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n const children = row[childrenField];\n\n if (Array.isArray(children) && children.length > 0) {\n keys.add(key);\n const childKeys = expandAll(children, config, key, depth + 1);\n for (const k of childKeys) keys.add(k);\n }\n }\n\n return keys;\n}\n\n/**\n * Collapses all nodes.\n * Returns an empty Set.\n */\nexport function collapseAll(): Set<string> {\n return new Set();\n}\n\n/**\n * Gets all descendants of a node from the flattened row list.\n * Useful for operations that need to affect an entire subtree.\n */\nexport function getDescendants(flattenedRows: FlattenedTreeRow[], parentKey: string): FlattenedTreeRow[] {\n const descendants: FlattenedTreeRow[] = [];\n let collecting = false;\n let parentDepth = -1;\n\n for (const row of flattenedRows) {\n if (row.key === parentKey) {\n collecting = true;\n parentDepth = row.depth;\n continue;\n }\n\n if (collecting) {\n if (row.depth > parentDepth) {\n descendants.push(row);\n } else {\n break; // No longer a descendant\n }\n }\n }\n\n return descendants;\n}\n\n/**\n * Finds the path from root to a specific row key.\n * Returns an array of keys from root to the target (inclusive).\n */\nexport function getPathToKey(\n rows: any[],\n targetKey: string,\n config: TreeConfig,\n parentKey: string | null = null,\n depth = 0\n): string[] | null {\n const childrenField = config.childrenField ?? 'children';\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n\n if (key === targetKey) {\n return [key];\n }\n\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n const childPath = getPathToKey(children, targetKey, config, key, depth + 1);\n if (childPath) {\n return [key, ...childPath];\n }\n }\n }\n\n return null;\n}\n\n/**\n * Expands all ancestors of a specific row to make it visible.\n * Returns a new Set with the required keys added.\n */\nexport function expandToKey(\n rows: any[],\n targetKey: string,\n config: TreeConfig,\n existingExpanded: Set<string>\n): Set<string> {\n const path = getPathToKey(rows, targetKey, config);\n if (!path) return existingExpanded;\n\n const newExpanded = new Set(existingExpanded);\n // Add all keys except the last one (the target itself)\n for (let i = 0; i < path.length - 1; i++) {\n newExpanded.add(path[i]);\n }\n return newExpanded;\n}\n","/**\n * Tree Structure Auto-Detection\n *\n * Utilities for detecting hierarchical tree data structures.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// The tree plugin intentionally uses `any` for maximum flexibility with user-defined row types.\n\n/**\n * Detects if the data has a tree structure by checking for children arrays.\n */\nexport function detectTreeStructure(rows: any[], childrenField = 'children'): boolean {\n if (!Array.isArray(rows) || rows.length === 0) return false;\n\n // Check if any row has a non-empty children array\n for (const row of rows) {\n if (row && Array.isArray(row[childrenField]) && row[childrenField].length > 0) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Attempts to infer the children field name from common patterns.\n * Returns the first field that contains an array with items.\n */\nexport function inferChildrenField(rows: any[]): string | null {\n if (!Array.isArray(rows) || rows.length === 0) return null;\n\n const commonArrayFields = ['children', 'items', 'nodes', 'subRows', 'nested'];\n\n for (const row of rows) {\n if (!row || typeof row !== 'object') continue;\n\n for (const field of commonArrayFields) {\n if (Array.isArray(row[field]) && row[field].length > 0) {\n return field;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Calculates the maximum depth of the tree.\n * Useful for layout calculations and virtualization.\n */\nexport function getMaxDepth(rows: any[], childrenField = 'children', currentDepth = 0): number {\n if (!Array.isArray(rows) || rows.length === 0) return currentDepth;\n\n let maxDepth = currentDepth;\n\n for (const row of rows) {\n if (!row) continue;\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n const childDepth = getMaxDepth(children, childrenField, currentDepth + 1);\n if (childDepth > maxDepth) {\n maxDepth = childDepth;\n }\n }\n }\n\n return maxDepth;\n}\n\n/**\n * Counts total nodes in the tree (including all descendants).\n */\nexport function countNodes(rows: any[], childrenField = 'children'): number {\n if (!Array.isArray(rows)) return 0;\n\n let count = 0;\n for (const row of rows) {\n if (!row) continue;\n count++;\n const children = row[childrenField];\n if (Array.isArray(children)) {\n count += countNodes(children, childrenField);\n }\n }\n\n return count;\n}\n","/**\n * Tree Data Plugin (Class-based)\n *\n * Enables hierarchical tree data with expand/collapse and auto-detection.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// The tree plugin intentionally uses `any` for maximum flexibility with user-defined row types.\n\nimport { BaseGridPlugin, CellClickEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { collapseAll, expandAll, expandToKey, flattenTree, toggleExpand } from './tree-data';\nimport { detectTreeStructure, inferChildrenField } from './tree-detect';\nimport type { FlattenedTreeRow, TreeConfig, TreeExpandDetail } from './types';\n\n/**\n * Tree Data Plugin for tbw-grid\n *\n * Provides hierarchical tree data display with expand/collapse functionality.\n *\n * @example\n * ```ts\n * new TreePlugin({ defaultExpanded: true, indentWidth: 24 })\n * ```\n */\nexport class TreePlugin extends BaseGridPlugin<TreeConfig> {\n readonly name = 'tree';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<TreeConfig> {\n return {\n enabled: true,\n childrenField: 'children',\n autoDetect: true,\n defaultExpanded: false,\n indentWidth: 20,\n showExpandIcons: true,\n };\n }\n\n // ===== Internal State =====\n\n /** Set of expanded row keys */\n private expandedKeys = new Set<string>();\n\n /** Whether initial expansion (based on defaultExpanded config) has been applied */\n private initialExpansionDone = false;\n\n /** Flattened tree rows for rendering */\n private flattenedRows: FlattenedTreeRow[] = [];\n\n /** Map from key to flattened row for quick lookup */\n private rowKeyMap = new Map<string, FlattenedTreeRow>();\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.expandedKeys.clear();\n this.initialExpansionDone = false;\n this.flattenedRows = [];\n this.rowKeyMap.clear();\n }\n\n // ===== Auto-Detection =====\n\n /**\n * Detects if tree functionality should be enabled based on data structure.\n * Called by the grid during plugin initialization.\n */\n detect(rows: readonly unknown[]): boolean {\n if (!this.config.autoDetect) return false;\n const childrenField = this.config.childrenField ?? inferChildrenField(rows as any[]) ?? 'children';\n return detectTreeStructure(rows as any[], childrenField);\n }\n\n // ===== Data Processing =====\n\n override processRows(rows: readonly unknown[]): any[] {\n const childrenField = this.config.childrenField ?? 'children';\n\n // Check if data is actually a tree\n if (!detectTreeStructure(rows as any[], childrenField)) {\n this.flattenedRows = [];\n this.rowKeyMap.clear();\n return [...rows];\n }\n\n // Initialize expansion state if needed (only once per grid lifecycle)\n if (this.config.defaultExpanded && !this.initialExpansionDone) {\n this.expandedKeys = expandAll(rows as any[], this.config);\n this.initialExpansionDone = true;\n }\n\n // Flatten tree\n this.flattenedRows = flattenTree(rows as any[], this.config, this.expandedKeys);\n\n // Build key map\n this.rowKeyMap.clear();\n for (const flatRow of this.flattenedRows) {\n this.rowKeyMap.set(flatRow.key, flatRow);\n }\n\n // Return flattened data for rendering with tree metadata\n return this.flattenedRows.map((fr) => ({\n ...fr.data,\n __treeKey: fr.key,\n __treeDepth: fr.depth,\n __treeHasChildren: fr.hasChildren,\n __treeExpanded: fr.isExpanded,\n }));\n }\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (this.flattenedRows.length === 0) return [...columns];\n\n const indentWidth = this.config.indentWidth ?? 20;\n const showExpandIcons = this.config.showExpandIcons ?? true;\n\n // Wrap first column's renderer to add tree indentation\n const cols = [...columns] as ColumnConfig[];\n if (cols.length > 0) {\n const firstCol = { ...cols[0] };\n const originalRenderer = firstCol.viewRenderer;\n\n firstCol.viewRenderer = (renderCtx) => {\n const { value, row, column: colConfig } = renderCtx;\n const depth = row.__treeDepth ?? 0;\n const hasChildren = row.__treeHasChildren ?? false;\n const isExpanded = row.__treeExpanded ?? false;\n\n const container = document.createElement('span');\n container.style.display = 'flex';\n container.style.alignItems = 'center';\n container.style.paddingLeft = `${depth * indentWidth}px`;\n\n // Expand/collapse icon\n if (hasChildren && showExpandIcons) {\n const icon = document.createElement('span');\n icon.className = 'tree-toggle';\n icon.textContent = isExpanded ? '▼' : '▶';\n icon.style.cursor = 'pointer';\n icon.style.marginRight = '4px';\n icon.style.fontSize = '10px';\n icon.setAttribute('data-tree-key', row.__treeKey);\n container.appendChild(icon);\n } else if (showExpandIcons) {\n // Spacer for alignment\n const spacer = document.createElement('span');\n spacer.style.width = '14px';\n spacer.style.display = 'inline-block';\n container.appendChild(spacer);\n }\n\n // Cell content\n const content = document.createElement('span');\n if (originalRenderer) {\n const rendered = originalRenderer(renderCtx);\n if (rendered instanceof Node) {\n content.appendChild(rendered);\n } else {\n content.textContent = String(rendered ?? value ?? '');\n }\n } else {\n content.textContent = String(value ?? '');\n }\n container.appendChild(content);\n\n return container;\n };\n\n cols[0] = firstCol;\n }\n\n return cols;\n }\n\n // ===== Event Handlers =====\n\n override onCellClick(event: CellClickEvent): boolean {\n const target = event.originalEvent?.target as HTMLElement;\n if (!target?.classList.contains('tree-toggle')) return false;\n\n const key = target.getAttribute('data-tree-key');\n if (!key) return false;\n\n const flatRow = this.rowKeyMap.get(key);\n if (!flatRow) return false;\n\n this.expandedKeys = toggleExpand(this.expandedKeys, key);\n\n this.emit<TreeExpandDetail>('tree-expand', {\n key,\n row: flatRow.data,\n expanded: this.expandedKeys.has(key),\n depth: flatRow.depth,\n });\n\n this.requestRender();\n return true;\n }\n\n // ===== Public API =====\n\n /**\n * Expand a specific node by key.\n */\n expand(key: string): void {\n this.expandedKeys.add(key);\n this.requestRender();\n }\n\n /**\n * Collapse a specific node by key.\n */\n collapse(key: string): void {\n this.expandedKeys.delete(key);\n this.requestRender();\n }\n\n /**\n * Toggle the expansion state of a node.\n */\n toggle(key: string): void {\n this.expandedKeys = toggleExpand(this.expandedKeys, key);\n this.requestRender();\n }\n\n /**\n * Expand all nodes in the tree.\n */\n expandAll(): void {\n this.expandedKeys = expandAll(this.rows as any[], this.config);\n this.requestRender();\n }\n\n /**\n * Collapse all nodes in the tree.\n */\n collapseAll(): void {\n this.expandedKeys = collapseAll();\n this.requestRender();\n }\n\n /**\n * Check if a node is currently expanded.\n */\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n /**\n * Get all currently expanded keys.\n */\n getExpandedKeys(): string[] {\n return [...this.expandedKeys];\n }\n\n /**\n * Get the flattened tree rows with metadata.\n */\n getFlattenedRows(): FlattenedTreeRow[] {\n return [...this.flattenedRows];\n }\n\n /**\n * Get a row's original data by its key.\n */\n getRowByKey(key: string): any | undefined {\n return this.rowKeyMap.get(key)?.data;\n }\n\n /**\n * Expand all ancestors of a node to make it visible.\n */\n expandToKey(key: string): void {\n this.expandedKeys = expandToKey(this.rows as any[], key, this.config, this.expandedKeys);\n this.requestRender();\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n .tree-toggle {\n cursor: pointer;\n user-select: none;\n transition: transform 0.2s;\n }\n .tree-toggle:hover {\n color: var(--tbw-tree-accent, var(--tbw-color-accent));\n }\n `;\n}\n"],"names":["generateRowKey","row","index","parentKey","flattenTree","rows","config","expandedKeys","depth","childrenField","result","i","key","children","hasChildren","isExpanded","childRows","toggleExpand","newExpanded","expandAll","keys","childKeys","k","collapseAll","getPathToKey","targetKey","childPath","expandToKey","existingExpanded","path","detectTreeStructure","inferChildrenField","commonArrayFields","field","getMaxDepth","currentDepth","maxDepth","childDepth","countNodes","count","TreePlugin","BaseGridPlugin","flatRow","fr","columns","indentWidth","showExpandIcons","cols","firstCol","originalRenderer","renderCtx","value","colConfig","container","icon","spacer","content","rendered","event","target"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ (function(i,d){typeof exports=="object"&&typeof module<"u"?d(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],d):(i=typeof globalThis<"u"?globalThis:i||self,d(i.TbwGridPlugin_undoRedo={},i.TbwGrid))})(this,(function(i,d){"use strict";function s(o,t,n){const c=[...o.undoStack,t];for(;c.length>n;)c.shift();return{undoStack:c,redoStack:[]}}function r(o){if(o.undoStack.length===0)return{newState:o,action:null};const t=[...o.undoStack],n=t.pop();return n?{newState:{undoStack:t,redoStack:[...o.redoStack,n]},action:n}:{newState:o,action:null}}function u(o){if(o.redoStack.length===0)return{newState:o,action:null};const t=[...o.redoStack],n=t.pop();return n?{newState:{undoStack:[...o.undoStack,n],redoStack:t},action:n}:{newState:o,action:null}}function k(o){return o.undoStack.length>0}function h(o){return o.redoStack.length>0}function l(){return{undoStack:[],redoStack:[]}}function f(o,t,n,c){return{type:"cell-edit",rowIndex:o,field:t,oldValue:n,newValue:c,timestamp:Date.now()}}class w extends d.BaseGridPlugin{name="undoRedo";version="1.0.0";get defaultConfig(){return{enabled:!0,maxHistorySize:100}}undoStack=[];redoStack=[];detach(){this.undoStack=[],this.redoStack=[]}onKeyDown(t){if(!this.config.enabled)return!1;const n=(t.ctrlKey||t.metaKey)&&t.key==="z"&&!t.shiftKey,c=(t.ctrlKey||t.metaKey)&&(t.key==="y"||t.key==="z"&&t.shiftKey);if(n){const e=r({undoStack:this.undoStack,redoStack:this.redoStack});if(e.action){const a=this.rows;a[e.action.rowIndex]&&(a[e.action.rowIndex][e.action.field]=e.action.oldValue),this.undoStack=e.newState.undoStack,this.redoStack=e.newState.redoStack,this.emit("undo",{action:e.action,type:"undo"}),this.requestRender()}return!0}if(c){const e=u({undoStack:this.undoStack,redoStack:this.redoStack});if(e.action){const a=this.rows;a[e.action.rowIndex]&&(a[e.action.rowIndex][e.action.field]=e.action.newValue),this.undoStack=e.newState.undoStack,this.redoStack=e.newState.redoStack,this.emit("redo",{action:e.action,type:"redo"}),this.requestRender()}return!0}return!1}recordEdit(t,n,c,e){const a=f(t,n,c,e),S=s({undoStack:this.undoStack,redoStack:this.redoStack},a,this.config.maxHistorySize??100);this.undoStack=S.undoStack,this.redoStack=S.redoStack}undo(){const t=r({undoStack:this.undoStack,redoStack:this.redoStack});if(t.action){const n=this.rows;n[t.action.rowIndex]&&(n[t.action.rowIndex][t.action.field]=t.action.oldValue),this.undoStack=t.newState.undoStack,this.redoStack=t.newState.redoStack,this.requestRender()}return t.action}redo(){const t=u({undoStack:this.undoStack,redoStack:this.redoStack});if(t.action){const n=this.rows;n[t.action.rowIndex]&&(n[t.action.rowIndex][t.action.field]=t.action.newValue),this.undoStack=t.newState.undoStack,this.redoStack=t.newState.redoStack,this.requestRender()}return t.action}canUndo(){return k({undoStack:this.undoStack,redoStack:this.redoStack})}canRedo(){return h({undoStack:this.undoStack,redoStack:this.redoStack})}clearHistory(){const t=l();this.undoStack=t.undoStack,this.redoStack=t.redoStack}getUndoStack(){return[...this.undoStack]}getRedoStack(){return[...this.redoStack]}}i.UndoRedoPlugin=w,Object.defineProperty(i,Symbol.toStringTag,{value:"Module"})}));
2
+ //# sourceMappingURL=undo-redo.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"undo-redo.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/undo-redo/history.ts","../../../../../libs/grid/src/lib/plugins/undo-redo/UndoRedoPlugin.ts"],"sourcesContent":["/**\n * Undo/Redo History Management\n *\n * Pure functions for managing the undo/redo stacks.\n * These functions are stateless and return new state objects.\n */\n\nimport type { EditAction, UndoRedoState } from './types';\n\n/**\n * Push a new action onto the undo stack.\n * Clears the redo stack since new actions invalidate redo history.\n *\n * @param state - Current undo/redo state\n * @param action - The action to add\n * @param maxSize - Maximum history size\n * @returns New state with the action added\n */\nexport function pushAction(state: UndoRedoState, action: EditAction, maxSize: number): UndoRedoState {\n const undoStack = [...state.undoStack, action];\n\n // Trim oldest actions if over max size\n while (undoStack.length > maxSize) {\n undoStack.shift();\n }\n\n return {\n undoStack,\n redoStack: [], // Clear redo on new action\n };\n}\n\n/**\n * Undo the most recent action.\n * Moves the action from undo stack to redo stack.\n *\n * @param state - Current undo/redo state\n * @returns New state and the action that was undone (or null if nothing to undo)\n */\nexport function undo(state: UndoRedoState): {\n newState: UndoRedoState;\n action: EditAction | null;\n} {\n if (state.undoStack.length === 0) {\n return { newState: state, action: null };\n }\n\n const undoStack = [...state.undoStack];\n const action = undoStack.pop();\n\n // This should never happen due to the length check above,\n // but TypeScript needs the explicit check\n if (!action) {\n return { newState: state, action: null };\n }\n\n return {\n newState: {\n undoStack,\n redoStack: [...state.redoStack, action],\n },\n action,\n };\n}\n\n/**\n * Redo the most recently undone action.\n * Moves the action from redo stack back to undo stack.\n *\n * @param state - Current undo/redo state\n * @returns New state and the action that was redone (or null if nothing to redo)\n */\nexport function redo(state: UndoRedoState): {\n newState: UndoRedoState;\n action: EditAction | null;\n} {\n if (state.redoStack.length === 0) {\n return { newState: state, action: null };\n }\n\n const redoStack = [...state.redoStack];\n const action = redoStack.pop();\n\n // This should never happen due to the length check above,\n // but TypeScript needs the explicit check\n if (!action) {\n return { newState: state, action: null };\n }\n\n return {\n newState: {\n undoStack: [...state.undoStack, action],\n redoStack,\n },\n action,\n };\n}\n\n/**\n * Check if there are any actions that can be undone.\n *\n * @param state - Current undo/redo state\n * @returns True if undo is available\n */\nexport function canUndo(state: UndoRedoState): boolean {\n return state.undoStack.length > 0;\n}\n\n/**\n * Check if there are any actions that can be redone.\n *\n * @param state - Current undo/redo state\n * @returns True if redo is available\n */\nexport function canRedo(state: UndoRedoState): boolean {\n return state.redoStack.length > 0;\n}\n\n/**\n * Clear all history, returning an empty state.\n *\n * @returns Fresh empty state\n */\nexport function clearHistory(): UndoRedoState {\n return { undoStack: [], redoStack: [] };\n}\n\n/**\n * Create a new edit action with the current timestamp.\n *\n * @param rowIndex - The row index where the edit occurred\n * @param field - The field (column key) that was edited\n * @param oldValue - The value before the edit\n * @param newValue - The value after the edit\n * @returns A new EditAction object\n */\nexport function createEditAction(rowIndex: number, field: string, oldValue: unknown, newValue: unknown): EditAction {\n return {\n type: 'cell-edit',\n rowIndex,\n field,\n oldValue,\n newValue,\n timestamp: Date.now(),\n };\n}\n","/**\n * Undo/Redo Plugin (Class-based)\n *\n * Provides undo/redo functionality for cell edits in tbw-grid.\n * Supports Ctrl+Z/Cmd+Z for undo and Ctrl+Y/Cmd+Y (or Ctrl+Shift+Z) for redo.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport { canRedo, canUndo, clearHistory, createEditAction, pushAction, redo, undo } from './history';\nimport type { EditAction, UndoRedoConfig, UndoRedoDetail } from './types';\n\n/**\n * Class-based Undo/Redo plugin for tbw-grid.\n *\n * Tracks cell edits and provides undo/redo functionality via keyboard shortcuts\n * or programmatic API.\n */\nexport class UndoRedoPlugin extends BaseGridPlugin<UndoRedoConfig> {\n readonly name = 'undoRedo';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<UndoRedoConfig> {\n return {\n enabled: true,\n maxHistorySize: 100,\n };\n }\n\n // State as class properties\n private undoStack: EditAction[] = [];\n private redoStack: EditAction[] = [];\n\n /**\n * Clean up state when plugin is detached.\n */\n override detach(): void {\n this.undoStack = [];\n this.redoStack = [];\n }\n\n /**\n * Handle keyboard shortcuts for undo/redo.\n * - Ctrl+Z / Cmd+Z: Undo\n * - Ctrl+Y / Cmd+Y / Ctrl+Shift+Z / Cmd+Shift+Z: Redo\n */\n override onKeyDown(event: KeyboardEvent): boolean {\n if (!this.config.enabled) return false;\n\n const isUndo = (event.ctrlKey || event.metaKey) && event.key === 'z' && !event.shiftKey;\n const isRedo = (event.ctrlKey || event.metaKey) && (event.key === 'y' || (event.key === 'z' && event.shiftKey));\n\n if (isUndo) {\n const result = undo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n // Apply undo - restore old value\n const rows = this.rows as Record<string, unknown>[];\n if (rows[result.action.rowIndex]) {\n rows[result.action.rowIndex][result.action.field] = result.action.oldValue;\n }\n\n // Update state from result\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n\n this.emit<UndoRedoDetail>('undo', {\n action: result.action,\n type: 'undo',\n });\n\n this.requestRender();\n }\n return true;\n }\n\n if (isRedo) {\n const result = redo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n // Apply redo - restore new value\n const rows = this.rows as Record<string, unknown>[];\n if (rows[result.action.rowIndex]) {\n rows[result.action.rowIndex][result.action.field] = result.action.newValue;\n }\n\n // Update state from result\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n\n this.emit<UndoRedoDetail>('redo', {\n action: result.action,\n type: 'redo',\n });\n\n this.requestRender();\n }\n return true;\n }\n\n return false;\n }\n\n // ===== Public API Methods =====\n\n /**\n * Record a cell edit for undo/redo tracking.\n * Call this when a cell value changes.\n *\n * @param rowIndex - The row index where the edit occurred\n * @param field - The field (column key) that was edited\n * @param oldValue - The value before the edit\n * @param newValue - The value after the edit\n */\n recordEdit(rowIndex: number, field: string, oldValue: unknown, newValue: unknown): void {\n const action = createEditAction(rowIndex, field, oldValue, newValue);\n const newState = pushAction(\n { undoStack: this.undoStack, redoStack: this.redoStack },\n action,\n this.config.maxHistorySize ?? 100\n );\n this.undoStack = newState.undoStack;\n this.redoStack = newState.redoStack;\n }\n\n /**\n * Programmatically undo the last action.\n *\n * @returns The undone action, or null if nothing to undo\n */\n undo(): EditAction | null {\n const result = undo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n const rows = this.rows as Record<string, unknown>[];\n if (rows[result.action.rowIndex]) {\n rows[result.action.rowIndex][result.action.field] = result.action.oldValue;\n }\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n this.requestRender();\n }\n return result.action;\n }\n\n /**\n * Programmatically redo the last undone action.\n *\n * @returns The redone action, or null if nothing to redo\n */\n redo(): EditAction | null {\n const result = redo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n const rows = this.rows as Record<string, unknown>[];\n if (rows[result.action.rowIndex]) {\n rows[result.action.rowIndex][result.action.field] = result.action.newValue;\n }\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n this.requestRender();\n }\n return result.action;\n }\n\n /**\n * Check if there are any actions that can be undone.\n */\n canUndo(): boolean {\n return canUndo({ undoStack: this.undoStack, redoStack: this.redoStack });\n }\n\n /**\n * Check if there are any actions that can be redone.\n */\n canRedo(): boolean {\n return canRedo({ undoStack: this.undoStack, redoStack: this.redoStack });\n }\n\n /**\n * Clear all undo/redo history.\n */\n clearHistory(): void {\n const newState = clearHistory();\n this.undoStack = newState.undoStack;\n this.redoStack = newState.redoStack;\n }\n\n /**\n * Get a copy of the current undo stack.\n */\n getUndoStack(): EditAction[] {\n return [...this.undoStack];\n }\n\n /**\n * Get a copy of the current redo stack.\n */\n getRedoStack(): EditAction[] {\n return [...this.redoStack];\n }\n}\n"],"names":["pushAction","state","action","maxSize","undoStack","undo","redo","redoStack","canUndo","canRedo","clearHistory","createEditAction","rowIndex","field","oldValue","newValue","UndoRedoPlugin","BaseGridPlugin","event","isUndo","isRedo","result","rows","newState"],"mappings":"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"}