@toolbox-web/grid 0.4.2 → 0.6.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 +2 -3
- package/all.js +1063 -1024
- package/all.js.map +1 -1
- package/index.js +1078 -912
- package/index.js.map +1 -1
- package/lib/core/grid.d.ts +28 -0
- package/lib/core/grid.d.ts.map +1 -1
- package/lib/core/internal/dom-builder.d.ts +2 -0
- package/lib/core/internal/dom-builder.d.ts.map +1 -1
- package/lib/core/internal/event-delegation.d.ts +21 -0
- package/lib/core/internal/event-delegation.d.ts.map +1 -1
- package/lib/core/internal/header.d.ts.map +1 -1
- package/lib/core/internal/resize.d.ts.map +1 -1
- package/lib/core/internal/rows.d.ts +1 -1
- package/lib/core/internal/rows.d.ts.map +1 -1
- package/lib/core/internal/shell.d.ts +19 -13
- package/lib/core/internal/shell.d.ts.map +1 -1
- package/lib/core/plugin/base-plugin.d.ts +13 -2
- package/lib/core/plugin/base-plugin.d.ts.map +1 -1
- package/lib/core/plugin/expander-column.d.ts.map +1 -1
- package/lib/core/plugin/plugin-manager.d.ts +6 -2
- package/lib/core/plugin/plugin-manager.d.ts.map +1 -1
- package/lib/core/types.d.ts +41 -3
- package/lib/core/types.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.js +22 -11
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js +59 -48
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js +71 -60
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/editing/EditingPlugin.d.ts +1 -0
- package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
- package/lib/plugins/editing/index.js +93 -80
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/export/index.js +29 -18
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/FilteringPlugin.d.ts +9 -1
- package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
- package/lib/plugins/filtering/index.js +199 -165
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts +1 -0
- package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts.map +1 -1
- package/lib/plugins/grouping-columns/index.js +79 -49
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts.map +1 -1
- package/lib/plugins/grouping-rows/index.js +98 -87
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js +70 -57
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js +48 -37
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts.map +1 -1
- package/lib/plugins/pinned-columns/index.js +71 -66
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-columns/pinned-columns.d.ts +2 -2
- package/lib/plugins/pinned-columns/pinned-columns.d.ts.map +1 -1
- package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts.map +1 -1
- package/lib/plugins/pinned-rows/index.js +63 -52
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/PivotPlugin.d.ts.map +1 -1
- package/lib/plugins/pivot/index.js +310 -299
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/reorder/ReorderPlugin.d.ts.map +1 -1
- package/lib/plugins/reorder/index.d.ts +1 -1
- package/lib/plugins/reorder/index.d.ts.map +1 -1
- package/lib/plugins/reorder/index.js +79 -68
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/selection/SelectionPlugin.d.ts.map +1 -1
- package/lib/plugins/selection/index.js +115 -105
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js +15 -4
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/TreePlugin.d.ts.map +1 -1
- package/lib/plugins/tree/index.js +41 -30
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js +29 -18
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/VisibilityPlugin.d.ts.map +1 -1
- package/lib/plugins/visibility/index.js +59 -47
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +6 -6
- package/public.d.ts +42 -0
- package/public.d.ts.map +1 -1
- package/themes/dg-theme-bootstrap.css +55 -53
- package/themes/dg-theme-contrast.css +42 -40
- package/themes/dg-theme-large.css +38 -37
- package/themes/dg-theme-material.css +54 -52
- package/themes/dg-theme-standard.css +19 -17
- package/themes/dg-theme-vibrant.css +16 -14
- package/umd/grid.all.umd.js +23 -22
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +15 -14
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/column-virtualization.umd.js +1 -1
- package/umd/plugins/column-virtualization.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-columns.umd.js +1 -1
- package/umd/plugins/grouping-columns.umd.js.map +1 -1
- package/umd/plugins/grouping-rows.umd.js +1 -1
- package/umd/plugins/grouping-rows.umd.js.map +1 -1
- package/umd/plugins/master-detail.umd.js +1 -1
- package/umd/plugins/master-detail.umd.js.map +1 -1
- package/umd/plugins/multi-sort.umd.js +1 -1
- package/umd/plugins/multi-sort.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/pinned-rows.umd.js +1 -1
- package/umd/plugins/pinned-rows.umd.js.map +1 -1
- package/umd/plugins/pivot.umd.js +1 -1
- package/umd/plugins/pivot.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/selection.umd.js +1 -1
- 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/visibility.umd.js +1 -1
- package/umd/plugins/visibility.umd.js.map +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(u,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],a):(u=typeof globalThis<"u"?globalThis:u||self,a(u.TbwGridPlugin_columnVirtualization={},u.TbwGrid))})(this,(function(u,a){"use strict";function f(e){if(e==null)return 100;if(typeof e=="number")return e;const t=parseFloat(e);return isNaN(t)?100:t}function d(e){return e.map(t=>f(t.width))}function c(e){const t=[];let i=0;for(const s of e)t.push(i),i+=f(s.width);return t}function g(e){return e.reduce((t,i)=>t+f(i.width),0)}function m(e,t,i,s,
|
|
1
|
+
(function(u,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],a):(u=typeof globalThis<"u"?globalThis:u||self,a(u.TbwGridPlugin_columnVirtualization={},u.TbwGrid))})(this,(function(u,a){"use strict";function f(e){if(e==null)return 100;if(typeof e=="number")return e;const t=parseFloat(e);return isNaN(t)?100:t}function d(e){return e.map(t=>f(t.width))}function c(e){const t=[];let i=0;for(const s of e)t.push(i),i+=f(s.width);return t}function g(e){return e.reduce((t,i)=>t+f(i.width),0)}function m(e,t,i,s,l){const o=i.length;if(o===0)return{startCol:0,endCol:0,visibleColumns:[]};let n=p(e,i,s);n=Math.max(0,n-l);const V=e+t;let h=n;for(let r=n;r<o;r++){if(i[r]>=V){h=r-1;break}h=r}h=Math.min(o-1,h+l);const C=[];for(let r=n;r<=h;r++)C.push(r);return{startCol:n,endCol:h,visibleColumns:C}}function p(e,t,i){let s=0,l=t.length-1;for(;s<l;){const o=Math.floor((s+l)/2);t[o]+i[o]<=e?s=o+1:l=o}return s}function W(e,t,i){return i?e>t:!1}class b extends a.BaseGridPlugin{name="columnVirtualization";get defaultConfig(){return{autoEnable:!0,threshold:30,overscan:3}}isVirtualized=!1;startCol=0;endCol=0;scrollLeft=0;totalWidth=0;columnWidths=[];columnOffsets=[];attach(t){super.attach(t);const i=this.columns;this.columnWidths=d(i),this.columnOffsets=c(i),this.totalWidth=g(i),this.endCol=i.length-1}detach(){this.columnWidths=[],this.columnOffsets=[],this.isVirtualized=!1,this.startCol=0,this.endCol=0,this.scrollLeft=0,this.totalWidth=0}processColumns(t){const i=W(t.length,this.config.threshold??30,this.config.autoEnable??!0);if(this.isVirtualized=i??!1,this.columnWidths=d(t),this.columnOffsets=c(t),this.totalWidth=g(t),!i)return this.startCol=0,this.endCol=t.length-1,[...t];const s=this.grid.clientWidth||800,l=m(this.scrollLeft,s,this.columnOffsets,this.columnWidths,this.config.overscan??3);return this.startCol=l.startCol,this.endCol=l.endCol,l.visibleColumns.map(o=>t[o])}afterRender(){if(!this.isVirtualized)return;const t=this.gridElement;if(!t)return;const i=this.columnOffsets[this.startCol]??0,s=t.querySelector(".header-row"),l=t.querySelectorAll(".data-grid-row");s&&(s.style.paddingLeft=`${i}px`),l.forEach(n=>{n.style.paddingLeft=`${i}px`});const o=t.querySelector(".rows-viewport .rows");o&&(o.style.width=`${this.totalWidth}px`)}onScroll(t){!this.isVirtualized||Math.abs(t.scrollLeft-this.scrollLeft)<1||(this.scrollLeft=t.scrollLeft,this.requestRender())}getIsVirtualized(){return this.isVirtualized}getVisibleColumnRange(){return{start:this.startCol,end:this.endCol}}scrollToColumn(t){const i=this.columnOffsets[t]??0,s=this.grid;s.scrollLeft=i}getColumnOffset(t){return this.columnOffsets[t]??0}getTotalWidth(){return this.totalWidth}}u.ColumnVirtualizationPlugin=b,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=column-virtualization.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"column-virtualization.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/column-virtualization/column-virtualization.ts","../../../../../libs/grid/src/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.ts"],"sourcesContent":["/**\n * Column Virtualization Core Logic\n *\n * Pure functions for horizontal column virtualization operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnVirtualizationViewport } from './types';\n\n/** Default column width when not specified */\nconst DEFAULT_COLUMN_WIDTH = 100;\n\n/**\n * Parse a column width value to pixels.\n * Handles number (px) and string formats.\n *\n * @param width - The width value from column config\n * @returns Width in pixels\n */\nexport function parseColumnWidth(width: string | number | undefined): number {\n if (width === undefined || width === null) {\n return DEFAULT_COLUMN_WIDTH;\n }\n\n if (typeof width === 'number') {\n return width;\n }\n\n // Handle string values - extract numeric part\n const numeric = parseFloat(width);\n if (!isNaN(numeric)) {\n return numeric;\n }\n\n return DEFAULT_COLUMN_WIDTH;\n}\n\n/**\n * Get array of column widths in pixels.\n *\n * @param columns - Column configurations\n * @returns Array of widths in pixels\n */\nexport function getColumnWidths(columns: readonly ColumnConfig[]): number[] {\n return columns.map((col) => parseColumnWidth(col.width));\n}\n\n/**\n * Compute cumulative left offsets for each column.\n *\n * @param columns - Column configurations\n * @returns Array of left offsets in pixels\n */\nexport function computeColumnOffsets(columns: readonly ColumnConfig[]): number[] {\n const offsets: number[] = [];\n let offset = 0;\n\n for (const col of columns) {\n offsets.push(offset);\n offset += parseColumnWidth(col.width);\n }\n\n return offsets;\n}\n\n/**\n * Compute total width of all columns.\n *\n * @param columns - Column configurations\n * @returns Total width in pixels\n */\nexport function computeTotalWidth(columns: readonly ColumnConfig[]): number {\n return columns.reduce((sum, col) => sum + parseColumnWidth(col.width), 0);\n}\n\n/**\n * Find the visible column range based on scroll position.\n * Uses binary search for efficient lookup in large column sets.\n *\n * @param scrollLeft - Current horizontal scroll position\n * @param viewportWidth - Width of the visible viewport\n * @param columnOffsets - Array of column left offsets\n * @param columnWidths - Array of column widths\n * @param overscan - Number of extra columns to render on each side\n * @returns Viewport information with visible column indices\n */\nexport function getVisibleColumnRange(\n scrollLeft: number,\n viewportWidth: number,\n columnOffsets: number[],\n columnWidths: number[],\n overscan: number\n): ColumnVirtualizationViewport {\n const columnCount = columnOffsets.length;\n\n if (columnCount === 0) {\n return { startCol: 0, endCol: 0, visibleColumns: [] };\n }\n\n // Binary search for first visible column\n let startCol = binarySearchFirstVisible(scrollLeft, columnOffsets, columnWidths);\n startCol = Math.max(0, startCol - overscan);\n\n // Find last visible column (without overscan first)\n const rightEdge = scrollLeft + viewportWidth;\n let endCol = startCol;\n\n for (let i = startCol; i < columnCount; i++) {\n if (columnOffsets[i] >= rightEdge) {\n endCol = i - 1;\n break;\n }\n endCol = i;\n }\n\n // Apply overscan to end (only once)\n endCol = Math.min(columnCount - 1, endCol + overscan);\n\n // Build array of visible column indices\n const visibleColumns: number[] = [];\n for (let i = startCol; i <= endCol; i++) {\n visibleColumns.push(i);\n }\n\n return { startCol, endCol, visibleColumns };\n}\n\n/**\n * Binary search to find the first column that is visible.\n *\n * @param scrollLeft - Current scroll position\n * @param columnOffsets - Array of column offsets\n * @param columnWidths - Array of column widths\n * @returns Index of first visible column\n */\nfunction binarySearchFirstVisible(scrollLeft: number, columnOffsets: number[], columnWidths: number[]): number {\n let low = 0;\n let high = columnOffsets.length - 1;\n\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const colRight = columnOffsets[mid] + columnWidths[mid];\n\n if (colRight <= scrollLeft) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n\n return low;\n}\n\n/**\n * Determine if column virtualization should be active.\n *\n * @param columnCount - Number of columns\n * @param threshold - Column count threshold\n * @param autoEnable - Whether auto-enable is configured\n * @returns True if virtualization should be active\n */\nexport function shouldVirtualize(columnCount: number, threshold: number, autoEnable: boolean): boolean {\n if (!autoEnable) return false;\n return columnCount > threshold;\n}\n","/**\n * Column Virtualization Plugin (Class-based)\n *\n * Provides horizontal column virtualization for grids with many columns.\n * Significantly improves rendering performance when dealing with >30 columns.\n */\n\nimport { BaseGridPlugin, ScrollEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n computeColumnOffsets,\n computeTotalWidth,\n getColumnWidths,\n getVisibleColumnRange,\n shouldVirtualize,\n} from './column-virtualization';\nimport type { ColumnVirtualizationConfig } from './types';\n\n/**\n * Column Virtualization Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ColumnVirtualizationPlugin({ threshold: 30, overscan: 3 })\n * ```\n */\nexport class ColumnVirtualizationPlugin extends BaseGridPlugin<ColumnVirtualizationConfig> {\n readonly name = 'columnVirtualization';\n\n protected override get defaultConfig(): Partial<ColumnVirtualizationConfig> {\n return {\n autoEnable: true,\n threshold: 30,\n overscan: 3,\n };\n }\n\n // #region Internal State\n private isVirtualized = false;\n private startCol = 0;\n private endCol = 0;\n private scrollLeft = 0;\n private totalWidth = 0;\n private columnWidths: number[] = [];\n private columnOffsets: number[] = [];\n // #endregion\n\n // #region Lifecycle\n\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Initialize state from current columns\n const columns = this.columns;\n this.columnWidths = getColumnWidths(columns);\n this.columnOffsets = computeColumnOffsets(columns);\n this.totalWidth = computeTotalWidth(columns);\n this.endCol = columns.length - 1;\n }\n\n override detach(): void {\n this.columnWidths = [];\n this.columnOffsets = [];\n this.isVirtualized = false;\n this.startCol = 0;\n this.endCol = 0;\n this.scrollLeft = 0;\n this.totalWidth = 0;\n }\n // #endregion\n\n // #region Hooks\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n const isVirtualized = shouldVirtualize(columns.length, this.config.threshold ?? 30, this.config.autoEnable ?? true);\n\n // Update state with current column metrics\n this.isVirtualized = isVirtualized ?? false;\n this.columnWidths = getColumnWidths(columns);\n this.columnOffsets = computeColumnOffsets(columns);\n this.totalWidth = computeTotalWidth(columns);\n\n if (!isVirtualized) {\n this.startCol = 0;\n this.endCol = columns.length - 1;\n return [...columns];\n }\n\n // Get viewport width from grid element\n const viewportWidth = (this.grid as unknown as HTMLElement).clientWidth || 800;\n const viewport = getVisibleColumnRange(\n this.scrollLeft,\n viewportWidth,\n this.columnOffsets,\n this.columnWidths,\n this.config.overscan ?? 3,\n );\n\n this.startCol = viewport.startCol;\n this.endCol = viewport.endCol;\n\n // Return only visible columns\n return viewport.visibleColumns.map((i) => columns[i]);\n }\n\n override afterRender(): void {\n if (!this.isVirtualized) return;\n\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n // Apply left padding to offset scrolled-out columns\n const leftPadding = this.columnOffsets[this.startCol] ?? 0;\n\n const headerRow = shadowRoot.querySelector('.header-row');\n const bodyRows = shadowRoot.querySelectorAll('.data-grid-row');\n\n if (headerRow) {\n (headerRow as HTMLElement).style.paddingLeft = `${leftPadding}px`;\n }\n\n bodyRows.forEach((row) => {\n (row as HTMLElement).style.paddingLeft = `${leftPadding}px`;\n });\n\n // Set total width for horizontal scrolling on the rows container\n const rowsContainer = shadowRoot.querySelector('.rows-viewport .rows');\n if (rowsContainer) {\n (rowsContainer as HTMLElement).style.width = `${this.totalWidth}px`;\n }\n }\n\n override onScroll(event: ScrollEvent): void {\n if (!this.isVirtualized) return;\n\n // Check if horizontal scroll position changed significantly\n const scrollDelta = Math.abs(event.scrollLeft - this.scrollLeft);\n if (scrollDelta < 1) return;\n\n // Update scroll position\n this.scrollLeft = event.scrollLeft;\n\n // Recalculate visible columns and request re-render\n this.requestRender();\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Check if column virtualization is currently active.\n */\n getIsVirtualized(): boolean {\n return this.isVirtualized;\n }\n\n /**\n * Get the current visible column range.\n */\n getVisibleColumnRange(): { start: number; end: number } {\n return { start: this.startCol, end: this.endCol };\n }\n\n /**\n * Scroll the grid to bring a specific column into view.\n * @param columnIndex - Index of the column to scroll to\n */\n scrollToColumn(columnIndex: number): void {\n const offset = this.columnOffsets[columnIndex] ?? 0;\n const gridEl = this.grid as unknown as HTMLElement;\n // Scroll the grid element itself (it's the scroll container)\n gridEl.scrollLeft = offset;\n }\n\n /**\n * Get the left offset for a specific column.\n * @param columnIndex - Index of the column\n */\n getColumnOffset(columnIndex: number): number {\n return this.columnOffsets[columnIndex] ?? 0;\n }\n\n /**\n * Get the total width of all columns.\n */\n getTotalWidth(): number {\n return this.totalWidth;\n }\n // #endregion\n}\n"],"names":["parseColumnWidth","width","numeric","getColumnWidths","columns","col","computeColumnOffsets","offsets","offset","computeTotalWidth","sum","getVisibleColumnRange","scrollLeft","viewportWidth","columnOffsets","columnWidths","overscan","columnCount","startCol","binarySearchFirstVisible","rightEdge","endCol","i","visibleColumns","low","high","mid","shouldVirtualize","threshold","autoEnable","ColumnVirtualizationPlugin","BaseGridPlugin","grid","isVirtualized","viewport","shadowRoot","leftPadding","headerRow","bodyRows","row","rowsContainer","event","columnIndex","gridEl"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"column-virtualization.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/column-virtualization/column-virtualization.ts","../../../../../libs/grid/src/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.ts"],"sourcesContent":["/**\n * Column Virtualization Core Logic\n *\n * Pure functions for horizontal column virtualization operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnVirtualizationViewport } from './types';\n\n/** Default column width when not specified */\nconst DEFAULT_COLUMN_WIDTH = 100;\n\n/**\n * Parse a column width value to pixels.\n * Handles number (px) and string formats.\n *\n * @param width - The width value from column config\n * @returns Width in pixels\n */\nexport function parseColumnWidth(width: string | number | undefined): number {\n if (width === undefined || width === null) {\n return DEFAULT_COLUMN_WIDTH;\n }\n\n if (typeof width === 'number') {\n return width;\n }\n\n // Handle string values - extract numeric part\n const numeric = parseFloat(width);\n if (!isNaN(numeric)) {\n return numeric;\n }\n\n return DEFAULT_COLUMN_WIDTH;\n}\n\n/**\n * Get array of column widths in pixels.\n *\n * @param columns - Column configurations\n * @returns Array of widths in pixels\n */\nexport function getColumnWidths(columns: readonly ColumnConfig[]): number[] {\n return columns.map((col) => parseColumnWidth(col.width));\n}\n\n/**\n * Compute cumulative left offsets for each column.\n *\n * @param columns - Column configurations\n * @returns Array of left offsets in pixels\n */\nexport function computeColumnOffsets(columns: readonly ColumnConfig[]): number[] {\n const offsets: number[] = [];\n let offset = 0;\n\n for (const col of columns) {\n offsets.push(offset);\n offset += parseColumnWidth(col.width);\n }\n\n return offsets;\n}\n\n/**\n * Compute total width of all columns.\n *\n * @param columns - Column configurations\n * @returns Total width in pixels\n */\nexport function computeTotalWidth(columns: readonly ColumnConfig[]): number {\n return columns.reduce((sum, col) => sum + parseColumnWidth(col.width), 0);\n}\n\n/**\n * Find the visible column range based on scroll position.\n * Uses binary search for efficient lookup in large column sets.\n *\n * @param scrollLeft - Current horizontal scroll position\n * @param viewportWidth - Width of the visible viewport\n * @param columnOffsets - Array of column left offsets\n * @param columnWidths - Array of column widths\n * @param overscan - Number of extra columns to render on each side\n * @returns Viewport information with visible column indices\n */\nexport function getVisibleColumnRange(\n scrollLeft: number,\n viewportWidth: number,\n columnOffsets: number[],\n columnWidths: number[],\n overscan: number\n): ColumnVirtualizationViewport {\n const columnCount = columnOffsets.length;\n\n if (columnCount === 0) {\n return { startCol: 0, endCol: 0, visibleColumns: [] };\n }\n\n // Binary search for first visible column\n let startCol = binarySearchFirstVisible(scrollLeft, columnOffsets, columnWidths);\n startCol = Math.max(0, startCol - overscan);\n\n // Find last visible column (without overscan first)\n const rightEdge = scrollLeft + viewportWidth;\n let endCol = startCol;\n\n for (let i = startCol; i < columnCount; i++) {\n if (columnOffsets[i] >= rightEdge) {\n endCol = i - 1;\n break;\n }\n endCol = i;\n }\n\n // Apply overscan to end (only once)\n endCol = Math.min(columnCount - 1, endCol + overscan);\n\n // Build array of visible column indices\n const visibleColumns: number[] = [];\n for (let i = startCol; i <= endCol; i++) {\n visibleColumns.push(i);\n }\n\n return { startCol, endCol, visibleColumns };\n}\n\n/**\n * Binary search to find the first column that is visible.\n *\n * @param scrollLeft - Current scroll position\n * @param columnOffsets - Array of column offsets\n * @param columnWidths - Array of column widths\n * @returns Index of first visible column\n */\nfunction binarySearchFirstVisible(scrollLeft: number, columnOffsets: number[], columnWidths: number[]): number {\n let low = 0;\n let high = columnOffsets.length - 1;\n\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const colRight = columnOffsets[mid] + columnWidths[mid];\n\n if (colRight <= scrollLeft) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n\n return low;\n}\n\n/**\n * Determine if column virtualization should be active.\n *\n * @param columnCount - Number of columns\n * @param threshold - Column count threshold\n * @param autoEnable - Whether auto-enable is configured\n * @returns True if virtualization should be active\n */\nexport function shouldVirtualize(columnCount: number, threshold: number, autoEnable: boolean): boolean {\n if (!autoEnable) return false;\n return columnCount > threshold;\n}\n","/**\n * Column Virtualization Plugin (Class-based)\n *\n * Provides horizontal column virtualization for grids with many columns.\n * Significantly improves rendering performance when dealing with >30 columns.\n */\n\nimport { BaseGridPlugin, ScrollEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n computeColumnOffsets,\n computeTotalWidth,\n getColumnWidths,\n getVisibleColumnRange,\n shouldVirtualize,\n} from './column-virtualization';\nimport type { ColumnVirtualizationConfig } from './types';\n\n/**\n * Column Virtualization Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ColumnVirtualizationPlugin({ threshold: 30, overscan: 3 })\n * ```\n */\nexport class ColumnVirtualizationPlugin extends BaseGridPlugin<ColumnVirtualizationConfig> {\n readonly name = 'columnVirtualization';\n\n protected override get defaultConfig(): Partial<ColumnVirtualizationConfig> {\n return {\n autoEnable: true,\n threshold: 30,\n overscan: 3,\n };\n }\n\n // #region Internal State\n private isVirtualized = false;\n private startCol = 0;\n private endCol = 0;\n private scrollLeft = 0;\n private totalWidth = 0;\n private columnWidths: number[] = [];\n private columnOffsets: number[] = [];\n // #endregion\n\n // #region Lifecycle\n\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Initialize state from current columns\n const columns = this.columns;\n this.columnWidths = getColumnWidths(columns);\n this.columnOffsets = computeColumnOffsets(columns);\n this.totalWidth = computeTotalWidth(columns);\n this.endCol = columns.length - 1;\n }\n\n override detach(): void {\n this.columnWidths = [];\n this.columnOffsets = [];\n this.isVirtualized = false;\n this.startCol = 0;\n this.endCol = 0;\n this.scrollLeft = 0;\n this.totalWidth = 0;\n }\n // #endregion\n\n // #region Hooks\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n const isVirtualized = shouldVirtualize(columns.length, this.config.threshold ?? 30, this.config.autoEnable ?? true);\n\n // Update state with current column metrics\n this.isVirtualized = isVirtualized ?? false;\n this.columnWidths = getColumnWidths(columns);\n this.columnOffsets = computeColumnOffsets(columns);\n this.totalWidth = computeTotalWidth(columns);\n\n if (!isVirtualized) {\n this.startCol = 0;\n this.endCol = columns.length - 1;\n return [...columns];\n }\n\n // Get viewport width from grid element\n const viewportWidth = (this.grid as unknown as HTMLElement).clientWidth || 800;\n const viewport = getVisibleColumnRange(\n this.scrollLeft,\n viewportWidth,\n this.columnOffsets,\n this.columnWidths,\n this.config.overscan ?? 3,\n );\n\n this.startCol = viewport.startCol;\n this.endCol = viewport.endCol;\n\n // Return only visible columns\n return viewport.visibleColumns.map((i) => columns[i]);\n }\n\n override afterRender(): void {\n if (!this.isVirtualized) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Apply left padding to offset scrolled-out columns\n const leftPadding = this.columnOffsets[this.startCol] ?? 0;\n\n const headerRow = gridEl.querySelector('.header-row');\n const bodyRows = gridEl.querySelectorAll('.data-grid-row');\n\n if (headerRow) {\n (headerRow as HTMLElement).style.paddingLeft = `${leftPadding}px`;\n }\n\n bodyRows.forEach((row) => {\n (row as HTMLElement).style.paddingLeft = `${leftPadding}px`;\n });\n\n // Set total width for horizontal scrolling on the rows container\n const rowsContainer = gridEl.querySelector('.rows-viewport .rows');\n if (rowsContainer) {\n (rowsContainer as HTMLElement).style.width = `${this.totalWidth}px`;\n }\n }\n\n override onScroll(event: ScrollEvent): void {\n if (!this.isVirtualized) return;\n\n // Check if horizontal scroll position changed significantly\n const scrollDelta = Math.abs(event.scrollLeft - this.scrollLeft);\n if (scrollDelta < 1) return;\n\n // Update scroll position\n this.scrollLeft = event.scrollLeft;\n\n // Recalculate visible columns and request re-render\n this.requestRender();\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Check if column virtualization is currently active.\n */\n getIsVirtualized(): boolean {\n return this.isVirtualized;\n }\n\n /**\n * Get the current visible column range.\n */\n getVisibleColumnRange(): { start: number; end: number } {\n return { start: this.startCol, end: this.endCol };\n }\n\n /**\n * Scroll the grid to bring a specific column into view.\n * @param columnIndex - Index of the column to scroll to\n */\n scrollToColumn(columnIndex: number): void {\n const offset = this.columnOffsets[columnIndex] ?? 0;\n const gridEl = this.grid as unknown as HTMLElement;\n // Scroll the grid element itself (it's the scroll container)\n gridEl.scrollLeft = offset;\n }\n\n /**\n * Get the left offset for a specific column.\n * @param columnIndex - Index of the column\n */\n getColumnOffset(columnIndex: number): number {\n return this.columnOffsets[columnIndex] ?? 0;\n }\n\n /**\n * Get the total width of all columns.\n */\n getTotalWidth(): number {\n return this.totalWidth;\n }\n // #endregion\n}\n"],"names":["parseColumnWidth","width","numeric","getColumnWidths","columns","col","computeColumnOffsets","offsets","offset","computeTotalWidth","sum","getVisibleColumnRange","scrollLeft","viewportWidth","columnOffsets","columnWidths","overscan","columnCount","startCol","binarySearchFirstVisible","rightEdge","endCol","i","visibleColumns","low","high","mid","shouldVirtualize","threshold","autoEnable","ColumnVirtualizationPlugin","BaseGridPlugin","grid","isVirtualized","viewport","gridEl","leftPadding","headerRow","bodyRows","row","rowsContainer","event","columnIndex"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(m,
|
|
1
|
+
(function(m,w){typeof exports=="object"&&typeof module<"u"?w(exports,require("../../core/plugin/base-plugin"),require("../../core/types")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin","../../core/types"],w):(m=typeof globalThis<"u"?globalThis:m||self,w(m.TbwGridPlugin_contextMenu={},m.TbwGrid,m.TbwGrid))})(this,(function(m,w,I){"use strict";const x="@layer tbw-plugins{.tbw-context-menu{position:fixed;background:var(--tbw-context-menu-bg, var(--tbw-color-panel-bg, light-dark(#f5f5f5, #2a2a2a)));color:var(--tbw-context-menu-fg, var(--tbw-color-fg, light-dark(#222, #eee)));border:1px solid var(--tbw-context-menu-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-border-radius, .25rem);box-shadow:0 2px 10px var(--tbw-color-shadow, rgba(0, 0, 0, .15));min-width:var(--tbw-menu-min-width, 10rem);padding:var(--tbw-spacing-xs, .25rem) 0;z-index:10000;font-size:var(--tbw-font-size-sm, .8125rem);font-family:var(--tbw-font-family, system-ui, sans-serif)}.tbw-context-menu-item{display:flex;align-items:center;padding:var(--tbw-menu-item-padding, .375rem .75rem);cursor:pointer;gap:var(--tbw-menu-item-gap, .5rem)}.tbw-context-menu-item:hover:not(.disabled){background:var(--tbw-context-menu-hover, var(--tbw-color-row-hover, light-dark(#e8e8e8, #3a3a3a)))}.tbw-context-menu-item.disabled{opacity:.5;cursor:default}.tbw-context-menu-item.danger{color:light-dark(#c00,#f66)}.tbw-context-menu-icon{width:var(--tbw-icon-size, 1rem);text-align:center}.tbw-context-menu-label{flex:1}.tbw-context-menu-shortcut{color:var(--tbw-color-fg-muted, light-dark(#888, #888));font-size:var(--tbw-font-size-xs, .6875rem)}.tbw-context-menu-arrow{font-size:var(--tbw-font-size-2xs, .625rem);color:var(--tbw-color-fg-muted, light-dark(#888, #888))}.tbw-context-menu-separator{height:1px;background:var(--tbw-context-menu-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));margin:var(--tbw-spacing-xs, .25rem) 0}}";function g(i,n){return(typeof i=="function"?i(n):i).filter(t=>!(t.hidden===!0||typeof t.hidden=="function"&&t.hidden(n)))}function k(i,n){return i.disabled===!0?!0:typeof i.disabled=="function"?i.disabled(n):!1}function v(i,n,r,t=I.DEFAULT_GRID_ICONS.submenuArrow){const l=document.createElement("div");l.className="tbw-context-menu",l.setAttribute("role","menu");for(const o of i){if(o.separator){const s=document.createElement("div");s.className="tbw-context-menu-separator",s.setAttribute("role","separator"),l.appendChild(s);continue}const e=document.createElement("div");e.className="tbw-context-menu-item",o.cssClass&&e.classList.add(o.cssClass),e.setAttribute("role","menuitem"),e.setAttribute("data-id",o.id);const d=k(o,n);if(d&&(e.classList.add("disabled"),e.setAttribute("aria-disabled","true")),o.icon){const s=document.createElement("span");s.className="tbw-context-menu-icon",s.innerHTML=o.icon,e.appendChild(s)}const a=document.createElement("span");if(a.className="tbw-context-menu-label",a.textContent=o.name,e.appendChild(a),o.shortcut){const s=document.createElement("span");s.className="tbw-context-menu-shortcut",s.textContent=o.shortcut,e.appendChild(s)}if(o.subMenu?.length){const s=document.createElement("span");s.className="tbw-context-menu-arrow",typeof t=="string"?s.innerHTML=t:t instanceof HTMLElement&&s.appendChild(t.cloneNode(!0)),e.appendChild(s),e.addEventListener("mouseenter",()=>{if(e.querySelector(".tbw-context-menu")||!o.subMenu)return;const b=g(o.subMenu,n),c=v(b,n,r,t);c.classList.add("tbw-context-submenu"),c.style.position="absolute",c.style.left="100%",c.style.top="0",e.style.position="relative",e.appendChild(c)}),e.addEventListener("mouseleave",()=>{const u=e.querySelector(".tbw-context-menu");u&&u.remove()})}!d&&o.action&&!o.subMenu&&e.addEventListener("click",s=>{s.stopPropagation(),r(o)}),l.appendChild(e)}return l}function C(i,n,r){i.style.position="fixed",i.style.left=`${n}px`,i.style.top=`${r}px`,i.style.visibility="hidden",i.style.zIndex="10000";const t=i.getBoundingClientRect(),l=window.innerWidth,o=window.innerHeight;let e=n,d=r;n+t.width>l&&(e=n-t.width),r+t.height>o&&(d=r-t.height),e=Math.max(0,e),d=Math.max(0,d),i.style.left=`${e}px`,i.style.top=`${d}px`,i.style.visibility="visible"}let h=null,p=null,f=null,y=0;const E=[{id:"copy",name:"Copy",shortcut:"Ctrl+C",action:i=>{i.grid?.plugins?.clipboard?.copy?.()}},{separator:!0,id:"sep1",name:""},{id:"export-csv",name:"Export CSV",action:i=>{i.grid?.plugins?.export?.exportCsv?.()}}];class H extends w.BaseGridPlugin{name="contextMenu";get defaultConfig(){return{items:E}}isOpen=!1;position={x:0,y:0};params=null;menuElement=null;attach(n){super.attach(n),this.installGlobalHandlers(),y++}detach(){this.menuElement&&(this.menuElement.remove(),this.menuElement=null),this.isOpen=!1,this.params=null,this.uninstallGlobalHandlers()}installGlobalHandlers(){!f&&typeof document<"u"&&typeof x=="string"&&x&&(f=document.createElement("style"),f.id="tbw-context-menu-styles",f.textContent=x,document.head.appendChild(f)),h||(h=()=>{document.querySelectorAll(".tbw-context-menu").forEach(r=>r.remove())},document.addEventListener("click",h)),p||(p=n=>{n.key==="Escape"&&document.querySelectorAll(".tbw-context-menu").forEach(t=>t.remove())},document.addEventListener("keydown",p))}uninstallGlobalHandlers(){y--,!(y>0)&&(h&&(document.removeEventListener("click",h),h=null),p&&(document.removeEventListener("keydown",p),p=null),f&&(f.remove(),f=null))}afterRender(){const n=this.gridElement;if(!n)return;const r=n.children[0];r&&r.getAttribute("data-context-menu-bound")!=="true"&&(r.setAttribute("data-context-menu-bound","true"),r.addEventListener("contextmenu",t=>{const l=t;l.preventDefault();const o=l.target,e=o.closest("[data-row][data-col]"),d=o.closest(".header-cell");let a;if(e){const u=parseInt(e.getAttribute("data-row")??"-1",10),b=parseInt(e.getAttribute("data-col")??"-1",10),c=this.columns[b],M=this.rows[u];a={row:M,rowIndex:u,column:c,columnIndex:b,field:c?.field??"",value:M?.[c?.field]??null,isHeader:!1,event:l}}else if(d){const u=parseInt(d.getAttribute("data-col")??"-1",10),b=this.columns[u];a={row:null,rowIndex:-1,column:b,columnIndex:u,field:b?.field??"",value:null,isHeader:!0,event:l}}else return;this.params=a,this.position={x:l.clientX,y:l.clientY};const s=g(this.config.items??E,a);s.length&&(this.menuElement&&this.menuElement.remove(),this.menuElement=v(s,a,u=>{u.action&&u.action(a),this.menuElement?.remove(),this.menuElement=null,this.isOpen=!1},this.gridIcons.submenuArrow),document.body.appendChild(this.menuElement),C(this.menuElement,l.clientX,l.clientY),this.isOpen=!0,this.emit("context-menu-open",{params:a,items:s}))}))}showMenu(n,r,t){const l={row:t.row??null,rowIndex:t.rowIndex??-1,column:t.column??null,columnIndex:t.columnIndex??-1,field:t.field??"",value:t.value??null,isHeader:t.isHeader??!1,event:t.event??new MouseEvent("contextmenu")},o=g(this.config.items??E,l);this.menuElement&&this.menuElement.remove(),this.menuElement=v(o,l,e=>{e.action&&e.action(l),this.menuElement?.remove(),this.menuElement=null,this.isOpen=!1},this.gridIcons.submenuArrow),document.body.appendChild(this.menuElement),C(this.menuElement,n,r),this.isOpen=!0}hideMenu(){this.menuElement&&(this.menuElement.remove(),this.menuElement=null,this.isOpen=!1)}isMenuOpen(){return this.isOpen}}m.ContextMenuPlugin=H,Object.defineProperty(m,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=context-menu.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-menu.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/context-menu/menu.ts","../../../../../libs/grid/src/lib/plugins/context-menu/ContextMenuPlugin.ts"],"sourcesContent":["/**\n * Context Menu Rendering Logic\n *\n * Pure functions for building and positioning context menus.\n */\n\nimport type { IconValue } from '../../core/types';\nimport { DEFAULT_GRID_ICONS } from '../../core/types';\nimport type { ContextMenuItem, ContextMenuParams } from './types';\n\n/**\n * Build the visible menu items by resolving dynamic items and filtering hidden ones.\n *\n * @param items - Menu items configuration (array or factory function)\n * @param params - Context menu parameters for evaluating dynamic properties\n * @returns Filtered array of visible menu items\n */\nexport function buildMenuItems(\n items: ContextMenuItem[] | ((params: ContextMenuParams) => ContextMenuItem[]),\n params: ContextMenuParams\n): ContextMenuItem[] {\n const menuItems = typeof items === 'function' ? items(params) : items;\n\n return menuItems.filter((item) => {\n if (item.hidden === true) return false;\n if (typeof item.hidden === 'function' && item.hidden(params)) return false;\n return true;\n });\n}\n\n/**\n * Check if a menu item is disabled.\n *\n * @param item - The menu item to check\n * @param params - Context menu parameters for evaluating dynamic disabled state\n * @returns True if the item is disabled\n */\nexport function isItemDisabled(item: ContextMenuItem, params: ContextMenuParams): boolean {\n if (item.disabled === true) return true;\n if (typeof item.disabled === 'function') return item.disabled(params);\n return false;\n}\n\n/**\n * Create the menu DOM element from a list of menu items.\n *\n * @param items - Array of menu items to render\n * @param params - Context menu parameters for evaluating dynamic properties\n * @param onAction - Callback when a menu item action is triggered\n * @param submenuArrow - Optional custom submenu arrow icon\n * @returns The created menu element\n */\nexport function createMenuElement(\n items: ContextMenuItem[],\n params: ContextMenuParams,\n onAction: (item: ContextMenuItem) => void,\n submenuArrow: IconValue = DEFAULT_GRID_ICONS.submenuArrow\n): HTMLElement {\n const menu = document.createElement('div');\n menu.className = 'tbw-context-menu';\n menu.setAttribute('role', 'menu');\n\n for (const item of items) {\n if (item.separator) {\n const separator = document.createElement('div');\n separator.className = 'tbw-context-menu-separator';\n separator.setAttribute('role', 'separator');\n menu.appendChild(separator);\n continue;\n }\n\n const menuItem = document.createElement('div');\n menuItem.className = 'tbw-context-menu-item';\n if (item.cssClass) menuItem.classList.add(item.cssClass);\n menuItem.setAttribute('role', 'menuitem');\n menuItem.setAttribute('data-id', item.id);\n\n const disabled = isItemDisabled(item, params);\n if (disabled) {\n menuItem.classList.add('disabled');\n menuItem.setAttribute('aria-disabled', 'true');\n }\n\n if (item.icon) {\n const icon = document.createElement('span');\n icon.className = 'tbw-context-menu-icon';\n icon.innerHTML = item.icon;\n menuItem.appendChild(icon);\n }\n\n const label = document.createElement('span');\n label.className = 'tbw-context-menu-label';\n label.textContent = item.name;\n menuItem.appendChild(label);\n\n if (item.shortcut) {\n const shortcut = document.createElement('span');\n shortcut.className = 'tbw-context-menu-shortcut';\n shortcut.textContent = item.shortcut;\n menuItem.appendChild(shortcut);\n }\n\n if (item.subMenu?.length) {\n const arrow = document.createElement('span');\n arrow.className = 'tbw-context-menu-arrow';\n // Use provided submenu arrow icon (string or HTMLElement)\n if (typeof submenuArrow === 'string') {\n arrow.innerHTML = submenuArrow;\n } else if (submenuArrow instanceof HTMLElement) {\n arrow.appendChild(submenuArrow.cloneNode(true));\n }\n menuItem.appendChild(arrow);\n\n // Add submenu on hover\n menuItem.addEventListener('mouseenter', () => {\n const existingSubMenu = menuItem.querySelector('.tbw-context-menu');\n if (existingSubMenu) return;\n if (!item.subMenu) return;\n\n const subMenuItems = buildMenuItems(item.subMenu, params);\n const subMenu = createMenuElement(subMenuItems, params, onAction, submenuArrow);\n subMenu.classList.add('tbw-context-submenu');\n subMenu.style.position = 'absolute';\n subMenu.style.left = '100%';\n subMenu.style.top = '0';\n menuItem.style.position = 'relative';\n menuItem.appendChild(subMenu);\n });\n\n menuItem.addEventListener('mouseleave', () => {\n const subMenu = menuItem.querySelector('.tbw-context-menu');\n if (subMenu) subMenu.remove();\n });\n }\n\n if (!disabled && item.action && !item.subMenu) {\n menuItem.addEventListener('click', (e) => {\n e.stopPropagation();\n onAction(item);\n });\n }\n\n menu.appendChild(menuItem);\n }\n\n return menu;\n}\n\n/**\n * Position the menu at the given viewport coordinates.\n * Menu is rendered in document.body with fixed positioning for top-layer behavior.\n *\n * @param menu - The menu element to position\n * @param x - Desired X coordinate (viewport)\n * @param y - Desired Y coordinate (viewport)\n */\nexport function positionMenu(menu: HTMLElement, x: number, y: number): void {\n // Use fixed positioning for top-layer behavior\n menu.style.position = 'fixed';\n menu.style.left = `${x}px`;\n menu.style.top = `${y}px`;\n menu.style.visibility = 'hidden';\n menu.style.zIndex = '10000';\n\n // Force layout to get dimensions\n const menuRect = menu.getBoundingClientRect();\n\n // Calculate visible area within viewport\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n let left = x;\n let top = y;\n\n // Check if menu overflows right edge of viewport\n if (x + menuRect.width > viewportWidth) {\n left = x - menuRect.width;\n }\n // Check if menu overflows bottom edge of viewport\n if (y + menuRect.height > viewportHeight) {\n top = y - menuRect.height;\n }\n\n // Ensure we don't go negative\n left = Math.max(0, left);\n top = Math.max(0, top);\n\n menu.style.left = `${left}px`;\n menu.style.top = `${top}px`;\n menu.style.visibility = 'visible';\n}\n","/**\n * Context Menu Plugin (Class-based)\n *\n * Provides right-click context menu functionality for tbw-grid.\n * Supports custom menu items, submenus, icons, shortcuts, and dynamic item generation.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport contextMenuStyles from './context-menu.css?inline';\nimport { buildMenuItems, createMenuElement, positionMenu } from './menu';\nimport type { ContextMenuConfig, ContextMenuItem, ContextMenuParams } from './types';\n\n/** Global click handler reference for cleanup */\nlet globalClickHandler: ((e: Event) => void) | null = null;\n/** Global keydown handler reference for cleanup */\nlet globalKeydownHandler: ((e: KeyboardEvent) => void) | null = null;\n/** Global stylesheet for context menu (injected once) */\nlet globalStyleSheet: HTMLStyleElement | null = null;\n/** Reference count for instances using global handlers */\nlet globalHandlerRefCount = 0;\n\n/** Default menu items when none are configured */\nconst defaultItems: ContextMenuItem[] = [\n {\n id: 'copy',\n name: 'Copy',\n shortcut: 'Ctrl+C',\n action: (params) => {\n const grid = (params as ContextMenuParams & { grid?: { plugins?: { clipboard?: { copy?: () => void } } } }).grid;\n grid?.plugins?.clipboard?.copy?.();\n },\n },\n { separator: true, id: 'sep1', name: '' },\n {\n id: 'export-csv',\n name: 'Export CSV',\n action: (params) => {\n const grid = (params as ContextMenuParams & { grid?: { plugins?: { export?: { exportCsv?: () => void } } } })\n .grid;\n grid?.plugins?.export?.exportCsv?.();\n },\n },\n];\n\n/**\n * Context Menu Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ContextMenuPlugin({\n * enabled: true,\n * items: [\n * { id: 'edit', name: 'Edit', action: (params) => console.log('Edit', params) },\n * { separator: true, id: 'sep', name: '' },\n * { id: 'delete', name: 'Delete', action: (params) => console.log('Delete', params) },\n * ],\n * })\n * ```\n */\nexport class ContextMenuPlugin extends BaseGridPlugin<ContextMenuConfig> {\n readonly name = 'contextMenu';\n\n protected override get defaultConfig(): Partial<ContextMenuConfig> {\n return {\n items: defaultItems,\n };\n }\n\n // #region Internal State\n private isOpen = false;\n private position = { x: 0, y: 0 };\n private params: ContextMenuParams | null = null;\n private menuElement: HTMLElement | null = null;\n // #endregion\n\n // #region Lifecycle\n\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n this.installGlobalHandlers();\n globalHandlerRefCount++;\n }\n\n override detach(): void {\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n }\n this.isOpen = false;\n this.params = null;\n this.uninstallGlobalHandlers();\n }\n // #endregion\n\n // #region Private Methods\n\n private installGlobalHandlers(): void {\n // Inject global stylesheet for context menu (once)\n // Only inject if we have valid CSS text (Vite's ?inline import)\n // When importing from source without Vite, the import is a module object, not a string\n if (\n !globalStyleSheet &&\n typeof document !== 'undefined' &&\n typeof contextMenuStyles === 'string' &&\n contextMenuStyles\n ) {\n globalStyleSheet = document.createElement('style');\n globalStyleSheet.id = 'tbw-context-menu-styles';\n globalStyleSheet.textContent = contextMenuStyles;\n document.head.appendChild(globalStyleSheet);\n }\n\n // Close menu on click outside\n if (!globalClickHandler) {\n globalClickHandler = () => {\n const menus = document.querySelectorAll('.tbw-context-menu');\n menus.forEach((menu) => menu.remove());\n };\n document.addEventListener('click', globalClickHandler);\n }\n\n // Close on escape\n if (!globalKeydownHandler) {\n globalKeydownHandler = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n const menus = document.querySelectorAll('.tbw-context-menu');\n menus.forEach((menu) => menu.remove());\n }\n };\n document.addEventListener('keydown', globalKeydownHandler);\n }\n }\n\n /**\n * Clean up global handlers when the last instance detaches.\n * Uses reference counting to ensure handlers persist while any grid uses the plugin.\n */\n private uninstallGlobalHandlers(): void {\n globalHandlerRefCount--;\n if (globalHandlerRefCount > 0) return;\n\n // Last instance - clean up all global resources\n if (globalClickHandler) {\n document.removeEventListener('click', globalClickHandler);\n globalClickHandler = null;\n }\n if (globalKeydownHandler) {\n document.removeEventListener('keydown', globalKeydownHandler);\n globalKeydownHandler = null;\n }\n if (globalStyleSheet) {\n globalStyleSheet.remove();\n globalStyleSheet = null;\n }\n }\n // #endregion\n\n // #region Hooks\n\n override afterRender(): void {\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n const container = shadowRoot.children[0];\n if (!container) return;\n\n // Check if handler already attached\n if (container.getAttribute('data-context-menu-bound') === 'true') return;\n container.setAttribute('data-context-menu-bound', 'true');\n\n container.addEventListener('contextmenu', (e: Event) => {\n const event = e as MouseEvent;\n event.preventDefault();\n\n const target = event.target as HTMLElement;\n const cell = target.closest('[data-row][data-col]');\n const header = target.closest('.header-cell');\n\n let params: ContextMenuParams;\n\n if (cell) {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n const column = this.columns[colIndex];\n const row = this.rows[rowIndex];\n\n params = {\n row,\n rowIndex,\n column,\n columnIndex: colIndex,\n field: column?.field ?? '',\n value: row?.[column?.field as keyof typeof row] ?? null,\n isHeader: false,\n event,\n };\n } else if (header) {\n const colIndex = parseInt(header.getAttribute('data-col') ?? '-1', 10);\n const column = this.columns[colIndex];\n\n params = {\n row: null,\n rowIndex: -1,\n column,\n columnIndex: colIndex,\n field: column?.field ?? '',\n value: null,\n isHeader: true,\n event,\n };\n } else {\n return;\n }\n\n this.params = params;\n this.position = { x: event.clientX, y: event.clientY };\n\n const items = buildMenuItems(this.config.items ?? defaultItems, params);\n if (!items.length) return;\n\n if (this.menuElement) {\n this.menuElement.remove();\n }\n\n this.menuElement = createMenuElement(\n items,\n params,\n (item) => {\n if (item.action) {\n item.action(params);\n }\n this.menuElement?.remove();\n this.menuElement = null;\n this.isOpen = false;\n },\n this.gridIcons.submenuArrow,\n );\n\n document.body.appendChild(this.menuElement);\n positionMenu(this.menuElement, event.clientX, event.clientY);\n this.isOpen = true;\n\n this.emit('context-menu-open', { params, items });\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Programmatically show the context menu at the specified position.\n * @param x - X coordinate\n * @param y - Y coordinate\n * @param params - Partial context menu parameters\n */\n showMenu(x: number, y: number, params: Partial<ContextMenuParams>): void {\n const fullParams: ContextMenuParams = {\n row: params.row ?? null,\n rowIndex: params.rowIndex ?? -1,\n column: params.column ?? null,\n columnIndex: params.columnIndex ?? -1,\n field: params.field ?? '',\n value: params.value ?? null,\n isHeader: params.isHeader ?? false,\n event: params.event ?? new MouseEvent('contextmenu'),\n };\n\n const items = buildMenuItems(this.config.items ?? defaultItems, fullParams);\n\n if (this.menuElement) {\n this.menuElement.remove();\n }\n\n this.menuElement = createMenuElement(\n items,\n fullParams,\n (item) => {\n if (item.action) item.action(fullParams);\n this.menuElement?.remove();\n this.menuElement = null;\n this.isOpen = false;\n },\n this.gridIcons.submenuArrow,\n );\n\n document.body.appendChild(this.menuElement);\n positionMenu(this.menuElement, x, y);\n this.isOpen = true;\n }\n\n /**\n * Hide the context menu.\n */\n hideMenu(): void {\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n this.isOpen = false;\n }\n }\n\n /**\n * Check if the context menu is currently open.\n * @returns Whether the menu is open\n */\n isMenuOpen(): boolean {\n return this.isOpen;\n }\n // #endregion\n\n // Styles are injected globally via installGlobalHandlers() since menu renders in document.body\n}\n"],"names":["buildMenuItems","items","params","item","isItemDisabled","createMenuElement","onAction","submenuArrow","DEFAULT_GRID_ICONS","menu","separator","menuItem","disabled","icon","label","shortcut","arrow","subMenuItems","subMenu","e","positionMenu","x","y","menuRect","viewportWidth","viewportHeight","left","top","globalClickHandler","globalKeydownHandler","globalStyleSheet","globalHandlerRefCount","defaultItems","ContextMenuPlugin","BaseGridPlugin","grid","contextMenuStyles","shadowRoot","container","event","target","cell","header","rowIndex","colIndex","column","row","fullParams"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"context-menu.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/context-menu/menu.ts","../../../../../libs/grid/src/lib/plugins/context-menu/ContextMenuPlugin.ts"],"sourcesContent":["/**\n * Context Menu Rendering Logic\n *\n * Pure functions for building and positioning context menus.\n */\n\nimport type { IconValue } from '../../core/types';\nimport { DEFAULT_GRID_ICONS } from '../../core/types';\nimport type { ContextMenuItem, ContextMenuParams } from './types';\n\n/**\n * Build the visible menu items by resolving dynamic items and filtering hidden ones.\n *\n * @param items - Menu items configuration (array or factory function)\n * @param params - Context menu parameters for evaluating dynamic properties\n * @returns Filtered array of visible menu items\n */\nexport function buildMenuItems(\n items: ContextMenuItem[] | ((params: ContextMenuParams) => ContextMenuItem[]),\n params: ContextMenuParams\n): ContextMenuItem[] {\n const menuItems = typeof items === 'function' ? items(params) : items;\n\n return menuItems.filter((item) => {\n if (item.hidden === true) return false;\n if (typeof item.hidden === 'function' && item.hidden(params)) return false;\n return true;\n });\n}\n\n/**\n * Check if a menu item is disabled.\n *\n * @param item - The menu item to check\n * @param params - Context menu parameters for evaluating dynamic disabled state\n * @returns True if the item is disabled\n */\nexport function isItemDisabled(item: ContextMenuItem, params: ContextMenuParams): boolean {\n if (item.disabled === true) return true;\n if (typeof item.disabled === 'function') return item.disabled(params);\n return false;\n}\n\n/**\n * Create the menu DOM element from a list of menu items.\n *\n * @param items - Array of menu items to render\n * @param params - Context menu parameters for evaluating dynamic properties\n * @param onAction - Callback when a menu item action is triggered\n * @param submenuArrow - Optional custom submenu arrow icon\n * @returns The created menu element\n */\nexport function createMenuElement(\n items: ContextMenuItem[],\n params: ContextMenuParams,\n onAction: (item: ContextMenuItem) => void,\n submenuArrow: IconValue = DEFAULT_GRID_ICONS.submenuArrow\n): HTMLElement {\n const menu = document.createElement('div');\n menu.className = 'tbw-context-menu';\n menu.setAttribute('role', 'menu');\n\n for (const item of items) {\n if (item.separator) {\n const separator = document.createElement('div');\n separator.className = 'tbw-context-menu-separator';\n separator.setAttribute('role', 'separator');\n menu.appendChild(separator);\n continue;\n }\n\n const menuItem = document.createElement('div');\n menuItem.className = 'tbw-context-menu-item';\n if (item.cssClass) menuItem.classList.add(item.cssClass);\n menuItem.setAttribute('role', 'menuitem');\n menuItem.setAttribute('data-id', item.id);\n\n const disabled = isItemDisabled(item, params);\n if (disabled) {\n menuItem.classList.add('disabled');\n menuItem.setAttribute('aria-disabled', 'true');\n }\n\n if (item.icon) {\n const icon = document.createElement('span');\n icon.className = 'tbw-context-menu-icon';\n icon.innerHTML = item.icon;\n menuItem.appendChild(icon);\n }\n\n const label = document.createElement('span');\n label.className = 'tbw-context-menu-label';\n label.textContent = item.name;\n menuItem.appendChild(label);\n\n if (item.shortcut) {\n const shortcut = document.createElement('span');\n shortcut.className = 'tbw-context-menu-shortcut';\n shortcut.textContent = item.shortcut;\n menuItem.appendChild(shortcut);\n }\n\n if (item.subMenu?.length) {\n const arrow = document.createElement('span');\n arrow.className = 'tbw-context-menu-arrow';\n // Use provided submenu arrow icon (string or HTMLElement)\n if (typeof submenuArrow === 'string') {\n arrow.innerHTML = submenuArrow;\n } else if (submenuArrow instanceof HTMLElement) {\n arrow.appendChild(submenuArrow.cloneNode(true));\n }\n menuItem.appendChild(arrow);\n\n // Add submenu on hover\n menuItem.addEventListener('mouseenter', () => {\n const existingSubMenu = menuItem.querySelector('.tbw-context-menu');\n if (existingSubMenu) return;\n if (!item.subMenu) return;\n\n const subMenuItems = buildMenuItems(item.subMenu, params);\n const subMenu = createMenuElement(subMenuItems, params, onAction, submenuArrow);\n subMenu.classList.add('tbw-context-submenu');\n subMenu.style.position = 'absolute';\n subMenu.style.left = '100%';\n subMenu.style.top = '0';\n menuItem.style.position = 'relative';\n menuItem.appendChild(subMenu);\n });\n\n menuItem.addEventListener('mouseleave', () => {\n const subMenu = menuItem.querySelector('.tbw-context-menu');\n if (subMenu) subMenu.remove();\n });\n }\n\n if (!disabled && item.action && !item.subMenu) {\n menuItem.addEventListener('click', (e) => {\n e.stopPropagation();\n onAction(item);\n });\n }\n\n menu.appendChild(menuItem);\n }\n\n return menu;\n}\n\n/**\n * Position the menu at the given viewport coordinates.\n * Menu is rendered in document.body with fixed positioning for top-layer behavior.\n *\n * @param menu - The menu element to position\n * @param x - Desired X coordinate (viewport)\n * @param y - Desired Y coordinate (viewport)\n */\nexport function positionMenu(menu: HTMLElement, x: number, y: number): void {\n // Use fixed positioning for top-layer behavior\n menu.style.position = 'fixed';\n menu.style.left = `${x}px`;\n menu.style.top = `${y}px`;\n menu.style.visibility = 'hidden';\n menu.style.zIndex = '10000';\n\n // Force layout to get dimensions\n const menuRect = menu.getBoundingClientRect();\n\n // Calculate visible area within viewport\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n let left = x;\n let top = y;\n\n // Check if menu overflows right edge of viewport\n if (x + menuRect.width > viewportWidth) {\n left = x - menuRect.width;\n }\n // Check if menu overflows bottom edge of viewport\n if (y + menuRect.height > viewportHeight) {\n top = y - menuRect.height;\n }\n\n // Ensure we don't go negative\n left = Math.max(0, left);\n top = Math.max(0, top);\n\n menu.style.left = `${left}px`;\n menu.style.top = `${top}px`;\n menu.style.visibility = 'visible';\n}\n","/**\n * Context Menu Plugin (Class-based)\n *\n * Provides right-click context menu functionality for tbw-grid.\n * Supports custom menu items, submenus, icons, shortcuts, and dynamic item generation.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport contextMenuStyles from './context-menu.css?inline';\nimport { buildMenuItems, createMenuElement, positionMenu } from './menu';\nimport type { ContextMenuConfig, ContextMenuItem, ContextMenuParams } from './types';\n\n/** Global click handler reference for cleanup */\nlet globalClickHandler: ((e: Event) => void) | null = null;\n/** Global keydown handler reference for cleanup */\nlet globalKeydownHandler: ((e: KeyboardEvent) => void) | null = null;\n/** Global stylesheet for context menu (injected once) */\nlet globalStyleSheet: HTMLStyleElement | null = null;\n/** Reference count for instances using global handlers */\nlet globalHandlerRefCount = 0;\n\n/** Default menu items when none are configured */\nconst defaultItems: ContextMenuItem[] = [\n {\n id: 'copy',\n name: 'Copy',\n shortcut: 'Ctrl+C',\n action: (params) => {\n const grid = (params as ContextMenuParams & { grid?: { plugins?: { clipboard?: { copy?: () => void } } } }).grid;\n grid?.plugins?.clipboard?.copy?.();\n },\n },\n { separator: true, id: 'sep1', name: '' },\n {\n id: 'export-csv',\n name: 'Export CSV',\n action: (params) => {\n const grid = (params as ContextMenuParams & { grid?: { plugins?: { export?: { exportCsv?: () => void } } } })\n .grid;\n grid?.plugins?.export?.exportCsv?.();\n },\n },\n];\n\n/**\n * Context Menu Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ContextMenuPlugin({\n * enabled: true,\n * items: [\n * { id: 'edit', name: 'Edit', action: (params) => console.log('Edit', params) },\n * { separator: true, id: 'sep', name: '' },\n * { id: 'delete', name: 'Delete', action: (params) => console.log('Delete', params) },\n * ],\n * })\n * ```\n */\nexport class ContextMenuPlugin extends BaseGridPlugin<ContextMenuConfig> {\n readonly name = 'contextMenu';\n\n protected override get defaultConfig(): Partial<ContextMenuConfig> {\n return {\n items: defaultItems,\n };\n }\n\n // #region Internal State\n private isOpen = false;\n private position = { x: 0, y: 0 };\n private params: ContextMenuParams | null = null;\n private menuElement: HTMLElement | null = null;\n // #endregion\n\n // #region Lifecycle\n\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n this.installGlobalHandlers();\n globalHandlerRefCount++;\n }\n\n override detach(): void {\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n }\n this.isOpen = false;\n this.params = null;\n this.uninstallGlobalHandlers();\n }\n // #endregion\n\n // #region Private Methods\n\n private installGlobalHandlers(): void {\n // Inject global stylesheet for context menu (once)\n // Only inject if we have valid CSS text (Vite's ?inline import)\n // When importing from source without Vite, the import is a module object, not a string\n if (\n !globalStyleSheet &&\n typeof document !== 'undefined' &&\n typeof contextMenuStyles === 'string' &&\n contextMenuStyles\n ) {\n globalStyleSheet = document.createElement('style');\n globalStyleSheet.id = 'tbw-context-menu-styles';\n globalStyleSheet.textContent = contextMenuStyles;\n document.head.appendChild(globalStyleSheet);\n }\n\n // Close menu on click outside\n if (!globalClickHandler) {\n globalClickHandler = () => {\n const menus = document.querySelectorAll('.tbw-context-menu');\n menus.forEach((menu) => menu.remove());\n };\n document.addEventListener('click', globalClickHandler);\n }\n\n // Close on escape\n if (!globalKeydownHandler) {\n globalKeydownHandler = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n const menus = document.querySelectorAll('.tbw-context-menu');\n menus.forEach((menu) => menu.remove());\n }\n };\n document.addEventListener('keydown', globalKeydownHandler);\n }\n }\n\n /**\n * Clean up global handlers when the last instance detaches.\n * Uses reference counting to ensure handlers persist while any grid uses the plugin.\n */\n private uninstallGlobalHandlers(): void {\n globalHandlerRefCount--;\n if (globalHandlerRefCount > 0) return;\n\n // Last instance - clean up all global resources\n if (globalClickHandler) {\n document.removeEventListener('click', globalClickHandler);\n globalClickHandler = null;\n }\n if (globalKeydownHandler) {\n document.removeEventListener('keydown', globalKeydownHandler);\n globalKeydownHandler = null;\n }\n if (globalStyleSheet) {\n globalStyleSheet.remove();\n globalStyleSheet = null;\n }\n }\n // #endregion\n\n // #region Hooks\n\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const container = gridEl.children[0];\n if (!container) return;\n\n // Check if handler already attached\n if (container.getAttribute('data-context-menu-bound') === 'true') return;\n container.setAttribute('data-context-menu-bound', 'true');\n\n container.addEventListener('contextmenu', (e: Event) => {\n const event = e as MouseEvent;\n event.preventDefault();\n\n const target = event.target as HTMLElement;\n const cell = target.closest('[data-row][data-col]');\n const header = target.closest('.header-cell');\n\n let params: ContextMenuParams;\n\n if (cell) {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n const column = this.columns[colIndex];\n const row = this.rows[rowIndex];\n\n params = {\n row,\n rowIndex,\n column,\n columnIndex: colIndex,\n field: column?.field ?? '',\n value: row?.[column?.field as keyof typeof row] ?? null,\n isHeader: false,\n event,\n };\n } else if (header) {\n const colIndex = parseInt(header.getAttribute('data-col') ?? '-1', 10);\n const column = this.columns[colIndex];\n\n params = {\n row: null,\n rowIndex: -1,\n column,\n columnIndex: colIndex,\n field: column?.field ?? '',\n value: null,\n isHeader: true,\n event,\n };\n } else {\n return;\n }\n\n this.params = params;\n this.position = { x: event.clientX, y: event.clientY };\n\n const items = buildMenuItems(this.config.items ?? defaultItems, params);\n if (!items.length) return;\n\n if (this.menuElement) {\n this.menuElement.remove();\n }\n\n this.menuElement = createMenuElement(\n items,\n params,\n (item) => {\n if (item.action) {\n item.action(params);\n }\n this.menuElement?.remove();\n this.menuElement = null;\n this.isOpen = false;\n },\n this.gridIcons.submenuArrow,\n );\n\n document.body.appendChild(this.menuElement);\n positionMenu(this.menuElement, event.clientX, event.clientY);\n this.isOpen = true;\n\n this.emit('context-menu-open', { params, items });\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Programmatically show the context menu at the specified position.\n * @param x - X coordinate\n * @param y - Y coordinate\n * @param params - Partial context menu parameters\n */\n showMenu(x: number, y: number, params: Partial<ContextMenuParams>): void {\n const fullParams: ContextMenuParams = {\n row: params.row ?? null,\n rowIndex: params.rowIndex ?? -1,\n column: params.column ?? null,\n columnIndex: params.columnIndex ?? -1,\n field: params.field ?? '',\n value: params.value ?? null,\n isHeader: params.isHeader ?? false,\n event: params.event ?? new MouseEvent('contextmenu'),\n };\n\n const items = buildMenuItems(this.config.items ?? defaultItems, fullParams);\n\n if (this.menuElement) {\n this.menuElement.remove();\n }\n\n this.menuElement = createMenuElement(\n items,\n fullParams,\n (item) => {\n if (item.action) item.action(fullParams);\n this.menuElement?.remove();\n this.menuElement = null;\n this.isOpen = false;\n },\n this.gridIcons.submenuArrow,\n );\n\n document.body.appendChild(this.menuElement);\n positionMenu(this.menuElement, x, y);\n this.isOpen = true;\n }\n\n /**\n * Hide the context menu.\n */\n hideMenu(): void {\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n this.isOpen = false;\n }\n }\n\n /**\n * Check if the context menu is currently open.\n * @returns Whether the menu is open\n */\n isMenuOpen(): boolean {\n return this.isOpen;\n }\n // #endregion\n\n // Styles are injected globally via installGlobalHandlers() since menu renders in document.body\n}\n"],"names":["buildMenuItems","items","params","item","isItemDisabled","createMenuElement","onAction","submenuArrow","DEFAULT_GRID_ICONS","menu","separator","menuItem","disabled","icon","label","shortcut","arrow","subMenuItems","subMenu","e","positionMenu","x","y","menuRect","viewportWidth","viewportHeight","left","top","globalClickHandler","globalKeydownHandler","globalStyleSheet","globalHandlerRefCount","defaultItems","ContextMenuPlugin","BaseGridPlugin","grid","contextMenuStyles","gridEl","container","event","target","cell","header","rowIndex","colIndex","column","row","fullParams"],"mappings":"m8DAiBO,SAASA,EACdC,EACAC,EACmB,CAGnB,OAFkB,OAAOD,GAAU,WAAaA,EAAMC,CAAM,EAAID,GAE/C,OAAQE,GACnB,EAAAA,EAAK,SAAW,IAChB,OAAOA,EAAK,QAAW,YAAcA,EAAK,OAAOD,CAAM,EAE5D,CACH,CASO,SAASE,EAAeD,EAAuBD,EAAoC,CACxF,OAAIC,EAAK,WAAa,GAAa,GAC/B,OAAOA,EAAK,UAAa,WAAmBA,EAAK,SAASD,CAAM,EAC7D,EACT,CAWO,SAASG,EACdJ,EACAC,EACAI,EACAC,EAA0BC,EAAAA,mBAAmB,aAChC,CACb,MAAMC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY,mBACjBA,EAAK,aAAa,OAAQ,MAAM,EAEhC,UAAWN,KAAQF,EAAO,CACxB,GAAIE,EAAK,UAAW,CAClB,MAAMO,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,UAAY,6BACtBA,EAAU,aAAa,OAAQ,WAAW,EAC1CD,EAAK,YAAYC,CAAS,EAC1B,QACF,CAEA,MAAMC,EAAW,SAAS,cAAc,KAAK,EAC7CA,EAAS,UAAY,wBACjBR,EAAK,UAAUQ,EAAS,UAAU,IAAIR,EAAK,QAAQ,EACvDQ,EAAS,aAAa,OAAQ,UAAU,EACxCA,EAAS,aAAa,UAAWR,EAAK,EAAE,EAExC,MAAMS,EAAWR,EAAeD,EAAMD,CAAM,EAM5C,GALIU,IACFD,EAAS,UAAU,IAAI,UAAU,EACjCA,EAAS,aAAa,gBAAiB,MAAM,GAG3CR,EAAK,KAAM,CACb,MAAMU,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,UAAY,wBACjBA,EAAK,UAAYV,EAAK,KACtBQ,EAAS,YAAYE,CAAI,CAC3B,CAEA,MAAMC,EAAQ,SAAS,cAAc,MAAM,EAK3C,GAJAA,EAAM,UAAY,yBAClBA,EAAM,YAAcX,EAAK,KACzBQ,EAAS,YAAYG,CAAK,EAEtBX,EAAK,SAAU,CACjB,MAAMY,EAAW,SAAS,cAAc,MAAM,EAC9CA,EAAS,UAAY,4BACrBA,EAAS,YAAcZ,EAAK,SAC5BQ,EAAS,YAAYI,CAAQ,CAC/B,CAEA,GAAIZ,EAAK,SAAS,OAAQ,CACxB,MAAMa,EAAQ,SAAS,cAAc,MAAM,EAC3CA,EAAM,UAAY,yBAEd,OAAOT,GAAiB,SAC1BS,EAAM,UAAYT,EACTA,aAAwB,aACjCS,EAAM,YAAYT,EAAa,UAAU,EAAI,CAAC,EAEhDI,EAAS,YAAYK,CAAK,EAG1BL,EAAS,iBAAiB,aAAc,IAAM,CAG5C,GAFwBA,EAAS,cAAc,mBAAmB,GAE9D,CAACR,EAAK,QAAS,OAEnB,MAAMc,EAAejB,EAAeG,EAAK,QAASD,CAAM,EAClDgB,EAAUb,EAAkBY,EAAcf,EAAQI,EAAUC,CAAY,EAC9EW,EAAQ,UAAU,IAAI,qBAAqB,EAC3CA,EAAQ,MAAM,SAAW,WACzBA,EAAQ,MAAM,KAAO,OACrBA,EAAQ,MAAM,IAAM,IACpBP,EAAS,MAAM,SAAW,WAC1BA,EAAS,YAAYO,CAAO,CAC9B,CAAC,EAEDP,EAAS,iBAAiB,aAAc,IAAM,CAC5C,MAAMO,EAAUP,EAAS,cAAc,mBAAmB,EACtDO,KAAiB,OAAA,CACvB,CAAC,CACH,CAEI,CAACN,GAAYT,EAAK,QAAU,CAACA,EAAK,SACpCQ,EAAS,iBAAiB,QAAUQ,GAAM,CACxCA,EAAE,gBAAA,EACFb,EAASH,CAAI,CACf,CAAC,EAGHM,EAAK,YAAYE,CAAQ,CAC3B,CAEA,OAAOF,CACT,CAUO,SAASW,EAAaX,EAAmBY,EAAWC,EAAiB,CAE1Eb,EAAK,MAAM,SAAW,QACtBA,EAAK,MAAM,KAAO,GAAGY,CAAC,KACtBZ,EAAK,MAAM,IAAM,GAAGa,CAAC,KACrBb,EAAK,MAAM,WAAa,SACxBA,EAAK,MAAM,OAAS,QAGpB,MAAMc,EAAWd,EAAK,sBAAA,EAGhBe,EAAgB,OAAO,WACvBC,EAAiB,OAAO,YAE9B,IAAIC,EAAOL,EACPM,EAAML,EAGND,EAAIE,EAAS,MAAQC,IACvBE,EAAOL,EAAIE,EAAS,OAGlBD,EAAIC,EAAS,OAASE,IACxBE,EAAML,EAAIC,EAAS,QAIrBG,EAAO,KAAK,IAAI,EAAGA,CAAI,EACvBC,EAAM,KAAK,IAAI,EAAGA,CAAG,EAErBlB,EAAK,MAAM,KAAO,GAAGiB,CAAI,KACzBjB,EAAK,MAAM,IAAM,GAAGkB,CAAG,KACvBlB,EAAK,MAAM,WAAa,SAC1B,CCjLA,IAAImB,EAAkD,KAElDC,EAA4D,KAE5DC,EAA4C,KAE5CC,EAAwB,EAG5B,MAAMC,EAAkC,CACtC,CACE,GAAI,OACJ,KAAM,OACN,SAAU,SACV,OAAS9B,GAAW,CACJA,EAA8F,MACtG,SAAS,WAAW,OAAA,CAC5B,CAAA,EAEF,CAAE,UAAW,GAAM,GAAI,OAAQ,KAAM,EAAA,EACrC,CACE,GAAI,aACJ,KAAM,aACN,OAASA,GAAW,CACJA,EACX,MACG,SAAS,QAAQ,YAAA,CACzB,CAAA,CAEJ,EAiBO,MAAM+B,UAA0BC,EAAAA,cAAkC,CAC9D,KAAO,cAEhB,IAAuB,eAA4C,CACjE,MAAO,CACL,MAAOF,CAAA,CAEX,CAGQ,OAAS,GACT,SAAW,CAAE,EAAG,EAAG,EAAG,CAAA,EACtB,OAAmC,KACnC,YAAkC,KAKjC,OAAOG,EAAiE,CAC/E,MAAM,OAAOA,CAAI,EACjB,KAAK,sBAAA,EACLJ,GACF,CAES,QAAe,CAClB,KAAK,cACP,KAAK,YAAY,OAAA,EACjB,KAAK,YAAc,MAErB,KAAK,OAAS,GACd,KAAK,OAAS,KACd,KAAK,wBAAA,CACP,CAKQ,uBAA8B,CAKlC,CAACD,GACD,OAAO,SAAa,KACpB,OAAOM,GAAsB,UAC7BA,IAEAN,EAAmB,SAAS,cAAc,OAAO,EACjDA,EAAiB,GAAK,0BACtBA,EAAiB,YAAcM,EAC/B,SAAS,KAAK,YAAYN,CAAgB,GAIvCF,IACHA,EAAqB,IAAM,CACX,SAAS,iBAAiB,mBAAmB,EACrD,QAASnB,GAASA,EAAK,QAAQ,CACvC,EACA,SAAS,iBAAiB,QAASmB,CAAkB,GAIlDC,IACHA,EAAwBV,GAAqB,CACvCA,EAAE,MAAQ,UACE,SAAS,iBAAiB,mBAAmB,EACrD,QAASV,GAASA,EAAK,QAAQ,CAEzC,EACA,SAAS,iBAAiB,UAAWoB,CAAoB,EAE7D,CAMQ,yBAAgC,CACtCE,IACI,EAAAA,EAAwB,KAGxBH,IACF,SAAS,oBAAoB,QAASA,CAAkB,EACxDA,EAAqB,MAEnBC,IACF,SAAS,oBAAoB,UAAWA,CAAoB,EAC5DA,EAAuB,MAErBC,IACFA,EAAiB,OAAA,EACjBA,EAAmB,MAEvB,CAKS,aAAoB,CAC3B,MAAMO,EAAS,KAAK,YACpB,GAAI,CAACA,EAAQ,OAEb,MAAMC,EAAYD,EAAO,SAAS,CAAC,EAC9BC,GAGDA,EAAU,aAAa,yBAAyB,IAAM,SAC1DA,EAAU,aAAa,0BAA2B,MAAM,EAExDA,EAAU,iBAAiB,cAAgBnB,GAAa,CACtD,MAAMoB,EAAQpB,EACdoB,EAAM,eAAA,EAEN,MAAMC,EAASD,EAAM,OACfE,EAAOD,EAAO,QAAQ,sBAAsB,EAC5CE,EAASF,EAAO,QAAQ,cAAc,EAE5C,IAAItC,EAEJ,GAAIuC,EAAM,CACR,MAAME,EAAW,SAASF,EAAK,aAAa,UAAU,GAAK,KAAM,EAAE,EAC7DG,EAAW,SAASH,EAAK,aAAa,UAAU,GAAK,KAAM,EAAE,EAC7DI,EAAS,KAAK,QAAQD,CAAQ,EAC9BE,EAAM,KAAK,KAAKH,CAAQ,EAE9BzC,EAAS,CACP,IAAA4C,EACA,SAAAH,EACA,OAAAE,EACA,YAAaD,EACb,MAAOC,GAAQ,OAAS,GACxB,MAAOC,IAAMD,GAAQ,KAAyB,GAAK,KACnD,SAAU,GACV,MAAAN,CAAA,CAEJ,SAAWG,EAAQ,CACjB,MAAME,EAAW,SAASF,EAAO,aAAa,UAAU,GAAK,KAAM,EAAE,EAC/DG,EAAS,KAAK,QAAQD,CAAQ,EAEpC1C,EAAS,CACP,IAAK,KACL,SAAU,GACV,OAAA2C,EACA,YAAaD,EACb,MAAOC,GAAQ,OAAS,GACxB,MAAO,KACP,SAAU,GACV,MAAAN,CAAA,CAEJ,KACE,QAGF,KAAK,OAASrC,EACd,KAAK,SAAW,CAAE,EAAGqC,EAAM,QAAS,EAAGA,EAAM,OAAA,EAE7C,MAAMtC,EAAQD,EAAe,KAAK,OAAO,OAASgC,EAAc9B,CAAM,EACjED,EAAM,SAEP,KAAK,aACP,KAAK,YAAY,OAAA,EAGnB,KAAK,YAAcI,EACjBJ,EACAC,EACCC,GAAS,CACJA,EAAK,QACPA,EAAK,OAAOD,CAAM,EAEpB,KAAK,aAAa,OAAA,EAClB,KAAK,YAAc,KACnB,KAAK,OAAS,EAChB,EACA,KAAK,UAAU,YAAA,EAGjB,SAAS,KAAK,YAAY,KAAK,WAAW,EAC1CkB,EAAa,KAAK,YAAamB,EAAM,QAASA,EAAM,OAAO,EAC3D,KAAK,OAAS,GAEd,KAAK,KAAK,oBAAqB,CAAE,OAAArC,EAAQ,MAAAD,EAAO,EAClD,CAAC,EACH,CAWA,SAASoB,EAAWC,EAAWpB,EAA0C,CACvE,MAAM6C,EAAgC,CACpC,IAAK7C,EAAO,KAAO,KACnB,SAAUA,EAAO,UAAY,GAC7B,OAAQA,EAAO,QAAU,KACzB,YAAaA,EAAO,aAAe,GACnC,MAAOA,EAAO,OAAS,GACvB,MAAOA,EAAO,OAAS,KACvB,SAAUA,EAAO,UAAY,GAC7B,MAAOA,EAAO,OAAS,IAAI,WAAW,aAAa,CAAA,EAG/CD,EAAQD,EAAe,KAAK,OAAO,OAASgC,EAAce,CAAU,EAEtE,KAAK,aACP,KAAK,YAAY,OAAA,EAGnB,KAAK,YAAc1C,EACjBJ,EACA8C,EACC5C,GAAS,CACJA,EAAK,QAAQA,EAAK,OAAO4C,CAAU,EACvC,KAAK,aAAa,OAAA,EAClB,KAAK,YAAc,KACnB,KAAK,OAAS,EAChB,EACA,KAAK,UAAU,YAAA,EAGjB,SAAS,KAAK,YAAY,KAAK,WAAW,EAC1C3B,EAAa,KAAK,YAAaC,EAAGC,CAAC,EACnC,KAAK,OAAS,EAChB,CAKA,UAAiB,CACX,KAAK,cACP,KAAK,YAAY,OAAA,EACjB,KAAK,YAAc,KACnB,KAAK,OAAS,GAElB,CAMA,YAAsB,CACpB,OAAO,KAAK,MACd,CAIF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(E,b){typeof exports=="object"&&typeof module<"u"?b(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],b):(E=typeof globalThis<"u"?globalThis:E||self,b(E.TbwGridPlugin_editing={},E.TbwGrid))})(this,(function(E,b){"use strict";function S(l){switch(l.type){case"number":return e=>{const t=document.createElement("input");return t.type="number",t.value=e.value!=null?String(e.value):"",t.addEventListener("blur",()=>e.commit(t.value===""?null:Number(t.value))),t.addEventListener("keydown",i=>{i.key==="Enter"&&e.commit(t.value===""?null:Number(t.value)),i.key==="Escape"&&e.cancel()}),t};case"boolean":return e=>{const t=document.createElement("input");return t.type="checkbox",t.checked=!!e.value,t.addEventListener("change",()=>e.commit(t.checked)),t};case"date":return e=>{const t=document.createElement("input");return t.type="date",e.value instanceof Date&&(t.valueAsDate=e.value),t.addEventListener("change",()=>e.commit(t.valueAsDate)),t.addEventListener("keydown",i=>{i.key==="Escape"&&e.cancel()}),t};case"select":case"typeahead":return e=>{const t=document.createElement("select"),i=e.column;i.multi&&(t.multiple=!0);const s=i.options;(typeof s=="function"?s():s||[]).forEach(r=>{const c=document.createElement("option");c.value=String(r.value),c.textContent=r.label,(i.multi&&Array.isArray(e.value)&&e.value.includes(r.value)||!i.multi&&e.value===r.value)&&(c.selected=!0),t.appendChild(c)});const o=()=>{if(i.multi){const r=[];Array.from(t.selectedOptions).forEach(c=>{r.push(c.value)}),e.commit(r)}else e.commit(t.value)};return t.addEventListener("change",o),t.addEventListener("blur",o),t.addEventListener("keydown",r=>{r.key==="Escape"&&e.cancel()}),t};default:return e=>{const t=document.createElement("input");return t.type="text",t.value=e.value!=null?String(e.value):"",t.addEventListener("blur",()=>e.commit(t.value)),t.addEventListener("keydown",i=>{i.key==="Enter"&&e.commit(t.value),i.key==="Escape"&&e.cancel()}),t}}}const R='input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])';function y(l){return!(typeof l!="string"||l==="__proto__"||l==="constructor"||l==="prototype")}function k(l){return(l.__editingCellCount??0)>0}function A(l){const e=(l.__editingCellCount??0)+1;l.__editingCellCount=e,l.setAttribute("data-has-editing","")}function L(l){l.__editingCellCount=0,l.removeAttribute("data-has-editing")}function w(l,e){return l instanceof HTMLInputElement?l.type==="checkbox"?l.checked:l.type==="number"?l.value===""?null:Number(l.value):l.type==="date"?l.valueAsDate:l.value:e?.type==="number"&&l.value!==""?Number(l.value):l.value}function T(l,e,t){const i=l.querySelector("input,textarea,select");i&&(i.addEventListener("blur",()=>{t(w(i,e))}),i instanceof HTMLInputElement&&i.type==="checkbox"?i.addEventListener("change",()=>t(i.checked)):i instanceof HTMLSelectElement&&i.addEventListener("change",()=>t(w(i,e))))}class O extends b.BaseGridPlugin{name="editing";get defaultConfig(){return{editOn:"click"}}#e=-1;#r=-1;#s=new Map;#t=new Set;#n=new Set;#o=!1;attach(e){super.attach(e);const t=this.disconnectSignal,i=e;i._activeEditRows=-1,i._rowEditSnapshots=new Map,i._changedRowIndices=new Set,Object.defineProperty(e,"changedRows",{get:()=>this.changedRows,configurable:!0}),Object.defineProperty(e,"changedRowIndices",{get:()=>this.changedRowIndices,configurable:!0}),e.resetChangedRows=s=>this.resetChangedRows(s),e.beginBulkEdit=(s,n)=>{n&&this.beginCellEdit(s,n)},document.addEventListener("keydown",s=>{s.key==="Escape"&&this.#e!==-1&&this.#i(this.#e,!0)},{capture:!0,signal:t}),document.addEventListener("mousedown",s=>{if(this.#e===-1)return;const n=i.findRenderedRowElement?.(this.#e);!n||(s.composedPath&&s.composedPath()||[]).includes(n)||this.#i(this.#e,!1)},{signal:t})}detach(){this.#e=-1,this.#r=-1,this.#s.clear(),this.#t.clear(),this.#n.clear(),super.detach()}onCellClick(e){const t=this.grid,i=this.config.editOn??t.effectiveConfig?.editOn;if(i===!1||i==="manual"||i!=="click"&&i!=="dblclick")return!1;const s=e.originalEvent.type==="dblclick";if(i==="click"&&s||i==="dblclick"&&!s)return!1;const{rowIndex:n}=e;return t._columns?.some(r=>r.editable)?(e.originalEvent.stopPropagation(),this.beginBulkEdit(n),!0):!1}onKeyDown(e){const t=this.grid;if(e.key==="Escape"&&this.#e!==-1)return this.#i(this.#e,!0),!0;if(e.key===" "||e.key==="Spacebar"){const i=t._focusRow,s=t._focusCol;if(i>=0&&s>=0){const n=t._visibleColumns[s],o=t._rows[i];if(n?.editable&&n.type==="boolean"&&o){const r=n.field;if(y(r)){const f=!o[r];return this.#c(i,n,f,o),e.preventDefault(),this.requestRender(),!0}}}return!1}if(e.key==="Enter"&&!e.shiftKey){if(this.#e!==-1)return!1;const i=this.config.editOn??t.effectiveConfig?.editOn;if(i===!1||i==="manual")return!1;const s=t._focusRow;return s>=0&&t._columns?.some(o=>o.editable)?(this.beginBulkEdit(s),!0):!1}return!1}afterRender(){const e=this.grid;if(this.#o&&(this.#o=!1,this.#u(e)),this.#n.size!==0)for(const t of this.#n){const[i,s]=t.split(":"),n=parseInt(i,10),o=parseInt(s,10),r=e.findRenderedRowElement?.(n);if(!r)continue;const c=r.querySelector(`.cell[data-col="${o}"]`);if(!c||c.classList.contains("editing"))continue;const f=e._rows[n],d=e._visibleColumns[o];f&&d&&this.#a(f,n,d,o,c,!0)}}onScrollRender(){this.afterRender()}get changedRows(){const e=this.grid;return Array.from(this.#t).map(t=>e._rows[t])}get changedRowIndices(){return Array.from(this.#t)}get activeEditRow(){return this.#e}get activeEditCol(){return this.#r}isRowEditing(e){return this.#e===e}isCellEditing(e,t){return this.#n.has(`${e}:${t}`)}isRowChanged(e){return this.#t.has(e)}resetChangedRows(e){const t=this.changedRows,i=this.changedRowIndices;this.#t.clear(),this.#l(),e||this.emit("changed-rows-reset",{rows:t,indices:i}),this.grid._rowPool?.forEach(n=>n.classList.remove("changed"))}beginCellEdit(e,t){const i=this.grid,s=i._visibleColumns.findIndex(c=>c.field===t);if(s===-1||!i._visibleColumns[s]?.editable)return;const r=i.findRenderedRowElement?.(e)?.querySelector(`.cell[data-col="${s}"]`);r&&this.#f(e,s,r)}beginBulkEdit(e){const t=this.grid;if((this.config.editOn??t.effectiveConfig?.editOn)===!1||!t._columns?.some(r=>r.editable))return;const n=t.findRenderedRowElement?.(e);if(!n)return;const o=t._rows[e];this.#d(e,o),Array.from(n.children).forEach((r,c)=>{const f=t._visibleColumns[c];if(f?.editable){const d=r;d.classList.contains("editing")||this.#a(o,e,f,c,d,!0)}}),setTimeout(()=>{let r=n.querySelector(`.cell[data-col="${t._focusCol}"]`);if(r?.classList.contains("editing")||(r=n.querySelector(".cell.editing")),r?.classList.contains("editing")){const c=r.querySelector(R);try{c?.focus({preventScroll:!0})}catch{}}},0)}commitActiveRowEdit(){this.#e!==-1&&this.#i(this.#e,!1)}cancelActiveRowEdit(){this.#e!==-1&&this.#i(this.#e,!0)}#f(e,t,i){const s=this.grid,n=s._rows[e],o=s._visibleColumns[t];!n||!o?.editable||i.classList.contains("editing")||(this.#e!==e&&this.#d(e,n),this.#r=t,this.#a(n,e,o,t,i,!1))}#l(){const e=this.grid;e._activeEditRows=this.#e,e._rowEditSnapshots=this.#s,e._changedRowIndices=this.#t}#d(e,t){this.#e!==e&&(this.#s.set(e,{...t}),this.#e=e,this.#l())}#i(e,t){if(this.#e!==e)return;const i=this.grid,s=this.#s.get(e),n=i._rows[e],o=i.findRenderedRowElement?.(e);if(!t&&o&&n&&o.querySelectorAll(".cell.editing").forEach(c=>{const f=Number(c.getAttribute("data-col"));if(isNaN(f))return;const d=i._visibleColumns[f];if(!d)return;const m=c.querySelector("input,textarea,select");if(m){const a=w(m,d);n[d.field]!==a&&this.#c(e,d,a,n)}}),t&&s&&n)Object.keys(s).forEach(r=>{n[r]=s[r]}),this.#t.delete(e);else if(!t){const r=this.#t.has(e);this.emit("row-commit",{rowIndex:e,row:n,changed:r,changedRows:this.changedRows,changedRowIndices:this.changedRowIndices})}this.#s.delete(e),this.#e=-1,this.#r=-1,this.#l();for(const r of this.#n)r.startsWith(`${e}:`)&&this.#n.delete(r);o&&(o.querySelectorAll(".cell.editing").forEach(r=>{r.classList.remove("editing"),L(r.parentElement)}),this.requestRender()),this.#o=!0,o||(this.#u(i),this.#o=!1)}#c(e,t,i,s){const n=t.field;if(!y(n))return;const o=s[n];if(o===i)return;const r=!this.#t.has(e);if(this.emitCancelable("cell-commit",{row:s,field:n,oldValue:o,value:i,rowIndex:e,changedRows:this.changedRows,changedRowIndices:this.changedRowIndices,firstTimeForRow:r}))return;s[n]=i,this.#t.add(e),this.#l();const d=this.grid.findRenderedRowElement?.(e);d&&d.classList.add("changed")}#a(e,t,i,s,n,o){if(!i.editable||n.classList.contains("editing"))return;const r=y(i.field)?e[i.field]:void 0;n.classList.add("editing"),this.#n.add(`${t}:${s}`);const c=n.parentElement;c&&A(c);let f=!1;const d=h=>{f||this.#e===-1||this.#c(t,i,h,e)},m=()=>{f=!0,y(i.field)&&(e[i.field]=r)},a=document.createElement("div");a.className="tbw-editor-host",n.innerHTML="",n.appendChild(a),a.addEventListener("keydown",h=>{h.key==="Enter"&&(h.stopPropagation(),h.preventDefault(),f=!0,this.#i(t,!1)),h.key==="Escape"&&(h.stopPropagation(),h.preventDefault(),m(),this.#i(t,!0))});const g=i,C=g.__editorTemplate,u=g.editor||(C?"template":S(i)),v=r;if(u==="template"&&C)this.#h(a,g,e,r,d,m,o,t);else if(typeof u=="string"){const h=document.createElement(u);h.value=v,h.addEventListener("change",()=>d(h.value)),a.appendChild(h),o||queueMicrotask(()=>{a.querySelector(R)?.focus({preventScroll:!0})})}else if(typeof u=="function"){const h={row:e,value:v,field:i.field,column:i,commit:d,cancel:m},p=u(h);typeof p=="string"?(a.innerHTML=p,T(a,i,d)):p instanceof Node&&a.appendChild(p),o||queueMicrotask(()=>{a.querySelector(R)?.focus({preventScroll:!0})})}else if(u&&typeof u=="object"){const h=this.grid,p=document.createElement("div");p.setAttribute("data-external-editor",""),p.setAttribute("data-field",i.field),a.appendChild(p);const _={row:e,value:v,field:i.field,column:i,commit:d,cancel:m};if(u.mount)try{u.mount({placeholder:p,context:_,spec:u})}catch(q){console.warn(`[tbw-grid] External editor mount error for column '${i.field}':`,q)}else h.dispatchEvent(new CustomEvent("mount-external-editor",{detail:{placeholder:p,spec:u,context:_}}))}}#h(e,t,i,s,n,o,r,c){const f=t.__editorTemplate;if(!f)return;const d=f.cloneNode(!0),m=t.__compiledEditor;m?d.innerHTML=m({row:i,value:s,field:t.field,column:t,commit:n,cancel:o}):d.querySelectorAll("*").forEach(g=>{g.childNodes.length===1&&g.firstChild?.nodeType===Node.TEXT_NODE&&(g.textContent=g.textContent?.replace(/{{\s*value\s*}}/g,s==null?"":String(s)).replace(/{{\s*row\.([a-zA-Z0-9_]+)\s*}}/g,(C,u)=>{if(!y(u))return"";const v=i[u];return v==null?"":String(v)})||"")});const a=d.querySelector("input,textarea,select");if(a){a instanceof HTMLInputElement&&a.type==="checkbox"?a.checked=!!s:a.value=String(s??"");let g=!1;a.addEventListener("blur",()=>{g||n(w(a,t))}),a.addEventListener("keydown",C=>{const u=C;u.key==="Enter"&&(u.stopPropagation(),u.preventDefault(),g=!0,n(w(a,t)),this.#i(c,!1)),u.key==="Escape"&&(u.stopPropagation(),u.preventDefault(),o(),this.#i(c,!0))}),a instanceof HTMLInputElement&&a.type==="checkbox"&&a.addEventListener("change",()=>n(a.checked)),r||setTimeout(()=>a.focus({preventScroll:!0}),0)}e.appendChild(d)}#u(e){queueMicrotask(()=>{try{const t=e._focusRow,i=e._focusCol,s=e.findRenderedRowElement?.(t);if(s){Array.from(e._bodyEl.querySelectorAll(".cell-focus")).forEach(o=>o.classList.remove("cell-focus"));const n=s.querySelector(`.cell[data-row="${t}"][data-col="${i}"]`);n&&(n.classList.add("cell-focus"),n.setAttribute("aria-selected","true"),n.hasAttribute("tabindex")||n.setAttribute("tabindex","-1"),n.focus({preventScroll:!0}))}}catch{}})}}E.EditingPlugin=O,E.FOCUSABLE_EDITOR_SELECTOR=R,E.clearEditingState=L,E.defaultEditorFor=S,E.hasEditingCells=k,Object.defineProperty(E,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(g,w){typeof exports=="object"&&typeof module<"u"?w(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],w):(g=typeof globalThis<"u"?globalThis:g||self,w(g.TbwGridPlugin_editing={},g.TbwGrid))})(this,(function(g,w){"use strict";const L="@layer tbw-plugins{tbw-grid{--tbw-editing-bg: var(--tbw-color-selection);--tbw-editing-row-bg: var(--tbw-editing-bg);--tbw-editing-border: var(--tbw-border-input, 1px solid var(--tbw-color-border-strong));--tbw-padding-editing-input: var(--tbw-cell-padding-input, 2px 6px);--tbw-font-size-editor: inherit;--tbw-editing-row-outline-color: var(--tbw-color-accent);--tbw-editing-row-outline-width: 1px}tbw-grid .data-grid-row:has(.editing){background:var(--tbw-editing-row-bg);outline:var(--tbw-editing-row-outline-width) solid var(--tbw-editing-row-outline-color);outline-offset:calc(-1 * var(--tbw-editing-row-outline-width))}tbw-grid .data-grid-row>.cell.editing{overflow:hidden;padding:0;display:flex;min-height:calc(var(--tbw-row-height) + 2px);align-items:center;justify-content:center}tbw-grid .data-grid-row>.cell.editing input:not([type=checkbox]),tbw-grid .data-grid-row>.cell.editing select,tbw-grid .data-grid-row>.cell.editing textarea{width:100%;height:100%;flex:1 1 auto;min-width:0;border:var(--tbw-editing-border);padding:var(--tbw-padding-editing-input);font-size:var(--tbw-font-size-editor)}tbw-grid .tbw-editor-host{display:contents}}";function S(l){switch(l.type){case"number":return e=>{const t=document.createElement("input");return t.type="number",t.value=e.value!=null?String(e.value):"",t.addEventListener("blur",()=>e.commit(t.value===""?null:Number(t.value))),t.addEventListener("keydown",i=>{i.key==="Enter"&&e.commit(t.value===""?null:Number(t.value)),i.key==="Escape"&&e.cancel()}),t};case"boolean":return e=>{const t=document.createElement("input");return t.type="checkbox",t.checked=!!e.value,t.addEventListener("change",()=>e.commit(t.checked)),t};case"date":return e=>{const t=document.createElement("input");return t.type="date",e.value instanceof Date&&(t.valueAsDate=e.value),t.addEventListener("change",()=>e.commit(t.valueAsDate)),t.addEventListener("keydown",i=>{i.key==="Escape"&&e.cancel()}),t};case"select":case"typeahead":return e=>{const t=document.createElement("select"),i=e.column;i.multi&&(t.multiple=!0);const s=i.options;(typeof s=="function"?s():s||[]).forEach(r=>{const c=document.createElement("option");c.value=String(r.value),c.textContent=r.label,(i.multi&&Array.isArray(e.value)&&e.value.includes(r.value)||!i.multi&&e.value===r.value)&&(c.selected=!0),t.appendChild(c)});const o=()=>{if(i.multi){const r=[];Array.from(t.selectedOptions).forEach(c=>{r.push(c.value)}),e.commit(r)}else e.commit(t.value)};return t.addEventListener("change",o),t.addEventListener("blur",o),t.addEventListener("keydown",r=>{r.key==="Escape"&&e.cancel()}),t};default:return e=>{const t=document.createElement("input");return t.type="text",t.value=e.value!=null?String(e.value):"",t.addEventListener("blur",()=>e.commit(t.value)),t.addEventListener("keydown",i=>{i.key==="Enter"&&e.commit(t.value),i.key==="Escape"&&e.cancel()}),t}}}const R='input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])';function v(l){return!(typeof l!="string"||l==="__proto__"||l==="constructor"||l==="prototype")}function A(l){return(l.__editingCellCount??0)>0}function T(l){const e=(l.__editingCellCount??0)+1;l.__editingCellCount=e,l.setAttribute("data-has-editing","")}function k(l){l.__editingCellCount=0,l.removeAttribute("data-has-editing")}function y(l,e){return l instanceof HTMLInputElement?l.type==="checkbox"?l.checked:l.type==="number"?l.value===""?null:Number(l.value):l.type==="date"?l.valueAsDate:l.value:e?.type==="number"&&l.value!==""?Number(l.value):l.value}function O(l,e,t){const i=l.querySelector("input,textarea,select");i&&(i.addEventListener("blur",()=>{t(y(i,e))}),i instanceof HTMLInputElement&&i.type==="checkbox"?i.addEventListener("change",()=>t(i.checked)):i instanceof HTMLSelectElement&&i.addEventListener("change",()=>t(y(i,e))))}class q extends w.BaseGridPlugin{name="editing";styles=L;get defaultConfig(){return{editOn:"click"}}#e=-1;#r=-1;#s=new Map;#t=new Set;#n=new Set;#o=!1;attach(e){super.attach(e);const t=this.disconnectSignal,i=e;i._activeEditRows=-1,i._rowEditSnapshots=new Map,i._changedRowIndices=new Set,Object.defineProperty(e,"changedRows",{get:()=>this.changedRows,configurable:!0}),Object.defineProperty(e,"changedRowIndices",{get:()=>this.changedRowIndices,configurable:!0}),e.resetChangedRows=s=>this.resetChangedRows(s),e.beginBulkEdit=(s,n)=>{n&&this.beginCellEdit(s,n)},document.addEventListener("keydown",s=>{s.key==="Escape"&&this.#e!==-1&&this.#i(this.#e,!0)},{capture:!0,signal:t}),document.addEventListener("mousedown",s=>{if(this.#e===-1)return;const n=i.findRenderedRowElement?.(this.#e);!n||(s.composedPath&&s.composedPath()||[]).includes(n)||this.#i(this.#e,!1)},{signal:t})}detach(){this.#e=-1,this.#r=-1,this.#s.clear(),this.#t.clear(),this.#n.clear(),super.detach()}onCellClick(e){const t=this.grid,i=this.config.editOn??t.effectiveConfig?.editOn;if(i===!1||i==="manual"||i!=="click"&&i!=="dblclick")return!1;const s=e.originalEvent.type==="dblclick";if(i==="click"&&s||i==="dblclick"&&!s)return!1;const{rowIndex:n}=e;return t._columns?.some(r=>r.editable)?(e.originalEvent.stopPropagation(),this.beginBulkEdit(n),!0):!1}onKeyDown(e){const t=this.grid;if(e.key==="Escape"&&this.#e!==-1)return this.#i(this.#e,!0),!0;if(e.key===" "||e.key==="Spacebar"){const i=t._focusRow,s=t._focusCol;if(i>=0&&s>=0){const n=t._visibleColumns[s],o=t._rows[i];if(n?.editable&&n.type==="boolean"&&o){const r=n.field;if(v(r)){const f=!o[r];return this.#c(i,n,f,o),e.preventDefault(),this.requestRender(),!0}}}return!1}if(e.key==="Enter"&&!e.shiftKey){if(this.#e!==-1)return!1;const i=this.config.editOn??t.effectiveConfig?.editOn;if(i===!1||i==="manual")return!1;const s=t._focusRow;return s>=0&&t._columns?.some(o=>o.editable)?(this.beginBulkEdit(s),!0):!1}return!1}afterRender(){const e=this.grid;if(this.#o&&(this.#o=!1,this.#u(e)),this.#n.size!==0)for(const t of this.#n){const[i,s]=t.split(":"),n=parseInt(i,10),o=parseInt(s,10),r=e.findRenderedRowElement?.(n);if(!r)continue;const c=r.querySelector(`.cell[data-col="${o}"]`);if(!c||c.classList.contains("editing"))continue;const f=e._rows[n],d=e._visibleColumns[o];f&&d&&this.#a(f,n,d,o,c,!0)}}onScrollRender(){this.afterRender()}get changedRows(){const e=this.grid;return Array.from(this.#t).map(t=>e._rows[t])}get changedRowIndices(){return Array.from(this.#t)}get activeEditRow(){return this.#e}get activeEditCol(){return this.#r}isRowEditing(e){return this.#e===e}isCellEditing(e,t){return this.#n.has(`${e}:${t}`)}isRowChanged(e){return this.#t.has(e)}resetChangedRows(e){const t=this.changedRows,i=this.changedRowIndices;this.#t.clear(),this.#l(),e||this.emit("changed-rows-reset",{rows:t,indices:i}),this.grid._rowPool?.forEach(n=>n.classList.remove("changed"))}beginCellEdit(e,t){const i=this.grid,s=i._visibleColumns.findIndex(c=>c.field===t);if(s===-1||!i._visibleColumns[s]?.editable)return;const r=i.findRenderedRowElement?.(e)?.querySelector(`.cell[data-col="${s}"]`);r&&this.#f(e,s,r)}beginBulkEdit(e){const t=this.grid;if((this.config.editOn??t.effectiveConfig?.editOn)===!1||!t._columns?.some(r=>r.editable))return;const n=t.findRenderedRowElement?.(e);if(!n)return;const o=t._rows[e];this.#d(e,o),Array.from(n.children).forEach((r,c)=>{const f=t._visibleColumns[c];if(f?.editable){const d=r;d.classList.contains("editing")||this.#a(o,e,f,c,d,!0)}}),setTimeout(()=>{let r=n.querySelector(`.cell[data-col="${t._focusCol}"]`);if(r?.classList.contains("editing")||(r=n.querySelector(".cell.editing")),r?.classList.contains("editing")){const c=r.querySelector(R);try{c?.focus({preventScroll:!0})}catch{}}},0)}commitActiveRowEdit(){this.#e!==-1&&this.#i(this.#e,!1)}cancelActiveRowEdit(){this.#e!==-1&&this.#i(this.#e,!0)}#f(e,t,i){const s=this.grid,n=s._rows[e],o=s._visibleColumns[t];!n||!o?.editable||i.classList.contains("editing")||(this.#e!==e&&this.#d(e,n),this.#r=t,this.#a(n,e,o,t,i,!1))}#l(){const e=this.grid;e._activeEditRows=this.#e,e._rowEditSnapshots=this.#s,e._changedRowIndices=this.#t}#d(e,t){this.#e!==e&&(this.#s.set(e,{...t}),this.#e=e,this.#l())}#i(e,t){if(this.#e!==e)return;const i=this.grid,s=this.#s.get(e),n=i._rows[e],o=i.findRenderedRowElement?.(e);if(!t&&o&&n&&o.querySelectorAll(".cell.editing").forEach(c=>{const f=Number(c.getAttribute("data-col"));if(isNaN(f))return;const d=i._visibleColumns[f];if(!d)return;const b=c.querySelector("input,textarea,select");if(b){const a=y(b,d);n[d.field]!==a&&this.#c(e,d,a,n)}}),t&&s&&n)Object.keys(s).forEach(r=>{n[r]=s[r]}),this.#t.delete(e);else if(!t){const r=this.#t.has(e);this.emit("row-commit",{rowIndex:e,row:n,changed:r,changedRows:this.changedRows,changedRowIndices:this.changedRowIndices})}this.#s.delete(e),this.#e=-1,this.#r=-1,this.#l();for(const r of this.#n)r.startsWith(`${e}:`)&&this.#n.delete(r);o&&(o.querySelectorAll(".cell.editing").forEach(r=>{r.classList.remove("editing"),k(r.parentElement)}),this.requestRender()),this.#o=!0,o||(this.#u(i),this.#o=!1)}#c(e,t,i,s){const n=t.field;if(!v(n))return;const o=s[n];if(o===i)return;const r=!this.#t.has(e);if(this.emitCancelable("cell-commit",{row:s,field:n,oldValue:o,value:i,rowIndex:e,changedRows:this.changedRows,changedRowIndices:this.changedRowIndices,firstTimeForRow:r}))return;s[n]=i,this.#t.add(e),this.#l();const d=this.grid.findRenderedRowElement?.(e);d&&d.classList.add("changed")}#a(e,t,i,s,n,o){if(!i.editable||n.classList.contains("editing"))return;const r=v(i.field)?e[i.field]:void 0;n.classList.add("editing"),this.#n.add(`${t}:${s}`);const c=n.parentElement;c&&T(c);let f=!1;const d=h=>{f||this.#e===-1||this.#c(t,i,h,e)},b=()=>{f=!0,v(i.field)&&(e[i.field]=r)},a=document.createElement("div");a.className="tbw-editor-host",n.innerHTML="",n.appendChild(a),a.addEventListener("keydown",h=>{h.key==="Enter"&&(h.stopPropagation(),h.preventDefault(),f=!0,this.#i(t,!1)),h.key==="Escape"&&(h.stopPropagation(),h.preventDefault(),b(),this.#i(t,!0))});const p=i,C=p.__editorTemplate,u=p.editor||(C?"template":S(i)),m=r;if(u==="template"&&C)this.#h(a,p,e,r,d,b,o,t);else if(typeof u=="string"){const h=document.createElement(u);h.value=m,h.addEventListener("change",()=>d(h.value)),a.appendChild(h),o||queueMicrotask(()=>{a.querySelector(R)?.focus({preventScroll:!0})})}else if(typeof u=="function"){const h={row:e,value:m,field:i.field,column:i,commit:d,cancel:b},E=u(h);typeof E=="string"?(a.innerHTML=E,O(a,i,d)):E instanceof Node&&a.appendChild(E),o||queueMicrotask(()=>{a.querySelector(R)?.focus({preventScroll:!0})})}else if(u&&typeof u=="object"){const h=this.grid,E=document.createElement("div");E.setAttribute("data-external-editor",""),E.setAttribute("data-field",i.field),a.appendChild(E);const _={row:e,value:m,field:i.field,column:i,commit:d,cancel:b};if(u.mount)try{u.mount({placeholder:E,context:_,spec:u})}catch(G){console.warn(`[tbw-grid] External editor mount error for column '${i.field}':`,G)}else h.dispatchEvent(new CustomEvent("mount-external-editor",{detail:{placeholder:E,spec:u,context:_}}))}}#h(e,t,i,s,n,o,r,c){const f=t.__editorTemplate;if(!f)return;const d=f.cloneNode(!0),b=t.__compiledEditor;b?d.innerHTML=b({row:i,value:s,field:t.field,column:t,commit:n,cancel:o}):d.querySelectorAll("*").forEach(p=>{p.childNodes.length===1&&p.firstChild?.nodeType===Node.TEXT_NODE&&(p.textContent=p.textContent?.replace(/{{\s*value\s*}}/g,s==null?"":String(s)).replace(/{{\s*row\.([a-zA-Z0-9_]+)\s*}}/g,(C,u)=>{if(!v(u))return"";const m=i[u];return m==null?"":String(m)})||"")});const a=d.querySelector("input,textarea,select");if(a){a instanceof HTMLInputElement&&a.type==="checkbox"?a.checked=!!s:a.value=String(s??"");let p=!1;a.addEventListener("blur",()=>{p||n(y(a,t))}),a.addEventListener("keydown",C=>{const u=C;u.key==="Enter"&&(u.stopPropagation(),u.preventDefault(),p=!0,n(y(a,t)),this.#i(c,!1)),u.key==="Escape"&&(u.stopPropagation(),u.preventDefault(),o(),this.#i(c,!0))}),a instanceof HTMLInputElement&&a.type==="checkbox"&&a.addEventListener("change",()=>n(a.checked)),r||setTimeout(()=>a.focus({preventScroll:!0}),0)}e.appendChild(d)}#u(e){queueMicrotask(()=>{try{const t=e._focusRow,i=e._focusCol,s=e.findRenderedRowElement?.(t);if(s){Array.from(e._bodyEl.querySelectorAll(".cell-focus")).forEach(o=>o.classList.remove("cell-focus"));const n=s.querySelector(`.cell[data-row="${t}"][data-col="${i}"]`);n&&(n.classList.add("cell-focus"),n.setAttribute("aria-selected","true"),n.hasAttribute("tabindex")||n.setAttribute("tabindex","-1"),n.focus({preventScroll:!0}))}}catch{}})}}g.EditingPlugin=q,g.FOCUSABLE_EDITOR_SELECTOR=R,g.clearEditingState=k,g.defaultEditorFor=S,g.hasEditingCells=A,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=editing.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editing.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/editing/editors.ts","../../../../../libs/grid/src/lib/plugins/editing/EditingPlugin.ts"],"sourcesContent":["/**\n * Default Editors Module\n *\n * Provides built-in editor factories for different column types.\n *\n * IMPORTANT: Editor factories should NOT call focus() on elements - they are called\n * before the element is appended to the DOM. The calling code (beginBulkEdit,\n * inlineEnterEdit) is responsible for focusing the correct editor after insertion.\n */\n\nimport type { ColumnConfig, EditorContext } from '../../core/types';\n\n/**\n * Returns a default editor factory function for the given column type.\n * Each editor handles commit on blur/Enter, and cancel on Escape.\n * Note: Focus is NOT called here - the calling code handles focusing after DOM insertion.\n */\nexport function defaultEditorFor(column: ColumnConfig<any>): (ctx: EditorContext) => HTMLElement | string {\n switch (column.type) {\n case 'number':\n return (ctx: EditorContext) => {\n const input = document.createElement('input');\n input.type = 'number';\n input.value = ctx.value != null ? String(ctx.value) : '';\n input.addEventListener('blur', () => ctx.commit(input.value === '' ? null : Number(input.value)));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') ctx.commit(input.value === '' ? null : Number(input.value));\n if (e.key === 'Escape') ctx.cancel();\n });\n return input;\n };\n case 'boolean':\n return (ctx: EditorContext) => {\n const input = document.createElement('input');\n input.type = 'checkbox';\n input.checked = !!ctx.value;\n input.addEventListener('change', () => ctx.commit(input.checked));\n return input;\n };\n case 'date':\n return (ctx: EditorContext) => {\n const input = document.createElement('input');\n input.type = 'date';\n if (ctx.value instanceof Date) input.valueAsDate = ctx.value;\n input.addEventListener('change', () => ctx.commit(input.valueAsDate));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n return input;\n };\n case 'select':\n case 'typeahead':\n return (ctx: EditorContext) => {\n const select = document.createElement('select');\n const col = ctx.column;\n if (col.multi) select.multiple = true;\n const rawOptions = col.options;\n const options = typeof rawOptions === 'function' ? rawOptions() : rawOptions || [];\n options.forEach((opt) => {\n const o = document.createElement('option');\n o.value = String(opt.value);\n o.textContent = opt.label;\n if (col.multi && Array.isArray(ctx.value) && ctx.value.includes(opt.value)) o.selected = true;\n else if (!col.multi && ctx.value === opt.value) o.selected = true;\n select.appendChild(o);\n });\n const commitValue = () => {\n if (col.multi) {\n const values: unknown[] = [];\n Array.from(select.selectedOptions).forEach((o) => {\n values.push(o.value);\n });\n ctx.commit(values);\n } else {\n ctx.commit(select.value);\n }\n };\n select.addEventListener('change', commitValue);\n select.addEventListener('blur', commitValue);\n select.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n return select;\n };\n default:\n return (ctx: EditorContext) => {\n const input = document.createElement('input');\n input.type = 'text';\n input.value = ctx.value != null ? String(ctx.value) : '';\n input.addEventListener('blur', () => ctx.commit(input.value));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') ctx.commit(input.value);\n if (e.key === 'Escape') ctx.cancel();\n });\n return input;\n };\n }\n}\n","/**\n * Editing Plugin\n *\n * Provides complete editing functionality for tbw-grid.\n * This plugin is FULLY SELF-CONTAINED - the grid has ZERO editing knowledge.\n *\n * The plugin:\n * - Owns all editing state (active cell, snapshots, changed rows)\n * - Uses event distribution (onCellClick, onKeyDown) to handle edit lifecycle\n * - Uses afterRender() hook to inject editors into cells\n * - Uses processColumns() to augment columns with editing metadata\n * - Emits its own events (cell-commit, row-commit, changed-rows-reset)\n *\n * Without this plugin, the grid cannot edit. With this plugin, editing\n * is fully functional without any core changes.\n */\n\nimport { BaseGridPlugin, type CellClickEvent, type GridElement } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ColumnInternal, InternalGrid, RowElementInternal } from '../../core/types';\nimport { defaultEditorFor } from './editors';\nimport type { CellCommitDetail, ChangedRowsResetDetail, EditingConfig, EditorContext, RowCommitDetail } from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * CSS selector for focusable editor elements within a cell.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Returns true if the given property key is safe to use on a plain object.\n */\nfunction isSafePropertyKey(key: unknown): key is string {\n if (typeof key !== 'string') return false;\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') return false;\n return true;\n}\n\n/**\n * Check if a row element has any cells in editing mode.\n */\nexport function hasEditingCells(rowEl: RowElementInternal): boolean {\n return (rowEl.__editingCellCount ?? 0) > 0;\n}\n\n/**\n * Increment the editing cell count on a row element.\n */\nfunction incrementEditingCount(rowEl: RowElementInternal): void {\n const count = (rowEl.__editingCellCount ?? 0) + 1;\n rowEl.__editingCellCount = count;\n rowEl.setAttribute('data-has-editing', '');\n}\n\n/**\n * Clear all editing state from a row element.\n */\nexport function clearEditingState(rowEl: RowElementInternal): void {\n rowEl.__editingCellCount = 0;\n rowEl.removeAttribute('data-has-editing');\n}\n\n/**\n * Get the typed value from an input element based on its type and column config.\n */\nfunction getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n column?: ColumnConfig<any>,\n): unknown {\n if (input instanceof HTMLInputElement) {\n if (input.type === 'checkbox') return input.checked;\n if (input.type === 'number') return input.value === '' ? null : Number(input.value);\n if (input.type === 'date') return input.valueAsDate;\n return input.value;\n }\n if (column?.type === 'number' && input.value !== '') {\n return Number(input.value);\n }\n return input.value;\n}\n\n/**\n * Auto-wire commit/cancel lifecycle for input elements in string-returned editors.\n */\nfunction wireEditorInputs(\n editorHost: HTMLElement,\n column: ColumnConfig<unknown>,\n commit: (value: unknown) => void,\n): void {\n const input = editorHost.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (!input) return;\n\n input.addEventListener('blur', () => {\n commit(getInputValue(input, column));\n });\n\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n } else if (input instanceof HTMLSelectElement) {\n input.addEventListener('change', () => commit(getInputValue(input, column)));\n }\n}\n\n// ============================================================================\n// EditingPlugin\n// ============================================================================\n\n/**\n * Editing Plugin for tbw-grid\n *\n * Provides complete cell/row editing functionality. Without this plugin,\n * the grid has no editing capability.\n *\n * @example\n * ```ts\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n *\n * const grid = document.createElement('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', editable: true },\n * { field: 'age', editable: true, type: 'number' }\n * ],\n * plugins: [new EditingPlugin({ editOn: 'dblclick' })]\n * };\n * ```\n */\nexport class EditingPlugin<T = unknown> extends BaseGridPlugin<EditingConfig> {\n readonly name = 'editing';\n\n protected override get defaultConfig(): Partial<EditingConfig> {\n return {\n editOn: 'click',\n };\n }\n\n // #region Editing State (fully owned by plugin)\n\n /** Currently active edit row index, or -1 if not editing */\n #activeEditRow = -1;\n\n /** Currently active edit column index, or -1 if not editing */\n #activeEditCol = -1;\n\n /** Snapshots of row data before editing started */\n #rowEditSnapshots = new Map<number, T>();\n\n /** Set of row indices that have been modified */\n #changedRowIndices = new Set<number>();\n\n /** Set of cells currently in edit mode: \"rowIndex:colIndex\" */\n #editingCells = new Set<string>();\n\n /** Flag to restore focus after next render (used when exiting edit mode) */\n #pendingFocusRestore = false;\n\n // #endregion\n\n // #region Lifecycle\n\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n const signal = this.disconnectSignal;\n const internalGrid = grid as unknown as InternalGrid<T>;\n\n // Inject editing state and methods onto grid for backward compatibility\n internalGrid._activeEditRows = -1;\n internalGrid._rowEditSnapshots = new Map();\n internalGrid._changedRowIndices = new Set();\n\n // Inject changedRows getter\n Object.defineProperty(grid, 'changedRows', {\n get: () => this.changedRows,\n configurable: true,\n });\n\n // Inject changedRowIndices getter\n Object.defineProperty(grid, 'changedRowIndices', {\n get: () => this.changedRowIndices,\n configurable: true,\n });\n\n // Inject resetChangedRows method\n (grid as any).resetChangedRows = (silent?: boolean) => this.resetChangedRows(silent);\n\n // Inject beginBulkEdit method (for backward compatibility)\n (grid as any).beginBulkEdit = (rowIndex: number, field?: string) => {\n if (field) {\n this.beginCellEdit(rowIndex, field);\n }\n // If no field specified, we can't start editing without a specific cell\n };\n\n // Document-level Escape to cancel editing\n document.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n if (e.key === 'Escape' && this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n },\n { capture: true, signal },\n );\n\n // Click outside to commit editing\n document.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\n if (this.#activeEditRow === -1) return;\n const rowEl = internalGrid.findRenderedRowElement?.(this.#activeEditRow);\n if (!rowEl) return;\n const path = (e.composedPath && e.composedPath()) || [];\n if (path.includes(rowEl)) return;\n this.#exitRowEdit(this.#activeEditRow, false);\n },\n { signal },\n );\n }\n\n override detach(): void {\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#rowEditSnapshots.clear();\n this.#changedRowIndices.clear();\n this.#editingCells.clear();\n super.detach();\n }\n\n // #endregion\n\n // #region Event Handlers (event distribution)\n\n /**\n * Handle cell clicks - start editing if configured for click mode.\n * Both click and dblclick events come through this handler.\n * Starts row-based editing (all editable cells in the row get editors).\n */\n override onCellClick(event: CellClickEvent): boolean | void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n\n // Check if editing is disabled\n if (editOn === false || editOn === 'manual') return false;\n\n // Check if this is click or dblclick mode\n if (editOn !== 'click' && editOn !== 'dblclick') return false;\n\n // Check if the event type matches the edit mode\n const isDoubleClick = event.originalEvent.type === 'dblclick';\n if (editOn === 'click' && isDoubleClick) return false; // In click mode, only handle single clicks\n if (editOn === 'dblclick' && !isDoubleClick) return false; // In dblclick mode, only handle double clicks\n\n const { rowIndex } = event;\n\n // Check if any column in the row is editable\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (!hasEditableColumn) return false;\n\n // Start row-based editing (all editable cells get editors)\n event.originalEvent.stopPropagation();\n this.beginBulkEdit(rowIndex);\n return true; // Handled\n }\n\n /**\n * Handle keyboard events for edit lifecycle.\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n\n // Escape: cancel current edit\n if (event.key === 'Escape' && this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n return true;\n }\n\n // Space: toggle boolean cells\n if (event.key === ' ' || event.key === 'Spacebar') {\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0 && focusCol >= 0) {\n const column = internalGrid._visibleColumns[focusCol];\n const rowData = internalGrid._rows[focusRow];\n if (column?.editable && column.type === 'boolean' && rowData) {\n const field = column.field;\n if (isSafePropertyKey(field)) {\n const currentValue = (rowData as Record<string, unknown>)[field];\n const newValue = !currentValue;\n this.#commitCellValue(focusRow, column, newValue, rowData);\n event.preventDefault();\n // Re-render to update the UI\n this.requestRender();\n return true;\n }\n }\n }\n // Space on non-boolean cell - don't block keyboard navigation\n return false;\n }\n\n // Enter: start row edit or commit\n if (event.key === 'Enter' && !event.shiftKey) {\n if (this.#activeEditRow !== -1) {\n // Already editing - let cell handlers deal with it\n return false;\n }\n\n // Start row-based editing (not just the focused cell)\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false || editOn === 'manual') return false;\n\n const focusRow = internalGrid._focusRow;\n if (focusRow >= 0) {\n // Check if ANY column in the row is editable\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (hasEditableColumn) {\n this.beginBulkEdit(focusRow);\n return true;\n }\n }\n // No editable columns - don't block keyboard navigation\n return false;\n }\n\n // Don't block other keyboard events\n return false;\n }\n\n // #endregion\n\n // #region Render Hooks\n\n /**\n * After render, reapply editors to cells in edit mode.\n * This handles virtualization - when a row scrolls back into view,\n * we need to re-inject the editor.\n */\n override afterRender(): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n\n // Restore focus after exiting edit mode\n if (this.#pendingFocusRestore) {\n this.#pendingFocusRestore = false;\n this.#restoreCellFocus(internalGrid);\n }\n\n if (this.#editingCells.size === 0) return;\n\n // Re-inject editors for any editing cells that are visible\n for (const cellKey of this.#editingCells) {\n const [rowStr, colStr] = cellKey.split(':');\n const rowIndex = parseInt(rowStr, 10);\n const colIndex = parseInt(colStr, 10);\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) continue;\n\n const cellEl = rowEl.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl || cellEl.classList.contains('editing')) continue;\n\n // Cell is visible but not in editing mode - reinject editor\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n if (rowData && column) {\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, true);\n }\n }\n }\n\n /**\n * On scroll render, reapply editors to recycled cells.\n */\n override onScrollRender(): void {\n this.afterRender();\n }\n\n // #endregion\n\n // #region Public API\n\n /**\n * Get all rows that have been modified.\n */\n get changedRows(): T[] {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n return Array.from(this.#changedRowIndices).map((i) => internalGrid._rows[i]);\n }\n\n /**\n * Get indices of all modified rows.\n */\n get changedRowIndices(): number[] {\n return Array.from(this.#changedRowIndices);\n }\n\n /**\n * Get the currently active edit row index, or -1 if not editing.\n */\n get activeEditRow(): number {\n return this.#activeEditRow;\n }\n\n /**\n * Get the currently active edit column index, or -1 if not editing.\n */\n get activeEditCol(): number {\n return this.#activeEditCol;\n }\n\n /**\n * Check if a specific row is currently being edited.\n */\n isRowEditing(rowIndex: number): boolean {\n return this.#activeEditRow === rowIndex;\n }\n\n /**\n * Check if a specific cell is currently being edited.\n */\n isCellEditing(rowIndex: number, colIndex: number): boolean {\n return this.#editingCells.has(`${rowIndex}:${colIndex}`);\n }\n\n /**\n * Check if a specific row has been modified.\n */\n isRowChanged(rowIndex: number): boolean {\n return this.#changedRowIndices.has(rowIndex);\n }\n\n /**\n * Reset all change tracking.\n */\n resetChangedRows(silent?: boolean): void {\n const rows = this.changedRows;\n const indices = this.changedRowIndices;\n this.#changedRowIndices.clear();\n this.#syncGridEditState();\n\n if (!silent) {\n this.emit<ChangedRowsResetDetail<T>>('changed-rows-reset', { rows, indices });\n }\n\n // Clear visual indicators\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n internalGrid._rowPool?.forEach((r) => r.classList.remove('changed'));\n }\n\n /**\n * Programmatically begin editing a cell.\n */\n beginCellEdit(rowIndex: number, field: string): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const colIndex = internalGrid._visibleColumns.findIndex((c) => c.field === field);\n if (colIndex === -1) return;\n\n const column = internalGrid._visibleColumns[colIndex];\n if (!column?.editable) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl) return;\n\n this.#beginCellEdit(rowIndex, colIndex, cellEl);\n }\n\n /**\n * Programmatically begin editing all editable cells in a row.\n */\n beginBulkEdit(rowIndex: number): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false) return;\n\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (!hasEditableColumn) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) return;\n\n // Start row edit\n const rowData = internalGrid._rows[rowIndex];\n this.#startRowEdit(rowIndex, rowData);\n\n // Enter edit mode on all editable cells\n Array.from(rowEl.children).forEach((cell, i) => {\n const col = internalGrid._visibleColumns[i];\n if (col?.editable) {\n const cellEl = cell as HTMLElement;\n if (!cellEl.classList.contains('editing')) {\n this.#injectEditor(rowData, rowIndex, col, i, cellEl, true);\n }\n }\n });\n\n // Focus the first editable cell\n setTimeout(() => {\n let targetCell = rowEl.querySelector(`.cell[data-col=\"${internalGrid._focusCol}\"]`);\n if (!targetCell?.classList.contains('editing')) {\n targetCell = rowEl.querySelector('.cell.editing');\n }\n if (targetCell?.classList.contains('editing')) {\n const editor = (targetCell as HTMLElement).querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n try {\n editor?.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n }\n }, 0);\n }\n\n /**\n * Commit the currently active row edit.\n */\n commitActiveRowEdit(): void {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, false);\n }\n }\n\n /**\n * Cancel the currently active row edit.\n */\n cancelActiveRowEdit(): void {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n }\n\n // #endregion\n\n // #region Internal Methods\n\n /**\n * Begin editing a single cell.\n */\n #beginCellEdit(rowIndex: number, colIndex: number, cellEl: HTMLElement): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n\n if (!rowData || !column?.editable) return;\n if (cellEl.classList.contains('editing')) return;\n\n // Start row edit if not already\n if (this.#activeEditRow !== rowIndex) {\n this.#startRowEdit(rowIndex, rowData);\n }\n\n this.#activeEditCol = colIndex;\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, false);\n }\n\n /**\n * Sync the internal grid state with the plugin's editing state.\n */\n #syncGridEditState(): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n internalGrid._activeEditRows = this.#activeEditRow;\n internalGrid._rowEditSnapshots = this.#rowEditSnapshots;\n internalGrid._changedRowIndices = this.#changedRowIndices;\n }\n\n /**\n * Snapshot original row data and mark as editing.\n */\n #startRowEdit(rowIndex: number, rowData: T): void {\n if (this.#activeEditRow !== rowIndex) {\n this.#rowEditSnapshots.set(rowIndex, { ...rowData });\n this.#activeEditRow = rowIndex;\n this.#syncGridEditState();\n }\n }\n\n /**\n * Exit editing for a row.\n */\n #exitRowEdit(rowIndex: number, revert: boolean): void {\n if (this.#activeEditRow !== rowIndex) return;\n\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const snapshot = this.#rowEditSnapshots.get(rowIndex);\n const current = internalGrid._rows[rowIndex];\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n\n // Collect and commit values from active editors before re-rendering\n if (!revert && rowEl && current) {\n const editingCells = rowEl.querySelectorAll('.cell.editing');\n editingCells.forEach((cell) => {\n const colIndex = Number((cell as HTMLElement).getAttribute('data-col'));\n if (isNaN(colIndex)) return;\n const col = internalGrid._visibleColumns[colIndex];\n if (!col) return;\n const input = cell.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n const val = getInputValue(input, col);\n if (current[col.field as keyof T] !== val) {\n this.#commitCellValue(rowIndex, col, val, current);\n }\n }\n });\n }\n\n // Revert if requested\n if (revert && snapshot && current) {\n Object.keys(snapshot as object).forEach((k) => {\n (current as Record<string, unknown>)[k] = (snapshot as Record<string, unknown>)[k];\n });\n this.#changedRowIndices.delete(rowIndex);\n } else if (!revert) {\n const changed = this.#changedRowIndices.has(rowIndex);\n this.emit<RowCommitDetail<T>>('row-commit', {\n rowIndex,\n row: current,\n changed,\n changedRows: this.changedRows,\n changedRowIndices: this.changedRowIndices,\n });\n }\n\n // Clear editing state\n this.#rowEditSnapshots.delete(rowIndex);\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#syncGridEditState();\n\n // Remove all editing cells for this row\n for (const cellKey of this.#editingCells) {\n if (cellKey.startsWith(`${rowIndex}:`)) {\n this.#editingCells.delete(cellKey);\n }\n }\n\n // Re-render the row to remove editors\n if (rowEl) {\n // Remove editing class and re-render cells\n rowEl.querySelectorAll('.cell.editing').forEach((cell) => {\n cell.classList.remove('editing');\n clearEditingState(cell.parentElement as RowElementInternal);\n });\n\n // Request grid re-render to restore cell content\n this.requestRender();\n }\n\n // Mark that focus should be restored after render completes\n this.#pendingFocusRestore = true;\n\n // If no render was scheduled (row not visible), restore focus immediately\n if (!rowEl) {\n this.#restoreCellFocus(internalGrid);\n this.#pendingFocusRestore = false;\n }\n }\n\n /**\n * Commit a single cell value change.\n */\n #commitCellValue(rowIndex: number, column: ColumnConfig<T>, newValue: unknown, rowData: T): void {\n const field = column.field;\n if (!isSafePropertyKey(field)) return;\n const oldValue = (rowData as Record<string, unknown>)[field];\n if (oldValue === newValue) return;\n\n const firstTime = !this.#changedRowIndices.has(rowIndex);\n\n // Emit cancelable event BEFORE applying the value\n const cancelled = this.emitCancelable<CellCommitDetail<T>>('cell-commit', {\n row: rowData,\n field,\n oldValue,\n value: newValue,\n rowIndex,\n changedRows: this.changedRows,\n changedRowIndices: this.changedRowIndices,\n firstTimeForRow: firstTime,\n });\n\n // If consumer called preventDefault(), abort the commit\n if (cancelled) return;\n\n // Apply the value and mark row as changed\n (rowData as Record<string, unknown>)[field] = newValue;\n this.#changedRowIndices.add(rowIndex);\n this.#syncGridEditState();\n\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (rowEl) rowEl.classList.add('changed');\n }\n\n /**\n * Inject an editor into a cell.\n */\n #injectEditor(\n rowData: T,\n rowIndex: number,\n column: ColumnConfig<T>,\n colIndex: number,\n cell: HTMLElement,\n skipFocus: boolean,\n ): void {\n if (!column.editable) return;\n if (cell.classList.contains('editing')) return;\n\n const originalValue = isSafePropertyKey(column.field)\n ? (rowData as Record<string, unknown>)[column.field]\n : undefined;\n\n cell.classList.add('editing');\n this.#editingCells.add(`${rowIndex}:${colIndex}`);\n\n const rowEl = cell.parentElement as RowElementInternal | null;\n if (rowEl) incrementEditingCount(rowEl);\n\n let editFinalized = false;\n const commit = (newValue: unknown) => {\n if (editFinalized || this.#activeEditRow === -1) return;\n this.#commitCellValue(rowIndex, column, newValue, rowData);\n };\n const cancel = () => {\n editFinalized = true;\n if (isSafePropertyKey(column.field)) {\n (rowData as Record<string, unknown>)[column.field] = originalValue;\n }\n };\n\n const editorHost = document.createElement('div');\n editorHost.className = 'tbw-editor-host';\n cell.innerHTML = '';\n cell.appendChild(editorHost);\n\n // Keydown handler for Enter/Escape\n editorHost.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Enter') {\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n e.preventDefault();\n cancel();\n this.#exitRowEdit(rowIndex, true);\n }\n });\n\n const colInternal = column as ColumnInternal<T>;\n const tplHolder = colInternal.__editorTemplate;\n const editorSpec = colInternal.editor || (tplHolder ? 'template' : defaultEditorFor(column));\n const value = originalValue;\n\n if (editorSpec === 'template' && tplHolder) {\n this.#renderTemplateEditor(editorHost, colInternal, rowData, originalValue, commit, cancel, skipFocus, rowIndex);\n } else if (typeof editorSpec === 'string') {\n const el = document.createElement(editorSpec) as HTMLElement & { value?: unknown };\n el.value = value;\n el.addEventListener('change', () => commit(el.value));\n editorHost.appendChild(el);\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (typeof editorSpec === 'function') {\n const ctx: EditorContext<T> = { row: rowData, value, field: column.field, column, commit, cancel };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const produced = (editorSpec as any)(ctx);\n if (typeof produced === 'string') {\n editorHost.innerHTML = produced;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n wireEditorInputs(editorHost, column as any, commit);\n } else if (produced instanceof Node) {\n editorHost.appendChild(produced);\n }\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (editorSpec && typeof editorSpec === 'object') {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const placeholder = document.createElement('div');\n placeholder.setAttribute('data-external-editor', '');\n placeholder.setAttribute('data-field', column.field);\n editorHost.appendChild(placeholder);\n const context: EditorContext<T> = { row: rowData, value, field: column.field, column, commit, cancel };\n if (editorSpec.mount) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n editorSpec.mount({ placeholder, context: context as any, spec: editorSpec });\n } catch (e) {\n console.warn(`[tbw-grid] External editor mount error for column '${column.field}':`, e);\n }\n } else {\n (internalGrid as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('mount-external-editor', { detail: { placeholder, spec: editorSpec, context } }),\n );\n }\n }\n }\n\n /**\n * Render a template-based editor.\n */\n #renderTemplateEditor(\n editorHost: HTMLElement,\n column: ColumnInternal<T>,\n rowData: T,\n originalValue: unknown,\n commit: (value: unknown) => void,\n cancel: () => void,\n skipFocus: boolean,\n rowIndex: number,\n ): void {\n const tplHolder = column.__editorTemplate;\n if (!tplHolder) return;\n\n const clone = tplHolder.cloneNode(true) as HTMLElement;\n const compiledEditor = column.__compiledEditor;\n\n if (compiledEditor) {\n clone.innerHTML = compiledEditor({\n row: rowData,\n value: originalValue,\n field: column.field,\n column,\n commit,\n cancel,\n });\n } else {\n clone.querySelectorAll<HTMLElement>('*').forEach((node) => {\n if (node.childNodes.length === 1 && node.firstChild?.nodeType === Node.TEXT_NODE) {\n node.textContent =\n node.textContent\n ?.replace(/{{\\s*value\\s*}}/g, originalValue == null ? '' : String(originalValue))\n .replace(/{{\\s*row\\.([a-zA-Z0-9_]+)\\s*}}/g, (_m, g: string) => {\n if (!isSafePropertyKey(g)) return '';\n const v = (rowData as Record<string, unknown>)[g];\n return v == null ? '' : String(v);\n }) || '';\n }\n });\n }\n\n const input = clone.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!originalValue;\n } else {\n input.value = String(originalValue ?? '');\n }\n\n let editFinalized = false;\n input.addEventListener('blur', () => {\n if (editFinalized) return;\n commit(getInputValue(input, column));\n });\n input.addEventListener('keydown', (evt) => {\n const e = evt as KeyboardEvent;\n if (e.key === 'Enter') {\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n commit(getInputValue(input, column));\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n e.preventDefault();\n cancel();\n this.#exitRowEdit(rowIndex, true);\n }\n });\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n }\n if (!skipFocus) {\n setTimeout(() => input.focus({ preventScroll: true }), 0);\n }\n }\n editorHost.appendChild(clone);\n }\n\n /**\n * Restore focus to cell after exiting edit mode.\n */\n #restoreCellFocus(internalGrid: InternalGrid<T>): void {\n queueMicrotask(() => {\n try {\n const rowIdx = internalGrid._focusRow;\n const colIdx = internalGrid._focusCol;\n const rowEl = internalGrid.findRenderedRowElement?.(rowIdx);\n if (rowEl) {\n Array.from(internalGrid._bodyEl.querySelectorAll('.cell-focus')).forEach((el) =>\n el.classList.remove('cell-focus'),\n );\n const cell = rowEl.querySelector(`.cell[data-row=\"${rowIdx}\"][data-col=\"${colIdx}\"]`) as HTMLElement | null;\n if (cell) {\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n cell.focus({ preventScroll: true });\n }\n }\n } catch {\n /* empty */\n }\n });\n }\n\n // #endregion\n}\n"],"names":["defaultEditorFor","column","ctx","input","e","select","col","rawOptions","opt","o","commitValue","values","FOCUSABLE_EDITOR_SELECTOR","isSafePropertyKey","key","hasEditingCells","rowEl","incrementEditingCount","count","clearEditingState","getInputValue","wireEditorInputs","editorHost","commit","EditingPlugin","BaseGridPlugin","#activeEditRow","#activeEditCol","#rowEditSnapshots","#changedRowIndices","#editingCells","#pendingFocusRestore","grid","signal","internalGrid","silent","rowIndex","field","#exitRowEdit","event","editOn","isDoubleClick","focusRow","focusCol","rowData","newValue","#commitCellValue","#restoreCellFocus","cellKey","rowStr","colStr","colIndex","cellEl","#injectEditor","i","rows","indices","#syncGridEditState","r","#beginCellEdit","#startRowEdit","cell","targetCell","editor","revert","snapshot","current","val","k","changed","oldValue","firstTime","skipFocus","originalValue","editFinalized","cancel","colInternal","tplHolder","editorSpec","value","#renderTemplateEditor","el","produced","placeholder","context","clone","compiledEditor","node","_m","g","evt","rowIdx","colIdx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"editing.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/editing/editors.ts","../../../../../libs/grid/src/lib/plugins/editing/EditingPlugin.ts"],"sourcesContent":["/**\n * Default Editors Module\n *\n * Provides built-in editor factories for different column types.\n *\n * IMPORTANT: Editor factories should NOT call focus() on elements - they are called\n * before the element is appended to the DOM. The calling code (beginBulkEdit,\n * inlineEnterEdit) is responsible for focusing the correct editor after insertion.\n */\n\nimport type { ColumnConfig, EditorContext } from '../../core/types';\n\n/**\n * Returns a default editor factory function for the given column type.\n * Each editor handles commit on blur/Enter, and cancel on Escape.\n * Note: Focus is NOT called here - the calling code handles focusing after DOM insertion.\n */\nexport function defaultEditorFor(column: ColumnConfig<any>): (ctx: EditorContext) => HTMLElement | string {\n switch (column.type) {\n case 'number':\n return (ctx: EditorContext) => {\n const input = document.createElement('input');\n input.type = 'number';\n input.value = ctx.value != null ? String(ctx.value) : '';\n input.addEventListener('blur', () => ctx.commit(input.value === '' ? null : Number(input.value)));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') ctx.commit(input.value === '' ? null : Number(input.value));\n if (e.key === 'Escape') ctx.cancel();\n });\n return input;\n };\n case 'boolean':\n return (ctx: EditorContext) => {\n const input = document.createElement('input');\n input.type = 'checkbox';\n input.checked = !!ctx.value;\n input.addEventListener('change', () => ctx.commit(input.checked));\n return input;\n };\n case 'date':\n return (ctx: EditorContext) => {\n const input = document.createElement('input');\n input.type = 'date';\n if (ctx.value instanceof Date) input.valueAsDate = ctx.value;\n input.addEventListener('change', () => ctx.commit(input.valueAsDate));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n return input;\n };\n case 'select':\n case 'typeahead':\n return (ctx: EditorContext) => {\n const select = document.createElement('select');\n const col = ctx.column;\n if (col.multi) select.multiple = true;\n const rawOptions = col.options;\n const options = typeof rawOptions === 'function' ? rawOptions() : rawOptions || [];\n options.forEach((opt) => {\n const o = document.createElement('option');\n o.value = String(opt.value);\n o.textContent = opt.label;\n if (col.multi && Array.isArray(ctx.value) && ctx.value.includes(opt.value)) o.selected = true;\n else if (!col.multi && ctx.value === opt.value) o.selected = true;\n select.appendChild(o);\n });\n const commitValue = () => {\n if (col.multi) {\n const values: unknown[] = [];\n Array.from(select.selectedOptions).forEach((o) => {\n values.push(o.value);\n });\n ctx.commit(values);\n } else {\n ctx.commit(select.value);\n }\n };\n select.addEventListener('change', commitValue);\n select.addEventListener('blur', commitValue);\n select.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n return select;\n };\n default:\n return (ctx: EditorContext) => {\n const input = document.createElement('input');\n input.type = 'text';\n input.value = ctx.value != null ? String(ctx.value) : '';\n input.addEventListener('blur', () => ctx.commit(input.value));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') ctx.commit(input.value);\n if (e.key === 'Escape') ctx.cancel();\n });\n return input;\n };\n }\n}\n","/**\n * Editing Plugin\n *\n * Provides complete editing functionality for tbw-grid.\n * This plugin is FULLY SELF-CONTAINED - the grid has ZERO editing knowledge.\n *\n * The plugin:\n * - Owns all editing state (active cell, snapshots, changed rows)\n * - Uses event distribution (onCellClick, onKeyDown) to handle edit lifecycle\n * - Uses afterRender() hook to inject editors into cells\n * - Uses processColumns() to augment columns with editing metadata\n * - Emits its own events (cell-commit, row-commit, changed-rows-reset)\n *\n * Without this plugin, the grid cannot edit. With this plugin, editing\n * is fully functional without any core changes.\n */\n\nimport { BaseGridPlugin, type CellClickEvent, type GridElement } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ColumnInternal, InternalGrid, RowElementInternal } from '../../core/types';\nimport styles from './editing.css?inline';\nimport { defaultEditorFor } from './editors';\nimport type { CellCommitDetail, ChangedRowsResetDetail, EditingConfig, EditorContext, RowCommitDetail } from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * CSS selector for focusable editor elements within a cell.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Returns true if the given property key is safe to use on a plain object.\n */\nfunction isSafePropertyKey(key: unknown): key is string {\n if (typeof key !== 'string') return false;\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') return false;\n return true;\n}\n\n/**\n * Check if a row element has any cells in editing mode.\n */\nexport function hasEditingCells(rowEl: RowElementInternal): boolean {\n return (rowEl.__editingCellCount ?? 0) > 0;\n}\n\n/**\n * Increment the editing cell count on a row element.\n */\nfunction incrementEditingCount(rowEl: RowElementInternal): void {\n const count = (rowEl.__editingCellCount ?? 0) + 1;\n rowEl.__editingCellCount = count;\n rowEl.setAttribute('data-has-editing', '');\n}\n\n/**\n * Clear all editing state from a row element.\n */\nexport function clearEditingState(rowEl: RowElementInternal): void {\n rowEl.__editingCellCount = 0;\n rowEl.removeAttribute('data-has-editing');\n}\n\n/**\n * Get the typed value from an input element based on its type and column config.\n */\nfunction getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n column?: ColumnConfig<any>,\n): unknown {\n if (input instanceof HTMLInputElement) {\n if (input.type === 'checkbox') return input.checked;\n if (input.type === 'number') return input.value === '' ? null : Number(input.value);\n if (input.type === 'date') return input.valueAsDate;\n return input.value;\n }\n if (column?.type === 'number' && input.value !== '') {\n return Number(input.value);\n }\n return input.value;\n}\n\n/**\n * Auto-wire commit/cancel lifecycle for input elements in string-returned editors.\n */\nfunction wireEditorInputs(\n editorHost: HTMLElement,\n column: ColumnConfig<unknown>,\n commit: (value: unknown) => void,\n): void {\n const input = editorHost.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (!input) return;\n\n input.addEventListener('blur', () => {\n commit(getInputValue(input, column));\n });\n\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n } else if (input instanceof HTMLSelectElement) {\n input.addEventListener('change', () => commit(getInputValue(input, column)));\n }\n}\n\n// ============================================================================\n// EditingPlugin\n// ============================================================================\n\n/**\n * Editing Plugin for tbw-grid\n *\n * Provides complete cell/row editing functionality. Without this plugin,\n * the grid has no editing capability.\n *\n * @example\n * ```ts\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n *\n * const grid = document.createElement('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', editable: true },\n * { field: 'age', editable: true, type: 'number' }\n * ],\n * plugins: [new EditingPlugin({ editOn: 'dblclick' })]\n * };\n * ```\n */\nexport class EditingPlugin<T = unknown> extends BaseGridPlugin<EditingConfig> {\n readonly name = 'editing';\n override readonly styles = styles;\n\n protected override get defaultConfig(): Partial<EditingConfig> {\n return {\n editOn: 'click',\n };\n }\n\n // #region Editing State (fully owned by plugin)\n\n /** Currently active edit row index, or -1 if not editing */\n #activeEditRow = -1;\n\n /** Currently active edit column index, or -1 if not editing */\n #activeEditCol = -1;\n\n /** Snapshots of row data before editing started */\n #rowEditSnapshots = new Map<number, T>();\n\n /** Set of row indices that have been modified */\n #changedRowIndices = new Set<number>();\n\n /** Set of cells currently in edit mode: \"rowIndex:colIndex\" */\n #editingCells = new Set<string>();\n\n /** Flag to restore focus after next render (used when exiting edit mode) */\n #pendingFocusRestore = false;\n\n // #endregion\n\n // #region Lifecycle\n\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n const signal = this.disconnectSignal;\n const internalGrid = grid as unknown as InternalGrid<T>;\n\n // Inject editing state and methods onto grid for backward compatibility\n internalGrid._activeEditRows = -1;\n internalGrid._rowEditSnapshots = new Map();\n internalGrid._changedRowIndices = new Set();\n\n // Inject changedRows getter\n Object.defineProperty(grid, 'changedRows', {\n get: () => this.changedRows,\n configurable: true,\n });\n\n // Inject changedRowIndices getter\n Object.defineProperty(grid, 'changedRowIndices', {\n get: () => this.changedRowIndices,\n configurable: true,\n });\n\n // Inject resetChangedRows method\n (grid as any).resetChangedRows = (silent?: boolean) => this.resetChangedRows(silent);\n\n // Inject beginBulkEdit method (for backward compatibility)\n (grid as any).beginBulkEdit = (rowIndex: number, field?: string) => {\n if (field) {\n this.beginCellEdit(rowIndex, field);\n }\n // If no field specified, we can't start editing without a specific cell\n };\n\n // Document-level Escape to cancel editing\n document.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n if (e.key === 'Escape' && this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n },\n { capture: true, signal },\n );\n\n // Click outside to commit editing\n document.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\n if (this.#activeEditRow === -1) return;\n const rowEl = internalGrid.findRenderedRowElement?.(this.#activeEditRow);\n if (!rowEl) return;\n const path = (e.composedPath && e.composedPath()) || [];\n if (path.includes(rowEl)) return;\n this.#exitRowEdit(this.#activeEditRow, false);\n },\n { signal },\n );\n }\n\n override detach(): void {\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#rowEditSnapshots.clear();\n this.#changedRowIndices.clear();\n this.#editingCells.clear();\n super.detach();\n }\n\n // #endregion\n\n // #region Event Handlers (event distribution)\n\n /**\n * Handle cell clicks - start editing if configured for click mode.\n * Both click and dblclick events come through this handler.\n * Starts row-based editing (all editable cells in the row get editors).\n */\n override onCellClick(event: CellClickEvent): boolean | void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n\n // Check if editing is disabled\n if (editOn === false || editOn === 'manual') return false;\n\n // Check if this is click or dblclick mode\n if (editOn !== 'click' && editOn !== 'dblclick') return false;\n\n // Check if the event type matches the edit mode\n const isDoubleClick = event.originalEvent.type === 'dblclick';\n if (editOn === 'click' && isDoubleClick) return false; // In click mode, only handle single clicks\n if (editOn === 'dblclick' && !isDoubleClick) return false; // In dblclick mode, only handle double clicks\n\n const { rowIndex } = event;\n\n // Check if any column in the row is editable\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (!hasEditableColumn) return false;\n\n // Start row-based editing (all editable cells get editors)\n event.originalEvent.stopPropagation();\n this.beginBulkEdit(rowIndex);\n return true; // Handled\n }\n\n /**\n * Handle keyboard events for edit lifecycle.\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n\n // Escape: cancel current edit\n if (event.key === 'Escape' && this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n return true;\n }\n\n // Space: toggle boolean cells\n if (event.key === ' ' || event.key === 'Spacebar') {\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0 && focusCol >= 0) {\n const column = internalGrid._visibleColumns[focusCol];\n const rowData = internalGrid._rows[focusRow];\n if (column?.editable && column.type === 'boolean' && rowData) {\n const field = column.field;\n if (isSafePropertyKey(field)) {\n const currentValue = (rowData as Record<string, unknown>)[field];\n const newValue = !currentValue;\n this.#commitCellValue(focusRow, column, newValue, rowData);\n event.preventDefault();\n // Re-render to update the UI\n this.requestRender();\n return true;\n }\n }\n }\n // Space on non-boolean cell - don't block keyboard navigation\n return false;\n }\n\n // Enter: start row edit or commit\n if (event.key === 'Enter' && !event.shiftKey) {\n if (this.#activeEditRow !== -1) {\n // Already editing - let cell handlers deal with it\n return false;\n }\n\n // Start row-based editing (not just the focused cell)\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false || editOn === 'manual') return false;\n\n const focusRow = internalGrid._focusRow;\n if (focusRow >= 0) {\n // Check if ANY column in the row is editable\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (hasEditableColumn) {\n this.beginBulkEdit(focusRow);\n return true;\n }\n }\n // No editable columns - don't block keyboard navigation\n return false;\n }\n\n // Don't block other keyboard events\n return false;\n }\n\n // #endregion\n\n // #region Render Hooks\n\n /**\n * After render, reapply editors to cells in edit mode.\n * This handles virtualization - when a row scrolls back into view,\n * we need to re-inject the editor.\n */\n override afterRender(): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n\n // Restore focus after exiting edit mode\n if (this.#pendingFocusRestore) {\n this.#pendingFocusRestore = false;\n this.#restoreCellFocus(internalGrid);\n }\n\n if (this.#editingCells.size === 0) return;\n\n // Re-inject editors for any editing cells that are visible\n for (const cellKey of this.#editingCells) {\n const [rowStr, colStr] = cellKey.split(':');\n const rowIndex = parseInt(rowStr, 10);\n const colIndex = parseInt(colStr, 10);\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) continue;\n\n const cellEl = rowEl.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl || cellEl.classList.contains('editing')) continue;\n\n // Cell is visible but not in editing mode - reinject editor\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n if (rowData && column) {\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, true);\n }\n }\n }\n\n /**\n * On scroll render, reapply editors to recycled cells.\n */\n override onScrollRender(): void {\n this.afterRender();\n }\n\n // #endregion\n\n // #region Public API\n\n /**\n * Get all rows that have been modified.\n */\n get changedRows(): T[] {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n return Array.from(this.#changedRowIndices).map((i) => internalGrid._rows[i]);\n }\n\n /**\n * Get indices of all modified rows.\n */\n get changedRowIndices(): number[] {\n return Array.from(this.#changedRowIndices);\n }\n\n /**\n * Get the currently active edit row index, or -1 if not editing.\n */\n get activeEditRow(): number {\n return this.#activeEditRow;\n }\n\n /**\n * Get the currently active edit column index, or -1 if not editing.\n */\n get activeEditCol(): number {\n return this.#activeEditCol;\n }\n\n /**\n * Check if a specific row is currently being edited.\n */\n isRowEditing(rowIndex: number): boolean {\n return this.#activeEditRow === rowIndex;\n }\n\n /**\n * Check if a specific cell is currently being edited.\n */\n isCellEditing(rowIndex: number, colIndex: number): boolean {\n return this.#editingCells.has(`${rowIndex}:${colIndex}`);\n }\n\n /**\n * Check if a specific row has been modified.\n */\n isRowChanged(rowIndex: number): boolean {\n return this.#changedRowIndices.has(rowIndex);\n }\n\n /**\n * Reset all change tracking.\n */\n resetChangedRows(silent?: boolean): void {\n const rows = this.changedRows;\n const indices = this.changedRowIndices;\n this.#changedRowIndices.clear();\n this.#syncGridEditState();\n\n if (!silent) {\n this.emit<ChangedRowsResetDetail<T>>('changed-rows-reset', { rows, indices });\n }\n\n // Clear visual indicators\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n internalGrid._rowPool?.forEach((r) => r.classList.remove('changed'));\n }\n\n /**\n * Programmatically begin editing a cell.\n */\n beginCellEdit(rowIndex: number, field: string): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const colIndex = internalGrid._visibleColumns.findIndex((c) => c.field === field);\n if (colIndex === -1) return;\n\n const column = internalGrid._visibleColumns[colIndex];\n if (!column?.editable) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl) return;\n\n this.#beginCellEdit(rowIndex, colIndex, cellEl);\n }\n\n /**\n * Programmatically begin editing all editable cells in a row.\n */\n beginBulkEdit(rowIndex: number): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false) return;\n\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (!hasEditableColumn) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) return;\n\n // Start row edit\n const rowData = internalGrid._rows[rowIndex];\n this.#startRowEdit(rowIndex, rowData);\n\n // Enter edit mode on all editable cells\n Array.from(rowEl.children).forEach((cell, i) => {\n const col = internalGrid._visibleColumns[i];\n if (col?.editable) {\n const cellEl = cell as HTMLElement;\n if (!cellEl.classList.contains('editing')) {\n this.#injectEditor(rowData, rowIndex, col, i, cellEl, true);\n }\n }\n });\n\n // Focus the first editable cell\n setTimeout(() => {\n let targetCell = rowEl.querySelector(`.cell[data-col=\"${internalGrid._focusCol}\"]`);\n if (!targetCell?.classList.contains('editing')) {\n targetCell = rowEl.querySelector('.cell.editing');\n }\n if (targetCell?.classList.contains('editing')) {\n const editor = (targetCell as HTMLElement).querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n try {\n editor?.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n }\n }, 0);\n }\n\n /**\n * Commit the currently active row edit.\n */\n commitActiveRowEdit(): void {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, false);\n }\n }\n\n /**\n * Cancel the currently active row edit.\n */\n cancelActiveRowEdit(): void {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n }\n\n // #endregion\n\n // #region Internal Methods\n\n /**\n * Begin editing a single cell.\n */\n #beginCellEdit(rowIndex: number, colIndex: number, cellEl: HTMLElement): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n\n if (!rowData || !column?.editable) return;\n if (cellEl.classList.contains('editing')) return;\n\n // Start row edit if not already\n if (this.#activeEditRow !== rowIndex) {\n this.#startRowEdit(rowIndex, rowData);\n }\n\n this.#activeEditCol = colIndex;\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, false);\n }\n\n /**\n * Sync the internal grid state with the plugin's editing state.\n */\n #syncGridEditState(): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n internalGrid._activeEditRows = this.#activeEditRow;\n internalGrid._rowEditSnapshots = this.#rowEditSnapshots;\n internalGrid._changedRowIndices = this.#changedRowIndices;\n }\n\n /**\n * Snapshot original row data and mark as editing.\n */\n #startRowEdit(rowIndex: number, rowData: T): void {\n if (this.#activeEditRow !== rowIndex) {\n this.#rowEditSnapshots.set(rowIndex, { ...rowData });\n this.#activeEditRow = rowIndex;\n this.#syncGridEditState();\n }\n }\n\n /**\n * Exit editing for a row.\n */\n #exitRowEdit(rowIndex: number, revert: boolean): void {\n if (this.#activeEditRow !== rowIndex) return;\n\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const snapshot = this.#rowEditSnapshots.get(rowIndex);\n const current = internalGrid._rows[rowIndex];\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n\n // Collect and commit values from active editors before re-rendering\n if (!revert && rowEl && current) {\n const editingCells = rowEl.querySelectorAll('.cell.editing');\n editingCells.forEach((cell) => {\n const colIndex = Number((cell as HTMLElement).getAttribute('data-col'));\n if (isNaN(colIndex)) return;\n const col = internalGrid._visibleColumns[colIndex];\n if (!col) return;\n const input = cell.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n const val = getInputValue(input, col);\n if (current[col.field as keyof T] !== val) {\n this.#commitCellValue(rowIndex, col, val, current);\n }\n }\n });\n }\n\n // Revert if requested\n if (revert && snapshot && current) {\n Object.keys(snapshot as object).forEach((k) => {\n (current as Record<string, unknown>)[k] = (snapshot as Record<string, unknown>)[k];\n });\n this.#changedRowIndices.delete(rowIndex);\n } else if (!revert) {\n const changed = this.#changedRowIndices.has(rowIndex);\n this.emit<RowCommitDetail<T>>('row-commit', {\n rowIndex,\n row: current,\n changed,\n changedRows: this.changedRows,\n changedRowIndices: this.changedRowIndices,\n });\n }\n\n // Clear editing state\n this.#rowEditSnapshots.delete(rowIndex);\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#syncGridEditState();\n\n // Remove all editing cells for this row\n for (const cellKey of this.#editingCells) {\n if (cellKey.startsWith(`${rowIndex}:`)) {\n this.#editingCells.delete(cellKey);\n }\n }\n\n // Re-render the row to remove editors\n if (rowEl) {\n // Remove editing class and re-render cells\n rowEl.querySelectorAll('.cell.editing').forEach((cell) => {\n cell.classList.remove('editing');\n clearEditingState(cell.parentElement as RowElementInternal);\n });\n\n // Request grid re-render to restore cell content\n this.requestRender();\n }\n\n // Mark that focus should be restored after render completes\n this.#pendingFocusRestore = true;\n\n // If no render was scheduled (row not visible), restore focus immediately\n if (!rowEl) {\n this.#restoreCellFocus(internalGrid);\n this.#pendingFocusRestore = false;\n }\n }\n\n /**\n * Commit a single cell value change.\n */\n #commitCellValue(rowIndex: number, column: ColumnConfig<T>, newValue: unknown, rowData: T): void {\n const field = column.field;\n if (!isSafePropertyKey(field)) return;\n const oldValue = (rowData as Record<string, unknown>)[field];\n if (oldValue === newValue) return;\n\n const firstTime = !this.#changedRowIndices.has(rowIndex);\n\n // Emit cancelable event BEFORE applying the value\n const cancelled = this.emitCancelable<CellCommitDetail<T>>('cell-commit', {\n row: rowData,\n field,\n oldValue,\n value: newValue,\n rowIndex,\n changedRows: this.changedRows,\n changedRowIndices: this.changedRowIndices,\n firstTimeForRow: firstTime,\n });\n\n // If consumer called preventDefault(), abort the commit\n if (cancelled) return;\n\n // Apply the value and mark row as changed\n (rowData as Record<string, unknown>)[field] = newValue;\n this.#changedRowIndices.add(rowIndex);\n this.#syncGridEditState();\n\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (rowEl) rowEl.classList.add('changed');\n }\n\n /**\n * Inject an editor into a cell.\n */\n #injectEditor(\n rowData: T,\n rowIndex: number,\n column: ColumnConfig<T>,\n colIndex: number,\n cell: HTMLElement,\n skipFocus: boolean,\n ): void {\n if (!column.editable) return;\n if (cell.classList.contains('editing')) return;\n\n const originalValue = isSafePropertyKey(column.field)\n ? (rowData as Record<string, unknown>)[column.field]\n : undefined;\n\n cell.classList.add('editing');\n this.#editingCells.add(`${rowIndex}:${colIndex}`);\n\n const rowEl = cell.parentElement as RowElementInternal | null;\n if (rowEl) incrementEditingCount(rowEl);\n\n let editFinalized = false;\n const commit = (newValue: unknown) => {\n if (editFinalized || this.#activeEditRow === -1) return;\n this.#commitCellValue(rowIndex, column, newValue, rowData);\n };\n const cancel = () => {\n editFinalized = true;\n if (isSafePropertyKey(column.field)) {\n (rowData as Record<string, unknown>)[column.field] = originalValue;\n }\n };\n\n const editorHost = document.createElement('div');\n editorHost.className = 'tbw-editor-host';\n cell.innerHTML = '';\n cell.appendChild(editorHost);\n\n // Keydown handler for Enter/Escape\n editorHost.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Enter') {\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n e.preventDefault();\n cancel();\n this.#exitRowEdit(rowIndex, true);\n }\n });\n\n const colInternal = column as ColumnInternal<T>;\n const tplHolder = colInternal.__editorTemplate;\n const editorSpec = colInternal.editor || (tplHolder ? 'template' : defaultEditorFor(column));\n const value = originalValue;\n\n if (editorSpec === 'template' && tplHolder) {\n this.#renderTemplateEditor(editorHost, colInternal, rowData, originalValue, commit, cancel, skipFocus, rowIndex);\n } else if (typeof editorSpec === 'string') {\n const el = document.createElement(editorSpec) as HTMLElement & { value?: unknown };\n el.value = value;\n el.addEventListener('change', () => commit(el.value));\n editorHost.appendChild(el);\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (typeof editorSpec === 'function') {\n const ctx: EditorContext<T> = { row: rowData, value, field: column.field, column, commit, cancel };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const produced = (editorSpec as any)(ctx);\n if (typeof produced === 'string') {\n editorHost.innerHTML = produced;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n wireEditorInputs(editorHost, column as any, commit);\n } else if (produced instanceof Node) {\n editorHost.appendChild(produced);\n }\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (editorSpec && typeof editorSpec === 'object') {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const placeholder = document.createElement('div');\n placeholder.setAttribute('data-external-editor', '');\n placeholder.setAttribute('data-field', column.field);\n editorHost.appendChild(placeholder);\n const context: EditorContext<T> = { row: rowData, value, field: column.field, column, commit, cancel };\n if (editorSpec.mount) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n editorSpec.mount({ placeholder, context: context as any, spec: editorSpec });\n } catch (e) {\n console.warn(`[tbw-grid] External editor mount error for column '${column.field}':`, e);\n }\n } else {\n (internalGrid as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('mount-external-editor', { detail: { placeholder, spec: editorSpec, context } }),\n );\n }\n }\n }\n\n /**\n * Render a template-based editor.\n */\n #renderTemplateEditor(\n editorHost: HTMLElement,\n column: ColumnInternal<T>,\n rowData: T,\n originalValue: unknown,\n commit: (value: unknown) => void,\n cancel: () => void,\n skipFocus: boolean,\n rowIndex: number,\n ): void {\n const tplHolder = column.__editorTemplate;\n if (!tplHolder) return;\n\n const clone = tplHolder.cloneNode(true) as HTMLElement;\n const compiledEditor = column.__compiledEditor;\n\n if (compiledEditor) {\n clone.innerHTML = compiledEditor({\n row: rowData,\n value: originalValue,\n field: column.field,\n column,\n commit,\n cancel,\n });\n } else {\n clone.querySelectorAll<HTMLElement>('*').forEach((node) => {\n if (node.childNodes.length === 1 && node.firstChild?.nodeType === Node.TEXT_NODE) {\n node.textContent =\n node.textContent\n ?.replace(/{{\\s*value\\s*}}/g, originalValue == null ? '' : String(originalValue))\n .replace(/{{\\s*row\\.([a-zA-Z0-9_]+)\\s*}}/g, (_m, g: string) => {\n if (!isSafePropertyKey(g)) return '';\n const v = (rowData as Record<string, unknown>)[g];\n return v == null ? '' : String(v);\n }) || '';\n }\n });\n }\n\n const input = clone.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!originalValue;\n } else {\n input.value = String(originalValue ?? '');\n }\n\n let editFinalized = false;\n input.addEventListener('blur', () => {\n if (editFinalized) return;\n commit(getInputValue(input, column));\n });\n input.addEventListener('keydown', (evt) => {\n const e = evt as KeyboardEvent;\n if (e.key === 'Enter') {\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n commit(getInputValue(input, column));\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n e.preventDefault();\n cancel();\n this.#exitRowEdit(rowIndex, true);\n }\n });\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n }\n if (!skipFocus) {\n setTimeout(() => input.focus({ preventScroll: true }), 0);\n }\n }\n editorHost.appendChild(clone);\n }\n\n /**\n * Restore focus to cell after exiting edit mode.\n */\n #restoreCellFocus(internalGrid: InternalGrid<T>): void {\n queueMicrotask(() => {\n try {\n const rowIdx = internalGrid._focusRow;\n const colIdx = internalGrid._focusCol;\n const rowEl = internalGrid.findRenderedRowElement?.(rowIdx);\n if (rowEl) {\n Array.from(internalGrid._bodyEl.querySelectorAll('.cell-focus')).forEach((el) =>\n el.classList.remove('cell-focus'),\n );\n const cell = rowEl.querySelector(`.cell[data-row=\"${rowIdx}\"][data-col=\"${colIdx}\"]`) as HTMLElement | null;\n if (cell) {\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n cell.focus({ preventScroll: true });\n }\n }\n } catch {\n /* empty */\n }\n });\n }\n\n // #endregion\n}\n"],"names":["defaultEditorFor","column","ctx","input","e","select","col","rawOptions","opt","o","commitValue","values","FOCUSABLE_EDITOR_SELECTOR","isSafePropertyKey","key","hasEditingCells","rowEl","incrementEditingCount","count","clearEditingState","getInputValue","wireEditorInputs","editorHost","commit","EditingPlugin","BaseGridPlugin","styles","#activeEditRow","#activeEditCol","#rowEditSnapshots","#changedRowIndices","#editingCells","#pendingFocusRestore","grid","signal","internalGrid","silent","rowIndex","field","#exitRowEdit","event","editOn","isDoubleClick","focusRow","focusCol","rowData","newValue","#commitCellValue","#restoreCellFocus","cellKey","rowStr","colStr","colIndex","cellEl","#injectEditor","i","rows","indices","#syncGridEditState","r","#beginCellEdit","#startRowEdit","cell","targetCell","editor","revert","snapshot","current","val","k","changed","oldValue","firstTime","skipFocus","originalValue","editFinalized","cancel","colInternal","tplHolder","editorSpec","value","#renderTemplateEditor","el","produced","placeholder","context","clone","compiledEditor","node","_m","g","v","evt","rowIdx","colIdx"],"mappings":"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"}
|