@toolbox-web/grid 1.6.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -15
- package/all.js +267 -158
- package/all.js.map +1 -1
- package/index.js +866 -722
- package/index.js.map +1 -1
- package/lib/core/grid.d.ts +68 -1
- package/lib/core/grid.d.ts.map +1 -1
- package/lib/core/internal/header.d.ts.map +1 -1
- package/lib/core/plugin/base-plugin.d.ts +182 -1
- package/lib/core/plugin/base-plugin.d.ts.map +1 -1
- package/lib/core/plugin/index.d.ts +1 -1
- package/lib/core/plugin/index.d.ts.map +1 -1
- package/lib/core/plugin/plugin-manager.d.ts +56 -1
- package/lib/core/plugin/plugin-manager.d.ts.map +1 -1
- package/lib/core/plugin/types.d.ts +36 -0
- package/lib/core/plugin/types.d.ts.map +1 -1
- package/lib/core/types.d.ts +1349 -31
- package/lib/core/types.d.ts.map +1 -1
- package/lib/plugins/clipboard/ClipboardPlugin.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.js +140 -87
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js +64 -7
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/ContextMenuPlugin.d.ts.map +1 -1
- package/lib/plugins/context-menu/index.js +123 -65
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/editing/EditingPlugin.d.ts +6 -1
- package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
- package/lib/plugins/editing/index.js +95 -13
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/export/index.js +91 -34
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/FilteringPlugin.d.ts +6 -1
- package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
- package/lib/plugins/filtering/index.js +192 -123
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/index.js +57 -0
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts +7 -2
- package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts.map +1 -1
- package/lib/plugins/grouping-rows/index.js +142 -60
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js +69 -12
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js +70 -13
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts +3 -3
- package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts.map +1 -1
- package/lib/plugins/pinned-columns/index.js +106 -36
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/index.js +57 -0
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js +57 -0
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/print/PrintPlugin.d.ts.map +1 -1
- package/lib/plugins/print/index.js +58 -1
- package/lib/plugins/print/index.js.map +1 -1
- package/lib/plugins/reorder/ReorderPlugin.d.ts.map +1 -1
- package/lib/plugins/reorder/column-drag.d.ts +2 -2
- package/lib/plugins/reorder/index.js +68 -17
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/responsive/ResponsivePlugin.d.ts +6 -1
- package/lib/plugins/responsive/ResponsivePlugin.d.ts.map +1 -1
- package/lib/plugins/responsive/index.js +125 -54
- package/lib/plugins/responsive/index.js.map +1 -1
- package/lib/plugins/row-reorder/index.js +169 -112
- package/lib/plugins/row-reorder/index.js.map +1 -1
- package/lib/plugins/selection/SelectionPlugin.d.ts +14 -2
- package/lib/plugins/selection/SelectionPlugin.d.ts.map +1 -1
- package/lib/plugins/selection/index.js +84 -7
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js +79 -22
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/TreePlugin.d.ts +7 -1
- package/lib/plugins/tree/TreePlugin.d.ts.map +1 -1
- package/lib/plugins/tree/index.js +140 -58
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/UndoRedoPlugin.d.ts +6 -1
- package/lib/plugins/undo-redo/UndoRedoPlugin.d.ts.map +1 -1
- package/lib/plugins/undo-redo/index.js +79 -10
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js +57 -0
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +1 -1
- package/public.d.ts +80 -2
- package/public.d.ts.map +1 -1
- package/umd/grid.all.umd.js +25 -25
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +15 -15
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/clipboard.umd.js +5 -5
- package/umd/plugins/clipboard.umd.js.map +1 -1
- package/umd/plugins/context-menu.umd.js +1 -1
- package/umd/plugins/context-menu.umd.js.map +1 -1
- package/umd/plugins/editing.umd.js +1 -1
- package/umd/plugins/editing.umd.js.map +1 -1
- package/umd/plugins/filtering.umd.js +1 -1
- package/umd/plugins/filtering.umd.js.map +1 -1
- package/umd/plugins/grouping-rows.umd.js +2 -2
- package/umd/plugins/grouping-rows.umd.js.map +1 -1
- package/umd/plugins/pinned-columns.umd.js +1 -1
- package/umd/plugins/pinned-columns.umd.js.map +1 -1
- package/umd/plugins/print.umd.js +1 -1
- package/umd/plugins/print.umd.js.map +1 -1
- package/umd/plugins/reorder.umd.js +1 -1
- package/umd/plugins/reorder.umd.js.map +1 -1
- package/umd/plugins/responsive.umd.js +1 -1
- package/umd/plugins/responsive.umd.js.map +1 -1
- package/umd/plugins/selection.umd.js +2 -2
- package/umd/plugins/selection.umd.js.map +1 -1
- package/umd/plugins/tree.umd.js +1 -1
- package/umd/plugins/tree.umd.js.map +1 -1
- package/umd/plugins/undo-redo.umd.js +1 -1
- package/umd/plugins/undo-redo.umd.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grouping-rows.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/grouping-rows/grouping-rows.ts","../../../../../libs/grid/src/lib/plugins/grouping-rows/GroupingRowsPlugin.ts"],"sourcesContent":["/**\n * Row Grouping Core Logic\n *\n * Pure functions for building grouped row models and aggregations.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { DefaultExpandedValue, GroupRowModelItem, RenderRow, RowGroupingConfig } from './types';\n\n// Re-export aggregator functions from core for backward compatibility\nexport { getAggregator, listAggregators, registerAggregator, runAggregator } from '../../core/internal/aggregators';\n\ninterface GroupNode {\n key: string; // composite key\n value: any;\n depth: number;\n rows: any[];\n children: Map<string, GroupNode>;\n parent?: GroupNode;\n}\n\ninterface BuildGroupingArgs {\n rows: any[];\n config: RowGroupingConfig;\n expanded: Set<string>;\n /** Initial expanded state to apply (processed by the plugin) */\n initialExpanded?: Set<string>;\n}\n\n/**\n * Build a flattened grouping projection (collapsed by default).\n * Returns empty array when groupOn not configured or all rows ungrouped.\n *\n * @param args - The grouping arguments\n * @returns Flattened array of render rows (groups + data rows)\n */\nexport function buildGroupedRowModel({ rows, config, expanded, initialExpanded }: BuildGroupingArgs): RenderRow[] {\n const groupOn = config.groupOn;\n if (typeof groupOn !== 'function') {\n return [];\n }\n\n const root: GroupNode = { key: '__root__', value: null, depth: -1, rows: [], children: new Map() };\n\n // Build tree structure\n rows.forEach((r) => {\n let path: any = groupOn(r);\n if (path == null || path === false) path = ['__ungrouped__'];\n else if (!Array.isArray(path)) path = [path];\n\n let parent = root;\n path.forEach((rawVal: any, depthIdx: number) => {\n const seg = rawVal == null ? '∅' : String(rawVal);\n const composite = parent.key === '__root__' ? seg : parent.key + '||' + seg;\n let node = parent.children.get(seg);\n if (!node) {\n node = { key: composite, value: rawVal, depth: depthIdx, rows: [], children: new Map(), parent };\n parent.children.set(seg, node);\n }\n parent = node;\n });\n parent.rows.push(r);\n });\n\n // All ungrouped? treat as no grouping\n if (root.children.size === 1 && root.children.has('__ungrouped__')) {\n const only = root.children.get('__ungrouped__')!;\n if (only.rows.length === rows.length) return [];\n }\n\n // Merge expanded sets - use initialExpanded on first render, then expanded takes over\n const effectiveExpanded = new Set([...expanded, ...(initialExpanded ?? [])]);\n\n // Flatten tree to array\n const flat: RenderRow[] = [];\n const visit = (node: GroupNode) => {\n if (node === root) {\n node.children.forEach((c) => visit(c));\n return;\n }\n\n const isExpanded = effectiveExpanded.has(node.key);\n flat.push({\n kind: 'group',\n key: node.key,\n value: node.value,\n depth: node.depth,\n rows: node.rows,\n expanded: isExpanded,\n });\n\n if (isExpanded) {\n if (node.children.size) {\n node.children.forEach((c) => visit(c));\n } else {\n node.rows.forEach((r) => flat.push({ kind: 'data', row: r, rowIndex: rows.indexOf(r) }));\n }\n }\n };\n visit(root);\n\n return flat;\n}\n\n/**\n * Toggle expansion state for a group key.\n *\n * @param expandedKeys - Current set of expanded keys\n * @param key - The group key to toggle\n * @returns New set with toggled state\n */\nexport function toggleGroupExpansion(expandedKeys: Set<string>, key: string): Set<string> {\n const newSet = new Set(expandedKeys);\n if (newSet.has(key)) {\n newSet.delete(key);\n } else {\n newSet.add(key);\n }\n return newSet;\n}\n\n/**\n * Expand all groups.\n *\n * @param rows - The flattened render rows\n * @returns Set of all group keys\n */\nexport function expandAllGroups(rows: RenderRow[]): Set<string> {\n const keys = new Set<string>();\n for (const row of rows) {\n if (row.kind === 'group') {\n keys.add(row.key);\n }\n }\n return keys;\n}\n\n/**\n * Collapse all groups.\n *\n * @returns Empty set\n */\nexport function collapseAllGroups(): Set<string> {\n return new Set();\n}\n\n/**\n * Resolve a defaultExpanded value to a set of keys to expand.\n * This needs to be called AFTER building the group model to get all keys.\n *\n * @param value - The defaultExpanded config value\n * @param allGroupKeys - All group keys from the model\n * @returns Set of keys to expand initially\n */\nexport function resolveDefaultExpanded(value: DefaultExpandedValue, allGroupKeys: string[]): Set<string> {\n if (value === true) {\n // Expand all groups\n return new Set(allGroupKeys);\n }\n if (value === false || value == null) {\n // Collapse all groups\n return new Set();\n }\n if (typeof value === 'number') {\n // Expand group at this index\n const key = allGroupKeys[value];\n return key ? new Set([key]) : new Set();\n }\n if (typeof value === 'string') {\n // Expand group with this key\n return new Set([value]);\n }\n if (Array.isArray(value)) {\n // Expand groups with these keys\n return new Set(value);\n }\n return new Set();\n}\n\n/**\n * Get all group keys from a flattened model.\n *\n * @param rows - The flattened render rows\n * @returns Array of group keys\n */\nexport function getGroupKeys(rows: RenderRow[]): string[] {\n return rows.filter((r): r is GroupRowModelItem => r.kind === 'group').map((r) => r.key);\n}\n\n/**\n * Count total rows in a group (including nested groups).\n *\n * @param groupRow - The group row\n * @returns Total row count\n */\nexport function getGroupRowCount(groupRow: RenderRow): number {\n if (groupRow.kind !== 'group') return 0;\n return groupRow.rows.length;\n}\n","/**\n * Row Grouping Plugin (Class-based)\n *\n * Enables hierarchical row grouping with expand/collapse and aggregations.\n */\n\nimport { BaseGridPlugin, CellClickEvent, type PluginManifest } from '../../core/plugin/base-plugin';\nimport { isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { RowElementInternal } from '../../core/types';\nimport {\n buildGroupedRowModel,\n collapseAllGroups,\n expandAllGroups,\n getGroupKeys,\n getGroupRowCount,\n resolveDefaultExpanded,\n runAggregator,\n toggleGroupExpansion,\n} from './grouping-rows';\nimport styles from './grouping-rows.css?inline';\nimport type {\n ExpandCollapseAnimation,\n GroupingRowsConfig,\n GroupRowModelItem,\n GroupToggleDetail,\n RenderRow,\n} from './types';\n\n/**\n * Group state information returned by getGroupState()\n */\nexport interface GroupState {\n /** Whether grouping is currently active */\n isActive: boolean;\n /** Number of expanded groups */\n expandedCount: number;\n /** Total number of groups */\n totalGroups: number;\n /** Array of expanded group keys */\n expandedKeys: string[];\n}\n\n/**\n * Row Grouping Plugin for tbw-grid\n *\n * Organizes rows into collapsible hierarchical groups. Perfect for organizing data\n * by category, department, status, or any other dimension—or even multiple dimensions\n * for nested grouping. Includes aggregation support for summarizing group data.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `groupOn` | `(row) => string[]` | - | Callback returning group path array |\n * | `defaultExpanded` | `boolean \\\\| number \\\\| string \\\\| string[]` | `false` | Initial expanded state |\n * | `showRowCount` | `boolean` | `true` | Show row count in group header |\n * | `indentWidth` | `number` | `20` | Indentation per level (pixels) |\n * | `fullWidth` | `boolean` | `true` | Group row spans full width |\n * | `animation` | `false \\\\| 'slide' \\\\| 'fade'` | `'slide'` | Expand/collapse animation |\n * | `accordion` | `boolean` | `false` | Only one group open at a time |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expandGroup` | `(path: string[]) => void` | Expand a specific group |\n * | `collapseGroup` | `(path: string[]) => void` | Collapse a specific group |\n * | `expandAll` | `() => void` | Expand all groups |\n * | `collapseAll` | `() => void` | Collapse all groups |\n * | `isGroupExpanded` | `(path: string[]) => boolean` | Check if group is expanded |\n * | `getGroupState` | `() => GroupState` | Get current grouping state |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-group-indent-width` | `1.25em` | Indentation per group level |\n * | `--tbw-grouping-rows-bg` | `var(--tbw-color-panel-bg)` | Group row background |\n * | `--tbw-grouping-rows-count-color` | `var(--tbw-color-fg-muted)` | Count badge color |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation |\n *\n * @example Single-Level Grouping by Department\n * ```ts\n * import '@toolbox-web/grid';\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Employee' },\n * { field: 'department', header: 'Department' },\n * { field: 'salary', header: 'Salary', type: 'currency' },\n * ],\n * plugins: [\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.department],\n * showRowCount: true,\n * defaultExpanded: false,\n * }),\n * ],\n * };\n * ```\n *\n * @example Multi-Level Grouping\n * ```ts\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.region, row.department, row.team],\n * indentWidth: 24,\n * animation: 'slide',\n * })\n * ```\n *\n * @see {@link GroupingRowsConfig} for all configuration options\n * @see {@link GroupState} for the group state structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingRowsPlugin extends BaseGridPlugin<GroupingRowsConfig> {\n /**\n * Plugin manifest - declares configuration validation rules.\n * @internal\n */\n static override readonly manifest: PluginManifest<GroupingRowsConfig> = {\n configRules: [\n {\n id: 'groupingRows/accordion-defaultExpanded',\n severity: 'warn',\n message:\n `\"accordion: true\" and \"defaultExpanded\" (non-false) are used together.\\n` +\n ` → In accordion mode, only one group can be open at a time.\\n` +\n ` → Using defaultExpanded with multiple groups will collapse to one on first toggle.\\n` +\n ` → Consider using \"defaultExpanded: false\" or a single group key/index with accordion mode.`,\n check: (config) =>\n config.accordion === true &&\n config.defaultExpanded !== false &&\n config.defaultExpanded !== undefined &&\n // Allow single group expansion with accordion\n !(typeof config.defaultExpanded === 'number') &&\n !(typeof config.defaultExpanded === 'string') &&\n // Warn if true or array with multiple items\n (config.defaultExpanded === true ||\n (Array.isArray(config.defaultExpanded) && config.defaultExpanded.length > 1)),\n },\n ],\n };\n\n /** @internal */\n readonly name = 'groupingRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingRowsConfig> {\n return {\n defaultExpanded: false,\n showRowCount: true,\n indentWidth: 20,\n aggregators: {},\n animation: 'slide',\n accordion: false,\n };\n }\n\n // #region Internal State\n private expandedKeys: Set<string> = new Set();\n private flattenedRows: RenderRow[] = [];\n private isActive = false;\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n /** Track if initial defaultExpanded has been applied */\n private hasAppliedDefaultExpanded = false;\n // #endregion\n\n // #region Animation\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.expandedKeys.clear();\n this.flattenedRows = [];\n this.isActive = false;\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.hasAppliedDefaultExpanded = false;\n }\n // #endregion\n\n // #region Hooks\n\n /**\n * Auto-detect grouping configuration from grid config.\n * Called by plugin system to determine if plugin should activate.\n */\n static detect(rows: readonly any[], config: any): boolean {\n return typeof config?.groupOn === 'function' || typeof config?.enableRowGrouping === 'boolean';\n }\n\n /** @internal */\n override processRows(rows: readonly any[]): any[] {\n const config = this.config;\n\n // Check if grouping is configured\n if (typeof config.groupOn !== 'function') {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // First build: get structure to know all group keys\n // (needed for index-based defaultExpanded)\n const initialBuild = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: new Set(), // Empty to get all root groups\n });\n\n // If no grouping produced, return original rows\n if (initialBuild.length === 0) {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // Resolve defaultExpanded on first render only\n let initialExpanded: Set<string> | undefined;\n if (!this.hasAppliedDefaultExpanded && this.expandedKeys.size === 0 && config.defaultExpanded !== false) {\n const allKeys = getGroupKeys(initialBuild);\n initialExpanded = resolveDefaultExpanded(config.defaultExpanded ?? false, allKeys);\n\n // Mark as applied and populate expandedKeys for subsequent toggles\n if (initialExpanded.size > 0) {\n this.expandedKeys = new Set(initialExpanded);\n this.hasAppliedDefaultExpanded = true;\n }\n }\n\n // Build with proper expanded state\n const grouped = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: this.expandedKeys,\n initialExpanded,\n });\n\n this.isActive = true;\n this.flattenedRows = grouped;\n\n // Track which data rows are newly visible (for animation)\n this.keysToAnimate.clear();\n const currentVisibleKeys = new Set<string>();\n grouped.forEach((item, idx) => {\n if (item.kind === 'data') {\n const key = `data-${idx}`;\n currentVisibleKeys.add(key);\n if (!this.previousVisibleKeys.has(key)) {\n this.keysToAnimate.add(key);\n }\n }\n });\n this.previousVisibleKeys = currentVisibleKeys;\n\n // Return flattened rows for rendering\n // The grid will need to handle group rows specially\n return grouped.map((item) => {\n if (item.kind === 'group') {\n return {\n __isGroupRow: true,\n __groupKey: item.key,\n __groupValue: item.value,\n __groupDepth: item.depth,\n __groupRows: item.rows,\n __groupExpanded: item.expanded,\n __groupRowCount: getGroupRowCount(item),\n };\n }\n return item.row;\n });\n }\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean | void {\n const row = event.row as Record<string, unknown> | undefined;\n\n // Check if this is a group row toggle\n if (row?.__isGroupRow) {\n const target = event.originalEvent.target as HTMLElement;\n if (target?.closest('.group-toggle')) {\n this.toggle(row.__groupKey as string);\n return true; // Prevent default\n }\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion on group rows\n if (event.key !== ' ') return;\n\n const focusRow = this.grid._focusRow;\n const row = this.rows[focusRow] as Record<string, unknown> | undefined;\n\n // Only handle SPACE on group rows\n if (!row?.__isGroupRow) return;\n\n event.preventDefault();\n this.toggle(row.__groupKey as string);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /**\n * Render a row. Returns true if we handled the row (group row), false otherwise.\n * @internal\n */\n override renderRow(row: any, rowEl: HTMLElement, _rowIndex: number): boolean {\n // Only handle group rows\n if (!row?.__isGroupRow) {\n return false;\n }\n\n const config = this.config;\n\n // If a custom renderer is provided, use it\n if (config.groupRowRenderer) {\n const toggleExpand = () => {\n this.toggle(row.__groupKey);\n };\n\n const result = config.groupRowRenderer({\n key: row.__groupKey,\n value: row.__groupValue,\n depth: row.__groupDepth,\n rows: row.__groupRows,\n expanded: row.__groupExpanded,\n toggleExpand,\n });\n\n if (result) {\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n if (typeof result === 'string') {\n rowEl.innerHTML = result;\n } else {\n rowEl.innerHTML = '';\n rowEl.appendChild(result);\n }\n return true;\n }\n }\n\n // Helper to toggle expansion\n const handleToggle = () => {\n this.toggle(row.__groupKey);\n };\n\n // Default group row rendering - keep data-grid-row class for focus/keyboard navigation\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n rowEl.setAttribute('role', 'row');\n rowEl.setAttribute('aria-expanded', String(row.__groupExpanded));\n // Use CSS variable for depth-based indentation\n rowEl.style.setProperty('--tbw-group-depth', String(row.__groupDepth || 0));\n if (config.indentWidth !== undefined) {\n rowEl.style.setProperty('--tbw-group-indent-width', `${config.indentWidth}px`);\n }\n // Clear any inline height from previous use (e.g., responsive card mode sets height: auto)\n // This ensures group rows use CSS-defined height, not stale inline styles from recycled elements\n rowEl.style.height = '';\n rowEl.innerHTML = '';\n\n const isFullWidth = config.fullWidth !== false; // default true\n\n if (isFullWidth) {\n this.renderFullWidthGroupRow(row, rowEl, handleToggle);\n } else {\n this.renderPerColumnGroupRow(row, rowEl, handleToggle);\n }\n\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-group-fade-in' : 'tbw-group-slide-in';\n for (const rowEl of body.querySelectorAll('.data-grid-row:not(.group-row)')) {\n const cell = rowEl.querySelector('.cell[data-row]');\n const idx = cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n const item = this.flattenedRows[idx];\n const key = item?.kind === 'data' ? `data-${idx}` : undefined;\n\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n // #endregion\n\n // #region Private Rendering Helpers\n\n /**\n * Create a toggle button for expanding/collapsing a group.\n */\n private createToggleButton(expanded: boolean, handleToggle: () => void): HTMLButtonElement {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `group-toggle${expanded ? ' expanded' : ''}`;\n btn.setAttribute('aria-label', expanded ? 'Collapse group' : 'Expand group');\n this.setIcon(btn, this.resolveIcon(expanded ? 'collapse' : 'expand'));\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n handleToggle();\n });\n return btn;\n }\n\n /**\n * Get the formatted label text for a group.\n */\n private getGroupLabelText(value: unknown, depth: number, key: string): string {\n const config = this.config;\n return config.formatLabel ? config.formatLabel(value, depth, key) : String(value);\n }\n\n private renderFullWidthGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const groupRows = row.__groupRows ?? [];\n\n // Full-width mode: single spanning cell with toggle + label + count + aggregates\n const cell = document.createElement('div');\n cell.className = 'cell group-full';\n cell.style.gridColumn = '1 / -1';\n cell.setAttribute('role', 'gridcell');\n cell.setAttribute('data-col', '0'); // Required for focus/click delegation\n\n // Toggle button\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n // Group label\n const label = document.createElement('span');\n label.className = 'group-label';\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\n cell.appendChild(label);\n\n // Row count\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = 'group-count';\n count.textContent = `(${row.__groupRowCount ?? row.__groupRows?.length ?? 0})`;\n cell.appendChild(count);\n }\n\n // Render aggregates if configured\n const aggregatorEntries = Object.entries(aggregators);\n if (aggregatorEntries.length > 0) {\n const aggregatesContainer = document.createElement('span');\n aggregatesContainer.className = 'group-aggregates';\n\n for (const [field, aggRef] of aggregatorEntries) {\n const col = this.columns.find((c) => c.field === field);\n const result = runAggregator(aggRef, groupRows, field, col);\n if (result != null) {\n const aggSpan = document.createElement('span');\n aggSpan.className = 'group-aggregate';\n aggSpan.setAttribute('data-field', field);\n // Use column header as label if available\n const colHeader = col?.header ?? field;\n aggSpan.textContent = `${colHeader}: ${result}`;\n aggregatesContainer.appendChild(aggSpan);\n }\n }\n\n if (aggregatesContainer.children.length > 0) {\n cell.appendChild(aggregatesContainer);\n }\n }\n\n rowEl.appendChild(cell);\n }\n\n private renderPerColumnGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const columns = this.columns;\n const groupRows = row.__groupRows ?? [];\n\n // Get grid template from the grid element\n const bodyEl = this.gridElement?.querySelector('.body') as HTMLElement | null;\n const gridTemplate = bodyEl?.style.gridTemplateColumns || '';\n if (gridTemplate) {\n rowEl.style.display = 'grid';\n rowEl.style.gridTemplateColumns = gridTemplate;\n }\n\n // Track whether we've rendered the toggle button yet (should be in first non-expander column)\n let toggleRendered = false;\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell group-cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('role', 'gridcell');\n\n // Skip expander columns (they're handled by other plugins like MasterDetail/Tree)\n // but still render an empty cell to maintain grid structure\n if (isExpanderColumn(col)) {\n cell.setAttribute('data-field', col.field);\n rowEl.appendChild(cell);\n return;\n }\n\n // First non-expander column gets the toggle button + label\n if (!toggleRendered) {\n toggleRendered = true;\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n const label = document.createElement('span');\n const firstColAgg = aggregators[col.field];\n if (firstColAgg) {\n const aggResult = runAggregator(firstColAgg, groupRows, col.field, col);\n label.textContent = aggResult != null ? String(aggResult) : String(row.__groupValue);\n } else {\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\n }\n cell.appendChild(label);\n\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = 'group-count';\n count.textContent = ` (${groupRows.length})`;\n cell.appendChild(count);\n }\n } else {\n // Other columns: run aggregator if defined\n const aggRef = aggregators[col.field];\n if (aggRef) {\n const result = runAggregator(aggRef, groupRows, col.field, col);\n cell.textContent = result != null ? String(result) : '';\n } else {\n cell.textContent = '';\n }\n }\n\n rowEl.appendChild(cell);\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Expand all groups.\n */\n expandAll(): void {\n this.expandedKeys = expandAllGroups(this.flattenedRows);\n this.requestRender();\n }\n\n /**\n * Collapse all groups.\n */\n collapseAll(): void {\n this.expandedKeys = collapseAllGroups();\n this.requestRender();\n }\n\n /**\n * Toggle expansion of a specific group.\n * In accordion mode, expanding a group will collapse all sibling groups.\n * @param key - The group key to toggle\n */\n toggle(key: string): void {\n const isExpanding = !this.expandedKeys.has(key);\n const config = this.config;\n\n // Find the group to get its depth for accordion mode\n const group = this.flattenedRows.find((r) => r.kind === 'group' && r.key === key) as GroupRowModelItem | undefined;\n\n // In accordion mode, collapse sibling groups when expanding\n if (config.accordion && isExpanding && group) {\n const newKeys = new Set<string>();\n // Keep only ancestors (keys that are prefixes of the current key) and the current key\n for (const existingKey of this.expandedKeys) {\n // Check if existingKey is an ancestor of the toggled key\n // Ancestors have composite keys that are prefixes of child keys (separated by '||')\n if (key.startsWith(existingKey + '||') || existingKey.startsWith(key + '||')) {\n // This is an ancestor or descendant - keep it only if ancestor\n if (key.startsWith(existingKey + '||')) {\n newKeys.add(existingKey);\n }\n } else {\n // Check depth - only keep groups at different depths\n const existingGroup = this.flattenedRows.find((r) => r.kind === 'group' && r.key === existingKey) as\n | GroupRowModelItem\n | undefined;\n if (existingGroup && existingGroup.depth !== group.depth) {\n newKeys.add(existingKey);\n }\n }\n }\n newKeys.add(key);\n this.expandedKeys = newKeys;\n } else {\n this.expandedKeys = toggleGroupExpansion(this.expandedKeys, key);\n }\n\n this.emit<GroupToggleDetail>('group-toggle', {\n key,\n expanded: this.expandedKeys.has(key),\n value: group?.value,\n depth: group?.depth ?? 0,\n });\n\n this.requestRender();\n }\n\n /**\n * Check if a specific group is expanded.\n * @param key - The group key to check\n * @returns Whether the group is expanded\n */\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n /**\n * Expand a specific group.\n * @param key - The group key to expand\n */\n expand(key: string): void {\n if (!this.expandedKeys.has(key)) {\n this.expandedKeys = new Set([...this.expandedKeys, key]);\n this.requestRender();\n }\n }\n\n /**\n * Collapse a specific group.\n * @param key - The group key to collapse\n */\n collapse(key: string): void {\n if (this.expandedKeys.has(key)) {\n const newKeys = new Set(this.expandedKeys);\n newKeys.delete(key);\n this.expandedKeys = newKeys;\n this.requestRender();\n }\n }\n\n /**\n * Get the current group state.\n * @returns Group state information\n */\n getGroupState(): GroupState {\n const groupRows = this.flattenedRows.filter((r) => r.kind === 'group');\n return {\n isActive: this.isActive,\n expandedCount: this.expandedKeys.size,\n totalGroups: groupRows.length,\n expandedKeys: [...this.expandedKeys],\n };\n }\n\n /**\n * Get the total count of visible rows (including group headers).\n * @returns Number of visible rows\n */\n getRowCount(): number {\n return this.flattenedRows.length;\n }\n\n /**\n * Refresh the grouped row model.\n * Call this after modifying groupOn or other config options.\n */\n refreshGroups(): void {\n this.requestRender();\n }\n\n /**\n * Get current expanded group keys.\n * @returns Array of expanded group keys\n */\n getExpandedGroups(): string[] {\n return [...this.expandedKeys];\n }\n\n /**\n * Get the flattened row model.\n * @returns Array of render rows (groups + data rows)\n */\n getFlattenedRows(): RenderRow[] {\n return this.flattenedRows;\n }\n\n /**\n * Check if grouping is currently active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Set the groupOn function dynamically.\n * @param fn - The groupOn function or undefined to disable\n */\n setGroupOn(fn: ((row: any) => any[] | any | null | false) | undefined): void {\n (this.config as GroupingRowsConfig).groupOn = fn;\n this.requestRender();\n }\n // #endregion\n}\n"],"names":["buildGroupedRowModel","rows","config","expanded","initialExpanded","groupOn","root","r","path","parent","rawVal","depthIdx","seg","composite","node","effectiveExpanded","flat","visit","c","isExpanded","toggleGroupExpansion","expandedKeys","key","newSet","expandAllGroups","keys","row","collapseAllGroups","resolveDefaultExpanded","value","allGroupKeys","getGroupKeys","getGroupRowCount","groupRow","GroupingRowsPlugin","BaseGridPlugin","styles","initialBuild","allKeys","grouped","currentVisibleKeys","item","idx","event","focusRow","rowEl","_rowIndex","toggleExpand","result","handleToggle","style","body","animClass","cell","btn","e","depth","aggregators","groupRows","label","count","aggregatorEntries","aggregatesContainer","field","aggRef","col","runAggregator","aggSpan","colHeader","columns","gridTemplate","toggleRendered","colIdx","isExpanderColumn","firstColAgg","aggResult","isExpanding","group","newKeys","existingKey","existingGroup","fn"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"grouping-rows.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/grouping-rows/grouping-rows.ts","../../../../../libs/grid/src/lib/plugins/grouping-rows/GroupingRowsPlugin.ts"],"sourcesContent":["/**\n * Row Grouping Core Logic\n *\n * Pure functions for building grouped row models and aggregations.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { DefaultExpandedValue, GroupRowModelItem, RenderRow, RowGroupingConfig } from './types';\n\n// Re-export aggregator functions from core for backward compatibility\nexport { getAggregator, listAggregators, registerAggregator, runAggregator } from '../../core/internal/aggregators';\n\ninterface GroupNode {\n key: string; // composite key\n value: any;\n depth: number;\n rows: any[];\n children: Map<string, GroupNode>;\n parent?: GroupNode;\n}\n\ninterface BuildGroupingArgs {\n rows: any[];\n config: RowGroupingConfig;\n expanded: Set<string>;\n /** Initial expanded state to apply (processed by the plugin) */\n initialExpanded?: Set<string>;\n}\n\n/**\n * Build a flattened grouping projection (collapsed by default).\n * Returns empty array when groupOn not configured or all rows ungrouped.\n *\n * @param args - The grouping arguments\n * @returns Flattened array of render rows (groups + data rows)\n */\nexport function buildGroupedRowModel({ rows, config, expanded, initialExpanded }: BuildGroupingArgs): RenderRow[] {\n const groupOn = config.groupOn;\n if (typeof groupOn !== 'function') {\n return [];\n }\n\n const root: GroupNode = { key: '__root__', value: null, depth: -1, rows: [], children: new Map() };\n\n // Build tree structure\n rows.forEach((r) => {\n let path: any = groupOn(r);\n if (path == null || path === false) path = ['__ungrouped__'];\n else if (!Array.isArray(path)) path = [path];\n\n let parent = root;\n path.forEach((rawVal: any, depthIdx: number) => {\n const seg = rawVal == null ? '∅' : String(rawVal);\n const composite = parent.key === '__root__' ? seg : parent.key + '||' + seg;\n let node = parent.children.get(seg);\n if (!node) {\n node = { key: composite, value: rawVal, depth: depthIdx, rows: [], children: new Map(), parent };\n parent.children.set(seg, node);\n }\n parent = node;\n });\n parent.rows.push(r);\n });\n\n // All ungrouped? treat as no grouping\n if (root.children.size === 1 && root.children.has('__ungrouped__')) {\n const only = root.children.get('__ungrouped__')!;\n if (only.rows.length === rows.length) return [];\n }\n\n // Merge expanded sets - use initialExpanded on first render, then expanded takes over\n const effectiveExpanded = new Set([...expanded, ...(initialExpanded ?? [])]);\n\n // Flatten tree to array\n const flat: RenderRow[] = [];\n const visit = (node: GroupNode) => {\n if (node === root) {\n node.children.forEach((c) => visit(c));\n return;\n }\n\n const isExpanded = effectiveExpanded.has(node.key);\n flat.push({\n kind: 'group',\n key: node.key,\n value: node.value,\n depth: node.depth,\n rows: node.rows,\n expanded: isExpanded,\n });\n\n if (isExpanded) {\n if (node.children.size) {\n node.children.forEach((c) => visit(c));\n } else {\n node.rows.forEach((r) => flat.push({ kind: 'data', row: r, rowIndex: rows.indexOf(r) }));\n }\n }\n };\n visit(root);\n\n return flat;\n}\n\n/**\n * Toggle expansion state for a group key.\n *\n * @param expandedKeys - Current set of expanded keys\n * @param key - The group key to toggle\n * @returns New set with toggled state\n */\nexport function toggleGroupExpansion(expandedKeys: Set<string>, key: string): Set<string> {\n const newSet = new Set(expandedKeys);\n if (newSet.has(key)) {\n newSet.delete(key);\n } else {\n newSet.add(key);\n }\n return newSet;\n}\n\n/**\n * Expand all groups.\n *\n * @param rows - The flattened render rows\n * @returns Set of all group keys\n */\nexport function expandAllGroups(rows: RenderRow[]): Set<string> {\n const keys = new Set<string>();\n for (const row of rows) {\n if (row.kind === 'group') {\n keys.add(row.key);\n }\n }\n return keys;\n}\n\n/**\n * Collapse all groups.\n *\n * @returns Empty set\n */\nexport function collapseAllGroups(): Set<string> {\n return new Set();\n}\n\n/**\n * Resolve a defaultExpanded value to a set of keys to expand.\n * This needs to be called AFTER building the group model to get all keys.\n *\n * @param value - The defaultExpanded config value\n * @param allGroupKeys - All group keys from the model\n * @returns Set of keys to expand initially\n */\nexport function resolveDefaultExpanded(value: DefaultExpandedValue, allGroupKeys: string[]): Set<string> {\n if (value === true) {\n // Expand all groups\n return new Set(allGroupKeys);\n }\n if (value === false || value == null) {\n // Collapse all groups\n return new Set();\n }\n if (typeof value === 'number') {\n // Expand group at this index\n const key = allGroupKeys[value];\n return key ? new Set([key]) : new Set();\n }\n if (typeof value === 'string') {\n // Expand group with this key\n return new Set([value]);\n }\n if (Array.isArray(value)) {\n // Expand groups with these keys\n return new Set(value);\n }\n return new Set();\n}\n\n/**\n * Get all group keys from a flattened model.\n *\n * @param rows - The flattened render rows\n * @returns Array of group keys\n */\nexport function getGroupKeys(rows: RenderRow[]): string[] {\n return rows.filter((r): r is GroupRowModelItem => r.kind === 'group').map((r) => r.key);\n}\n\n/**\n * Count total rows in a group (including nested groups).\n *\n * @param groupRow - The group row\n * @returns Total row count\n */\nexport function getGroupRowCount(groupRow: RenderRow): number {\n if (groupRow.kind !== 'group') return 0;\n return groupRow.rows.length;\n}\n","/**\n * Row Grouping Plugin (Class-based)\n *\n * Enables hierarchical row grouping with expand/collapse and aggregations.\n */\n\nimport { BaseGridPlugin, CellClickEvent, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport { isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { RowElementInternal } from '../../core/types';\nimport {\n buildGroupedRowModel,\n collapseAllGroups,\n expandAllGroups,\n getGroupKeys,\n getGroupRowCount,\n resolveDefaultExpanded,\n runAggregator,\n toggleGroupExpansion,\n} from './grouping-rows';\nimport styles from './grouping-rows.css?inline';\nimport type {\n ExpandCollapseAnimation,\n GroupingRowsConfig,\n GroupRowModelItem,\n GroupToggleDetail,\n RenderRow,\n} from './types';\n\n/**\n * Group state information returned by getGroupState()\n */\nexport interface GroupState {\n /** Whether grouping is currently active */\n isActive: boolean;\n /** Number of expanded groups */\n expandedCount: number;\n /** Total number of groups */\n totalGroups: number;\n /** Array of expanded group keys */\n expandedKeys: string[];\n}\n\n/**\n * Row Grouping Plugin for tbw-grid\n *\n * Organizes rows into collapsible hierarchical groups. Perfect for organizing data\n * by category, department, status, or any other dimension—or even multiple dimensions\n * for nested grouping. Includes aggregation support for summarizing group data.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `groupOn` | `(row) => string[]` | - | Callback returning group path array |\n * | `defaultExpanded` | `boolean \\\\| number \\\\| string \\\\| string[]` | `false` | Initial expanded state |\n * | `showRowCount` | `boolean` | `true` | Show row count in group header |\n * | `indentWidth` | `number` | `20` | Indentation per level (pixels) |\n * | `fullWidth` | `boolean` | `true` | Group row spans full width |\n * | `animation` | `false \\\\| 'slide' \\\\| 'fade'` | `'slide'` | Expand/collapse animation |\n * | `accordion` | `boolean` | `false` | Only one group open at a time |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expandGroup` | `(path: string[]) => void` | Expand a specific group |\n * | `collapseGroup` | `(path: string[]) => void` | Collapse a specific group |\n * | `expandAll` | `() => void` | Expand all groups |\n * | `collapseAll` | `() => void` | Collapse all groups |\n * | `isGroupExpanded` | `(path: string[]) => boolean` | Check if group is expanded |\n * | `getGroupState` | `() => GroupState` | Get current grouping state |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-group-indent-width` | `1.25em` | Indentation per group level |\n * | `--tbw-grouping-rows-bg` | `var(--tbw-color-panel-bg)` | Group row background |\n * | `--tbw-grouping-rows-count-color` | `var(--tbw-color-fg-muted)` | Count badge color |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation |\n *\n * @example Single-Level Grouping by Department\n * ```ts\n * import '@toolbox-web/grid';\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Employee' },\n * { field: 'department', header: 'Department' },\n * { field: 'salary', header: 'Salary', type: 'currency' },\n * ],\n * plugins: [\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.department],\n * showRowCount: true,\n * defaultExpanded: false,\n * }),\n * ],\n * };\n * ```\n *\n * @example Multi-Level Grouping\n * ```ts\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.region, row.department, row.team],\n * indentWidth: 24,\n * animation: 'slide',\n * })\n * ```\n *\n * @see {@link GroupingRowsConfig} for all configuration options\n * @see {@link GroupState} for the group state structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingRowsPlugin extends BaseGridPlugin<GroupingRowsConfig> {\n /**\n * Plugin manifest - declares configuration validation rules and events.\n * @internal\n */\n static override readonly manifest: PluginManifest<GroupingRowsConfig> = {\n events: [\n {\n type: 'grouping-state-change',\n description: 'Emitted when groups are expanded/collapsed. Subscribers can react to row visibility changes.',\n },\n ],\n queries: [\n {\n type: 'canMoveRow',\n description: 'Returns false for group header rows (cannot be reordered)',\n },\n ],\n configRules: [\n {\n id: 'groupingRows/accordion-defaultExpanded',\n severity: 'warn',\n message:\n `\"accordion: true\" and \"defaultExpanded\" (non-false) are used together.\\n` +\n ` → In accordion mode, only one group can be open at a time.\\n` +\n ` → Using defaultExpanded with multiple groups will collapse to one on first toggle.\\n` +\n ` → Consider using \"defaultExpanded: false\" or a single group key/index with accordion mode.`,\n check: (config) =>\n config.accordion === true &&\n config.defaultExpanded !== false &&\n config.defaultExpanded !== undefined &&\n // Allow single group expansion with accordion\n !(typeof config.defaultExpanded === 'number') &&\n !(typeof config.defaultExpanded === 'string') &&\n // Warn if true or array with multiple items\n (config.defaultExpanded === true ||\n (Array.isArray(config.defaultExpanded) && config.defaultExpanded.length > 1)),\n },\n ],\n };\n\n /** @internal */\n readonly name = 'groupingRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingRowsConfig> {\n return {\n defaultExpanded: false,\n showRowCount: true,\n indentWidth: 20,\n aggregators: {},\n animation: 'slide',\n accordion: false,\n };\n }\n\n // #region Internal State\n private expandedKeys: Set<string> = new Set();\n private flattenedRows: RenderRow[] = [];\n private isActive = false;\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n /** Track if initial defaultExpanded has been applied */\n private hasAppliedDefaultExpanded = false;\n // #endregion\n\n // #region Animation\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.expandedKeys.clear();\n this.flattenedRows = [];\n this.isActive = false;\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.hasAppliedDefaultExpanded = false;\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'canMoveRow') {\n // Group header rows cannot be reordered\n const row = query.context as any;\n if (row && row.__isGroupRow === true) {\n return false;\n }\n }\n return undefined;\n }\n // #endregion\n\n // #region Hooks\n\n /**\n * Auto-detect grouping configuration from grid config.\n * Called by plugin system to determine if plugin should activate.\n */\n static detect(rows: readonly any[], config: any): boolean {\n return typeof config?.groupOn === 'function' || typeof config?.enableRowGrouping === 'boolean';\n }\n\n /** @internal */\n override processRows(rows: readonly any[]): any[] {\n const config = this.config;\n\n // Check if grouping is configured\n if (typeof config.groupOn !== 'function') {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // First build: get structure to know all group keys\n // (needed for index-based defaultExpanded)\n const initialBuild = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: new Set(), // Empty to get all root groups\n });\n\n // If no grouping produced, return original rows\n if (initialBuild.length === 0) {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // Resolve defaultExpanded on first render only\n let initialExpanded: Set<string> | undefined;\n if (!this.hasAppliedDefaultExpanded && this.expandedKeys.size === 0 && config.defaultExpanded !== false) {\n const allKeys = getGroupKeys(initialBuild);\n initialExpanded = resolveDefaultExpanded(config.defaultExpanded ?? false, allKeys);\n\n // Mark as applied and populate expandedKeys for subsequent toggles\n if (initialExpanded.size > 0) {\n this.expandedKeys = new Set(initialExpanded);\n this.hasAppliedDefaultExpanded = true;\n }\n }\n\n // Build with proper expanded state\n const grouped = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: this.expandedKeys,\n initialExpanded,\n });\n\n this.isActive = true;\n this.flattenedRows = grouped;\n\n // Track which data rows are newly visible (for animation)\n this.keysToAnimate.clear();\n const currentVisibleKeys = new Set<string>();\n grouped.forEach((item, idx) => {\n if (item.kind === 'data') {\n const key = `data-${idx}`;\n currentVisibleKeys.add(key);\n if (!this.previousVisibleKeys.has(key)) {\n this.keysToAnimate.add(key);\n }\n }\n });\n this.previousVisibleKeys = currentVisibleKeys;\n\n // Return flattened rows for rendering\n // The grid will need to handle group rows specially\n return grouped.map((item) => {\n if (item.kind === 'group') {\n return {\n __isGroupRow: true,\n __groupKey: item.key,\n __groupValue: item.value,\n __groupDepth: item.depth,\n __groupRows: item.rows,\n __groupExpanded: item.expanded,\n __groupRowCount: getGroupRowCount(item),\n };\n }\n return item.row;\n });\n }\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean | void {\n const row = event.row as Record<string, unknown> | undefined;\n\n // Check if this is a group row toggle\n if (row?.__isGroupRow) {\n const target = event.originalEvent.target as HTMLElement;\n if (target?.closest('.group-toggle')) {\n this.toggle(row.__groupKey as string);\n return true; // Prevent default\n }\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion on group rows\n if (event.key !== ' ') return;\n\n const focusRow = this.grid._focusRow;\n const row = this.rows[focusRow] as Record<string, unknown> | undefined;\n\n // Only handle SPACE on group rows\n if (!row?.__isGroupRow) return;\n\n event.preventDefault();\n this.toggle(row.__groupKey as string);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /**\n * Render a row. Returns true if we handled the row (group row), false otherwise.\n * @internal\n */\n override renderRow(row: any, rowEl: HTMLElement, _rowIndex: number): boolean {\n // Only handle group rows\n if (!row?.__isGroupRow) {\n return false;\n }\n\n const config = this.config;\n\n // If a custom renderer is provided, use it\n if (config.groupRowRenderer) {\n const toggleExpand = () => {\n this.toggle(row.__groupKey);\n };\n\n const result = config.groupRowRenderer({\n key: row.__groupKey,\n value: row.__groupValue,\n depth: row.__groupDepth,\n rows: row.__groupRows,\n expanded: row.__groupExpanded,\n toggleExpand,\n });\n\n if (result) {\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n if (typeof result === 'string') {\n rowEl.innerHTML = result;\n } else {\n rowEl.innerHTML = '';\n rowEl.appendChild(result);\n }\n return true;\n }\n }\n\n // Helper to toggle expansion\n const handleToggle = () => {\n this.toggle(row.__groupKey);\n };\n\n // Default group row rendering - keep data-grid-row class for focus/keyboard navigation\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n rowEl.setAttribute('role', 'row');\n rowEl.setAttribute('aria-expanded', String(row.__groupExpanded));\n // Use CSS variable for depth-based indentation\n rowEl.style.setProperty('--tbw-group-depth', String(row.__groupDepth || 0));\n if (config.indentWidth !== undefined) {\n rowEl.style.setProperty('--tbw-group-indent-width', `${config.indentWidth}px`);\n }\n // Clear any inline height from previous use (e.g., responsive card mode sets height: auto)\n // This ensures group rows use CSS-defined height, not stale inline styles from recycled elements\n rowEl.style.height = '';\n rowEl.innerHTML = '';\n\n const isFullWidth = config.fullWidth !== false; // default true\n\n if (isFullWidth) {\n this.renderFullWidthGroupRow(row, rowEl, handleToggle);\n } else {\n this.renderPerColumnGroupRow(row, rowEl, handleToggle);\n }\n\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-group-fade-in' : 'tbw-group-slide-in';\n for (const rowEl of body.querySelectorAll('.data-grid-row:not(.group-row)')) {\n const cell = rowEl.querySelector('.cell[data-row]');\n const idx = cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n const item = this.flattenedRows[idx];\n const key = item?.kind === 'data' ? `data-${idx}` : undefined;\n\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n // #endregion\n\n // #region Private Rendering Helpers\n\n /**\n * Create a toggle button for expanding/collapsing a group.\n */\n private createToggleButton(expanded: boolean, handleToggle: () => void): HTMLButtonElement {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `group-toggle${expanded ? ' expanded' : ''}`;\n btn.setAttribute('aria-label', expanded ? 'Collapse group' : 'Expand group');\n this.setIcon(btn, this.resolveIcon(expanded ? 'collapse' : 'expand'));\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n handleToggle();\n });\n return btn;\n }\n\n /**\n * Get the formatted label text for a group.\n */\n private getGroupLabelText(value: unknown, depth: number, key: string): string {\n const config = this.config;\n return config.formatLabel ? config.formatLabel(value, depth, key) : String(value);\n }\n\n private renderFullWidthGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const groupRows = row.__groupRows ?? [];\n\n // Full-width mode: single spanning cell with toggle + label + count + aggregates\n const cell = document.createElement('div');\n cell.className = 'cell group-full';\n cell.style.gridColumn = '1 / -1';\n cell.setAttribute('role', 'gridcell');\n cell.setAttribute('data-col', '0'); // Required for focus/click delegation\n\n // Toggle button\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n // Group label\n const label = document.createElement('span');\n label.className = 'group-label';\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\n cell.appendChild(label);\n\n // Row count\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = 'group-count';\n count.textContent = `(${row.__groupRowCount ?? row.__groupRows?.length ?? 0})`;\n cell.appendChild(count);\n }\n\n // Render aggregates if configured\n const aggregatorEntries = Object.entries(aggregators);\n if (aggregatorEntries.length > 0) {\n const aggregatesContainer = document.createElement('span');\n aggregatesContainer.className = 'group-aggregates';\n\n for (const [field, aggRef] of aggregatorEntries) {\n const col = this.columns.find((c) => c.field === field);\n const result = runAggregator(aggRef, groupRows, field, col);\n if (result != null) {\n const aggSpan = document.createElement('span');\n aggSpan.className = 'group-aggregate';\n aggSpan.setAttribute('data-field', field);\n // Use column header as label if available\n const colHeader = col?.header ?? field;\n aggSpan.textContent = `${colHeader}: ${result}`;\n aggregatesContainer.appendChild(aggSpan);\n }\n }\n\n if (aggregatesContainer.children.length > 0) {\n cell.appendChild(aggregatesContainer);\n }\n }\n\n rowEl.appendChild(cell);\n }\n\n private renderPerColumnGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const columns = this.columns;\n const groupRows = row.__groupRows ?? [];\n\n // Get grid template from the grid element\n const bodyEl = this.gridElement?.querySelector('.body') as HTMLElement | null;\n const gridTemplate = bodyEl?.style.gridTemplateColumns || '';\n if (gridTemplate) {\n rowEl.style.display = 'grid';\n rowEl.style.gridTemplateColumns = gridTemplate;\n }\n\n // Track whether we've rendered the toggle button yet (should be in first non-expander column)\n let toggleRendered = false;\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell group-cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('role', 'gridcell');\n\n // Skip expander columns (they're handled by other plugins like MasterDetail/Tree)\n // but still render an empty cell to maintain grid structure\n if (isExpanderColumn(col)) {\n cell.setAttribute('data-field', col.field);\n rowEl.appendChild(cell);\n return;\n }\n\n // First non-expander column gets the toggle button + label\n if (!toggleRendered) {\n toggleRendered = true;\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n const label = document.createElement('span');\n const firstColAgg = aggregators[col.field];\n if (firstColAgg) {\n const aggResult = runAggregator(firstColAgg, groupRows, col.field, col);\n label.textContent = aggResult != null ? String(aggResult) : String(row.__groupValue);\n } else {\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\n }\n cell.appendChild(label);\n\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = 'group-count';\n count.textContent = ` (${groupRows.length})`;\n cell.appendChild(count);\n }\n } else {\n // Other columns: run aggregator if defined\n const aggRef = aggregators[col.field];\n if (aggRef) {\n const result = runAggregator(aggRef, groupRows, col.field, col);\n cell.textContent = result != null ? String(result) : '';\n } else {\n cell.textContent = '';\n }\n }\n\n rowEl.appendChild(cell);\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Expand all groups.\n */\n expandAll(): void {\n this.expandedKeys = expandAllGroups(this.flattenedRows);\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Collapse all groups.\n */\n collapseAll(): void {\n this.expandedKeys = collapseAllGroups();\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Toggle expansion of a specific group.\n * In accordion mode, expanding a group will collapse all sibling groups.\n * @param key - The group key to toggle\n */\n toggle(key: string): void {\n const isExpanding = !this.expandedKeys.has(key);\n const config = this.config;\n\n // Find the group to get its depth for accordion mode\n const group = this.flattenedRows.find((r) => r.kind === 'group' && r.key === key) as GroupRowModelItem | undefined;\n\n // In accordion mode, collapse sibling groups when expanding\n if (config.accordion && isExpanding && group) {\n const newKeys = new Set<string>();\n // Keep only ancestors (keys that are prefixes of the current key) and the current key\n for (const existingKey of this.expandedKeys) {\n // Check if existingKey is an ancestor of the toggled key\n // Ancestors have composite keys that are prefixes of child keys (separated by '||')\n if (key.startsWith(existingKey + '||') || existingKey.startsWith(key + '||')) {\n // This is an ancestor or descendant - keep it only if ancestor\n if (key.startsWith(existingKey + '||')) {\n newKeys.add(existingKey);\n }\n } else {\n // Check depth - only keep groups at different depths\n const existingGroup = this.flattenedRows.find((r) => r.kind === 'group' && r.key === existingKey) as\n | GroupRowModelItem\n | undefined;\n if (existingGroup && existingGroup.depth !== group.depth) {\n newKeys.add(existingKey);\n }\n }\n }\n newKeys.add(key);\n this.expandedKeys = newKeys;\n } else {\n this.expandedKeys = toggleGroupExpansion(this.expandedKeys, key);\n }\n\n this.emit<GroupToggleDetail>('group-toggle', {\n key,\n expanded: this.expandedKeys.has(key),\n value: group?.value,\n depth: group?.depth ?? 0,\n });\n\n // Notify other plugins that grouping state changed (row visibility changed)\n this.emitPluginEvent('grouping-state-change', {\n expandedKeys: [...this.expandedKeys],\n });\n\n this.requestRender();\n }\n\n /**\n * Check if a specific group is expanded.\n * @param key - The group key to check\n * @returns Whether the group is expanded\n */\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n /**\n * Expand a specific group.\n * @param key - The group key to expand\n */\n expand(key: string): void {\n if (!this.expandedKeys.has(key)) {\n this.expandedKeys = new Set([...this.expandedKeys, key]);\n this.requestRender();\n }\n }\n\n /**\n * Collapse a specific group.\n * @param key - The group key to collapse\n */\n collapse(key: string): void {\n if (this.expandedKeys.has(key)) {\n const newKeys = new Set(this.expandedKeys);\n newKeys.delete(key);\n this.expandedKeys = newKeys;\n this.requestRender();\n }\n }\n\n /**\n * Get the current group state.\n * @returns Group state information\n */\n getGroupState(): GroupState {\n const groupRows = this.flattenedRows.filter((r) => r.kind === 'group');\n return {\n isActive: this.isActive,\n expandedCount: this.expandedKeys.size,\n totalGroups: groupRows.length,\n expandedKeys: [...this.expandedKeys],\n };\n }\n\n /**\n * Get the total count of visible rows (including group headers).\n * @returns Number of visible rows\n */\n getRowCount(): number {\n return this.flattenedRows.length;\n }\n\n /**\n * Refresh the grouped row model.\n * Call this after modifying groupOn or other config options.\n */\n refreshGroups(): void {\n this.requestRender();\n }\n\n /**\n * Get current expanded group keys.\n * @returns Array of expanded group keys\n */\n getExpandedGroups(): string[] {\n return [...this.expandedKeys];\n }\n\n /**\n * Get the flattened row model.\n * @returns Array of render rows (groups + data rows)\n */\n getFlattenedRows(): RenderRow[] {\n return this.flattenedRows;\n }\n\n /**\n * Check if grouping is currently active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Set the groupOn function dynamically.\n * @param fn - The groupOn function or undefined to disable\n */\n setGroupOn(fn: ((row: any) => any[] | any | null | false) | undefined): void {\n (this.config as GroupingRowsConfig).groupOn = fn;\n this.requestRender();\n }\n // #endregion\n}\n"],"names":["buildGroupedRowModel","rows","config","expanded","initialExpanded","groupOn","root","r","path","parent","rawVal","depthIdx","seg","composite","node","effectiveExpanded","flat","visit","c","isExpanded","toggleGroupExpansion","expandedKeys","key","newSet","expandAllGroups","keys","row","collapseAllGroups","resolveDefaultExpanded","value","allGroupKeys","getGroupKeys","getGroupRowCount","groupRow","GroupingRowsPlugin","BaseGridPlugin","styles","query","initialBuild","allKeys","grouped","currentVisibleKeys","item","idx","event","focusRow","rowEl","_rowIndex","toggleExpand","result","handleToggle","style","body","animClass","cell","btn","e","depth","aggregators","groupRows","label","count","aggregatorEntries","aggregatesContainer","field","aggRef","col","runAggregator","aggSpan","colHeader","columns","gridTemplate","toggleRendered","colIdx","isExpanderColumn","firstColAgg","aggResult","isExpanding","group","newKeys","existingKey","existingGroup","fn"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(f,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):(f=typeof globalThis<"u"?globalThis:f||self,d(f.TbwGridPlugin_pinnedColumns={},f.TbwGrid))})(this,(function(f,d){"use strict";function
|
|
1
|
+
(function(f,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):(f=typeof globalThis<"u"?globalThis:f||self,d(f.TbwGridPlugin_pinnedColumns={},f.TbwGrid))})(this,(function(f,d){"use strict";function p(n){return n.filter(t=>t.sticky==="left")}function k(n){return n.filter(t=>t.sticky==="right")}function u(n){return n.some(t=>t.sticky==="left"||t.sticky==="right")}function y(n,t){const s=Array.from(n.querySelectorAll(".header-row .cell"));if(!s.length)return;const i=new Map;t.forEach((r,o)=>{r.field&&i.set(r.field,o)});let c=0;for(const r of t)if(r.sticky==="left"){const o=i.get(r.field),e=s.find(l=>l.getAttribute("data-field")===r.field);e&&(e.classList.add("sticky-left"),e.style.position="sticky",e.style.left=c+"px",o!==void 0&&n.querySelectorAll(`.data-grid-row .cell[data-col="${o}"]`).forEach(l=>{l.classList.add("sticky-left"),l.style.position="sticky",l.style.left=c+"px"}),c+=e.offsetWidth)}let a=0;for(const r of[...t].reverse())if(r.sticky==="right"){const o=i.get(r.field),e=s.find(l=>l.getAttribute("data-field")===r.field);e&&(e.classList.add("sticky-right"),e.style.position="sticky",e.style.right=a+"px",o!==void 0&&n.querySelectorAll(`.data-grid-row .cell[data-col="${o}"]`).forEach(l=>{l.classList.add("sticky-right"),l.style.position="sticky",l.style.right=a+"px"}),a+=e.offsetWidth)}}function h(n){n.querySelectorAll(".sticky-left, .sticky-right").forEach(s=>{s.classList.remove("sticky-left","sticky-right"),s.style.position="",s.style.left="",s.style.right=""})}const g="canMoveColumn";class m extends d.BaseGridPlugin{static manifest={ownedProperties:[{property:"sticky",level:"column",description:'the "sticky" column property',isUsed:t=>t==="left"||t==="right"}],queries:[{type:g,description:"Prevents pinned (sticky) columns from being moved/reordered"},{type:"getStickyOffsets",description:"Returns the sticky offsets for left/right pinned columns"}]};name="pinnedColumns";get defaultConfig(){return{}}isApplied=!1;leftOffsets=new Map;rightOffsets=new Map;detach(){this.leftOffsets.clear(),this.rightOffsets.clear(),this.isApplied=!1}static detect(t,s){const i=s?.columns;return Array.isArray(i)?u(i):!1}processColumns(t){return this.isApplied=u([...t]),[...t]}afterRender(){if(!this.isApplied)return;const t=this.grid,s=[...this.columns];if(!u(s)){h(t),this.isApplied=!1;return}queueMicrotask(()=>{y(t,s)})}handleQuery(t){switch(t.type){case g:{const s=t.context,i=s.sticky;if(i==="left"||i==="right")return!1;const c=s.meta?.sticky;return c==="left"||c==="right"?!1:void 0}case"getStickyOffsets":return{left:Object.fromEntries(this.leftOffsets),right:Object.fromEntries(this.rightOffsets)};default:return}}refreshStickyOffsets(){const t=[...this.columns];y(this.grid,t)}getLeftPinnedColumns(){const t=[...this.columns];return p(t)}getRightPinnedColumns(){const t=[...this.columns];return k(t)}clearStickyPositions(){h(this.grid)}getHorizontalScrollOffsets(t,s){if(!this.isApplied)return;let i=0,c=0;if(t){const r=t.querySelectorAll(".sticky-left"),o=t.querySelectorAll(".sticky-right");r.forEach(e=>{i+=e.offsetWidth}),o.forEach(e=>{c+=e.offsetWidth})}else this.grid.querySelectorAll(".header-row .cell").forEach(e=>{e.classList.contains("sticky-left")?i+=e.offsetWidth:e.classList.contains("sticky-right")&&(c+=e.offsetWidth)});const a=s?.classList.contains("sticky-left")||s?.classList.contains("sticky-right");return{left:i,right:c,skipScroll:a}}}f.PinnedColumnsPlugin=m,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=pinned-columns.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinned-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/pinned-columns/pinned-columns.ts","../../../../../libs/grid/src/lib/plugins/pinned-columns/PinnedColumnsPlugin.ts"],"sourcesContent":["/**\n * Sticky Columns Core Logic\n *\n * Pure functions for applying sticky (pinned) column positioning.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { StickyPosition } from './types';\n\n/**\n * Get columns that should be sticky on the left.\n *\n * @param columns - Array of column configurations\n * @returns Array of columns with sticky='left'\n */\nexport function getLeftStickyColumns(columns: any[]): any[] {\n return columns.filter((col) => col.sticky === 'left');\n}\n\n/**\n * Get columns that should be sticky on the right.\n *\n * @param columns - Array of column configurations\n * @returns Array of columns with sticky='right'\n */\nexport function getRightStickyColumns(columns: any[]): any[] {\n return columns.filter((col) => col.sticky === 'right');\n}\n\n/**\n * Check if any columns have sticky positioning.\n *\n * @param columns - Array of column configurations\n * @returns True if any column has sticky position\n */\nexport function hasStickyColumns(columns: any[]): boolean {\n return columns.some((col) => col.sticky === 'left' || col.sticky === 'right');\n}\n\n/**\n * Get the sticky position of a column.\n *\n * @param column - Column configuration\n * @returns The sticky position or null if not sticky\n */\nexport function getColumnStickyPosition(column: any): StickyPosition | null {\n if (column.sticky === 'left') return 'left';\n if (column.sticky === 'right') return 'right';\n return null;\n}\n\n/**\n * Calculate left offsets for sticky-left columns.\n * Returns a map of field -> offset in pixels.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @returns Map of field to left offset\n */\nexport function calculateLeftStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n for (const col of columns) {\n if (col.sticky === 'left') {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Calculate right offsets for sticky-right columns.\n * Processes columns in reverse order.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @returns Map of field to right offset\n */\nexport function calculateRightStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n // Process in reverse for right-sticky columns\n const reversed = [...columns].reverse();\n for (const col of reversed) {\n if (col.sticky === 'right') {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Apply sticky offsets to header and body cells.\n * This modifies the DOM elements in place.\n *\n * @param host - The grid host element (render root for DOM queries)\n * @param columns - Array of column configurations\n */\nexport function applyStickyOffsets(host: HTMLElement, columns: any[]): void {\n // With light DOM, query the host element directly\n const headerCells = Array.from(host.querySelectorAll('.header-row .cell')) as HTMLElement[];\n if (!headerCells.length) return;\n\n // Build column index map for matching body cells (which use data-col, not data-field)\n const fieldToIndex = new Map<string, number>();\n columns.forEach((col, i) => {\n if (col.field) fieldToIndex.set(col.field, i);\n });\n\n // Apply left sticky\n let left = 0;\n for (const col of columns) {\n if (col.sticky === 'left') {\n const colIndex = fieldToIndex.get(col.field);\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-left');\n cell.style.position = 'sticky';\n cell.style.left = left + 'px';\n // Body cells use data-col (column index), not data-field\n if (colIndex !== undefined) {\n host.querySelectorAll(`.data-grid-row .cell[data-col=\"${colIndex}\"]`).forEach((el) => {\n el.classList.add('sticky-left');\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.left = left + 'px';\n });\n }\n left += cell.offsetWidth;\n }\n }\n }\n\n // Apply right sticky (process in reverse)\n let right = 0;\n for (const col of [...columns].reverse()) {\n if (col.sticky === 'right') {\n const colIndex = fieldToIndex.get(col.field);\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-right');\n cell.style.position = 'sticky';\n cell.style.right = right + 'px';\n // Body cells use data-col (column index), not data-field\n if (colIndex !== undefined) {\n host.querySelectorAll(`.data-grid-row .cell[data-col=\"${colIndex}\"]`).forEach((el) => {\n el.classList.add('sticky-right');\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.right = right + 'px';\n });\n }\n right += cell.offsetWidth;\n }\n }\n }\n}\n\n/**\n * Clear sticky positioning from all cells.\n *\n * @param host - The grid host element (render root for DOM queries)\n */\nexport function clearStickyOffsets(host: HTMLElement): void {\n // With light DOM, query the host element directly\n const cells = host.querySelectorAll('.sticky-left, .sticky-right');\n cells.forEach((cell) => {\n cell.classList.remove('sticky-left', 'sticky-right');\n (cell as HTMLElement).style.position = '';\n (cell as HTMLElement).style.left = '';\n (cell as HTMLElement).style.right = '';\n });\n}\n","/**\n * Pinned Columns Plugin (Class-based)\n *\n * Enables column pinning (sticky left/right positioning).\n */\n\nimport type { PluginManifest } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin, PLUGIN_QUERIES, type PluginQuery } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n applyStickyOffsets,\n clearStickyOffsets,\n getLeftStickyColumns,\n getRightStickyColumns,\n hasStickyColumns,\n} from './pinned-columns';\nimport type { PinnedColumnsConfig } from './types';\n\n/**\n * Pinned Columns Plugin for tbw-grid\n *\n * Freezes columns to the left or right edge of the grid—essential for keeping key\n * identifiers or action buttons visible while scrolling through wide datasets. Just set\n * `pinned: 'left'` or `pinned: 'right'` on your column definitions.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n * ```\n *\n * ## Column Configuration\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `pinned` | `'left' \\| 'right'` | Pin column to left or right edge |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-shadow` | `4px 0 8px rgba(0,0,0,0.1)` | Shadow on pinned column edge |\n * | `--tbw-pinned-border` | `var(--tbw-color-border)` | Border between pinned and scrollable |\n *\n * @example Pin ID Left and Actions Right\n * ```ts\n * import '@toolbox-web/grid';\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'left', width: 80 },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * { field: 'department', header: 'Department' },\n * { field: 'actions', header: 'Actions', pinned: 'right', width: 120 },\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @example Left Pinned Only\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'left' },\n * { field: 'name', header: 'Name' },\n * // ... scrollable columns\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link PinnedColumnsConfig} for configuration options\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedColumnsPlugin extends BaseGridPlugin<PinnedColumnsConfig> {\n /**\n * Plugin manifest - declares owned properties for configuration validation.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'sticky',\n level: 'column',\n description: 'the \"sticky\" column property',\n isUsed: (v) => v === 'left' || v === 'right',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'pinnedColumns';\n\n /** @internal */\n protected override get defaultConfig(): Partial<PinnedColumnsConfig> {\n return {};\n }\n\n // #region Internal State\n private isApplied = false;\n private leftOffsets = new Map<string, number>();\n private rightOffsets = new Map<string, number>();\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.leftOffsets.clear();\n this.rightOffsets.clear();\n this.isApplied = false;\n }\n // #endregion\n\n // #region Detection\n\n /**\n * Auto-detect sticky columns from column configuration.\n */\n static detect(rows: readonly unknown[], config: { columns?: ColumnConfig[] }): boolean {\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasStickyColumns(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // Mark that we have sticky columns to apply\n this.isApplied = hasStickyColumns([...columns]);\n return [...columns];\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isApplied) {\n return;\n }\n\n const host = this.grid as unknown as HTMLElement;\n const columns = [...this.columns];\n\n if (!hasStickyColumns(columns)) {\n clearStickyOffsets(host);\n this.isApplied = false;\n return;\n }\n\n // Apply sticky offsets after a microtask to ensure DOM is ready\n queueMicrotask(() => {\n applyStickyOffsets(host, columns);\n });\n }\n\n /**\n * Handle inter-plugin queries.\n * @internal\n */\n override onPluginQuery(query: PluginQuery): unknown {\n switch (query.type) {\n case PLUGIN_QUERIES.CAN_MOVE_COLUMN: {\n // Prevent pinned columns from being moved/reordered.\n // Pinned columns have fixed positions and should not be draggable.\n const column = query.context as ColumnConfig;\n const sticky = (column as ColumnConfig & { sticky?: 'left' | 'right' }).sticky;\n if (sticky === 'left' || sticky === 'right') {\n return false;\n }\n // Also check meta.sticky for backwards compatibility\n const metaSticky = (column.meta as { sticky?: 'left' | 'right' } | undefined)?.sticky;\n if (metaSticky === 'left' || metaSticky === 'right') {\n return false;\n }\n return undefined; // Let other plugins or default behavior decide\n }\n default:\n return undefined;\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Re-apply sticky offsets (e.g., after column resize).\n */\n refreshStickyOffsets(): void {\n const columns = [...this.columns];\n applyStickyOffsets(this.grid as unknown as HTMLElement, columns);\n }\n\n /**\n * Get columns pinned to the left.\n */\n getLeftPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n return getLeftStickyColumns(columns);\n }\n\n /**\n * Get columns pinned to the right.\n */\n getRightPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n return getRightStickyColumns(columns);\n }\n\n /**\n * Clear all sticky positioning.\n */\n clearStickyPositions(): void {\n clearStickyOffsets(this.grid as unknown as HTMLElement);\n }\n\n /**\n * Report horizontal scroll boundary offsets for pinned columns.\n * Used by keyboard navigation to ensure focused cells aren't hidden behind sticky columns.\n * @internal\n */\n override getHorizontalScrollOffsets(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } | undefined {\n if (!this.isApplied) {\n return undefined;\n }\n\n let left = 0;\n let right = 0;\n\n if (rowEl) {\n // Calculate from rendered cells in the row\n const stickyLeftCells = rowEl.querySelectorAll('.sticky-left');\n const stickyRightCells = rowEl.querySelectorAll('.sticky-right');\n stickyLeftCells.forEach((el) => {\n left += (el as HTMLElement).offsetWidth;\n });\n stickyRightCells.forEach((el) => {\n right += (el as HTMLElement).offsetWidth;\n });\n } else {\n // Fall back to header row if no row element provided\n const host = this.grid as unknown as HTMLElement;\n const headerCells = host.querySelectorAll('.header-row .cell');\n headerCells.forEach((cell) => {\n if (cell.classList.contains('sticky-left')) {\n left += (cell as HTMLElement).offsetWidth;\n } else if (cell.classList.contains('sticky-right')) {\n right += (cell as HTMLElement).offsetWidth;\n }\n });\n }\n\n // Skip horizontal scrolling if focused cell is pinned (it's always visible)\n const skipScroll =\n focusedCell?.classList.contains('sticky-left') || focusedCell?.classList.contains('sticky-right');\n\n return { left, right, skipScroll };\n }\n // #endregion\n}\n"],"names":["getLeftStickyColumns","columns","col","getRightStickyColumns","hasStickyColumns","applyStickyOffsets","host","headerCells","fieldToIndex","i","left","colIndex","cell","c","el","right","clearStickyOffsets","PinnedColumnsPlugin","BaseGridPlugin","v","rows","config","query","PLUGIN_QUERIES","column","sticky","metaSticky","rowEl","focusedCell","stickyLeftCells","stickyRightCells","skipScroll"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pinned-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/pinned-columns/pinned-columns.ts","../../../../../libs/grid/src/lib/plugins/pinned-columns/PinnedColumnsPlugin.ts"],"sourcesContent":["/**\n * Sticky Columns Core Logic\n *\n * Pure functions for applying sticky (pinned) column positioning.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { StickyPosition } from './types';\n\n/**\n * Get columns that should be sticky on the left.\n *\n * @param columns - Array of column configurations\n * @returns Array of columns with sticky='left'\n */\nexport function getLeftStickyColumns(columns: any[]): any[] {\n return columns.filter((col) => col.sticky === 'left');\n}\n\n/**\n * Get columns that should be sticky on the right.\n *\n * @param columns - Array of column configurations\n * @returns Array of columns with sticky='right'\n */\nexport function getRightStickyColumns(columns: any[]): any[] {\n return columns.filter((col) => col.sticky === 'right');\n}\n\n/**\n * Check if any columns have sticky positioning.\n *\n * @param columns - Array of column configurations\n * @returns True if any column has sticky position\n */\nexport function hasStickyColumns(columns: any[]): boolean {\n return columns.some((col) => col.sticky === 'left' || col.sticky === 'right');\n}\n\n/**\n * Get the sticky position of a column.\n *\n * @param column - Column configuration\n * @returns The sticky position or null if not sticky\n */\nexport function getColumnStickyPosition(column: any): StickyPosition | null {\n if (column.sticky === 'left') return 'left';\n if (column.sticky === 'right') return 'right';\n return null;\n}\n\n/**\n * Calculate left offsets for sticky-left columns.\n * Returns a map of field -> offset in pixels.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @returns Map of field to left offset\n */\nexport function calculateLeftStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n for (const col of columns) {\n if (col.sticky === 'left') {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Calculate right offsets for sticky-right columns.\n * Processes columns in reverse order.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @returns Map of field to right offset\n */\nexport function calculateRightStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n // Process in reverse for right-sticky columns\n const reversed = [...columns].reverse();\n for (const col of reversed) {\n if (col.sticky === 'right') {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Apply sticky offsets to header and body cells.\n * This modifies the DOM elements in place.\n *\n * @param host - The grid host element (render root for DOM queries)\n * @param columns - Array of column configurations\n */\nexport function applyStickyOffsets(host: HTMLElement, columns: any[]): void {\n // With light DOM, query the host element directly\n const headerCells = Array.from(host.querySelectorAll('.header-row .cell')) as HTMLElement[];\n if (!headerCells.length) return;\n\n // Build column index map for matching body cells (which use data-col, not data-field)\n const fieldToIndex = new Map<string, number>();\n columns.forEach((col, i) => {\n if (col.field) fieldToIndex.set(col.field, i);\n });\n\n // Apply left sticky\n let left = 0;\n for (const col of columns) {\n if (col.sticky === 'left') {\n const colIndex = fieldToIndex.get(col.field);\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-left');\n cell.style.position = 'sticky';\n cell.style.left = left + 'px';\n // Body cells use data-col (column index), not data-field\n if (colIndex !== undefined) {\n host.querySelectorAll(`.data-grid-row .cell[data-col=\"${colIndex}\"]`).forEach((el) => {\n el.classList.add('sticky-left');\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.left = left + 'px';\n });\n }\n left += cell.offsetWidth;\n }\n }\n }\n\n // Apply right sticky (process in reverse)\n let right = 0;\n for (const col of [...columns].reverse()) {\n if (col.sticky === 'right') {\n const colIndex = fieldToIndex.get(col.field);\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-right');\n cell.style.position = 'sticky';\n cell.style.right = right + 'px';\n // Body cells use data-col (column index), not data-field\n if (colIndex !== undefined) {\n host.querySelectorAll(`.data-grid-row .cell[data-col=\"${colIndex}\"]`).forEach((el) => {\n el.classList.add('sticky-right');\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.right = right + 'px';\n });\n }\n right += cell.offsetWidth;\n }\n }\n }\n}\n\n/**\n * Clear sticky positioning from all cells.\n *\n * @param host - The grid host element (render root for DOM queries)\n */\nexport function clearStickyOffsets(host: HTMLElement): void {\n // With light DOM, query the host element directly\n const cells = host.querySelectorAll('.sticky-left, .sticky-right');\n cells.forEach((cell) => {\n cell.classList.remove('sticky-left', 'sticky-right');\n (cell as HTMLElement).style.position = '';\n (cell as HTMLElement).style.left = '';\n (cell as HTMLElement).style.right = '';\n });\n}\n","/**\n * Pinned Columns Plugin (Class-based)\n *\n * Enables column pinning (sticky left/right positioning).\n */\n\nimport type { PluginManifest, PluginQuery } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n applyStickyOffsets,\n clearStickyOffsets,\n getLeftStickyColumns,\n getRightStickyColumns,\n hasStickyColumns,\n} from './pinned-columns';\nimport type { PinnedColumnsConfig } from './types';\n\n/** Query type constant for checking if a column can be moved */\nconst QUERY_CAN_MOVE_COLUMN = 'canMoveColumn';\n\n/**\n * Pinned Columns Plugin for tbw-grid\n *\n * Freezes columns to the left or right edge of the grid—essential for keeping key\n * identifiers or action buttons visible while scrolling through wide datasets. Just set\n * `pinned: 'left'` or `pinned: 'right'` on your column definitions.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n * ```\n *\n * ## Column Configuration\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `pinned` | `'left' \\| 'right'` | Pin column to left or right edge |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-shadow` | `4px 0 8px rgba(0,0,0,0.1)` | Shadow on pinned column edge |\n * | `--tbw-pinned-border` | `var(--tbw-color-border)` | Border between pinned and scrollable |\n *\n * @example Pin ID Left and Actions Right\n * ```ts\n * import '@toolbox-web/grid';\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'left', width: 80 },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * { field: 'department', header: 'Department' },\n * { field: 'actions', header: 'Actions', pinned: 'right', width: 120 },\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @example Left Pinned Only\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'left' },\n * { field: 'name', header: 'Name' },\n * // ... scrollable columns\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link PinnedColumnsConfig} for configuration options\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedColumnsPlugin extends BaseGridPlugin<PinnedColumnsConfig> {\n /**\n * Plugin manifest - declares owned properties and handled queries.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'sticky',\n level: 'column',\n description: 'the \"sticky\" column property',\n isUsed: (v) => v === 'left' || v === 'right',\n },\n ],\n queries: [\n {\n type: QUERY_CAN_MOVE_COLUMN,\n description: 'Prevents pinned (sticky) columns from being moved/reordered',\n },\n {\n type: 'getStickyOffsets',\n description: 'Returns the sticky offsets for left/right pinned columns',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'pinnedColumns';\n\n /** @internal */\n protected override get defaultConfig(): Partial<PinnedColumnsConfig> {\n return {};\n }\n\n // #region Internal State\n private isApplied = false;\n private leftOffsets = new Map<string, number>();\n private rightOffsets = new Map<string, number>();\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.leftOffsets.clear();\n this.rightOffsets.clear();\n this.isApplied = false;\n }\n // #endregion\n\n // #region Detection\n\n /**\n * Auto-detect sticky columns from column configuration.\n */\n static detect(rows: readonly unknown[], config: { columns?: ColumnConfig[] }): boolean {\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasStickyColumns(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // Mark that we have sticky columns to apply\n this.isApplied = hasStickyColumns([...columns]);\n return [...columns];\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isApplied) {\n return;\n }\n\n const host = this.grid as unknown as HTMLElement;\n const columns = [...this.columns];\n\n if (!hasStickyColumns(columns)) {\n clearStickyOffsets(host);\n this.isApplied = false;\n return;\n }\n\n // Apply sticky offsets after a microtask to ensure DOM is ready\n queueMicrotask(() => {\n applyStickyOffsets(host, columns);\n });\n }\n\n /**\n * Handle inter-plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n switch (query.type) {\n case QUERY_CAN_MOVE_COLUMN: {\n // Prevent pinned columns from being moved/reordered.\n // Pinned columns have fixed positions and should not be draggable.\n const column = query.context as ColumnConfig;\n const sticky = (column as ColumnConfig & { sticky?: 'left' | 'right' }).sticky;\n if (sticky === 'left' || sticky === 'right') {\n return false;\n }\n // Also check meta.sticky for backwards compatibility\n const metaSticky = (column.meta as { sticky?: 'left' | 'right' } | undefined)?.sticky;\n if (metaSticky === 'left' || metaSticky === 'right') {\n return false;\n }\n return undefined; // Let other plugins or default behavior decide\n }\n case 'getStickyOffsets': {\n // Return the calculated sticky offsets for column virtualization\n return {\n left: Object.fromEntries(this.leftOffsets),\n right: Object.fromEntries(this.rightOffsets),\n };\n }\n default:\n return undefined;\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Re-apply sticky offsets (e.g., after column resize).\n */\n refreshStickyOffsets(): void {\n const columns = [...this.columns];\n applyStickyOffsets(this.grid as unknown as HTMLElement, columns);\n }\n\n /**\n * Get columns pinned to the left.\n */\n getLeftPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n return getLeftStickyColumns(columns);\n }\n\n /**\n * Get columns pinned to the right.\n */\n getRightPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n return getRightStickyColumns(columns);\n }\n\n /**\n * Clear all sticky positioning.\n */\n clearStickyPositions(): void {\n clearStickyOffsets(this.grid as unknown as HTMLElement);\n }\n\n /**\n * Report horizontal scroll boundary offsets for pinned columns.\n * Used by keyboard navigation to ensure focused cells aren't hidden behind sticky columns.\n * @internal\n */\n override getHorizontalScrollOffsets(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } | undefined {\n if (!this.isApplied) {\n return undefined;\n }\n\n let left = 0;\n let right = 0;\n\n if (rowEl) {\n // Calculate from rendered cells in the row\n const stickyLeftCells = rowEl.querySelectorAll('.sticky-left');\n const stickyRightCells = rowEl.querySelectorAll('.sticky-right');\n stickyLeftCells.forEach((el) => {\n left += (el as HTMLElement).offsetWidth;\n });\n stickyRightCells.forEach((el) => {\n right += (el as HTMLElement).offsetWidth;\n });\n } else {\n // Fall back to header row if no row element provided\n const host = this.grid as unknown as HTMLElement;\n const headerCells = host.querySelectorAll('.header-row .cell');\n headerCells.forEach((cell) => {\n if (cell.classList.contains('sticky-left')) {\n left += (cell as HTMLElement).offsetWidth;\n } else if (cell.classList.contains('sticky-right')) {\n right += (cell as HTMLElement).offsetWidth;\n }\n });\n }\n\n // Skip horizontal scrolling if focused cell is pinned (it's always visible)\n const skipScroll =\n focusedCell?.classList.contains('sticky-left') || focusedCell?.classList.contains('sticky-right');\n\n return { left, right, skipScroll };\n }\n // #endregion\n}\n"],"names":["getLeftStickyColumns","columns","col","getRightStickyColumns","hasStickyColumns","applyStickyOffsets","host","headerCells","fieldToIndex","i","left","colIndex","cell","c","el","right","clearStickyOffsets","QUERY_CAN_MOVE_COLUMN","PinnedColumnsPlugin","BaseGridPlugin","v","rows","config","query","column","sticky","metaSticky","rowEl","focusedCell","stickyLeftCells","stickyRightCells","skipScroll"],"mappings":"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"}
|
package/umd/plugins/print.umd.js
CHANGED
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
|
|
73
73
|
Note: Output will be limited to ${i.maxRows.toLocaleString()} rows.`:"";if(!confirm(`This grid has ${a.toLocaleString()} rows. Printing large datasets may cause performance issues or browser slowdowns.${m}
|
|
74
74
|
|
|
75
|
-
Click OK to continue, or Cancel to abort.`))return}i.maxRows>0&&a>i.maxRows&&(l=i.maxRows,d=!0),this.#n=!0;const w=performance.now();this.emit("print-start",{rowCount:l,limitApplied:d,originalRowCount:a});try{const m=this.#s;this.#r={bypassThreshold:m._virtualization?.bypassThreshold??24},this.#c(),d&&(this.#o=this.sourceRows,this.grid.rows=this.sourceRows.slice(0,l),await new Promise(r=>setTimeout(r,50))),(i.includeTitle||i.includeTimestamp)&&this.#d(i),await this.#p(),await new Promise(r=>requestAnimationFrame(r)),await new Promise(r=>requestAnimationFrame(r)),t.classList.add(`print-${i.orientation}`),await new Promise(r=>requestAnimationFrame(r)),await new Promise(r=>requestAnimationFrame(r)),i.isolate?await this.#h(i):await this.#m(),this.emit("print-complete",{success:!0,rowCount:l,duration:Math.round(performance.now()-w)})}catch(m){console.error("[PrintPlugin] Print failed:",m),this.emit("print-complete",{success:!1,rowCount:0,duration:Math.round(performance.now()-w)})}finally{this.#u(),this.#n=!1}}#d(e){const t=this.gridElement;if(t){if(this.#t=document.createElement("div"),this.#t.className="tbw-print-header",e.includeTitle){const i=e.title||this.grid.effectiveConfig?.shell?.header?.title||"Grid Data",s=document.createElement("div");s.className="tbw-print-header-title",s.textContent=i,this.#t.appendChild(s)}if(e.includeTimestamp){const i=document.createElement("div");i.className="tbw-print-header-timestamp",i.textContent=`Printed: ${new Date().toLocaleString()}`,this.#t.appendChild(i)}t.insertBefore(this.#t,t.firstChild),this.#i=document.createElement("div"),this.#i.className="tbw-print-footer",this.#i.textContent=`Page generated from ${window.location.hostname}`,t.appendChild(this.#i)}}async#p(){const e=this.#s;if(!e._virtualization)return;const t=this.rows.length;e._virtualization.bypassThreshold=t+100,e.refreshVirtualWindow(!0),await new Promise(i=>setTimeout(i,100))}async#m(){return new Promise(e=>{const t=()=>{window.removeEventListener("afterprint",t),e()};window.addEventListener("afterprint",t),window.print(),setTimeout(()=>{window.removeEventListener("afterprint",t),e()},1e3)})}async#h(e){const t=this.gridElement;t&&await c(t,{orientation:e.orientation})}#c(){const e=this.columns;if(e){this.#e=new Map;for(const t of e)t.printHidden&&t.field&&(this.#e.set(t.field,!t.hidden),this.grid.setColumnVisible(t.field,!1))}}#w(){if(this.#e){for(const[e,t]of this.#e)this.grid.setColumnVisible(e,t);this.#e=null}}#u(){const e=this.gridElement;if(!e)return;this.#w(),e.classList.remove("print-portrait","print-landscape"),this.#a!==null&&(e.style.transform="",e.style.transformOrigin="",e.style.width="",this.#a=null),this.#t&&(this.#t.remove(),this.#t=null),this.#i&&(this.#i.remove(),this.#i=null);const t=this.#s;this.#r&&t._virtualization&&(t._virtualization.bypassThreshold=this.#r.bypassThreshold,t.refreshVirtualWindow(!0),this.#r=null),this.#o!==null&&(this.grid.rows=this.#o,this.#o=null)}afterRender(){this.config?.button&&!this.#l&&(this.#g(),this.#l=!0)}#l=!1;#g(){this.#s.registerToolbarContent?.({id:"print-button",order:900,render:t=>{const i=document.createElement("button");i.className="tbw-toolbar-btn tbw-print-btn",i.title="Print grid",i.type="button";const s=this.resolveIcon("print")||"🖨️";this.setIcon(i,s),i.addEventListener("click",()=>{this.print()},{signal:this.disconnectSignal}),t.appendChild(i)}})}}o.PrintPlugin=f,o.printGridIsolated=c,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})}));
|
|
75
|
+
Click OK to continue, or Cancel to abort.`))return}i.maxRows>0&&a>i.maxRows&&(l=i.maxRows,d=!0),this.#n=!0;const w=performance.now();this.emit("print-start",{rowCount:l,limitApplied:d,originalRowCount:a});try{const m=this.#s;this.#r={bypassThreshold:m._virtualization?.bypassThreshold??24},this.#c(),d&&(this.#o=this.sourceRows,this.grid.rows=this.sourceRows.slice(0,l),await new Promise(r=>setTimeout(r,50))),(i.includeTitle||i.includeTimestamp)&&this.#d(i),await this.#p(),await new Promise(r=>requestAnimationFrame(r)),await new Promise(r=>requestAnimationFrame(r)),t.classList.add(`print-${i.orientation}`),await new Promise(r=>requestAnimationFrame(r)),await new Promise(r=>requestAnimationFrame(r)),i.isolate?await this.#h(i):await this.#m(),this.emit("print-complete",{success:!0,rowCount:l,duration:Math.round(performance.now()-w)})}catch(m){console.error("[PrintPlugin] Print failed:",m),this.emit("print-complete",{success:!1,rowCount:0,duration:Math.round(performance.now()-w)})}finally{this.#u(),this.#n=!1}}#d(e){const t=this.gridElement;if(t){if(this.#t=document.createElement("div"),this.#t.className="tbw-print-header",e.includeTitle){const i=e.title||this.grid.effectiveConfig?.shell?.header?.title||"Grid Data",s=document.createElement("div");s.className="tbw-print-header-title",s.textContent=i,this.#t.appendChild(s)}if(e.includeTimestamp){const i=document.createElement("div");i.className="tbw-print-header-timestamp",i.textContent=`Printed: ${new Date().toLocaleString()}`,this.#t.appendChild(i)}t.insertBefore(this.#t,t.firstChild),this.#i=document.createElement("div"),this.#i.className="tbw-print-footer",this.#i.textContent=`Page generated from ${window.location.hostname}`,t.appendChild(this.#i)}}async#p(){const e=this.#s;if(!e._virtualization)return;const t=this.rows.length;e._virtualization.bypassThreshold=t+100,e.refreshVirtualWindow(!0),await new Promise(i=>setTimeout(i,100))}async#m(){return new Promise(e=>{const t=()=>{window.removeEventListener("afterprint",t),e()};window.addEventListener("afterprint",t),window.print(),setTimeout(()=>{typeof window<"u"&&window.removeEventListener("afterprint",t),e()},1e3)})}async#h(e){const t=this.gridElement;t&&await c(t,{orientation:e.orientation})}#c(){const e=this.columns;if(e){this.#e=new Map;for(const t of e)t.printHidden&&t.field&&(this.#e.set(t.field,!t.hidden),this.grid.setColumnVisible(t.field,!1))}}#w(){if(this.#e){for(const[e,t]of this.#e)this.grid.setColumnVisible(e,t);this.#e=null}}#u(){const e=this.gridElement;if(!e)return;this.#w(),e.classList.remove("print-portrait","print-landscape"),this.#a!==null&&(e.style.transform="",e.style.transformOrigin="",e.style.width="",this.#a=null),this.#t&&(this.#t.remove(),this.#t=null),this.#i&&(this.#i.remove(),this.#i=null);const t=this.#s;this.#r&&t._virtualization&&(t._virtualization.bypassThreshold=this.#r.bypassThreshold,t.refreshVirtualWindow(!0),this.#r=null),this.#o!==null&&(this.grid.rows=this.#o,this.#o=null)}afterRender(){this.config?.button&&!this.#l&&(this.#g(),this.#l=!0)}#l=!1;#g(){this.#s.registerToolbarContent?.({id:"print-button",order:900,render:t=>{const i=document.createElement("button");i.className="tbw-toolbar-btn tbw-print-btn",i.title="Print grid",i.type="button";const s=this.resolveIcon("print")||"🖨️";this.setIcon(i,s),i.addEventListener("click",()=>{this.print()},{signal:this.disconnectSignal}),t.appendChild(i)}})}}o.PrintPlugin=f,o.printGridIsolated=c,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})}));
|
|
76
76
|
//# sourceMappingURL=print.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"print.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/print/print-isolated.ts","../../../../../libs/grid/src/lib/plugins/print/PrintPlugin.ts"],"sourcesContent":["/**\n * Utility for printing a grid in isolation by hiding all other page content.\n *\n * This approach keeps the grid in place (with virtualization disabled by PrintPlugin)\n * and uses CSS to hide everything else on the page during printing.\n */\n\nimport type { PrintOrientation } from './types';\n\nexport interface PrintIsolatedOptions {\n /** Page orientation hint */\n orientation?: PrintOrientation;\n}\n\n/** ID for the isolation stylesheet */\nconst ISOLATION_STYLE_ID = 'tbw-print-isolation-style';\n\n/**\n * Create a stylesheet that hides everything except the target grid.\n * Uses the grid's ID to target it specifically.\n */\nfunction createIsolationStylesheet(gridId: string, orientation: PrintOrientation): HTMLStyleElement {\n const style = document.createElement('style');\n style.id = ISOLATION_STYLE_ID;\n style.textContent = `\n /* Print isolation: hide everything except the target grid */\n @media print {\n /* Hide all body children by default */\n body > *:not(#${gridId}) {\n display: none !important;\n }\n\n /* But show the grid and ensure it's not hidden by ancestor rules */\n #${gridId} {\n display: block !important;\n position: static !important;\n visibility: visible !important;\n opacity: 1 !important;\n overflow: visible !important;\n height: auto !important;\n width: 100% !important;\n max-height: none !important;\n margin: 0 !important;\n padding: 0 !important;\n transform: none !important;\n }\n\n /* If grid is nested, we need to show its ancestors too */\n #${gridId},\n #${gridId} * {\n visibility: visible !important;\n }\n\n /* Walk up the DOM and show all ancestors of the grid */\n body *:has(> #${gridId}),\n body *:has(#${gridId}) {\n display: block !important;\n visibility: visible !important;\n opacity: 1 !important;\n overflow: visible !important;\n height: auto !important;\n position: static !important;\n transform: none !important;\n background: transparent !important;\n border: none !important;\n padding: 0 !important;\n margin: 0 !important;\n }\n\n /* Hide siblings of ancestors (everything that's not in the path to the grid) */\n body *:has(#${gridId}) > *:not(:has(#${gridId})):not(#${gridId}) {\n display: none !important;\n }\n\n /* Page settings */\n @page {\n size: ${orientation};\n margin: 1cm;\n }\n\n /* Ensure proper print styling */\n body {\n margin: 0 !important;\n padding: 0 !important;\n background: white !important;\n color-scheme: light !important;\n }\n }\n\n /* Screen: also apply isolation for print preview */\n @media screen {\n /* When this stylesheet is active, we're about to print */\n /* No screen-specific rules needed - isolation only applies to print */\n }\n `;\n return style;\n}\n\n/**\n * Print a grid in isolation by hiding all other page content.\n *\n * This function adds a temporary stylesheet that uses CSS to hide everything\n * on the page except the target grid during printing. The grid stays in place\n * with all its data (virtualization should be disabled separately).\n *\n * @param gridElement - The tbw-grid element to print (must have an ID)\n * @param options - Optional configuration\n * @returns Promise that resolves when the print dialog closes\n *\n * @example\n * ```typescript\n * import { printGridIsolated } from '@toolbox-web/grid/plugins/print';\n *\n * const grid = document.querySelector('tbw-grid');\n * await printGridIsolated(grid, { orientation: 'landscape' });\n * ```\n */\nexport async function printGridIsolated(gridElement: HTMLElement, options: PrintIsolatedOptions = {}): Promise<void> {\n const { orientation = 'landscape' } = options;\n\n const gridId = gridElement.id;\n\n // Warn if multiple elements share this ID (user-set IDs could collide)\n const elementsWithId = document.querySelectorAll(`#${CSS.escape(gridId)}`);\n if (elementsWithId.length > 1) {\n console.warn(\n `[tbw-grid:print] Multiple elements found with id=\"${gridId}\". ` +\n `Print isolation may not work correctly. Ensure each grid has a unique ID.`,\n );\n }\n\n // Remove any existing isolation stylesheet\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n\n // Add the isolation stylesheet\n const isolationStyle = createIsolationStylesheet(gridId, orientation);\n document.head.appendChild(isolationStyle);\n\n return new Promise((resolve) => {\n // Listen for afterprint event to cleanup\n const onAfterPrint = () => {\n window.removeEventListener('afterprint', onAfterPrint);\n // Remove isolation stylesheet\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n resolve();\n };\n window.addEventListener('afterprint', onAfterPrint);\n\n // Trigger print\n window.print();\n\n // Fallback timeout in case afterprint doesn't fire (some browsers)\n setTimeout(() => {\n window.removeEventListener('afterprint', onAfterPrint);\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n resolve();\n }, 5000);\n });\n}\n","/**\n * Print Plugin (Class-based)\n *\n * Provides print layout functionality for tbw-grid.\n * Temporarily disables virtualization to render all rows and uses\n * @media print CSS for print-optimized styling.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { InternalGrid, ToolbarContentDefinition } from '../../core/types';\nimport { printGridIsolated } from './print-isolated';\nimport styles from './print.css?inline';\nimport type { PrintCompleteDetail, PrintConfig, PrintParams, PrintStartDetail } from './types';\n\n/**\n * Extended grid interface for PrintPlugin internal access.\n * Includes registerToolbarContent which is available on the grid class\n * but not exposed in the standard plugin API.\n */\ninterface PrintGridRef extends InternalGrid {\n registerToolbarContent?(content: ToolbarContentDefinition): void;\n unregisterToolbarContent?(contentId: string): void;\n}\n\n/** Default configuration */\nconst DEFAULT_CONFIG: Required<PrintConfig> = {\n button: false,\n orientation: 'landscape',\n warnThreshold: 500,\n maxRows: 0,\n includeTitle: true,\n includeTimestamp: true,\n title: '',\n isolate: false,\n};\n\n/**\n * Print Plugin for tbw-grid\n *\n * Enables printing the full grid content by temporarily disabling virtualization\n * and applying print-optimized styles. Handles large datasets gracefully with\n * configurable row limits.\n *\n * ## Installation\n *\n * ```ts\n * import { PrintPlugin } from '@toolbox-web/grid/plugins/print';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `button` | `boolean` | `false` | Show print button in toolbar |\n * | `orientation` | `'portrait' \\| 'landscape'` | `'landscape'` | Page orientation |\n * | `warnThreshold` | `number` | `500` | Show confirmation dialog when rows exceed this (0 = no warning) |\n * | `maxRows` | `number` | `0` | Hard limit on printed rows (0 = unlimited) |\n * | `includeTitle` | `boolean` | `true` | Include grid title in print |\n * | `includeTimestamp` | `boolean` | `true` | Include timestamp in footer |\n * | `title` | `string` | `''` | Custom print title |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `print` | `(params?) => Promise<void>` | Trigger print dialog |\n * | `isPrinting` | `() => boolean` | Check if print is in progress |\n *\n * ## Events\n *\n * | Event | Detail | Description |\n * |-------|--------|-------------|\n * | `print-start` | `PrintStartDetail` | Fired when print begins |\n * | `print-complete` | `PrintCompleteDetail` | Fired when print completes |\n *\n * @example Basic Print\n * ```ts\n * import { PrintPlugin } from '@toolbox-web/grid/plugins/print';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * plugins: [new PrintPlugin()],\n * };\n *\n * // Trigger print\n * const printPlugin = grid.getPlugin(PrintPlugin);\n * await printPlugin.print();\n * ```\n *\n * @example With Toolbar Button\n * ```ts\n * grid.gridConfig = {\n * plugins: [new PrintPlugin({ button: true, orientation: 'landscape' })],\n * };\n * ```\n *\n * @see {@link PrintConfig} for all configuration options\n */\nexport class PrintPlugin extends BaseGridPlugin<PrintConfig> {\n /** @internal */\n readonly name = 'print';\n\n /** @internal */\n override readonly version = '1.0.0';\n\n /** CSS styles for print mode */\n override readonly styles = styles;\n\n /** Current print state */\n #printing = false;\n\n /** Saved column visibility state */\n #savedHiddenColumns: Map<string, boolean> | null = null;\n\n /** Saved virtualization state */\n #savedVirtualization: { bypassThreshold: number } | null = null;\n\n /** Saved rows when maxRows limit is applied */\n #savedRows: unknown[] | null = null;\n\n /** Print header element */\n #printHeader: HTMLElement | null = null;\n\n /** Print footer element */\n #printFooter: HTMLElement | null = null;\n\n /** Applied scale factor (legacy, used for cleanup) */\n #appliedScale: number | null = null;\n\n /**\n * Get the grid typed as PrintGridRef for internal access.\n */\n get #internalGrid(): PrintGridRef {\n return this.grid as unknown as PrintGridRef;\n }\n\n /**\n * Check if print is currently in progress\n */\n isPrinting(): boolean {\n return this.#printing;\n }\n\n /**\n * Trigger the browser print dialog\n *\n * This method:\n * 1. Validates row count against maxRows limit\n * 2. Disables virtualization to render all rows\n * 3. Applies print-specific CSS classes\n * 4. Opens the browser print dialog (or isolated window if `isolate: true`)\n * 5. Restores normal state after printing\n *\n * @param params - Optional parameters to override config for this print\n * @param params.isolate - If true, prints in an isolated window containing only the grid\n * @returns Promise that resolves when print dialog closes\n */\n async print(params?: PrintParams): Promise<void> {\n if (this.#printing) {\n console.warn('[PrintPlugin] Print already in progress');\n return;\n }\n\n const grid = this.gridElement;\n if (!grid) {\n console.warn('[PrintPlugin] Grid not available');\n return;\n }\n\n const config = { ...DEFAULT_CONFIG, ...this.config, ...params };\n const rows = this.rows;\n const originalRowCount = rows.length;\n let rowCount = originalRowCount;\n let limitApplied = false;\n\n // Check if we should warn about large datasets\n if (config.warnThreshold > 0 && originalRowCount > config.warnThreshold) {\n const limitInfo =\n config.maxRows > 0 ? `\\n\\nNote: Output will be limited to ${config.maxRows.toLocaleString()} rows.` : '';\n const proceed = confirm(\n `This grid has ${originalRowCount.toLocaleString()} rows. ` +\n `Printing large datasets may cause performance issues or browser slowdowns.${limitInfo}\\n\\n` +\n `Click OK to continue, or Cancel to abort.`,\n );\n if (!proceed) {\n return;\n }\n }\n\n // Apply hard row limit if configured\n if (config.maxRows > 0 && originalRowCount > config.maxRows) {\n rowCount = config.maxRows;\n limitApplied = true;\n }\n\n this.#printing = true;\n\n // Track timing for duration reporting\n const startTime = performance.now();\n\n // Emit print-start event\n this.emit<PrintStartDetail>('print-start', {\n rowCount,\n limitApplied,\n originalRowCount,\n });\n\n try {\n // Save current virtualization state\n const internalGrid = this.#internalGrid;\n this.#savedVirtualization = {\n bypassThreshold: internalGrid._virtualization?.bypassThreshold ?? 24,\n };\n\n // Hide columns marked with printHidden\n this.#hidePrintColumns();\n\n // Apply row limit if configured\n if (limitApplied) {\n this.#savedRows = this.sourceRows;\n // Set limited rows on the grid\n (this.grid as unknown as { rows: unknown[] }).rows = this.sourceRows.slice(0, rowCount);\n // Wait for grid to process new rows\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n\n // Add print header if configured\n if (config.includeTitle || config.includeTimestamp) {\n this.#addPrintHeader(config);\n }\n\n // Disable virtualization to render all rows\n // This forces the grid to render all rows in the DOM\n await this.#disableVirtualization();\n\n // Wait for next frame to ensure DOM is updated\n await new Promise((resolve) => requestAnimationFrame(resolve));\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n // Add orientation class for @page rules\n grid.classList.add(`print-${config.orientation}`);\n\n // Wait for next frame to ensure DOM is updated\n await new Promise((resolve) => requestAnimationFrame(resolve));\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n // Trigger browser print dialog (isolated or inline)\n if (config.isolate) {\n await this.#printInIsolatedWindow(config);\n } else {\n await this.#triggerPrint();\n }\n\n // Emit print-complete event\n this.emit<PrintCompleteDetail>('print-complete', {\n success: true,\n rowCount,\n duration: Math.round(performance.now() - startTime),\n });\n } catch (error) {\n console.error('[PrintPlugin] Print failed:', error);\n this.emit<PrintCompleteDetail>('print-complete', {\n success: false,\n rowCount: 0,\n duration: Math.round(performance.now() - startTime),\n });\n } finally {\n // Restore normal state\n this.#cleanup();\n this.#printing = false;\n }\n }\n\n /**\n * Add print header with title and timestamp\n */\n #addPrintHeader(config: Required<PrintConfig>): void {\n const grid = this.gridElement;\n if (!grid) return;\n\n // Create print header\n this.#printHeader = document.createElement('div');\n this.#printHeader.className = 'tbw-print-header';\n\n // Title\n if (config.includeTitle) {\n const title = config.title || this.grid.effectiveConfig?.shell?.header?.title || 'Grid Data';\n const titleEl = document.createElement('div');\n titleEl.className = 'tbw-print-header-title';\n titleEl.textContent = title;\n this.#printHeader.appendChild(titleEl);\n }\n\n // Timestamp\n if (config.includeTimestamp) {\n const timestampEl = document.createElement('div');\n timestampEl.className = 'tbw-print-header-timestamp';\n timestampEl.textContent = `Printed: ${new Date().toLocaleString()}`;\n this.#printHeader.appendChild(timestampEl);\n }\n\n // Insert at the beginning of the grid\n grid.insertBefore(this.#printHeader, grid.firstChild);\n\n // Create print footer\n this.#printFooter = document.createElement('div');\n this.#printFooter.className = 'tbw-print-footer';\n this.#printFooter.textContent = `Page generated from ${window.location.hostname}`;\n grid.appendChild(this.#printFooter);\n }\n\n /**\n * Disable virtualization to render all rows\n */\n async #disableVirtualization(): Promise<void> {\n const internalGrid = this.#internalGrid;\n if (!internalGrid._virtualization) return;\n\n // Set bypass threshold higher than total row count to disable virtualization\n // This makes the grid render all rows (up to maxRows) instead of just visible ones\n const totalRows = this.rows.length;\n internalGrid._virtualization.bypassThreshold = totalRows + 100;\n\n // Force a full refresh to re-render with virtualization disabled\n internalGrid.refreshVirtualWindow(true);\n\n // Wait for render to complete\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n\n /**\n * Trigger the browser print dialog\n */\n async #triggerPrint(): Promise<void> {\n return new Promise((resolve) => {\n // Listen for afterprint event\n const onAfterPrint = () => {\n window.removeEventListener('afterprint', onAfterPrint);\n resolve();\n };\n window.addEventListener('afterprint', onAfterPrint);\n\n // Trigger print\n window.print();\n\n // Fallback timeout in case afterprint doesn't fire (some browsers)\n setTimeout(() => {\n window.removeEventListener('afterprint', onAfterPrint);\n resolve();\n }, 1000);\n });\n }\n\n /**\n * Print in isolation by hiding all other page content.\n * This excludes navigation, sidebars, etc. while keeping the grid in place.\n */\n async #printInIsolatedWindow(config: Required<PrintConfig>): Promise<void> {\n const grid = this.gridElement;\n if (!grid) return;\n\n await printGridIsolated(grid, {\n orientation: config.orientation,\n });\n }\n\n /**\n * Hide columns marked with printHidden: true\n */\n #hidePrintColumns(): void {\n const columns = this.columns;\n if (!columns) return;\n\n // Save current hidden state and hide print columns\n this.#savedHiddenColumns = new Map();\n\n for (const col of columns) {\n if (col.printHidden && col.field) {\n // Save current visibility state (true = visible, false = hidden)\n this.#savedHiddenColumns.set(col.field, !col.hidden);\n // Hide the column for printing\n this.grid.setColumnVisible(col.field, false);\n }\n }\n }\n\n /**\n * Restore columns that were hidden for printing\n */\n #restorePrintColumns(): void {\n if (!this.#savedHiddenColumns) return;\n\n for (const [field, wasVisible] of this.#savedHiddenColumns) {\n // Restore original visibility\n this.grid.setColumnVisible(field, wasVisible);\n }\n\n this.#savedHiddenColumns = null;\n }\n\n /**\n * Cleanup after printing\n */\n #cleanup(): void {\n const grid = this.gridElement;\n if (!grid) return;\n\n // Restore columns that were hidden for printing\n this.#restorePrintColumns();\n\n // Remove orientation classes (both original and possibly switched)\n grid.classList.remove('print-portrait', 'print-landscape');\n\n // Remove scaling transform if applied (legacy)\n if (this.#appliedScale !== null) {\n grid.style.transform = '';\n grid.style.transformOrigin = '';\n grid.style.width = '';\n this.#appliedScale = null;\n }\n\n // Remove print header/footer\n if (this.#printHeader) {\n this.#printHeader.remove();\n this.#printHeader = null;\n }\n if (this.#printFooter) {\n this.#printFooter.remove();\n this.#printFooter = null;\n }\n\n // Restore virtualization\n const internalGrid = this.#internalGrid;\n if (this.#savedVirtualization && internalGrid._virtualization) {\n internalGrid._virtualization.bypassThreshold = this.#savedVirtualization.bypassThreshold;\n internalGrid.refreshVirtualWindow(true);\n this.#savedVirtualization = null;\n }\n\n // Restore original rows if they were limited\n if (this.#savedRows !== null) {\n (this.grid as unknown as { rows: unknown[] }).rows = this.#savedRows;\n this.#savedRows = null;\n }\n }\n\n /**\n * Register toolbar button if configured\n * @internal\n */\n override afterRender(): void {\n // Register toolbar on first render when button is enabled\n if (this.config?.button && !this.#toolbarRegistered) {\n this.#registerToolbarButton();\n this.#toolbarRegistered = true;\n }\n }\n\n /** Track if toolbar button is registered */\n #toolbarRegistered = false;\n\n /**\n * Register print button in toolbar\n */\n #registerToolbarButton(): void {\n const grid = this.#internalGrid;\n\n // Register toolbar content\n grid.registerToolbarContent?.({\n id: 'print-button',\n order: 900, // High order to appear at the end\n render: (container: HTMLElement) => {\n const button = document.createElement('button');\n button.className = 'tbw-toolbar-btn tbw-print-btn';\n button.title = 'Print grid';\n button.type = 'button';\n\n // Use print icon\n const icon = this.resolveIcon('print') || '🖨️';\n this.setIcon(button, icon);\n\n button.addEventListener(\n 'click',\n () => {\n this.print();\n },\n { signal: this.disconnectSignal },\n );\n\n container.appendChild(button);\n },\n });\n }\n}\n"],"names":["ISOLATION_STYLE_ID","createIsolationStylesheet","gridId","orientation","style","printGridIsolated","gridElement","options","isolationStyle","resolve","onAfterPrint","DEFAULT_CONFIG","PrintPlugin","BaseGridPlugin","styles","#printing","#savedHiddenColumns","#savedVirtualization","#savedRows","#printHeader","#printFooter","#appliedScale","#internalGrid","params","grid","config","originalRowCount","rowCount","limitApplied","limitInfo","startTime","internalGrid","#hidePrintColumns","#addPrintHeader","#disableVirtualization","#printInIsolatedWindow","#triggerPrint","error","#cleanup","title","titleEl","timestampEl","totalRows","columns","col","#restorePrintColumns","field","wasVisible","#toolbarRegistered","#registerToolbarButton","container","button","icon"],"mappings":"iUAeA,MAAMA,EAAqB,4BAM3B,SAASC,EAA0BC,EAAgBC,EAAiD,CAClG,MAAMC,EAAQ,SAAS,cAAc,OAAO,EAC5C,OAAAA,EAAM,GAAKJ,EACXI,EAAM,YAAc;AAAA;AAAA;AAAA;AAAA,sBAIAF,CAAM;AAAA;AAAA;AAAA;AAAA;AAAA,SAKnBA,CAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAeNA,CAAM;AAAA,SACNA,CAAM;AAAA;AAAA;AAAA;AAAA;AAAA,sBAKOA,CAAM;AAAA,oBACRA,CAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAeNA,CAAM,mBAAmBA,CAAM,WAAWA,CAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAMpDC,CAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAmBlBC,CACT,CAqBA,eAAsBC,EAAkBC,EAA0BC,EAAgC,GAAmB,CACnH,KAAM,CAAE,YAAAJ,EAAc,WAAA,EAAgBI,EAEhCL,EAASI,EAAY,GAGJ,SAAS,iBAAiB,IAAI,IAAI,OAAOJ,CAAM,CAAC,EAAE,EACtD,OAAS,GAC1B,QAAQ,KACN,qDAAqDA,CAAM,8EAAA,EAM/D,SAAS,eAAeF,CAAkB,GAAG,OAAA,EAG7C,MAAMQ,EAAiBP,EAA0BC,EAAQC,CAAW,EACpE,gBAAS,KAAK,YAAYK,CAAc,EAEjC,IAAI,QAASC,GAAY,CAE9B,MAAMC,EAAe,IAAM,CACzB,OAAO,oBAAoB,aAAcA,CAAY,EAErD,SAAS,eAAeV,CAAkB,GAAG,OAAA,EAC7CS,EAAA,CACF,EACA,OAAO,iBAAiB,aAAcC,CAAY,EAGlD,OAAO,MAAA,EAGP,WAAW,IAAM,CACf,OAAO,oBAAoB,aAAcA,CAAY,EACrD,SAAS,eAAeV,CAAkB,GAAG,OAAA,EAC7CS,EAAA,CACF,EAAG,GAAI,CACT,CAAC,CACH,iwECrIME,EAAwC,CAC5C,OAAQ,GACR,YAAa,YACb,cAAe,IACf,QAAS,EACT,aAAc,GACd,iBAAkB,GAClB,MAAO,GACP,QAAS,EACX,EAgEO,MAAMC,UAAoBC,EAAAA,cAA4B,CAElD,KAAO,QAGE,QAAU,QAGV,OAASC,EAG3BC,GAAY,GAGZC,GAAmD,KAGnDC,GAA2D,KAG3DC,GAA+B,KAG/BC,GAAmC,KAGnCC,GAAmC,KAGnCC,GAA+B,KAK/B,GAAIC,IAA8B,CAChC,OAAO,KAAK,IACd,CAKA,YAAsB,CACpB,OAAO,KAAKP,EACd,CAgBA,MAAM,MAAMQ,EAAqC,CAC/C,GAAI,KAAKR,GAAW,CAClB,QAAQ,KAAK,yCAAyC,EACtD,MACF,CAEA,MAAMS,EAAO,KAAK,YAClB,GAAI,CAACA,EAAM,CACT,QAAQ,KAAK,kCAAkC,EAC/C,MACF,CAEA,MAAMC,EAAS,CAAE,GAAGd,EAAgB,GAAG,KAAK,OAAQ,GAAGY,CAAA,EAEjDG,EADO,KAAK,KACY,OAC9B,IAAIC,EAAWD,EACXE,EAAe,GAGnB,GAAIH,EAAO,cAAgB,GAAKC,EAAmBD,EAAO,cAAe,CACvE,MAAMI,EACJJ,EAAO,QAAU,EAAI;AAAA;AAAA,kCAAuCA,EAAO,QAAQ,eAAA,CAAgB,SAAW,GAMxG,GAAI,CALY,QACd,iBAAiBC,EAAiB,eAAA,CAAgB,oFAC6BG,CAAS;AAAA;AAAA,0CAAA,EAIxF,MAEJ,CAGIJ,EAAO,QAAU,GAAKC,EAAmBD,EAAO,UAClDE,EAAWF,EAAO,QAClBG,EAAe,IAGjB,KAAKb,GAAY,GAGjB,MAAMe,EAAY,YAAY,IAAA,EAG9B,KAAK,KAAuB,cAAe,CACzC,SAAAH,EACA,aAAAC,EACA,iBAAAF,CAAA,CACD,EAED,GAAI,CAEF,MAAMK,EAAe,KAAKT,GAC1B,KAAKL,GAAuB,CAC1B,gBAAiBc,EAAa,iBAAiB,iBAAmB,EAAA,EAIpE,KAAKC,GAAA,EAGDJ,IACF,KAAKV,GAAa,KAAK,WAEtB,KAAK,KAAwC,KAAO,KAAK,WAAW,MAAM,EAAGS,CAAQ,EAEtF,MAAM,IAAI,QAASlB,GAAY,WAAWA,EAAS,EAAE,CAAC,IAIpDgB,EAAO,cAAgBA,EAAO,mBAChC,KAAKQ,GAAgBR,CAAM,EAK7B,MAAM,KAAKS,GAAA,EAGX,MAAM,IAAI,QAASzB,GAAY,sBAAsBA,CAAO,CAAC,EAC7D,MAAM,IAAI,QAASA,GAAY,sBAAsBA,CAAO,CAAC,EAG7De,EAAK,UAAU,IAAI,SAASC,EAAO,WAAW,EAAE,EAGhD,MAAM,IAAI,QAAShB,GAAY,sBAAsBA,CAAO,CAAC,EAC7D,MAAM,IAAI,QAASA,GAAY,sBAAsBA,CAAO,CAAC,EAGzDgB,EAAO,QACT,MAAM,KAAKU,GAAuBV,CAAM,EAExC,MAAM,KAAKW,GAAA,EAIb,KAAK,KAA0B,iBAAkB,CAC/C,QAAS,GACT,SAAAT,EACA,SAAU,KAAK,MAAM,YAAY,IAAA,EAAQG,CAAS,CAAA,CACnD,CACH,OAASO,EAAO,CACd,QAAQ,MAAM,8BAA+BA,CAAK,EAClD,KAAK,KAA0B,iBAAkB,CAC/C,QAAS,GACT,SAAU,EACV,SAAU,KAAK,MAAM,YAAY,IAAA,EAAQP,CAAS,CAAA,CACnD,CACH,QAAA,CAEE,KAAKQ,GAAA,EACL,KAAKvB,GAAY,EACnB,CACF,CAKAkB,GAAgBR,EAAqC,CACnD,MAAMD,EAAO,KAAK,YAClB,GAAKA,EAOL,IAJA,KAAKL,GAAe,SAAS,cAAc,KAAK,EAChD,KAAKA,GAAa,UAAY,mBAG1BM,EAAO,aAAc,CACvB,MAAMc,EAAQd,EAAO,OAAS,KAAK,KAAK,iBAAiB,OAAO,QAAQ,OAAS,YAC3Ee,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAY,yBACpBA,EAAQ,YAAcD,EACtB,KAAKpB,GAAa,YAAYqB,CAAO,CACvC,CAGA,GAAIf,EAAO,iBAAkB,CAC3B,MAAMgB,EAAc,SAAS,cAAc,KAAK,EAChDA,EAAY,UAAY,6BACxBA,EAAY,YAAc,YAAY,IAAI,KAAA,EAAO,gBAAgB,GACjE,KAAKtB,GAAa,YAAYsB,CAAW,CAC3C,CAGAjB,EAAK,aAAa,KAAKL,GAAcK,EAAK,UAAU,EAGpD,KAAKJ,GAAe,SAAS,cAAc,KAAK,EAChD,KAAKA,GAAa,UAAY,mBAC9B,KAAKA,GAAa,YAAc,uBAAuB,OAAO,SAAS,QAAQ,GAC/EI,EAAK,YAAY,KAAKJ,EAAY,EACpC,CAKA,KAAMc,IAAwC,CAC5C,MAAMH,EAAe,KAAKT,GAC1B,GAAI,CAACS,EAAa,gBAAiB,OAInC,MAAMW,EAAY,KAAK,KAAK,OAC5BX,EAAa,gBAAgB,gBAAkBW,EAAY,IAG3DX,EAAa,qBAAqB,EAAI,EAGtC,MAAM,IAAI,QAAStB,GAAY,WAAWA,EAAS,GAAG,CAAC,CACzD,CAKA,KAAM2B,IAA+B,CACnC,OAAO,IAAI,QAAS3B,GAAY,CAE9B,MAAMC,EAAe,IAAM,CACzB,OAAO,oBAAoB,aAAcA,CAAY,EACrDD,EAAA,CACF,EACA,OAAO,iBAAiB,aAAcC,CAAY,EAGlD,OAAO,MAAA,EAGP,WAAW,IAAM,CACf,OAAO,oBAAoB,aAAcA,CAAY,EACrDD,EAAA,CACF,EAAG,GAAI,CACT,CAAC,CACH,CAMA,KAAM0B,GAAuBV,EAA8C,CACzE,MAAMD,EAAO,KAAK,YACbA,GAEL,MAAMnB,EAAkBmB,EAAM,CAC5B,YAAaC,EAAO,WAAA,CACrB,CACH,CAKAO,IAA0B,CACxB,MAAMW,EAAU,KAAK,QACrB,GAAKA,EAGL,MAAK3B,OAA0B,IAE/B,UAAW4B,KAAOD,EACZC,EAAI,aAAeA,EAAI,QAEzB,KAAK5B,GAAoB,IAAI4B,EAAI,MAAO,CAACA,EAAI,MAAM,EAEnD,KAAK,KAAK,iBAAiBA,EAAI,MAAO,EAAK,GAGjD,CAKAC,IAA6B,CAC3B,GAAK,KAAK7B,GAEV,UAAW,CAAC8B,EAAOC,CAAU,IAAK,KAAK/B,GAErC,KAAK,KAAK,iBAAiB8B,EAAOC,CAAU,EAG9C,KAAK/B,GAAsB,KAC7B,CAKAsB,IAAiB,CACf,MAAMd,EAAO,KAAK,YAClB,GAAI,CAACA,EAAM,OAGX,KAAKqB,GAAA,EAGLrB,EAAK,UAAU,OAAO,iBAAkB,iBAAiB,EAGrD,KAAKH,KAAkB,OACzBG,EAAK,MAAM,UAAY,GACvBA,EAAK,MAAM,gBAAkB,GAC7BA,EAAK,MAAM,MAAQ,GACnB,KAAKH,GAAgB,MAInB,KAAKF,KACP,KAAKA,GAAa,OAAA,EAClB,KAAKA,GAAe,MAElB,KAAKC,KACP,KAAKA,GAAa,OAAA,EAClB,KAAKA,GAAe,MAItB,MAAMW,EAAe,KAAKT,GACtB,KAAKL,IAAwBc,EAAa,kBAC5CA,EAAa,gBAAgB,gBAAkB,KAAKd,GAAqB,gBACzEc,EAAa,qBAAqB,EAAI,EACtC,KAAKd,GAAuB,MAI1B,KAAKC,KAAe,OACrB,KAAK,KAAwC,KAAO,KAAKA,GAC1D,KAAKA,GAAa,KAEtB,CAMS,aAAoB,CAEvB,KAAK,QAAQ,QAAU,CAAC,KAAK8B,KAC/B,KAAKC,GAAA,EACL,KAAKD,GAAqB,GAE9B,CAGAA,GAAqB,GAKrBC,IAA+B,CAChB,KAAK3B,GAGb,yBAAyB,CAC5B,GAAI,eACJ,MAAO,IACP,OAAS4B,GAA2B,CAClC,MAAMC,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,UAAY,gCACnBA,EAAO,MAAQ,aACfA,EAAO,KAAO,SAGd,MAAMC,EAAO,KAAK,YAAY,OAAO,GAAK,MAC1C,KAAK,QAAQD,EAAQC,CAAI,EAEzBD,EAAO,iBACL,QACA,IAAM,CACJ,KAAK,MAAA,CACP,EACA,CAAE,OAAQ,KAAK,gBAAA,CAAiB,EAGlCD,EAAU,YAAYC,CAAM,CAC9B,CAAA,CACD,CACH,CACF"}
|
|
1
|
+
{"version":3,"file":"print.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/print/print-isolated.ts","../../../../../libs/grid/src/lib/plugins/print/PrintPlugin.ts"],"sourcesContent":["/**\n * Utility for printing a grid in isolation by hiding all other page content.\n *\n * This approach keeps the grid in place (with virtualization disabled by PrintPlugin)\n * and uses CSS to hide everything else on the page during printing.\n */\n\nimport type { PrintOrientation } from './types';\n\nexport interface PrintIsolatedOptions {\n /** Page orientation hint */\n orientation?: PrintOrientation;\n}\n\n/** ID for the isolation stylesheet */\nconst ISOLATION_STYLE_ID = 'tbw-print-isolation-style';\n\n/**\n * Create a stylesheet that hides everything except the target grid.\n * Uses the grid's ID to target it specifically.\n */\nfunction createIsolationStylesheet(gridId: string, orientation: PrintOrientation): HTMLStyleElement {\n const style = document.createElement('style');\n style.id = ISOLATION_STYLE_ID;\n style.textContent = `\n /* Print isolation: hide everything except the target grid */\n @media print {\n /* Hide all body children by default */\n body > *:not(#${gridId}) {\n display: none !important;\n }\n\n /* But show the grid and ensure it's not hidden by ancestor rules */\n #${gridId} {\n display: block !important;\n position: static !important;\n visibility: visible !important;\n opacity: 1 !important;\n overflow: visible !important;\n height: auto !important;\n width: 100% !important;\n max-height: none !important;\n margin: 0 !important;\n padding: 0 !important;\n transform: none !important;\n }\n\n /* If grid is nested, we need to show its ancestors too */\n #${gridId},\n #${gridId} * {\n visibility: visible !important;\n }\n\n /* Walk up the DOM and show all ancestors of the grid */\n body *:has(> #${gridId}),\n body *:has(#${gridId}) {\n display: block !important;\n visibility: visible !important;\n opacity: 1 !important;\n overflow: visible !important;\n height: auto !important;\n position: static !important;\n transform: none !important;\n background: transparent !important;\n border: none !important;\n padding: 0 !important;\n margin: 0 !important;\n }\n\n /* Hide siblings of ancestors (everything that's not in the path to the grid) */\n body *:has(#${gridId}) > *:not(:has(#${gridId})):not(#${gridId}) {\n display: none !important;\n }\n\n /* Page settings */\n @page {\n size: ${orientation};\n margin: 1cm;\n }\n\n /* Ensure proper print styling */\n body {\n margin: 0 !important;\n padding: 0 !important;\n background: white !important;\n color-scheme: light !important;\n }\n }\n\n /* Screen: also apply isolation for print preview */\n @media screen {\n /* When this stylesheet is active, we're about to print */\n /* No screen-specific rules needed - isolation only applies to print */\n }\n `;\n return style;\n}\n\n/**\n * Print a grid in isolation by hiding all other page content.\n *\n * This function adds a temporary stylesheet that uses CSS to hide everything\n * on the page except the target grid during printing. The grid stays in place\n * with all its data (virtualization should be disabled separately).\n *\n * @param gridElement - The tbw-grid element to print (must have an ID)\n * @param options - Optional configuration\n * @returns Promise that resolves when the print dialog closes\n *\n * @example\n * ```typescript\n * import { printGridIsolated } from '@toolbox-web/grid/plugins/print';\n *\n * const grid = document.querySelector('tbw-grid');\n * await printGridIsolated(grid, { orientation: 'landscape' });\n * ```\n */\nexport async function printGridIsolated(gridElement: HTMLElement, options: PrintIsolatedOptions = {}): Promise<void> {\n const { orientation = 'landscape' } = options;\n\n const gridId = gridElement.id;\n\n // Warn if multiple elements share this ID (user-set IDs could collide)\n const elementsWithId = document.querySelectorAll(`#${CSS.escape(gridId)}`);\n if (elementsWithId.length > 1) {\n console.warn(\n `[tbw-grid:print] Multiple elements found with id=\"${gridId}\". ` +\n `Print isolation may not work correctly. Ensure each grid has a unique ID.`,\n );\n }\n\n // Remove any existing isolation stylesheet\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n\n // Add the isolation stylesheet\n const isolationStyle = createIsolationStylesheet(gridId, orientation);\n document.head.appendChild(isolationStyle);\n\n return new Promise((resolve) => {\n // Listen for afterprint event to cleanup\n const onAfterPrint = () => {\n window.removeEventListener('afterprint', onAfterPrint);\n // Remove isolation stylesheet\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n resolve();\n };\n window.addEventListener('afterprint', onAfterPrint);\n\n // Trigger print\n window.print();\n\n // Fallback timeout in case afterprint doesn't fire (some browsers)\n setTimeout(() => {\n window.removeEventListener('afterprint', onAfterPrint);\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n resolve();\n }, 5000);\n });\n}\n","/**\n * Print Plugin (Class-based)\n *\n * Provides print layout functionality for tbw-grid.\n * Temporarily disables virtualization to render all rows and uses\n * @media print CSS for print-optimized styling.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { InternalGrid, ToolbarContentDefinition } from '../../core/types';\nimport { printGridIsolated } from './print-isolated';\nimport styles from './print.css?inline';\nimport type { PrintCompleteDetail, PrintConfig, PrintParams, PrintStartDetail } from './types';\n\n/**\n * Extended grid interface for PrintPlugin internal access.\n * Includes registerToolbarContent which is available on the grid class\n * but not exposed in the standard plugin API.\n */\ninterface PrintGridRef extends InternalGrid {\n registerToolbarContent?(content: ToolbarContentDefinition): void;\n unregisterToolbarContent?(contentId: string): void;\n}\n\n/** Default configuration */\nconst DEFAULT_CONFIG: Required<PrintConfig> = {\n button: false,\n orientation: 'landscape',\n warnThreshold: 500,\n maxRows: 0,\n includeTitle: true,\n includeTimestamp: true,\n title: '',\n isolate: false,\n};\n\n/**\n * Print Plugin for tbw-grid\n *\n * Enables printing the full grid content by temporarily disabling virtualization\n * and applying print-optimized styles. Handles large datasets gracefully with\n * configurable row limits.\n *\n * ## Installation\n *\n * ```ts\n * import { PrintPlugin } from '@toolbox-web/grid/plugins/print';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `button` | `boolean` | `false` | Show print button in toolbar |\n * | `orientation` | `'portrait' \\| 'landscape'` | `'landscape'` | Page orientation |\n * | `warnThreshold` | `number` | `500` | Show confirmation dialog when rows exceed this (0 = no warning) |\n * | `maxRows` | `number` | `0` | Hard limit on printed rows (0 = unlimited) |\n * | `includeTitle` | `boolean` | `true` | Include grid title in print |\n * | `includeTimestamp` | `boolean` | `true` | Include timestamp in footer |\n * | `title` | `string` | `''` | Custom print title |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `print` | `(params?) => Promise<void>` | Trigger print dialog |\n * | `isPrinting` | `() => boolean` | Check if print is in progress |\n *\n * ## Events\n *\n * | Event | Detail | Description |\n * |-------|--------|-------------|\n * | `print-start` | `PrintStartDetail` | Fired when print begins |\n * | `print-complete` | `PrintCompleteDetail` | Fired when print completes |\n *\n * @example Basic Print\n * ```ts\n * import { PrintPlugin } from '@toolbox-web/grid/plugins/print';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * plugins: [new PrintPlugin()],\n * };\n *\n * // Trigger print\n * const printPlugin = grid.getPlugin(PrintPlugin);\n * await printPlugin.print();\n * ```\n *\n * @example With Toolbar Button\n * ```ts\n * grid.gridConfig = {\n * plugins: [new PrintPlugin({ button: true, orientation: 'landscape' })],\n * };\n * ```\n *\n * @see {@link PrintConfig} for all configuration options\n */\nexport class PrintPlugin extends BaseGridPlugin<PrintConfig> {\n /** @internal */\n readonly name = 'print';\n\n /** @internal */\n override readonly version = '1.0.0';\n\n /** CSS styles for print mode */\n override readonly styles = styles;\n\n /** Current print state */\n #printing = false;\n\n /** Saved column visibility state */\n #savedHiddenColumns: Map<string, boolean> | null = null;\n\n /** Saved virtualization state */\n #savedVirtualization: { bypassThreshold: number } | null = null;\n\n /** Saved rows when maxRows limit is applied */\n #savedRows: unknown[] | null = null;\n\n /** Print header element */\n #printHeader: HTMLElement | null = null;\n\n /** Print footer element */\n #printFooter: HTMLElement | null = null;\n\n /** Applied scale factor (legacy, used for cleanup) */\n #appliedScale: number | null = null;\n\n /**\n * Get the grid typed as PrintGridRef for internal access.\n */\n get #internalGrid(): PrintGridRef {\n return this.grid as unknown as PrintGridRef;\n }\n\n /**\n * Check if print is currently in progress\n */\n isPrinting(): boolean {\n return this.#printing;\n }\n\n /**\n * Trigger the browser print dialog\n *\n * This method:\n * 1. Validates row count against maxRows limit\n * 2. Disables virtualization to render all rows\n * 3. Applies print-specific CSS classes\n * 4. Opens the browser print dialog (or isolated window if `isolate: true`)\n * 5. Restores normal state after printing\n *\n * @param params - Optional parameters to override config for this print\n * @param params.isolate - If true, prints in an isolated window containing only the grid\n * @returns Promise that resolves when print dialog closes\n */\n async print(params?: PrintParams): Promise<void> {\n if (this.#printing) {\n console.warn('[PrintPlugin] Print already in progress');\n return;\n }\n\n const grid = this.gridElement;\n if (!grid) {\n console.warn('[PrintPlugin] Grid not available');\n return;\n }\n\n const config = { ...DEFAULT_CONFIG, ...this.config, ...params };\n const rows = this.rows;\n const originalRowCount = rows.length;\n let rowCount = originalRowCount;\n let limitApplied = false;\n\n // Check if we should warn about large datasets\n if (config.warnThreshold > 0 && originalRowCount > config.warnThreshold) {\n const limitInfo =\n config.maxRows > 0 ? `\\n\\nNote: Output will be limited to ${config.maxRows.toLocaleString()} rows.` : '';\n const proceed = confirm(\n `This grid has ${originalRowCount.toLocaleString()} rows. ` +\n `Printing large datasets may cause performance issues or browser slowdowns.${limitInfo}\\n\\n` +\n `Click OK to continue, or Cancel to abort.`,\n );\n if (!proceed) {\n return;\n }\n }\n\n // Apply hard row limit if configured\n if (config.maxRows > 0 && originalRowCount > config.maxRows) {\n rowCount = config.maxRows;\n limitApplied = true;\n }\n\n this.#printing = true;\n\n // Track timing for duration reporting\n const startTime = performance.now();\n\n // Emit print-start event\n this.emit<PrintStartDetail>('print-start', {\n rowCount,\n limitApplied,\n originalRowCount,\n });\n\n try {\n // Save current virtualization state\n const internalGrid = this.#internalGrid;\n this.#savedVirtualization = {\n bypassThreshold: internalGrid._virtualization?.bypassThreshold ?? 24,\n };\n\n // Hide columns marked with printHidden\n this.#hidePrintColumns();\n\n // Apply row limit if configured\n if (limitApplied) {\n this.#savedRows = this.sourceRows;\n // Set limited rows on the grid\n (this.grid as unknown as { rows: unknown[] }).rows = this.sourceRows.slice(0, rowCount);\n // Wait for grid to process new rows\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n\n // Add print header if configured\n if (config.includeTitle || config.includeTimestamp) {\n this.#addPrintHeader(config);\n }\n\n // Disable virtualization to render all rows\n // This forces the grid to render all rows in the DOM\n await this.#disableVirtualization();\n\n // Wait for next frame to ensure DOM is updated\n await new Promise((resolve) => requestAnimationFrame(resolve));\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n // Add orientation class for @page rules\n grid.classList.add(`print-${config.orientation}`);\n\n // Wait for next frame to ensure DOM is updated\n await new Promise((resolve) => requestAnimationFrame(resolve));\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n // Trigger browser print dialog (isolated or inline)\n if (config.isolate) {\n await this.#printInIsolatedWindow(config);\n } else {\n await this.#triggerPrint();\n }\n\n // Emit print-complete event\n this.emit<PrintCompleteDetail>('print-complete', {\n success: true,\n rowCount,\n duration: Math.round(performance.now() - startTime),\n });\n } catch (error) {\n console.error('[PrintPlugin] Print failed:', error);\n this.emit<PrintCompleteDetail>('print-complete', {\n success: false,\n rowCount: 0,\n duration: Math.round(performance.now() - startTime),\n });\n } finally {\n // Restore normal state\n this.#cleanup();\n this.#printing = false;\n }\n }\n\n /**\n * Add print header with title and timestamp\n */\n #addPrintHeader(config: Required<PrintConfig>): void {\n const grid = this.gridElement;\n if (!grid) return;\n\n // Create print header\n this.#printHeader = document.createElement('div');\n this.#printHeader.className = 'tbw-print-header';\n\n // Title\n if (config.includeTitle) {\n const title = config.title || this.grid.effectiveConfig?.shell?.header?.title || 'Grid Data';\n const titleEl = document.createElement('div');\n titleEl.className = 'tbw-print-header-title';\n titleEl.textContent = title;\n this.#printHeader.appendChild(titleEl);\n }\n\n // Timestamp\n if (config.includeTimestamp) {\n const timestampEl = document.createElement('div');\n timestampEl.className = 'tbw-print-header-timestamp';\n timestampEl.textContent = `Printed: ${new Date().toLocaleString()}`;\n this.#printHeader.appendChild(timestampEl);\n }\n\n // Insert at the beginning of the grid\n grid.insertBefore(this.#printHeader, grid.firstChild);\n\n // Create print footer\n this.#printFooter = document.createElement('div');\n this.#printFooter.className = 'tbw-print-footer';\n this.#printFooter.textContent = `Page generated from ${window.location.hostname}`;\n grid.appendChild(this.#printFooter);\n }\n\n /**\n * Disable virtualization to render all rows\n */\n async #disableVirtualization(): Promise<void> {\n const internalGrid = this.#internalGrid;\n if (!internalGrid._virtualization) return;\n\n // Set bypass threshold higher than total row count to disable virtualization\n // This makes the grid render all rows (up to maxRows) instead of just visible ones\n const totalRows = this.rows.length;\n internalGrid._virtualization.bypassThreshold = totalRows + 100;\n\n // Force a full refresh to re-render with virtualization disabled\n internalGrid.refreshVirtualWindow(true);\n\n // Wait for render to complete\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n\n /**\n * Trigger the browser print dialog\n */\n async #triggerPrint(): Promise<void> {\n return new Promise((resolve) => {\n // Listen for afterprint event\n const onAfterPrint = () => {\n window.removeEventListener('afterprint', onAfterPrint);\n resolve();\n };\n window.addEventListener('afterprint', onAfterPrint);\n\n // Trigger print\n window.print();\n\n // Fallback timeout in case afterprint doesn't fire (some browsers)\n setTimeout(() => {\n // Guard against test environment teardown where window may be undefined\n if (typeof window !== 'undefined') {\n window.removeEventListener('afterprint', onAfterPrint);\n }\n resolve();\n }, 1000);\n });\n }\n\n /**\n * Print in isolation by hiding all other page content.\n * This excludes navigation, sidebars, etc. while keeping the grid in place.\n */\n async #printInIsolatedWindow(config: Required<PrintConfig>): Promise<void> {\n const grid = this.gridElement;\n if (!grid) return;\n\n await printGridIsolated(grid, {\n orientation: config.orientation,\n });\n }\n\n /**\n * Hide columns marked with printHidden: true\n */\n #hidePrintColumns(): void {\n const columns = this.columns;\n if (!columns) return;\n\n // Save current hidden state and hide print columns\n this.#savedHiddenColumns = new Map();\n\n for (const col of columns) {\n if (col.printHidden && col.field) {\n // Save current visibility state (true = visible, false = hidden)\n this.#savedHiddenColumns.set(col.field, !col.hidden);\n // Hide the column for printing\n this.grid.setColumnVisible(col.field, false);\n }\n }\n }\n\n /**\n * Restore columns that were hidden for printing\n */\n #restorePrintColumns(): void {\n if (!this.#savedHiddenColumns) return;\n\n for (const [field, wasVisible] of this.#savedHiddenColumns) {\n // Restore original visibility\n this.grid.setColumnVisible(field, wasVisible);\n }\n\n this.#savedHiddenColumns = null;\n }\n\n /**\n * Cleanup after printing\n */\n #cleanup(): void {\n const grid = this.gridElement;\n if (!grid) return;\n\n // Restore columns that were hidden for printing\n this.#restorePrintColumns();\n\n // Remove orientation classes (both original and possibly switched)\n grid.classList.remove('print-portrait', 'print-landscape');\n\n // Remove scaling transform if applied (legacy)\n if (this.#appliedScale !== null) {\n grid.style.transform = '';\n grid.style.transformOrigin = '';\n grid.style.width = '';\n this.#appliedScale = null;\n }\n\n // Remove print header/footer\n if (this.#printHeader) {\n this.#printHeader.remove();\n this.#printHeader = null;\n }\n if (this.#printFooter) {\n this.#printFooter.remove();\n this.#printFooter = null;\n }\n\n // Restore virtualization\n const internalGrid = this.#internalGrid;\n if (this.#savedVirtualization && internalGrid._virtualization) {\n internalGrid._virtualization.bypassThreshold = this.#savedVirtualization.bypassThreshold;\n internalGrid.refreshVirtualWindow(true);\n this.#savedVirtualization = null;\n }\n\n // Restore original rows if they were limited\n if (this.#savedRows !== null) {\n (this.grid as unknown as { rows: unknown[] }).rows = this.#savedRows;\n this.#savedRows = null;\n }\n }\n\n /**\n * Register toolbar button if configured\n * @internal\n */\n override afterRender(): void {\n // Register toolbar on first render when button is enabled\n if (this.config?.button && !this.#toolbarRegistered) {\n this.#registerToolbarButton();\n this.#toolbarRegistered = true;\n }\n }\n\n /** Track if toolbar button is registered */\n #toolbarRegistered = false;\n\n /**\n * Register print button in toolbar\n */\n #registerToolbarButton(): void {\n const grid = this.#internalGrid;\n\n // Register toolbar content\n grid.registerToolbarContent?.({\n id: 'print-button',\n order: 900, // High order to appear at the end\n render: (container: HTMLElement) => {\n const button = document.createElement('button');\n button.className = 'tbw-toolbar-btn tbw-print-btn';\n button.title = 'Print grid';\n button.type = 'button';\n\n // Use print icon\n const icon = this.resolveIcon('print') || '🖨️';\n this.setIcon(button, icon);\n\n button.addEventListener(\n 'click',\n () => {\n this.print();\n },\n { signal: this.disconnectSignal },\n );\n\n container.appendChild(button);\n },\n });\n }\n}\n"],"names":["ISOLATION_STYLE_ID","createIsolationStylesheet","gridId","orientation","style","printGridIsolated","gridElement","options","isolationStyle","resolve","onAfterPrint","DEFAULT_CONFIG","PrintPlugin","BaseGridPlugin","styles","#printing","#savedHiddenColumns","#savedVirtualization","#savedRows","#printHeader","#printFooter","#appliedScale","#internalGrid","params","grid","config","originalRowCount","rowCount","limitApplied","limitInfo","startTime","internalGrid","#hidePrintColumns","#addPrintHeader","#disableVirtualization","#printInIsolatedWindow","#triggerPrint","error","#cleanup","title","titleEl","timestampEl","totalRows","columns","col","#restorePrintColumns","field","wasVisible","#toolbarRegistered","#registerToolbarButton","container","button","icon"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(c,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("../../core/internal/keyboard"),require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/keyboard","../../core/plugin/base-plugin"],g):(c=typeof globalThis<"u"?globalThis:c||self,g(c.TbwGridPlugin_reorder={},c.TbwGrid,c.TbwGrid))})(this,(function(c,g,
|
|
1
|
+
(function(c,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("../../core/internal/keyboard"),require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/keyboard","../../core/plugin/base-plugin"],g):(c=typeof globalThis<"u"?globalThis:c||self,g(c.TbwGridPlugin_reorder={},c.TbwGrid,c.TbwGrid))})(this,(function(c,g,p){"use strict";function b(l){const e=l.meta??{};return e.lockPosition!==!0&&e.suppressMovable!==!0}function h(l,e,r){if(e===r||e<0||e>=l.length||r<0||r>l.length)return l;const n=[...l],[t]=n.splice(e,1);return n.splice(r,0,t),n}const v='@layer tbw-plugins{.header-row>.cell[draggable=true]{cursor:grab;position:relative}.header-row>.cell.dragging{opacity:.5;cursor:grabbing}.header-row>.cell.drop-before:before{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent));z-index:1}.header-row>.cell.drop-after:after{content:"";position:absolute;right:0;top:0;bottom:0;width:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent));z-index:1}.cell.flip-animating{transition:transform var(--tbw-animation-duration, .2s) ease-out;will-change:transform;z-index:1}@keyframes reorder-fade-in{0%{opacity:0}to{opacity:1}}.cell.fade-animating{animation:reorder-fade-in var(--tbw-animation-duration, .2s) ease-out backwards}}';class C extends p.BaseGridPlugin{name="reorder";styles=v;get defaultConfig(){return{animation:"flip"}}get animationType(){return this.isAnimationEnabled?this.config.animation!==void 0?this.config.animation:"flip":!1}get animationDuration(){return this.config.animationDuration!==void 0?this.config.animationDuration:super.animationDuration}isDragging=!1;draggedField=null;draggedIndex=null;dropIndex=null;canMoveColumnWithPlugins(e){return!e||!b(e)?!1:!this.grid.query("canMoveColumn",e).includes(!1)}clearDragClasses(){this.gridElement?.querySelectorAll(".header-row > .cell").forEach(e=>{e.classList.remove("dragging","drop-target","drop-before","drop-after")})}attach(e){super.attach(e),e.addEventListener("column-reorder-request",r=>{const n=r.detail;n?.field&&typeof n.toIndex=="number"&&this.moveColumn(n.field,n.toIndex)},{signal:this.disconnectSignal})}detach(){this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null}afterRender(){const e=this.gridElement;if(!e)return;e.querySelectorAll(".header-row > .cell").forEach(n=>{const t=n,s=t.getAttribute("data-field");if(!s)return;const d=this.columns.find(i=>i.field===s);if(!this.canMoveColumnWithPlugins(d)){t.draggable=!1;return}t.draggable=!0,!t.getAttribute("data-dragstart-bound")&&(t.setAttribute("data-dragstart-bound","true"),t.addEventListener("dragstart",i=>{const a=this.getColumnOrder().indexOf(s);this.isDragging=!0,this.draggedField=s,this.draggedIndex=a,i.dataTransfer&&(i.dataTransfer.effectAllowed="move",i.dataTransfer.setData("text/plain",s)),t.classList.add("dragging")}),t.addEventListener("dragend",()=>{this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null,this.clearDragClasses()}),t.addEventListener("dragover",i=>{if(i.preventDefault(),!this.isDragging||this.draggedField===s)return;const o=t.getBoundingClientRect(),a=o.left+o.width/2,f=this.getColumnOrder().indexOf(s);this.dropIndex=i.clientX<a?f:f+1,t.classList.add("drop-target"),t.classList.toggle("drop-before",i.clientX<a),t.classList.toggle("drop-after",i.clientX>=a)}),t.addEventListener("dragleave",()=>{t.classList.remove("drop-target","drop-before","drop-after")}),t.addEventListener("drop",i=>{i.preventDefault();const o=this.draggedField,a=this.draggedIndex,u=this.dropIndex;if(!this.isDragging||o===null||a===null||u===null)return;const f=u>a?u-1:u,w=this.getColumnOrder(),m=h(w,a,f),O={field:o,fromIndex:a,toIndex:f,columnOrder:m};this.emitCancelable("column-move",O)||this.updateColumnOrder(m)}))})}onKeyDown(e){if(!e.altKey||e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const r=this.grid,n=r._focusCol,t=r._visibleColumns;if(n<0||n>=t.length)return;const s=t[n];if(!this.canMoveColumnWithPlugins(s))return;const d=this.getColumnOrder(),i=d.indexOf(s.field);if(i===-1)return;const o=e.key==="ArrowLeft"?i-1:i+1;if(o<0||o>=d.length)return;const a=t.find(u=>u.field===d[o]);if(this.canMoveColumnWithPlugins(a))return this.moveColumn(s.field,o),r._focusCol=o,g.ensureCellVisible(this.grid),e.preventDefault(),e.stopPropagation(),!0}getColumnOrder(){return this.grid.getColumnOrder()}moveColumn(e,r){const n=this.getColumnOrder(),t=n.indexOf(e);if(t===-1)return;const s=h(n,t,r);this.emitCancelable("column-move",{field:e,fromIndex:t,toIndex:r,columnOrder:s})||this.updateColumnOrder(s)}setColumnOrder(e){this.updateColumnOrder(e)}resetColumnOrder(){const e=this.columns.map(r=>r.field);this.updateColumnOrder(e)}captureHeaderPositions(){const e=new Map;return this.gridElement?.querySelectorAll(".header-row > .cell[data-field]").forEach(r=>{const n=r.getAttribute("data-field");n&&e.set(n,r.getBoundingClientRect().left)}),e}animateFLIP(e){const r=this.gridElement;if(!r||e.size===0)return;const n=new Map;if(r.querySelectorAll(".header-row > .cell[data-field]").forEach(d=>{const i=d.getAttribute("data-field");if(!i)return;const o=e.get(i);if(o===void 0)return;const a=o-d.getBoundingClientRect().left;Math.abs(a)>1&&n.set(i,a)}),n.size===0)return;const t=[];if(r.querySelectorAll(".cell[data-field]").forEach(d=>{const i=n.get(d.getAttribute("data-field")??"");if(i!==void 0){const o=d;o.style.transform=`translateX(${i}px)`,t.push(o)}}),t.length===0)return;this.gridElement.offsetHeight;const s=this.animationDuration;requestAnimationFrame(()=>{t.forEach(d=>{d.classList.add("flip-animating"),d.style.transform=""}),setTimeout(()=>{t.forEach(d=>{d.style.transform="",d.classList.remove("flip-animating")})},s+50)})}animateFade(e){const r=this.gridElement;if(!r){e();return}const n=this.captureHeaderPositions();e();const t=new Set;if(r.querySelectorAll(".header-row > .cell[data-field]").forEach(i=>{const o=i.getAttribute("data-field");if(!o)return;const a=n.get(o);if(a===void 0)return;const u=i.getBoundingClientRect().left;Math.abs(a-u)>1&&t.add(o)}),t.size===0)return;const s=[];if(r.querySelectorAll(".cell[data-field]").forEach(i=>{const o=i.getAttribute("data-field");if(o&&t.has(o)){const a=i;a.classList.add("fade-animating"),s.push(a)}}),s.length===0)return;const d=this.animationDuration;setTimeout(()=>{s.forEach(i=>i.classList.remove("fade-animating"))},d+50)}updateColumnOrder(e){const r=this.animationType;if(r==="flip"&&this.gridElement){const n=this.captureHeaderPositions();this.grid.setColumnOrder(e),requestAnimationFrame(()=>{this.gridElement.offsetHeight,this.animateFLIP(n)})}else r==="fade"?this.animateFade(()=>this.grid.setColumnOrder(e)):this.grid.setColumnOrder(e);this.grid.requestStateChange?.()}}c.ReorderPlugin=C,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=reorder.umd.js.map
|