@toolbox-web/grid 3.0.0-beta.2 → 3.0.0-beta.3
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/all.js +2 -2
- package/all.js.map +1 -1
- package/index.js +1 -1
- package/lib/features/registry.js +1 -1
- package/lib/plugins/clipboard/clipboard-payload.d.ts +47 -0
- package/lib/plugins/clipboard/copy.d.ts +9 -2
- package/lib/plugins/clipboard/index.js +1 -1
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/clipboard/types.d.ts +18 -0
- package/lib/plugins/column-virtualization/index.js +1 -1
- package/lib/plugins/context-menu/index.js +1 -1
- package/lib/plugins/editing/index.js +1 -1
- package/lib/plugins/export/index.js +1 -1
- package/lib/plugins/filtering/index.js +1 -1
- package/lib/plugins/grouping-columns/index.js +1 -1
- package/lib/plugins/grouping-rows/index.js +1 -1
- package/lib/plugins/master-detail/index.js +1 -1
- package/lib/plugins/multi-sort/index.js +1 -1
- package/lib/plugins/pinned-columns/index.js +1 -1
- package/lib/plugins/pinned-rows/index.js +1 -1
- package/lib/plugins/pivot/index.js +1 -1
- package/lib/plugins/print/index.js +1 -1
- package/lib/plugins/reorder-columns/index.js +1 -1
- package/lib/plugins/responsive/index.js +1 -1
- package/lib/plugins/row-drag-drop/index.js +1 -1
- package/lib/plugins/selection/index.js +1 -1
- package/lib/plugins/server-side/index.js +1 -1
- package/lib/plugins/shell/index.js +1 -1
- package/lib/plugins/sticky-rows/index.js +1 -1
- package/lib/plugins/tooltip/index.js +1 -1
- package/lib/plugins/tree/index.js +1 -1
- package/lib/plugins/undo-redo/index.js +1 -1
- package/lib/plugins/visibility/index.js +1 -1
- package/package.json +1 -1
- package/umd/grid.all.umd.js +1 -1
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +1 -1
- package/umd/plugins/clipboard.umd.js +1 -1
- package/umd/plugins/clipboard.umd.js.map +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/internal/value-accessor"),require("../../core/plugin/base-plugin"),require("../../core/internal/diagnostics")):"function"==typeof define&&define.amd?define(["exports","../../core/internal/value-accessor","../../core/plugin/base-plugin","../../core/internal/diagnostics"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_clipboard={},e.TbwGrid,e.TbwGrid,e.TbwGrid)}(this,function(e,t,n,
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/internal/value-accessor"),require("../../core/plugin/base-plugin"),require("../../core/internal/diagnostics")):"function"==typeof define&&define.amd?define(["exports","../../core/internal/value-accessor","../../core/plugin/base-plugin","../../core/internal/diagnostics"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_clipboard={},e.TbwGrid,e.TbwGrid,e.TbwGrid)}(this,function(e,t,n,r){"use strict";function o(e,t,n=!0){let r=e;if(n&&(r=r.filter(e=>!e.hidden&&!e.field.startsWith("__")&&!0!==e.utility)),t?.length){const e=new Set(t);r=r.filter(t=>e.has(t.field))}return r}const i="data-tbw-clip";const l=e=>e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");function s(e,t,n){const r=e?`<thead><tr>${e.map(e=>`<th>${l(e)}</th>`).join("")}</tr></thead>`:"",o=`<tbody>${t.map(e=>`<tr>${e.map(e=>`<td>${l(e)}</td>`).join("")}</tr>`).join("")}</tbody>`;return`<table ${i}="${function(e){return btoa(encodeURIComponent(JSON.stringify(e)))}(n)}">${r}${o}</table>`}function a(e,t){const n=t.delimiter??"\t",r=t.newline??"\n",o=e.replace(/\r\n/g,"\n").replace(/\r/g,"\n"),i=o.length,l=[];let s=[],a=0,c=!1,u=!1;const d=e=>{const t=o.slice(a,e);return u?function(e){let t="",n=!1;for(let r=0;r<e.length;r++){const o=e[r];'"'===o?n?'"'===e[r+1]?(t+='"',r++):n=!1:n=!0:t+=o}return t}(t):t},h=()=>{(s.length>1||s.some(e=>""!==e.trim()))&&l.push(s),s=[]};for(let f=0;f<i;f++){const e=o[f];'"'===e?(u=!0,c?'"'===o[f+1]?f++:c=!1:c=!0):e!==n||c?e!==r||c||(s.push(d(f)),a=f+1,u=!1,h()):(s.push(d(f)),a=f+1,u=!1)}return s.push(d(i)),h(),l}function c(e,n){const{rows:r,rawRows:o,target:i,fields:l,fillSelection:s}=e;if(!i||0===r.length)return;const a=o??r,c=n.rows,d=(n.effectiveConfig.columns??[]).map(e=>e.field),[h=()=>!1]=n.query("getCellEditableResolver"),f=new Map;let p=c.length;const g=(e,t,n,r)=>{if(!t||!h(t,r))return;let o=f.get(e);o||(o={},f.set(e,o)),o[t]=u(n)};if(s&&i.bounds){const e=a.length;for(let t=i.row;t<=i.bounds.endRow&&!(t>=c.length);t++){const n=a[(t-i.row)%e],r=n.length;if(0===r)continue;const o=c[t];l.forEach((e,i)=>g(t,e,n[i%r],o))}}else{const e=i.bounds?i.bounds.endRow:1/0;a.forEach((t,n)=>{const r=i.row+n;if(r>e)return;if(i.bounds&&r>=c.length)return;!i.bounds&&r>=p&&(p=r+1);const o=c[r]??{};t.forEach((e,t)=>g(r,l[t],e,o))})}if(0===f.size&&p===c.length)return;if(p>c.length){const e=[...c];for(;e.length<p;){const t={};d.forEach(e=>t[e]=""),e.push(t)}n.rows=e}const m=n.rows,w=[];let y=!1;for(const[u,b]of f){const e=m[u];if(!e)continue;let r;try{r=n.getRowId(e)}catch{r=void 0}if(null==r){for(const[n,r]of Object.entries(b))e[n]=r,t.invalidateAccessorCache(e,n);y=!0}else w.push({id:r,changes:b})}w.length>0?n.updateRows(w,"api"):y&&p===c.length&&(n.rows=[...m])}function u(e){if(null===e||"object"!=typeof e)return e;try{return structuredClone(e)}catch{return e}}class d extends n.BaseGridPlugin{static manifest={queries:[{type:"clipboard:copy",description:"Triggers a copy operation and returns the copied text"}]};static dependencies=[{name:"selection",required:!1,reason:"Enables copy/paste of selected cells instead of entire grid"}];name="clipboard";get defaultConfig(){return{includeHeaders:!1,delimiter:"\t",newline:"\n",quoteStrings:!1}}lastCopied=null;#e=null;attach(e){super.attach(e),e.addEventListener("paste",e=>this.#t(e),{signal:this.disconnectSignal})}detach(){this.lastCopied=null,this.#e=null}handleQuery(e){if("clipboard:copy"===e.type)return this.copy(),!0}onKeyDown(e){return!(!e.ctrlKey&&!e.metaKey||"c"!==e.key)&&(e.preventDefault(),this.#n(e.target),!0)}#n(e){const t=this.#r();if(t&&0===t.ranges.length){let n,r;if(t.anchor)n=t.anchor.row,r=this.visibleColumns[t.anchor.col]?.field;else{const t=this.#o(e);t&&(n=t.row,r=this.columns[t.col]?.field)}if(null==n||null==r)return;return void this.copy({rowIndices:[n],columns:[r]})}this.copy()}#t(e){if(this.#i(e.target))return;const t=e.clipboardData?.getData("text/plain");if(!t)return;e.preventDefault();const n=a(t,this.config),r=this.#r(),o=r?.ranges??[],l=o[0];let s=1/0,c=1/0,u=-1/0,d=-1/0;for(const i of o)s=Math.min(s,i.from.row,i.to.row),u=Math.max(u,i.from.row,i.to.row),c=Math.min(c,i.from.col,i.to.col),d=Math.max(d,i.from.col,i.to.col);const h=r?.anchor??null,f=l?s:h?.row??0,p=l?c:h?.col??0,g=!(!l||"range"!==r?.mode&&"row"!==r?.mode||s===u&&c===d)?{endRow:u,endCol:d}:null,m=g?.endCol??this.visibleColumns.length-1;let w;if(this.#l(n),this.#e&&this.#e.text===t&&this.#e.rawRows.length===n.length)w=this.#e.rawRows;else{const t=function(e){if(!e||-1===e.indexOf(i))return null;try{const t=(new DOMParser).parseFromString(e,"text/html"),n=t.querySelector(`[${i}]`)?.getAttribute(i);if(!n)return null;const r=JSON.parse(decodeURIComponent(atob(n)));return r&&Array.isArray(r.rows)&&Array.isArray(r.fields)?r:null}catch{return null}}(e.clipboardData?.getData("text/html"));t&&t.rows.length===n.length&&(w=t.rows)}const y=this.visibleColumns[p],b=y?{row:f,col:p,field:y.field,bounds:g}:null,C=!0===this.config.fillSelection&&null!==g,x=[],v=n[0]?.length??0,D=C?m-p+1:v;for(let i=0;i<D&&p+i<=m;i++){const e=this.visibleColumns[p+i];e&&x.push(e.field)}const S={rows:n,text:t,target:b,fields:x,fillSelection:C,rawRows:w};this.emit("paste",S),this.#s(S)}#s(e){if(!this.grid)return;const{pasteHandler:t}=this.config;if(null===t)return;(t??c)(e,this.grid)}#l(e){if(!this.config.includeHeaders)return;if(e.length<=1)return;const t=e[0];if(0===t.length)return;const n=this.#a();for(const r of t)if(!n.has(r))return;e.shift()}#a(){const e=new Set;for(const t of this.columns){const n=t.header||t.field;n&&e.add(n)}return e}#i(e){return e instanceof Element&&!!e.closest('input, textarea, select, [contenteditable=""], [contenteditable="true"]')}#r(){const e=this.grid?.query("getSelection");return e?.[0]}#c(e){const t=this.#r();let n,r;if(e?.columns)n=o(this.columns,e.columns);else if(t?.ranges.length&&"row"!==t.mode){const e=t.ranges[t.ranges.length-1],r=Math.min(e.from.col,e.to.col),i=Math.max(e.from.col,e.to.col);n=o(this.visibleColumns.slice(r,i+1))}else n=o(this.columns);if(e?.rowIndices)r=function(e,t){return t?.length?[...t].sort((e,t)=>e-t).map(t=>e[t]).filter(e=>null!=e):e}(this.rows,e.rowIndices);else if(t?.ranges.length){const e=t.ranges[t.ranges.length-1],n=Math.min(e.from.row,e.to.row),o=Math.max(e.from.row,e.to.row);r=[];for(let t=n;t<=o;t++){const e=this.rows[t];e&&r.push(e)}}else r=this.rows;return{columns:n,rows:r}}#u(e,t,n){return this.#d(this.#h(e,t,n),n)}#h(e,n,r){const o=r?.includeHeaders??this.config.includeHeaders??!1,i=r?.processCell??this.config.processCell;return{header:o?e.map(e=>e.header||e.field):null,rows:n.map(n=>e.map(e=>{const r=t.resolveCellValue(n,e);return i?i(r,e.field,n):this.#f(e,r,n)}))}}#d(e,t){const n=t?.delimiter??this.config.delimiter??"\t",r=t?.newline??this.config.newline??"\n",o=[];e.header&&o.push(e.header.join(n));for(const i of e.rows)o.push(i.join(n));return o.join(r)}#f(e,t,n){if(e.format)try{const r=e.format(t,n);return null==r?"":String(r)}catch{}if(e.renderer||e.viewRenderer){const t=this.#p(e.field,n);if(null!=t)return t}return function(e){return null==e?"":e instanceof Date?e.toISOString():"object"==typeof e?JSON.stringify(e):String(e)}(t)}#p(e,t){const n=this.gridElement;if(!n)return null;const r=this.rows.indexOf(t);if(-1===r)return null;const o=n.querySelector(`.cell[data-row="${r}"][data-field="${e}"]`);return o?o.textContent?.trim()??null:null}#o(e){const t=e.closest(".cell[data-field]");if(!t)return null;const n=t.dataset.field,r=t.dataset.row;if(!n||!r)return null;const o=parseInt(r,10);if(isNaN(o))return null;const i=this.columns.findIndex(e=>e.field===n);return-1===i?null:{row:o,col:i}}getSelectionAsText(e){const{columns:t,rows:n}=this.#c(e);return 0===t.length||0===n.length?"":this.#u(t,n,e)}async copy(e){const{columns:n,rows:o}=this.#c(e);if(0===n.length||0===o.length)return"";const i=this.#h(n,o,e),l=this.#d(i,e),a=o.map(e=>n.map(n=>u(t.resolveCellValue(e,n)))),c=n.map(e=>e.field);return await async function(e,t){if(t&&"undefined"!=typeof ClipboardItem&&navigator.clipboard?.write)try{return await navigator.clipboard.write([new ClipboardItem({"text/plain":new Blob([e],{type:"text/plain"}),"text/html":new Blob([t],{type:"text/html"})})]),!0}catch{}try{return await navigator.clipboard.writeText(e),!0}catch(n){r.warnDiagnostic(r.CLIPBOARD_FAILED,`Clipboard API failed: ${n}`);const t=document.createElement("textarea");t.value=e,t.style.position="fixed",t.style.opacity="0",t.style.pointerEvents="none",document.body.appendChild(t),t.select();const o=document.execCommand("copy");return document.body.removeChild(t),o}}(l,s(i.header,i.rows,{v:1,fields:c,rows:a})),this.lastCopied={text:l,timestamp:Date.now()},this.#e={text:l,rawRows:a},this.emit("copy",{text:l,rowCount:o.length,columnCount:n.length}),l}async copyRows(e,t){return 0===e.length?"":this.copy({...t,rowIndices:e})}async paste(){const e=await async function(){try{return await navigator.clipboard.readText()}catch{return""}}();return e?a(e,this.config):null}getLastCopied(){return this.lastCopied}}e.ClipboardPlugin=d,e.defaultPasteHandler=c,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
|
|
2
2
|
//# sourceMappingURL=clipboard.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/shared/data-collection.ts","../../../../../libs/grid/src/lib/plugins/clipboard/paste.ts","../../../../../libs/grid/src/lib/plugins/clipboard/types.ts","../../../../../libs/grid/src/lib/plugins/clipboard/ClipboardPlugin.ts","../../../../../libs/grid/src/lib/plugins/clipboard/copy.ts"],"sourcesContent":["/**\n * Shared Data Collection Utilities\n *\n * Pure functions for resolving columns and formatting values, shared between\n * the Clipboard and Export plugins. Each plugin bundles its own copy of this\n * module (no chunk splitting) since plugin builds inline sibling imports.\n *\n * @internal\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Resolve which columns to include in a data export or copy operation.\n *\n * Filters out hidden columns, utility columns (`meta.utility`), and\n * internal columns (`__`-prefixed fields). Optionally restricts to an\n * explicit set of field names.\n *\n * @param columns - All column configurations\n * @param fields - If provided, only include columns whose field is in this list\n * @param onlyVisible - When `true` (default), exclude hidden and internal columns\n * @returns Filtered column array preserving original order\n */\nexport function resolveColumns(\n columns: readonly ColumnConfig[],\n fields?: string[],\n onlyVisible = true,\n): ColumnConfig[] {\n let result = columns as ColumnConfig[];\n\n if (onlyVisible) {\n result = result.filter((c) => !c.hidden && !c.field.startsWith('__') && c.utility !== true);\n }\n\n if (fields?.length) {\n const fieldSet = new Set(fields);\n result = result.filter((c) => fieldSet.has(c.field));\n }\n\n return result;\n}\n\n/**\n * Resolve which rows to include, optionally filtered to specific indices.\n *\n * @param rows - All row data\n * @param indices - If provided, only include rows at these indices (sorted ascending)\n * @returns Filtered row array\n */\nexport function resolveRows<T>(rows: readonly T[], indices?: number[]): T[] {\n if (!indices?.length) return rows as T[];\n\n return [...indices]\n .sort((a, b) => a - b)\n .map((i) => rows[i])\n .filter((r): r is T => r != null);\n}\n\n/**\n * Format a raw cell value as a text string.\n *\n * Provides the common null / Date / object → string conversion shared by\n * both clipboard and export output builders.\n *\n * @param value - The cell value to format\n * @returns A plain-text representation of the value\n */\nexport function formatValueAsText(value: unknown): string {\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n return String(value);\n}\n","/**\n * Clipboard Paste Logic\n *\n * Pure functions for reading and parsing clipboard data.\n */\n\nimport type { ClipboardConfig } from './types';\n\n/**\n * Parse clipboard text into a 2D array of cell values.\n *\n * Handles:\n * - Quoted values (with escaped double quotes \"\")\n * - Multi-line quoted values (newlines within quotes are preserved)\n * - Custom delimiters and newlines\n * - Empty lines (filtered out only if they contain no data)\n *\n * Implementation: char-by-char state machine for delimiter/newline/quote\n * detection, but cell values are extracted via `slice(start, end)` rather\n * than `+= char` accumulation. The vast majority of cells have no quotes,\n * so the hot path is a single substring slice per cell. Cells that did\n * contain quotes fall back to {@link unquoteCell} for the strip + escape\n * pass.\n *\n * @param text - Raw clipboard text\n * @param config - Clipboard configuration\n * @returns 2D array where each sub-array is a row of cell values\n */\nexport function parseClipboardText(text: string, config: ClipboardConfig): string[][] {\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Normalize Windows / old-Mac line endings.\n const normalized = text.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n const len = normalized.length;\n\n const rows: string[][] = [];\n let currentRow: string[] = [];\n let cellStart = 0;\n let inQuotes = false;\n let cellHasQuotes = false;\n\n const finalizeCell = (endIdx: number): string => {\n const raw = normalized.slice(cellStart, endIdx);\n return cellHasQuotes ? unquoteCell(raw) : raw;\n };\n\n const pushRow = () => {\n // Filter purely-empty rows (single empty cell or all-whitespace cells),\n // matching the original char-by-char implementation's contract.\n if (currentRow.length > 1 || currentRow.some((c) => c.trim() !== '')) {\n rows.push(currentRow);\n }\n currentRow = [];\n };\n\n for (let i = 0; i < len; i++) {\n const char = normalized[i];\n\n if (char === '\"') {\n cellHasQuotes = true;\n if (!inQuotes) {\n inQuotes = true;\n } else if (normalized[i + 1] === '\"') {\n // Escaped quote (\"\") — skip the second quote, stay inside the quoted run.\n i++;\n } else {\n inQuotes = false;\n }\n } else if (char === delimiter && !inQuotes) {\n currentRow.push(finalizeCell(i));\n cellStart = i + 1;\n cellHasQuotes = false;\n } else if (char === newline && !inQuotes) {\n currentRow.push(finalizeCell(i));\n cellStart = i + 1;\n cellHasQuotes = false;\n pushRow();\n }\n }\n\n // Final cell + row.\n currentRow.push(finalizeCell(len));\n pushRow();\n\n return rows;\n}\n\n/**\n * Strip surrounding quotes and unescape `\"\"` → `\"` from a cell value that\n * contained at least one quote character. Only invoked from the slow path\n * of {@link parseClipboardText}.\n */\nfunction unquoteCell(raw: string): string {\n let out = '';\n let inQuotes = false;\n for (let i = 0; i < raw.length; i++) {\n const c = raw[i];\n if (c === '\"') {\n if (!inQuotes) {\n inQuotes = true;\n } else if (raw[i + 1] === '\"') {\n out += '\"';\n i++;\n } else {\n inQuotes = false;\n }\n } else {\n out += c;\n }\n }\n return out;\n}\n\n/**\n * Read text from the system clipboard.\n *\n * Uses the modern Clipboard API. Returns empty string if\n * the API is not available or permission is denied.\n *\n * @returns Promise resolving to clipboard text or empty string\n */\nexport async function readFromClipboard(): Promise<string> {\n try {\n return await navigator.clipboard.readText();\n } catch {\n // Permission denied or API not available\n return '';\n }\n}\n","/**\n * Clipboard Plugin Types\n *\n * Type definitions for clipboard copy/paste functionality.\n */\n\nimport { invalidateAccessorCache } from '../../core/internal/value-accessor';\nimport type { GridElement } from '../../core/plugin/base-plugin';\nimport type { CellEditablePredicate } from '../../core/plugin/types';\n\n/**\n * Custom paste handler function.\n *\n * @param detail - The parsed paste data with target and field info\n * @param grid - The grid element to update\n * @returns `false` to prevent the default paste behavior, or `void`/`true` to allow it\n *\n * @example\n * ```ts\n * // Custom handler that validates before pasting\n * new ClipboardPlugin({\n * pasteHandler: (detail, grid) => {\n * if (!detail.target) return false;\n * // Apply custom validation/transformation...\n * applyPasteData(detail, grid);\n * return false; // We handled it, skip default\n * }\n * })\n * ```\n * @since 0.4.2\n */\nexport type PasteHandler = (detail: PasteDetail, grid: GridElement) => boolean | void;\n\n/**\n * Options for programmatic copy operations.\n *\n * Allows callers to control exactly which columns and rows are included\n * in a copy, independently of the current selection state.\n *\n * @example Copy specific columns from selected rows\n * ```ts\n * const clipboard = grid.getPluginByName('clipboard');\n * // User selected rows 0, 2, 4 via a dialog, chose columns to include\n * const text = await clipboard.copy({\n * rowIndices: [0, 2, 4],\n * columns: ['name', 'email'],\n * includeHeaders: true,\n * });\n * ```\n * @since 1.14.0\n */\nexport interface CopyOptions {\n /** Specific column fields to include. If omitted, uses current selection or all visible columns. */\n columns?: string[];\n /** Specific row indices to copy. If omitted, uses current selection or all rows. */\n rowIndices?: number[];\n /** Include column headers in copied text. Defaults to the plugin config value. */\n includeHeaders?: boolean;\n /** Column delimiter override. Defaults to the plugin config value. */\n delimiter?: string;\n /** Row delimiter override. Defaults to the plugin config value. */\n newline?: string;\n /** Custom cell value processor for this operation. Overrides the plugin config's `processCell`. */\n processCell?: (value: unknown, field: string, row: unknown) => string;\n}\n\n/** Configuration options for the clipboard plugin * @since 0.1.1\n */\nexport interface ClipboardConfig {\n /** Include column headers in copied text (default: false) */\n includeHeaders?: boolean;\n /** Column delimiter character (default: '\\t' for tab) */\n delimiter?: string;\n /** Row delimiter/newline character (default: '\\n') */\n newline?: string;\n /** Wrap string values with quotes (default: false) */\n quoteStrings?: boolean;\n /** Custom cell value processor for copy operations */\n processCell?: (value: unknown, field: string, row: unknown) => string;\n /**\n * When `true`, pasting into a multi-cell selection larger than the clipboard\n * source tiles (repeats) the source to fill the whole selection: a single\n * copied cell fills every selected cell, a 1×2 source fills as\n * `val1, val2, val1, val2`, and a 2×2 block tiles across the selection.\n *\n * Only applies when a bounded (multi-cell) selection is active — it never\n * grows the grid. Defaults to `false` (paste writes only the source extent).\n *\n * @default false\n * @since 3.0.0\n */\n fillSelection?: boolean;\n /**\n * Custom paste handler. By default, the plugin applies pasted data to `grid.rows`\n * starting at the target cell.\n *\n * - Set to a custom function to handle paste yourself\n * - Set to `null` to disable auto-paste (event still fires)\n * - Return `false` from handler to prevent default behavior\n *\n * @default defaultPasteHandler (auto-applies paste data)\n */\n pasteHandler?: PasteHandler | null;\n}\n\n/** Internal state managed by the clipboard plugin */\nexport interface ClipboardState {\n /** The last copied text (for reference/debugging) */\n lastCopied: string | null;\n}\n\n/** Event detail emitted after a successful copy operation * @since 0.1.1\n */\nexport interface CopyDetail {\n /** The text that was copied to clipboard */\n text: string;\n /** Number of rows copied */\n rowCount: number;\n /** Number of columns copied */\n columnCount: number;\n}\n\n/** Target cell coordinates and bounds for paste operations * @since 0.4.2\n */\nexport interface PasteTarget {\n /** Target row index (top-left of paste area) */\n row: number;\n /** Target column index (top-left of paste area) */\n col: number;\n /** Target column field name (for easy data mapping) */\n field: string;\n /**\n * Selection bounds that constrain the paste area.\n * If set, paste data will be clipped to fit within these bounds.\n * If null, paste expands freely from the target cell.\n */\n bounds: {\n /** End row index (inclusive) */\n endRow: number;\n /** End column index (inclusive) */\n endCol: number;\n } | null;\n}\n\n/** Event detail emitted after a paste operation * @since 0.1.1\n */\nexport interface PasteDetail {\n /** Parsed rows from clipboard (2D array of cell values) */\n rows: string[][];\n /** Raw text that was pasted */\n text: string;\n /** The target cell where paste starts (top-left of paste area). Null if no cell is selected. */\n target: PasteTarget | null;\n /**\n * Column fields for each column in the paste range, starting from target.col.\n * Useful for mapping parsed cell values to data fields.\n * Length matches the width of the pasted data (or available columns, whichever is smaller).\n * When `fillSelection` is active, this spans the full selection width so the source can be tiled.\n */\n fields: string[];\n /**\n * When `true`, the paste handler tiles the clipboard source across the full\n * selection bounds (see {@link ClipboardConfig.fillSelection}). Set by the\n * plugin from config; only ever `true` when `target.bounds` is set.\n * @since 3.0.0\n */\n fillSelection?: boolean;\n}\n\n/**\n * Default paste handler that applies pasted data to the grid.\n *\n * This is the built-in handler used when no custom `pasteHandler` is configured.\n *\n * Edits are routed through `grid.updateRows(…, 'api')` so paste participates in\n * the full edit pipeline — dirty tracking, cancelable validation, undo/redo\n * history, and abortion — exactly like an interactive edit. Rows without a\n * resolvable ID (no `getRowId`) fall back to a direct in-place write, which\n * still updates values but does not track them.\n *\n * Behavior:\n * - Single cell selection: paste expands freely, adds new rows if needed\n * - Range/row selection: paste is clipped to fit within selection bounds\n * - Fill-selection: when `detail.fillSelection` is set, the source is tiled\n * (repeated via modulo indexing) to fill the whole selection bounds\n * - Non-editable cells: values are skipped (column alignment preserved).\n * Editability is resolved by the editing plugin via the\n * `getCellEditableResolver` query — including row-conditional `editable`.\n * Without the editing plugin, no cell is editable and paste is a no-op.\n *\n * @param detail - The parsed paste data from clipboard\n * @param grid - The grid element to update\n * @since 0.4.2\n */\nexport function defaultPasteHandler(detail: PasteDetail, grid: GridElement): void {\n const { rows: pastedRows, target, fields, fillSelection } = detail;\n\n // No target = nothing to do\n if (!target || pastedRows.length === 0) return;\n\n const currentRows = grid.rows as Record<string, unknown>[];\n const columns = grid.effectiveConfig.columns ?? [];\n const allFields = columns.map((col) => col.field);\n\n // Editability is owned by the editing plugin (row-conditional included). With\n // no editing plugin loaded, nothing is editable and paste is a no-op.\n const [canEditCell = () => false] = grid.query<CellEditablePredicate>('getCellEditableResolver');\n\n // Accumulate the intended edits per target row (honoring editability, tiling,\n // and selection bounds). Rows beyond current data are only created for an\n // unbounded (single-cell expansion) paste.\n const editsByRow = new Map<number, Record<string, unknown>>();\n let rowCountAfter = currentRows.length;\n\n const addEdit = (rowIndex: number, field: string | undefined, value: unknown, evalRow: Record<string, unknown>) => {\n if (!field || !canEditCell(field, evalRow)) return;\n let changes = editsByRow.get(rowIndex);\n if (!changes) {\n changes = {};\n editsByRow.set(rowIndex, changes);\n }\n changes[field] = value;\n };\n\n if (fillSelection && target.bounds) {\n // Fill-selection (tile) mode: repeat the source across the selection bounds\n // using modulo indexing. Never grows the grid. Editability is resolved\n // against the pre-paste row (bounded mode never grows).\n const srcRows = pastedRows.length;\n for (let rowIndex = target.row; rowIndex <= target.bounds.endRow; rowIndex++) {\n if (rowIndex >= currentRows.length) break;\n const sourceRow = pastedRows[(rowIndex - target.row) % srcRows];\n const srcCols = sourceRow.length;\n if (srcCols === 0) continue;\n const evalRow = currentRows[rowIndex];\n fields.forEach((field, colOffset) => addEdit(rowIndex, field, sourceRow[colOffset % srcCols], evalRow));\n }\n } else {\n const maxPasteRow = target.bounds ? target.bounds.endRow : Infinity;\n pastedRows.forEach((rowData, rowOffset) => {\n const targetRowIndex = target.row + rowOffset;\n if (targetRowIndex > maxPasteRow) return;\n if (target.bounds && targetRowIndex >= currentRows.length) return; // bounded: don't grow\n if (!target.bounds && targetRowIndex >= rowCountAfter) rowCountAfter = targetRowIndex + 1;\n // Editability resolved against the pre-paste row (empty for grown rows).\n const evalRow = currentRows[targetRowIndex] ?? {};\n rowData.forEach((cellValue, colOffset) => addEdit(targetRowIndex, fields[colOffset], cellValue, evalRow));\n });\n }\n\n if (editsByRow.size === 0 && rowCountAfter === currentRows.length) return;\n\n // Grow the grid first for unbounded pastes that extend past existing data.\n // New rows start empty; their pasted values are applied in the write phase.\n if (rowCountAfter > currentRows.length) {\n const grown = [...currentRows];\n while (grown.length < rowCountAfter) {\n const emptyRow: Record<string, unknown> = {};\n allFields.forEach((f) => (emptyRow[f] = ''));\n grown.push(emptyRow);\n }\n grid.rows = grown;\n }\n\n const rowsNow = grid.rows as Record<string, unknown>[];\n\n // Route edits through updateRows so paste flows through the edit pipeline\n // (dirty tracking, validation, history, abortion). Rows without a resolvable\n // ID fall back to a direct in-place write.\n const updates: Array<{ id: string; changes: Record<string, unknown> }> = [];\n let directWrote = false;\n for (const [rowIndex, changes] of editsByRow) {\n const row = rowsNow[rowIndex];\n if (!row) continue;\n let id: string | undefined;\n try {\n id = grid.getRowId(row);\n } catch {\n id = undefined;\n }\n if (id == null) {\n // No stable ID → can't route through updateRow; write directly. Invalidate\n // the value-accessor cache per field (row identity is unchanged), matching\n // RowManager.updateRow — otherwise a `column.valueAccessor` display can go\n // stale after paste.\n for (const [field, value] of Object.entries(changes)) {\n (row as Record<string, unknown>)[field] = value;\n invalidateAccessorCache(row as object, field);\n }\n directWrote = true;\n } else {\n updates.push({ id, changes });\n }\n }\n\n if (updates.length > 0) {\n grid.updateRows(updates, 'api');\n } else if (directWrote && rowCountAfter === currentRows.length) {\n // Direct writes only, no structural grow above — trigger a render.\n grid.rows = [...rowsNow];\n }\n}\n\n// Module Augmentation - Register plugin name for type-safe getPluginByName()\ndeclare module '../../core/types' {\n interface DataGridEventMap {\n /** Fired after a successful copy operation. Provides the copied text, row count, and column count. @group Clipboard Events */\n copy: CopyDetail;\n /** Fired after a paste operation. Provides parsed rows, target cell, and column fields. @group Clipboard Events */\n paste: PasteDetail;\n }\n\n interface PluginNameMap {\n clipboard: import('./ClipboardPlugin').ClipboardPlugin;\n }\n}\n","/**\n * Clipboard Plugin (Class-based)\n *\n * Provides copy/paste functionality for tbw-grid.\n * Supports Ctrl+C/Cmd+C for copying and Ctrl+V/Cmd+V for pasting.\n *\n * **With Selection plugin:** Copies selected cells/rows/range\n * **Without Selection plugin:** Copies entire grid\n */\n\nimport { resolveCellValue } from '../../core/internal/value-accessor';\nimport {\n BaseGridPlugin,\n type GridElement,\n type PluginDependency,\n type PluginManifest,\n type PluginQuery,\n} from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { formatValueAsText, resolveColumns, resolveRows } from '../shared/data-collection';\nimport { copyToClipboard } from './copy';\nimport { parseClipboardText, readFromClipboard } from './paste';\nimport {\n defaultPasteHandler,\n type ClipboardConfig,\n type CopyDetail,\n type CopyOptions,\n type PasteDetail,\n type PasteTarget,\n} from './types';\n\n/**\n * Clipboard Plugin for tbw-grid\n *\n * Brings familiar copy/cut/paste functionality with full keyboard shortcut support\n * (Ctrl+C, Ctrl+X, Ctrl+V). Handles single cells, multi-cell selections, and integrates\n * seamlessly with Excel and other spreadsheet applications via tab-delimited output.\n *\n * > **Optional Dependency:** Works best with SelectionPlugin for copying/pasting selected\n * > cells. Without SelectionPlugin, copies the entire grid and pastes at row 0, column 0.\n *\n * ## Installation\n *\n * ```ts\n * import { ClipboardPlugin } from '@toolbox-web/grid/plugins/clipboard';\n * ```\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Ctrl+C` / `Cmd+C` | Copy selected cells |\n * | `Ctrl+V` / `Cmd+V` | Paste into selected cells |\n * | `Ctrl+X` / `Cmd+X` | Cut selected cells |\n *\n * ## Paste Behavior by Selection Type\n *\n * | Selection Type | Paste Behavior |\n * |----------------|----------------|\n * | Single cell | Paste expands freely from that cell |\n * | Range selection | Paste is clipped to fit within the selected range |\n * | Row selection | Paste is clipped to the selected rows |\n * | No selection | Paste starts at row 0, column 0 |\n *\n * With `fillSelection: true`, pasting a smaller source into a larger bounded\n * selection tiles the source to fill the whole selection (e.g. one cell fills\n * all selected cells; a 1×2 source fills as `val1, val2, val1, val2`).\n *\n * @example Basic Usage with Excel Compatibility\n * ```ts\n * import '@toolbox-web/grid';\n * import { ClipboardPlugin } from '@toolbox-web/grid/plugins/clipboard';\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [\n * new SelectionPlugin({ mode: 'range' }),\n * new ClipboardPlugin({\n * includeHeaders: true,\n * delimiter: '\\t', // Tab for Excel\n * }),\n * ],\n * };\n * ```\n *\n * @example Custom Paste Handler\n * ```ts\n * new ClipboardPlugin({\n * pasteHandler: (grid, target, data) => {\n * // Validate or transform data before applying\n * console.log('Pasting', data.length, 'rows');\n * return defaultPasteHandler(grid, target, data);\n * },\n * })\n * ```\n *\n * @see {@link ClipboardConfig} for all configuration options\n * @see SelectionPlugin for enhanced copy/paste with selection\n *\n * @internal Extends BaseGridPlugin\n * @since 0.1.1\n */\nexport class ClipboardPlugin extends BaseGridPlugin<ClipboardConfig> {\n /**\n * Plugin manifest — declares queries for inter-plugin communication.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n queries: [{ type: 'clipboard:copy', description: 'Triggers a copy operation and returns the copied text' }],\n };\n\n /**\n * Plugin dependencies - ClipboardPlugin works best with SelectionPlugin.\n *\n * Without SelectionPlugin: copies entire grid, pastes at row 0 col 0.\n * With SelectionPlugin: copies/pastes based on selection.\n */\n /** @internal */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'selection', required: false, reason: 'Enables copy/paste of selected cells instead of entire grid' },\n ];\n\n /** @internal */\n readonly name = 'clipboard';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ClipboardConfig> {\n return {\n includeHeaders: false,\n delimiter: '\\t',\n newline: '\\n',\n quoteStrings: false,\n };\n }\n\n // #region Internal State\n /** The last copied text (for reference/debugging) */\n private lastCopied: { text: string; timestamp: number } | null = null;\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n // Listen for native paste events to get clipboard data synchronously\n // This is more reliable than the async Clipboard API in iframe contexts\n grid.addEventListener('paste', (e) => this.#handleNativePaste(e), {\n signal: this.disconnectSignal,\n });\n }\n\n /** @internal */\n override detach(): void {\n this.lastCopied = null;\n }\n\n /** @internal */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'clipboard:copy') {\n this.copy();\n return true;\n }\n return undefined;\n }\n // #endregion\n\n // #region Event Handlers\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean {\n const isCopy = (event.ctrlKey || event.metaKey) && event.key === 'c';\n\n if (isCopy) {\n // Prevent the browser's default copy action so it doesn't overwrite\n // our clipboard write with whatever text is selected in the DOM.\n event.preventDefault();\n this.#handleCopy(event.target as HTMLElement);\n return true;\n }\n\n // For paste, we do NOT return true - let the native paste event fire\n // so we can access clipboardData synchronously in #handleNativePaste\n return false;\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Handle copy operation from keyboard shortcut.\n *\n * For keyboard-triggered copies, respects the current selection or\n * falls back to the focused cell from the DOM.\n */\n #handleCopy(target: HTMLElement): void {\n const selection = this.#getSelection();\n\n // Selection plugin exists but nothing selected → try focused cell from DOM\n if (selection && selection.ranges.length === 0) {\n const focused = this.#getFocusedCellFromDOM(target);\n if (!focused) return;\n const col = this.columns[focused.col];\n if (!col) return;\n this.copy({ rowIndices: [focused.row], columns: [col.field] });\n return;\n }\n\n // Delegate to the public copy() method (selection or full grid)\n this.copy();\n }\n\n /**\n * Handle native paste event (preferred method - works in iframes).\n * Uses synchronous clipboardData from the native paste event.\n *\n * Flow:\n * 1. Parse clipboard text\n * 2. Build target/fields info from selection\n * 3. Emit 'paste' event (for listeners)\n * 4. Call paste handler (if configured) to apply data to grid\n *\n * Selection behavior:\n * - Single cell: paste starts at cell, expands freely\n * - Range/row: paste is clipped to fit within selection bounds\n * - No selection: paste starts at row 0, col 0\n */\n #handleNativePaste(event: ClipboardEvent): void {\n // If the paste target is an editable form control or contenteditable element\n // (e.g. an active cell editor injected by EditingPlugin, a filter input, a\n // header search box), let the browser deliver the paste to that control\n // natively. Without this guard `event.preventDefault()` below would swallow\n // the user's keystroke and the editor input would never receive the text.\n if (this.#isEditablePasteTarget(event.target)) return;\n\n const text = event.clipboardData?.getData('text/plain');\n if (!text) return;\n\n // Prevent default to avoid pasting into contenteditable elements\n event.preventDefault();\n\n const parsed = parseClipboardText(text, this.config);\n\n // Get target cell from selection via query\n const selection = this.#getSelection();\n const ranges = selection?.ranges ?? [];\n const firstRange = ranges[0];\n\n // Aggregate the bounding box across ALL selected ranges. A multi-cell\n // selection is frequently expressed as several single-cell ranges (clicking\n // or dragging cell-by-cell), so inspecting `ranges[0]` alone would treat it\n // as a single cell and skip fill/clip — pasting into only the first cell.\n // min/max also normalizes reversed (bottom-up / right-to-left) drags.\n let selMinRow = Infinity;\n let selMinCol = Infinity;\n let selMaxRow = -Infinity;\n let selMaxCol = -Infinity;\n for (const r of ranges) {\n selMinRow = Math.min(selMinRow, r.from.row, r.to.row);\n selMaxRow = Math.max(selMaxRow, r.from.row, r.to.row);\n selMinCol = Math.min(selMinCol, r.from.col, r.to.col);\n selMaxCol = Math.max(selMaxCol, r.from.col, r.to.col);\n }\n\n // Determine target cell (top-left of the whole selection)\n const targetRow = firstRange ? selMinRow : 0;\n const targetCol = firstRange ? selMinCol : 0;\n\n // Multi-cell when the selection spans more than one cell in total, whether\n // it's one large range or many single-cell ranges.\n const isMultiCell =\n !!firstRange &&\n (selection?.mode === 'range' || selection?.mode === 'row') &&\n (selMinRow !== selMaxRow || selMinCol !== selMaxCol);\n\n const bounds = isMultiCell ? { endRow: selMaxRow, endCol: selMaxCol } : null;\n // Selection range indices are visible-column indices (from data-col)\n const maxCol = bounds?.endCol ?? this.visibleColumns.length - 1;\n\n // Drop a copied header row so a round-trip paste writes only values into\n // cells (never the header label). Only strips when this grid is configured\n // to include headers AND the first parsed row matches the target columns'\n // labels — external pastes without a matching header row are left intact.\n this.#dropCopiedHeaderRow(parsed, targetCol, maxCol);\n\n // Build target info\n const column = this.visibleColumns[targetCol];\n const target: PasteTarget | null = column ? { row: targetRow, col: targetCol, field: column.field, bounds } : null;\n\n // Fill-selection tiling only applies to a bounded (multi-cell) selection.\n const fillSelection = this.config.fillSelection === true && bounds !== null;\n\n // Build field list for paste width (constrained by bounds if set). When\n // filling the selection, extend fields across the full selection width so\n // the source can be tiled to fill every selected column; otherwise cap at\n // the clipboard's own width.\n const fields: string[] = [];\n const pasteWidth = parsed[0]?.length ?? 0;\n const fieldWidth = fillSelection ? maxCol - targetCol + 1 : pasteWidth;\n for (let i = 0; i < fieldWidth && targetCol + i <= maxCol; i++) {\n const col = this.visibleColumns[targetCol + i];\n if (col) {\n fields.push(col.field);\n }\n }\n\n const detail: PasteDetail = { rows: parsed, text, target, fields, fillSelection };\n\n // Emit the event for any listeners\n this.emit<PasteDetail>('paste', detail);\n\n // Apply paste data using the configured handler (or default)\n this.#applyPasteHandler(detail);\n }\n\n /**\n * Apply the paste handler to update grid data.\n *\n * Uses the configured `pasteHandler`, or the default handler if not specified.\n * Set `pasteHandler: null` in config to disable auto-paste.\n */\n #applyPasteHandler(detail: PasteDetail): void {\n if (!this.grid) return;\n\n const { pasteHandler } = this.config;\n\n // pasteHandler: null means explicitly disabled\n if (pasteHandler === null) return;\n\n // Use custom handler or default\n const handler = pasteHandler ?? defaultPasteHandler;\n handler(detail, this.grid);\n }\n\n /**\n * Drop a header row that this grid itself copied, so a round-trip paste\n * never lands a header label in a data cell.\n *\n * Guarded on two conditions to avoid eating a legitimate first data row:\n * 1. The grid is configured with `includeHeaders` (it copies headers), and\n * 2. every cell of the first parsed row equals the corresponding target\n * column's label (`header || field`).\n *\n * When both hold, the first row is removed in place. External pastes (no\n * matching header row) are left untouched.\n */\n #dropCopiedHeaderRow(parsed: string[][], targetCol: number, maxCol: number): void {\n if (!this.config.includeHeaders) return;\n // Never strip away the only row — that would make paste a no-op.\n if (parsed.length <= 1) return;\n\n const headerRow = parsed[0];\n if (headerRow.length === 0) return;\n\n for (let i = 0; i < headerRow.length && targetCol + i <= maxCol; i++) {\n const col = this.visibleColumns[targetCol + i];\n // Ran off the available columns, or a cell doesn't match its label →\n // not a copied header, keep the row.\n if (!col || headerRow[i] !== (col.header || col.field)) return;\n }\n\n parsed.shift();\n }\n\n /**\n * True when the paste event originated from a focusable form control or\n * contenteditable element — i.e. somewhere the user expects the browser's\n * native paste behavior (typing into an editor input). Lets EditingPlugin\n * editors, filter inputs, and other text fields receive paste normally\n * instead of having ClipboardPlugin swallow the event.\n */\n #isEditablePasteTarget(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false;\n if (target.closest('input, textarea, select, [contenteditable=\"\"], [contenteditable=\"true\"]')) {\n return true;\n }\n return false;\n }\n\n /**\n * Get the current selection via Query System.\n * Returns undefined if no selection plugin is loaded or nothing is selected.\n */\n #getSelection(): SelectionQueryResult | undefined {\n const responses = this.grid?.query<SelectionQueryResult>('getSelection');\n return responses?.[0];\n }\n\n /**\n * Resolve columns and rows to include based on options and/or current selection.\n *\n * Priority for columns:\n * 1. `options.columns` (explicit field list)\n * 2. Selection range column bounds (range/cell mode only)\n * 3. All visible non-utility columns\n *\n * Priority for rows:\n * 1. `options.rowIndices` (explicit indices)\n * 2. Selection range row bounds\n * 3. All rows\n */\n #resolveData(options?: CopyOptions): { columns: ColumnConfig[]; rows: Record<string, unknown>[] } {\n const selection = this.#getSelection();\n\n // --- Columns ---\n let columns: ColumnConfig[];\n if (options?.columns) {\n // Caller specified exact fields\n columns = resolveColumns(this.columns, options.columns);\n } else if (selection?.ranges.length && selection.mode !== 'row') {\n // Range/cell selection: restrict to selection column bounds\n // Selection indices are visible-column indices (from data-col)\n const range = selection.ranges[selection.ranges.length - 1];\n const minCol = Math.min(range.from.col, range.to.col);\n const maxCol = Math.max(range.from.col, range.to.col);\n columns = resolveColumns(this.visibleColumns.slice(minCol, maxCol + 1));\n } else {\n // Row selection or no selection: all visible columns\n columns = resolveColumns(this.columns);\n }\n\n // --- Rows ---\n let rows: Record<string, unknown>[];\n if (options?.rowIndices) {\n // Caller specified exact row indices\n rows = resolveRows(this.rows as Record<string, unknown>[], options.rowIndices);\n } else if (selection?.ranges.length) {\n // Selection range: extract contiguous row range\n const range = selection.ranges[selection.ranges.length - 1];\n const minRow = Math.min(range.from.row, range.to.row);\n const maxRow = Math.max(range.from.row, range.to.row);\n rows = [];\n for (let r = minRow; r <= maxRow; r++) {\n const row = this.rows[r] as Record<string, unknown> | undefined;\n if (row) rows.push(row);\n }\n } else {\n // No selection: all rows\n rows = this.rows as Record<string, unknown>[];\n }\n\n return { columns, rows };\n }\n\n /**\n * Build delimited text from resolved columns and rows.\n *\n * When no `processCell` callback is configured, uses \"copy what you see\" logic:\n * 1. Column `format` function (includes typeDefaults merged at config time)\n * 2. DOM cell textContent for columns with custom renderers (visible rows only)\n * 3. Raw value via `formatValueAsText` as final fallback\n */\n #buildText(columns: ColumnConfig[], rows: Record<string, unknown>[], options?: CopyOptions): string {\n const delimiter = options?.delimiter ?? this.config.delimiter ?? '\\t';\n const newline = options?.newline ?? this.config.newline ?? '\\n';\n const includeHeaders = options?.includeHeaders ?? this.config.includeHeaders ?? false;\n const processCell = options?.processCell ?? this.config.processCell;\n\n const lines: string[] = [];\n\n // Header row\n if (includeHeaders) {\n lines.push(columns.map((c) => c.header || c.field).join(delimiter));\n }\n\n // Data rows\n for (const row of rows) {\n const cells = columns.map((col) => {\n const value = resolveCellValue(row, col);\n if (processCell) return processCell(value, col.field, row);\n return this.#formatCellAsDisplayed(col, value, row);\n });\n lines.push(cells.join(delimiter));\n }\n\n return lines.join(newline);\n }\n\n /**\n * Format a cell value the way the grid displays it.\n *\n * Priority:\n * 1. Column `format` function (includes typeDefaults applied at config merge time)\n * 2. DOM textContent for columns with a custom renderer (virtualized rows only)\n * 3. Raw value via `formatValueAsText`\n */\n #formatCellAsDisplayed(col: ColumnConfig, value: unknown, row: Record<string, unknown>): string {\n // 1. Column format function (covers col.format and typeDefaults)\n if (col.format) {\n try {\n const formatted = col.format(value, row);\n return formatted == null ? '' : String(formatted);\n } catch {\n // Format failed — fall through to next strategy\n }\n }\n\n // 2. DOM textContent for columns with custom renderers\n if (col.renderer || col.viewRenderer) {\n const text = this.#readCellTextFromDOM(col.field, row);\n if (text != null) return text;\n }\n\n // 3. Fallback to raw value formatting\n return formatValueAsText(value);\n }\n\n /**\n * Try to read a cell's displayed text from the DOM.\n *\n * Only works for rows currently rendered in the virtualized viewport.\n * Returns `null` if the row is not in the DOM (caller should fall back).\n */\n #readCellTextFromDOM(field: string, row: Record<string, unknown>): string | null {\n const host = this.gridElement;\n if (!host) return null;\n\n // Find the row index in the grid's current row array\n const rowIndex = this.rows.indexOf(row);\n if (rowIndex === -1) return null;\n\n // Query the rendered cell by row index and field name\n const cell = host.querySelector<HTMLElement>(`.cell[data-row=\"${rowIndex}\"][data-field=\"${field}\"]`);\n if (!cell) return null;\n\n return cell.textContent?.trim() ?? null;\n }\n\n /**\n * Get focused cell coordinates from DOM.\n * Used as fallback when SelectionPlugin has no selection.\n */\n #getFocusedCellFromDOM(target: HTMLElement): { row: number; col: number } | null {\n const cell = target.closest('[data-field-cache]') as HTMLElement | null;\n if (!cell) return null;\n\n const field = cell.dataset.fieldCache;\n const rowIndexStr = cell.dataset.row;\n if (!field || !rowIndexStr) return null;\n\n const row = parseInt(rowIndexStr, 10);\n if (isNaN(row)) return null;\n\n const col = this.columns.findIndex((c) => c.field === field);\n if (col === -1) return null;\n\n return { row, col };\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the text representation of the current selection (or specified data)\n * without writing to the system clipboard.\n *\n * Useful for previewing what would be copied, or for feeding the text into\n * a custom UI (e.g., a \"copy with column picker\" dialog).\n *\n * @param options - Control which columns/rows to include\n * @returns Delimited text, or empty string if nothing to copy\n *\n * @example Get text for specific columns\n * ```ts\n * const clipboard = grid.getPluginByName('clipboard');\n * const text = clipboard.getSelectionAsText({\n * columns: ['name', 'email'],\n * includeHeaders: true,\n * });\n * console.log(text);\n * // \"Name\\tEmail\\nAlice\\talice@example.com\\n...\"\n * ```\n */\n getSelectionAsText(options?: CopyOptions): string {\n const { columns, rows } = this.#resolveData(options);\n if (columns.length === 0 || rows.length === 0) return '';\n return this.#buildText(columns, rows, options);\n }\n\n /**\n * Copy data to the system clipboard.\n *\n * Without options, copies the current selection (or entire grid if no selection).\n * With options, copies exactly the specified columns and/or rows — ideal for\n * \"copy with column picker\" workflows where the user selects rows first,\n * then chooses which columns to include via a dialog.\n *\n * @param options - Control which columns/rows to include\n * @returns The copied text\n *\n * @example Copy current selection (default)\n * ```ts\n * const clipboard = grid.getPluginByName('clipboard');\n * await clipboard.copy();\n * ```\n *\n * @example Copy specific columns from specific rows\n * ```ts\n * // User selected rows in the grid, then picked columns in a dialog\n * const selectedRowIndices = [0, 3, 7];\n * const chosenColumns = ['name', 'department', 'salary'];\n * await clipboard.copy({\n * rowIndices: selectedRowIndices,\n * columns: chosenColumns,\n * includeHeaders: true,\n * });\n * ```\n */\n async copy(options?: CopyOptions): Promise<string> {\n const { columns, rows } = this.#resolveData(options);\n if (columns.length === 0 || rows.length === 0) {\n return '';\n }\n\n const text = this.#buildText(columns, rows, options);\n await copyToClipboard(text);\n this.lastCopied = { text, timestamp: Date.now() };\n this.emit<CopyDetail>('copy', {\n text,\n rowCount: rows.length,\n columnCount: columns.length,\n });\n return text;\n }\n\n /**\n * Copy specific rows by index to clipboard.\n *\n * Convenience wrapper around {@link copy} for row-based workflows.\n * Supports non-contiguous row indices (e.g., `[0, 3, 7]`).\n *\n * @param indices - Array of row indices to copy\n * @param options - Additional copy options (columns, headers, etc.)\n * @returns The copied text\n *\n * @example\n * ```ts\n * const clipboard = grid.getPluginByName('clipboard');\n * // Copy only rows 0 and 5, including just name and email columns\n * await clipboard.copyRows([0, 5], { columns: ['name', 'email'] });\n * ```\n */\n async copyRows(indices: number[], options?: Omit<CopyOptions, 'rowIndices'>): Promise<string> {\n if (indices.length === 0) return '';\n return this.copy({ ...options, rowIndices: indices });\n }\n\n /**\n * Read and parse clipboard content.\n * @returns Parsed 2D array of cell values, or null if clipboard is empty\n */\n async paste(): Promise<string[][] | null> {\n const text = await readFromClipboard();\n if (!text) return null;\n return parseClipboardText(text, this.config);\n }\n\n /**\n * Get the last copied text and timestamp.\n * @returns The last copied info or null\n */\n getLastCopied(): { text: string; timestamp: number } | null {\n return this.lastCopied;\n }\n // #endregion\n}\n\n// #region Internal Types\n\n/**\n * Range representation for clipboard operations.\n */\ninterface CellRange {\n from: { row: number; col: number };\n to: { row: number; col: number };\n}\n\n/**\n * Selection result returned by the Query System.\n * Matches the SelectionResult type from SelectionPlugin.\n */\ninterface SelectionQueryResult {\n mode: 'cell' | 'row' | 'range';\n ranges: CellRange[];\n anchor: { row: number; col: number } | null;\n}\n// #endregion\n\n// Re-export types\nexport type { ClipboardConfig, CopyDetail, CopyOptions, PasteDetail } from './types';\n","/**\n * Clipboard Copy Logic\n *\n * Pure functions for copying grid data to clipboard.\n */\n\nimport { CLIPBOARD_FAILED, warnDiagnostic } from '../../core/internal/diagnostics';\nimport { resolveCellValue } from '../../core/internal/value-accessor';\nimport type { ColumnConfig } from '../../core/types';\nimport type { ClipboardConfig } from './types';\n\n/** Parameters for building clipboard text */\nexport interface CopyParams {\n /** All grid rows */\n rows: unknown[];\n /** Column configurations */\n columns: ColumnConfig[];\n /** Selected row indices */\n selectedIndices: Set<number> | number[];\n /** Clipboard configuration */\n config: ClipboardConfig;\n}\n\n/**\n * Format a cell value for clipboard output.\n *\n * Uses custom processCell if provided, otherwise applies default formatting:\n * - null/undefined → empty string\n * - Date → ISO string\n * - Object → JSON string\n * - Other → String conversion with optional quoting\n *\n * Dispatch order is `typeof`-first: string + number cover the vast majority\n * of cell values, so the cheap typeof check short-circuits before the\n * `instanceof Date` probe (which V8 can't fold into a fast path).\n *\n * @param value - The cell value to format\n * @param field - The field name\n * @param row - The full row object\n * @param config - Clipboard configuration\n * @returns Formatted string value\n */\nexport function formatCellValue(value: unknown, field: string, row: unknown, config: ClipboardConfig): string {\n if (config.processCell) {\n return config.processCell(value, field, row);\n }\n\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Hot path: strings.\n if (typeof value === 'string') {\n if (config.quoteStrings || value.includes(delimiter) || value.includes(newline) || value.includes('\"')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n }\n if (typeof value === 'number') return String(value);\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n return String(value);\n}\n\n/**\n * Build clipboard text from selected rows and columns.\n *\n * @param params - Copy parameters including rows, columns, selection, and config\n * @returns Tab-separated (or custom delimiter) text ready for clipboard\n */\nexport function buildClipboardText(params: CopyParams): string {\n const { rows, columns, selectedIndices, config } = params;\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Filter to visible columns (not hidden, not internal __ prefixed)\n const visibleColumns = columns.filter((c) => !c.hidden && !c.field.startsWith('__'));\n\n const lines: string[] = [];\n\n // Add header row if configured\n if (config.includeHeaders) {\n const headerCells = visibleColumns.map((c) => {\n const header = c.header || c.field;\n // Quote headers if they contain special characters\n if (header.includes(delimiter) || header.includes(newline) || header.includes('\"')) {\n return `\"${header.replace(/\"/g, '\"\"')}\"`;\n }\n return header;\n });\n lines.push(headerCells.join(delimiter));\n }\n\n // Convert indices to sorted array\n const indices = selectedIndices instanceof Set ? [...selectedIndices] : selectedIndices;\n const sortedIndices = [...indices].sort((a, b) => a - b);\n\n // Build data rows\n for (const idx of sortedIndices) {\n const row = rows[idx];\n if (!row) continue;\n\n const cells = visibleColumns.map((col) => formatCellValue(resolveCellValue(row, col), col.field, row, config));\n lines.push(cells.join(delimiter));\n }\n\n return lines.join(newline);\n}\n\n/**\n * Copy text to the system clipboard.\n *\n * Uses the modern Clipboard API when available, with fallback\n * to execCommand for older browsers.\n *\n * @param text - The text to copy\n * @returns Promise resolving to true if successful, false otherwise\n */\nexport async function copyToClipboard(text: string): Promise<boolean> {\n try {\n await navigator.clipboard.writeText(text);\n return true;\n } catch (err) {\n warnDiagnostic(CLIPBOARD_FAILED, `Clipboard API failed: ${err}`);\n // Fallback for older browsers or when Clipboard API is not available\n const textarea = document.createElement('textarea');\n textarea.value = text;\n textarea.style.position = 'fixed';\n textarea.style.opacity = '0';\n textarea.style.pointerEvents = 'none';\n document.body.appendChild(textarea);\n textarea.select();\n const success = document.execCommand('copy');\n document.body.removeChild(textarea);\n return success;\n }\n}\n"],"names":["resolveColumns","columns","fields","onlyVisible","result","filter","c","hidden","field","startsWith","utility","length","fieldSet","Set","has","parseClipboardText","text","config","delimiter","newline","normalized","replace","len","rows","currentRow","cellStart","inQuotes","cellHasQuotes","finalizeCell","endIdx","raw","slice","out","i","unquoteCell","pushRow","some","trim","push","char","defaultPasteHandler","detail","grid","pastedRows","target","fillSelection","currentRows","allFields","effectiveConfig","map","col","canEditCell","query","editsByRow","Map","rowCountAfter","addEdit","rowIndex","value","evalRow","changes","get","set","bounds","srcRows","row","endRow","sourceRow","srcCols","forEach","colOffset","maxPasteRow","Infinity","rowData","rowOffset","targetRowIndex","cellValue","size","grown","emptyRow","f","rowsNow","updates","directWrote","id","getRowId","Object","entries","invalidateAccessorCache","updateRows","ClipboardPlugin","BaseGridPlugin","static","queries","type","description","name","required","reason","defaultConfig","includeHeaders","quoteStrings","lastCopied","attach","super","addEventListener","e","this","handleNativePaste","signal","disconnectSignal","detach","handleQuery","copy","onKeyDown","event","ctrlKey","metaKey","key","preventDefault","handleCopy","selection","getSelection","ranges","focused","getFocusedCellFromDOM","rowIndices","isEditablePasteTarget","clipboardData","getData","parsed","firstRange","selMinRow","selMinCol","selMaxRow","selMaxCol","r","Math","min","from","to","max","targetRow","targetCol","mode","endCol","maxCol","visibleColumns","dropCopiedHeaderRow","column","pasteWidth","fieldWidth","emit","applyPasteHandler","pasteHandler","headerRow","header","shift","Element","closest","responses","resolveData","options","range","minCol","indices","sort","a","b","resolveRows","minRow","maxRow","buildText","processCell","lines","join","cells","resolveCellValue","formatCellAsDisplayed","format","formatted","String","renderer","viewRenderer","readCellTextFromDOM","Date","toISOString","JSON","stringify","formatValueAsText","host","gridElement","indexOf","cell","querySelector","textContent","dataset","fieldCache","rowIndexStr","parseInt","isNaN","findIndex","getSelectionAsText","async","navigator","clipboard","writeText","err","warnDiagnostic","CLIPBOARD_FAILED","textarea","document","createElement","style","position","opacity","pointerEvents","body","appendChild","select","success","execCommand","removeChild","copyToClipboard","timestamp","now","rowCount","columnCount","copyRows","paste","readText","readFromClipboard","getLastCopied"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"clipboard.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/shared/data-collection.ts","../../../../../libs/grid/src/lib/plugins/clipboard/clipboard-payload.ts","../../../../../libs/grid/src/lib/plugins/clipboard/paste.ts","../../../../../libs/grid/src/lib/plugins/clipboard/types.ts","../../../../../libs/grid/src/lib/plugins/clipboard/ClipboardPlugin.ts","../../../../../libs/grid/src/lib/plugins/clipboard/copy.ts"],"sourcesContent":["/**\n * Shared Data Collection Utilities\n *\n * Pure functions for resolving columns and formatting values, shared between\n * the Clipboard and Export plugins. Each plugin bundles its own copy of this\n * module (no chunk splitting) since plugin builds inline sibling imports.\n *\n * @internal\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Resolve which columns to include in a data export or copy operation.\n *\n * Filters out hidden columns, utility columns (`meta.utility`), and\n * internal columns (`__`-prefixed fields). Optionally restricts to an\n * explicit set of field names.\n *\n * @param columns - All column configurations\n * @param fields - If provided, only include columns whose field is in this list\n * @param onlyVisible - When `true` (default), exclude hidden and internal columns\n * @returns Filtered column array preserving original order\n */\nexport function resolveColumns(\n columns: readonly ColumnConfig[],\n fields?: string[],\n onlyVisible = true,\n): ColumnConfig[] {\n let result = columns as ColumnConfig[];\n\n if (onlyVisible) {\n result = result.filter((c) => !c.hidden && !c.field.startsWith('__') && c.utility !== true);\n }\n\n if (fields?.length) {\n const fieldSet = new Set(fields);\n result = result.filter((c) => fieldSet.has(c.field));\n }\n\n return result;\n}\n\n/**\n * Resolve which rows to include, optionally filtered to specific indices.\n *\n * @param rows - All row data\n * @param indices - If provided, only include rows at these indices (sorted ascending)\n * @returns Filtered row array\n */\nexport function resolveRows<T>(rows: readonly T[], indices?: number[]): T[] {\n if (!indices?.length) return rows as T[];\n\n return [...indices]\n .sort((a, b) => a - b)\n .map((i) => rows[i])\n .filter((r): r is T => r != null);\n}\n\n/**\n * Format a raw cell value as a text string.\n *\n * Provides the common null / Date / object → string conversion shared by\n * both clipboard and export output builders.\n *\n * @param value - The cell value to format\n * @returns A plain-text representation of the value\n */\nexport function formatValueAsText(value: unknown): string {\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n return String(value);\n}\n","/**\n * Structured clipboard payload codec.\n *\n * Copy writes both `text/plain` (WYSIWYG for external targets) and `text/html`.\n * The HTML is a real `<table>` of the display text whose root element also\n * carries a base64-encoded structured payload in a `data-tbw-clip` attribute.\n * A same-app paste — even **cross-grid or cross-window** — can then recover the\n * raw cell values losslessly, while external targets just see the table/text.\n *\n * Kept in its own module so `copy.ts` and `paste.ts` share the format without\n * importing each other.\n */\n\n/**\n * Structured clipboard payload carried in `text/html`. `rows` are the RAW cell\n * values (data rows only), `fields` the source column fields in the same order.\n * @internal\n */\nexport interface ClipboardPayload {\n /** Payload schema version. */\n v: 1;\n /** Source column fields, in column order. */\n fields: string[];\n /** Raw cell values, one inner array per data row (no header). */\n rows: unknown[][];\n}\n\n/** Attribute (on the payload table) that marks our structured HTML clipboard. */\nexport const CLIPBOARD_PAYLOAD_ATTR = 'data-tbw-clip';\n\n/** Unicode-safe base64 encode of a JSON payload for an HTML attribute. */\nfunction encodePayload(payload: ClipboardPayload): string {\n return btoa(encodeURIComponent(JSON.stringify(payload)));\n}\n\nconst escapeHtml = (s: string): string =>\n s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n\n/**\n * Build the `text/html` clipboard representation: a real `<table>` of the\n * DISPLAY text (so external targets like Excel get a proper table) whose root\n * element carries the base64-encoded structured {@link ClipboardPayload} in a\n * `data-tbw-clip` attribute for lossless same-app paste.\n */\nexport function buildClipboardHtml(\n header: string[] | null,\n displayRows: string[][],\n payload: ClipboardPayload,\n): string {\n const thead = header ? `<thead><tr>${header.map((h) => `<th>${escapeHtml(h)}</th>`).join('')}</tr></thead>` : '';\n const tbody = `<tbody>${displayRows\n .map((r) => `<tr>${r.map((c) => `<td>${escapeHtml(c)}</td>`).join('')}</tr>`)\n .join('')}</tbody>`;\n return `<table ${CLIPBOARD_PAYLOAD_ATTR}=\"${encodePayload(payload)}\">${thead}${tbody}</table>`;\n}\n\n/**\n * Extract the structured {@link ClipboardPayload} embedded in a `text/html`\n * clipboard representation written by {@link buildClipboardHtml}. Enables a\n * lossless **cross-grid / cross-window** paste of object-valued cells (the\n * in-memory buffer only covers the same grid instance).\n *\n * Returns `null` when the HTML has no `data-tbw-clip` marker (e.g. an external\n * paste from Excel) or the payload can't be decoded. Parsing is defensive —\n * malformed input never throws.\n *\n * @param html - Raw `text/html` from `clipboardData.getData('text/html')`\n */\nexport function parseClipboardHtmlPayload(html: string | undefined | null): ClipboardPayload | null {\n if (!html || html.indexOf(CLIPBOARD_PAYLOAD_ATTR) === -1) return null;\n try {\n const doc = new DOMParser().parseFromString(html, 'text/html');\n const encoded = doc.querySelector(`[${CLIPBOARD_PAYLOAD_ATTR}]`)?.getAttribute(CLIPBOARD_PAYLOAD_ATTR);\n if (!encoded) return null;\n const payload = JSON.parse(decodeURIComponent(atob(encoded))) as ClipboardPayload;\n if (payload && Array.isArray(payload.rows) && Array.isArray(payload.fields)) {\n return payload;\n }\n return null;\n } catch {\n return null;\n }\n}\n","/**\n * Clipboard Paste Logic\n *\n * Pure functions for reading and parsing clipboard data.\n */\n\nimport type { ClipboardConfig } from './types';\n\n/**\n * Parse clipboard text into a 2D array of cell values.\n *\n * Handles:\n * - Quoted values (with escaped double quotes \"\")\n * - Multi-line quoted values (newlines within quotes are preserved)\n * - Custom delimiters and newlines\n * - Empty lines (filtered out only if they contain no data)\n *\n * Implementation: char-by-char state machine for delimiter/newline/quote\n * detection, but cell values are extracted via `slice(start, end)` rather\n * than `+= char` accumulation. The vast majority of cells have no quotes,\n * so the hot path is a single substring slice per cell. Cells that did\n * contain quotes fall back to {@link unquoteCell} for the strip + escape\n * pass.\n *\n * @param text - Raw clipboard text\n * @param config - Clipboard configuration\n * @returns 2D array where each sub-array is a row of cell values\n */\nexport function parseClipboardText(text: string, config: ClipboardConfig): string[][] {\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Normalize Windows / old-Mac line endings.\n const normalized = text.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n const len = normalized.length;\n\n const rows: string[][] = [];\n let currentRow: string[] = [];\n let cellStart = 0;\n let inQuotes = false;\n let cellHasQuotes = false;\n\n const finalizeCell = (endIdx: number): string => {\n const raw = normalized.slice(cellStart, endIdx);\n return cellHasQuotes ? unquoteCell(raw) : raw;\n };\n\n const pushRow = () => {\n // Filter purely-empty rows (single empty cell or all-whitespace cells),\n // matching the original char-by-char implementation's contract.\n if (currentRow.length > 1 || currentRow.some((c) => c.trim() !== '')) {\n rows.push(currentRow);\n }\n currentRow = [];\n };\n\n for (let i = 0; i < len; i++) {\n const char = normalized[i];\n\n if (char === '\"') {\n cellHasQuotes = true;\n if (!inQuotes) {\n inQuotes = true;\n } else if (normalized[i + 1] === '\"') {\n // Escaped quote (\"\") — skip the second quote, stay inside the quoted run.\n i++;\n } else {\n inQuotes = false;\n }\n } else if (char === delimiter && !inQuotes) {\n currentRow.push(finalizeCell(i));\n cellStart = i + 1;\n cellHasQuotes = false;\n } else if (char === newline && !inQuotes) {\n currentRow.push(finalizeCell(i));\n cellStart = i + 1;\n cellHasQuotes = false;\n pushRow();\n }\n }\n\n // Final cell + row.\n currentRow.push(finalizeCell(len));\n pushRow();\n\n return rows;\n}\n\n/**\n * Strip surrounding quotes and unescape `\"\"` → `\"` from a cell value that\n * contained at least one quote character. Only invoked from the slow path\n * of {@link parseClipboardText}.\n */\nfunction unquoteCell(raw: string): string {\n let out = '';\n let inQuotes = false;\n for (let i = 0; i < raw.length; i++) {\n const c = raw[i];\n if (c === '\"') {\n if (!inQuotes) {\n inQuotes = true;\n } else if (raw[i + 1] === '\"') {\n out += '\"';\n i++;\n } else {\n inQuotes = false;\n }\n } else {\n out += c;\n }\n }\n return out;\n}\n\n/**\n * Read text from the system clipboard.\n *\n * Uses the modern Clipboard API. Returns empty string if\n * the API is not available or permission is denied.\n *\n * @returns Promise resolving to clipboard text or empty string\n */\nexport async function readFromClipboard(): Promise<string> {\n try {\n return await navigator.clipboard.readText();\n } catch {\n // Permission denied or API not available\n return '';\n }\n}\n","/**\n * Clipboard Plugin Types\n *\n * Type definitions for clipboard copy/paste functionality.\n */\n\nimport { invalidateAccessorCache } from '../../core/internal/value-accessor';\nimport type { GridElement } from '../../core/plugin/base-plugin';\nimport type { CellEditablePredicate } from '../../core/plugin/types';\n\n/**\n * Custom paste handler function.\n *\n * @param detail - The parsed paste data with target and field info\n * @param grid - The grid element to update\n * @returns `false` to prevent the default paste behavior, or `void`/`true` to allow it\n *\n * @example\n * ```ts\n * // Custom handler that validates before pasting\n * new ClipboardPlugin({\n * pasteHandler: (detail, grid) => {\n * if (!detail.target) return false;\n * // Apply custom validation/transformation...\n * applyPasteData(detail, grid);\n * return false; // We handled it, skip default\n * }\n * })\n * ```\n * @since 0.4.2\n */\nexport type PasteHandler = (detail: PasteDetail, grid: GridElement) => boolean | void;\n\n/**\n * Options for programmatic copy operations.\n *\n * Allows callers to control exactly which columns and rows are included\n * in a copy, independently of the current selection state.\n *\n * @example Copy specific columns from selected rows\n * ```ts\n * const clipboard = grid.getPluginByName('clipboard');\n * // User selected rows 0, 2, 4 via a dialog, chose columns to include\n * const text = await clipboard.copy({\n * rowIndices: [0, 2, 4],\n * columns: ['name', 'email'],\n * includeHeaders: true,\n * });\n * ```\n * @since 1.14.0\n */\nexport interface CopyOptions {\n /** Specific column fields to include. If omitted, uses current selection or all visible columns. */\n columns?: string[];\n /** Specific row indices to copy. If omitted, uses current selection or all rows. */\n rowIndices?: number[];\n /** Include column headers in copied text. Defaults to the plugin config value. */\n includeHeaders?: boolean;\n /** Column delimiter override. Defaults to the plugin config value. */\n delimiter?: string;\n /** Row delimiter override. Defaults to the plugin config value. */\n newline?: string;\n /** Custom cell value processor for this operation. Overrides the plugin config's `processCell`. */\n processCell?: (value: unknown, field: string, row: unknown) => string;\n}\n\n/** Configuration options for the clipboard plugin * @since 0.1.1\n */\nexport interface ClipboardConfig {\n /** Include column headers in copied text (default: false) */\n includeHeaders?: boolean;\n /** Column delimiter character (default: '\\t' for tab) */\n delimiter?: string;\n /** Row delimiter/newline character (default: '\\n') */\n newline?: string;\n /** Wrap string values with quotes (default: false) */\n quoteStrings?: boolean;\n /** Custom cell value processor for copy operations */\n processCell?: (value: unknown, field: string, row: unknown) => string;\n /**\n * When `true`, pasting into a multi-cell selection larger than the clipboard\n * source tiles (repeats) the source to fill the whole selection: a single\n * copied cell fills every selected cell, a 1×2 source fills as\n * `val1, val2, val1, val2`, and a 2×2 block tiles across the selection.\n *\n * Only applies when a bounded (multi-cell) selection is active — it never\n * grows the grid. Defaults to `false` (paste writes only the source extent).\n *\n * @default false\n * @since 3.0.0\n */\n fillSelection?: boolean;\n /**\n * Custom paste handler. By default, the plugin applies pasted data to `grid.rows`\n * starting at the target cell.\n *\n * - Set to a custom function to handle paste yourself\n * - Set to `null` to disable auto-paste (event still fires)\n * - Return `false` from handler to prevent default behavior\n *\n * @default defaultPasteHandler (auto-applies paste data)\n */\n pasteHandler?: PasteHandler | null;\n}\n\n/** Internal state managed by the clipboard plugin */\nexport interface ClipboardState {\n /** The last copied text (for reference/debugging) */\n lastCopied: string | null;\n}\n\n/** Event detail emitted after a successful copy operation * @since 0.1.1\n */\nexport interface CopyDetail {\n /** The text that was copied to clipboard */\n text: string;\n /** Number of rows copied */\n rowCount: number;\n /** Number of columns copied */\n columnCount: number;\n}\n\n/** Target cell coordinates and bounds for paste operations * @since 0.4.2\n */\nexport interface PasteTarget {\n /** Target row index (top-left of paste area) */\n row: number;\n /** Target column index (top-left of paste area) */\n col: number;\n /** Target column field name (for easy data mapping) */\n field: string;\n /**\n * Selection bounds that constrain the paste area.\n * If set, paste data will be clipped to fit within these bounds.\n * If null, paste expands freely from the target cell.\n */\n bounds: {\n /** End row index (inclusive) */\n endRow: number;\n /** End column index (inclusive) */\n endCol: number;\n } | null;\n}\n\n/** Event detail emitted after a paste operation * @since 0.1.1\n */\nexport interface PasteDetail {\n /** Parsed rows from clipboard (2D array of cell values) */\n rows: string[][];\n /** Raw text that was pasted */\n text: string;\n /** The target cell where paste starts (top-left of paste area). Null if no cell is selected. */\n target: PasteTarget | null;\n /**\n * Column fields for each column in the paste range, starting from target.col.\n * Useful for mapping parsed cell values to data fields.\n * Length matches the width of the pasted data (or available columns, whichever is smaller).\n * When `fillSelection` is active, this spans the full selection width so the source can be tiled.\n */\n fields: string[];\n /**\n * When `true`, the paste handler tiles the clipboard source across the full\n * selection bounds (see {@link ClipboardConfig.fillSelection}). Set by the\n * plugin from config; only ever `true` when `target.bounds` is set.\n * @since 3.0.0\n */\n fillSelection?: boolean;\n /**\n * Raw (structured) cell values for a **same-grid** paste, aligned 1:1 with\n * {@link rows} (same dimensions, data rows only). Present only when the paste\n * text exactly matches what this grid last copied — so an object-valued cell\n * (e.g. `{id,name}`, arrays) round-trips losslessly instead of degrading to\n * the copied display text. `undefined` for external/cross-grid pastes, where\n * only the text is available. The default handler prefers `rawRows` over\n * `rows` for values (cloning each so targets don't share references).\n * @since 3.0.0\n */\n rawRows?: unknown[][];\n}\n\n/**\n * Default paste handler that applies pasted data to the grid.\n *\n * This is the built-in handler used when no custom `pasteHandler` is configured.\n *\n * Edits are routed through `grid.updateRows(…, 'api')` so paste participates in\n * the full edit pipeline — dirty tracking, cancelable validation, undo/redo\n * history, and abortion — exactly like an interactive edit. Rows without a\n * resolvable ID (no `getRowId`) fall back to a direct in-place write, which\n * still updates values but does not track them.\n *\n * Behavior:\n * - Single cell selection: paste expands freely, adds new rows if needed\n * - Range/row selection: paste is clipped to fit within selection bounds\n * - Fill-selection: when `detail.fillSelection` is set, the source is tiled\n * (repeated via modulo indexing) to fill the whole selection bounds\n * - Non-editable cells: values are skipped (column alignment preserved).\n * Editability is resolved by the editing plugin via the\n * `getCellEditableResolver` query — including row-conditional `editable`.\n * Without the editing plugin, no cell is editable and paste is a no-op.\n *\n * @param detail - The parsed paste data from clipboard\n * @param grid - The grid element to update\n * @since 0.4.2\n */\nexport function defaultPasteHandler(detail: PasteDetail, grid: GridElement): void {\n const { rows: pastedRows, rawRows, target, fields, fillSelection } = detail;\n\n // No target = nothing to do\n if (!target || pastedRows.length === 0) return;\n\n // Prefer the raw structured values for a same-grid paste (lossless object\n // round-trip); fall back to the parsed text for external/cross-grid pastes.\n const valueRows: unknown[][] = rawRows ?? pastedRows;\n\n const currentRows = grid.rows as Record<string, unknown>[];\n const columns = grid.effectiveConfig.columns ?? [];\n const allFields = columns.map((col) => col.field);\n\n // Editability is owned by the editing plugin (row-conditional included). With\n // no editing plugin loaded, nothing is editable and paste is a no-op.\n const [canEditCell = () => false] = grid.query<CellEditablePredicate>('getCellEditableResolver');\n\n // Accumulate the intended edits per target row (honoring editability, tiling,\n // and selection bounds). Rows beyond current data are only created for an\n // unbounded (single-cell expansion) paste.\n const editsByRow = new Map<number, Record<string, unknown>>();\n let rowCountAfter = currentRows.length;\n\n const addEdit = (rowIndex: number, field: string | undefined, value: unknown, evalRow: Record<string, unknown>) => {\n if (!field || !canEditCell(field, evalRow)) return;\n let changes = editsByRow.get(rowIndex);\n if (!changes) {\n changes = {};\n editsByRow.set(rowIndex, changes);\n }\n // Clone structured values so tiled/multi-target pastes never share a\n // reference (mutating one row's object would otherwise mutate the source\n // and every other target). No-op for primitives (the text path).\n changes[field] = cloneStructured(value);\n };\n\n if (fillSelection && target.bounds) {\n // Fill-selection (tile) mode: repeat the source across the selection bounds\n // using modulo indexing. Never grows the grid. Editability is resolved\n // against the pre-paste row (bounded mode never grows).\n const srcRows = valueRows.length;\n for (let rowIndex = target.row; rowIndex <= target.bounds.endRow; rowIndex++) {\n if (rowIndex >= currentRows.length) break;\n const sourceRow = valueRows[(rowIndex - target.row) % srcRows];\n const srcCols = sourceRow.length;\n if (srcCols === 0) continue;\n const evalRow = currentRows[rowIndex];\n fields.forEach((field, colOffset) => addEdit(rowIndex, field, sourceRow[colOffset % srcCols], evalRow));\n }\n } else {\n const maxPasteRow = target.bounds ? target.bounds.endRow : Infinity;\n valueRows.forEach((rowData, rowOffset) => {\n const targetRowIndex = target.row + rowOffset;\n if (targetRowIndex > maxPasteRow) return;\n if (target.bounds && targetRowIndex >= currentRows.length) return; // bounded: don't grow\n if (!target.bounds && targetRowIndex >= rowCountAfter) rowCountAfter = targetRowIndex + 1;\n // Editability resolved against the pre-paste row (empty for grown rows).\n const evalRow = currentRows[targetRowIndex] ?? {};\n rowData.forEach((cellValue, colOffset) => addEdit(targetRowIndex, fields[colOffset], cellValue, evalRow));\n });\n }\n\n if (editsByRow.size === 0 && rowCountAfter === currentRows.length) return;\n\n // Grow the grid first for unbounded pastes that extend past existing data.\n // New rows start empty; their pasted values are applied in the write phase.\n if (rowCountAfter > currentRows.length) {\n const grown = [...currentRows];\n while (grown.length < rowCountAfter) {\n const emptyRow: Record<string, unknown> = {};\n allFields.forEach((f) => (emptyRow[f] = ''));\n grown.push(emptyRow);\n }\n grid.rows = grown;\n }\n\n const rowsNow = grid.rows as Record<string, unknown>[];\n\n // Route edits through updateRows so paste flows through the edit pipeline\n // (dirty tracking, validation, history, abortion). Rows without a resolvable\n // ID fall back to a direct in-place write.\n const updates: Array<{ id: string; changes: Record<string, unknown> }> = [];\n let directWrote = false;\n for (const [rowIndex, changes] of editsByRow) {\n const row = rowsNow[rowIndex];\n if (!row) continue;\n let id: string | undefined;\n try {\n id = grid.getRowId(row);\n } catch {\n id = undefined;\n }\n if (id == null) {\n // No stable ID → can't route through updateRow; write directly. Invalidate\n // the value-accessor cache per field (row identity is unchanged), matching\n // RowManager.updateRow — otherwise a `column.valueAccessor` display can go\n // stale after paste.\n for (const [field, value] of Object.entries(changes)) {\n (row as Record<string, unknown>)[field] = value;\n invalidateAccessorCache(row as object, field);\n }\n directWrote = true;\n } else {\n updates.push({ id, changes });\n }\n }\n\n if (updates.length > 0) {\n grid.updateRows(updates, 'api');\n } else if (directWrote && rowCountAfter === currentRows.length) {\n // Direct writes only, no structural grow above — trigger a render.\n grid.rows = [...rowsNow];\n }\n}\n\n/**\n * Deep-clone a structured cell value so tiled/multi-target pastes don't share a\n * reference. Primitives (and `null`) are returned as-is. Falls back to the\n * original value if it isn't structured-cloneable.\n * @internal\n */\nexport function cloneStructured(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value;\n try {\n return structuredClone(value);\n } catch {\n return value;\n }\n}\n\n// Module Augmentation - Register plugin name for type-safe getPluginByName()\ndeclare module '../../core/types' {\n interface DataGridEventMap {\n /** Fired after a successful copy operation. Provides the copied text, row count, and column count. @group Clipboard Events */\n copy: CopyDetail;\n /** Fired after a paste operation. Provides parsed rows, target cell, and column fields. @group Clipboard Events */\n paste: PasteDetail;\n }\n\n interface PluginNameMap {\n clipboard: import('./ClipboardPlugin').ClipboardPlugin;\n }\n}\n","/**\n * Clipboard Plugin (Class-based)\n *\n * Provides copy/paste functionality for tbw-grid.\n * Supports Ctrl+C/Cmd+C for copying and Ctrl+V/Cmd+V for pasting.\n *\n * **With Selection plugin:** Copies selected cells/rows/range\n * **Without Selection plugin:** Copies entire grid\n */\n\nimport { resolveCellValue } from '../../core/internal/value-accessor';\nimport {\n BaseGridPlugin,\n type GridElement,\n type PluginDependency,\n type PluginManifest,\n type PluginQuery,\n} from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { formatValueAsText, resolveColumns, resolveRows } from '../shared/data-collection';\nimport { buildClipboardHtml, parseClipboardHtmlPayload } from './clipboard-payload';\nimport { copyToClipboard } from './copy';\nimport { parseClipboardText, readFromClipboard } from './paste';\nimport {\n cloneStructured,\n defaultPasteHandler,\n type ClipboardConfig,\n type CopyDetail,\n type CopyOptions,\n type PasteDetail,\n type PasteTarget,\n} from './types';\n\n/**\n * Clipboard Plugin for tbw-grid\n *\n * Brings familiar copy/cut/paste functionality with full keyboard shortcut support\n * (Ctrl+C, Ctrl+X, Ctrl+V). Handles single cells, multi-cell selections, and integrates\n * seamlessly with Excel and other spreadsheet applications via tab-delimited output.\n *\n * > **Optional Dependency:** Works best with SelectionPlugin for copying/pasting selected\n * > cells. Without SelectionPlugin, copies the entire grid and pastes at row 0, column 0.\n *\n * ## Installation\n *\n * ```ts\n * import { ClipboardPlugin } from '@toolbox-web/grid/plugins/clipboard';\n * ```\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Ctrl+C` / `Cmd+C` | Copy selected cells |\n * | `Ctrl+V` / `Cmd+V` | Paste into selected cells |\n * | `Ctrl+X` / `Cmd+X` | Cut selected cells |\n *\n * ## Paste Behavior by Selection Type\n *\n * | Selection Type | Paste Behavior |\n * |----------------|----------------|\n * | Single cell | Paste expands freely from that cell |\n * | Range selection | Paste is clipped to fit within the selected range |\n * | Row selection | Paste is clipped to the selected rows |\n * | No selection | Paste starts at row 0, column 0 |\n *\n * With `fillSelection: true`, pasting a smaller source into a larger bounded\n * selection tiles the source to fill the whole selection (e.g. one cell fills\n * all selected cells; a 1×2 source fills as `val1, val2, val1, val2`).\n *\n * @example Basic Usage with Excel Compatibility\n * ```ts\n * import '@toolbox-web/grid';\n * import { ClipboardPlugin } from '@toolbox-web/grid/plugins/clipboard';\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [\n * new SelectionPlugin({ mode: 'range' }),\n * new ClipboardPlugin({\n * includeHeaders: true,\n * delimiter: '\\t', // Tab for Excel\n * }),\n * ],\n * };\n * ```\n *\n * @example Custom Paste Handler\n * ```ts\n * new ClipboardPlugin({\n * pasteHandler: (grid, target, data) => {\n * // Validate or transform data before applying\n * console.log('Pasting', data.length, 'rows');\n * return defaultPasteHandler(grid, target, data);\n * },\n * })\n * ```\n *\n * @see {@link ClipboardConfig} for all configuration options\n * @see SelectionPlugin for enhanced copy/paste with selection\n *\n * @internal Extends BaseGridPlugin\n * @since 0.1.1\n */\nexport class ClipboardPlugin extends BaseGridPlugin<ClipboardConfig> {\n /**\n * Plugin manifest — declares queries for inter-plugin communication.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n queries: [{ type: 'clipboard:copy', description: 'Triggers a copy operation and returns the copied text' }],\n };\n\n /**\n * Plugin dependencies - ClipboardPlugin works best with SelectionPlugin.\n *\n * Without SelectionPlugin: copies entire grid, pastes at row 0 col 0.\n * With SelectionPlugin: copies/pastes based on selection.\n */\n /** @internal */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'selection', required: false, reason: 'Enables copy/paste of selected cells instead of entire grid' },\n ];\n\n /** @internal */\n readonly name = 'clipboard';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ClipboardConfig> {\n return {\n includeHeaders: false,\n delimiter: '\\t',\n newline: '\\n',\n quoteStrings: false,\n };\n }\n\n // #region Internal State\n /** The last copied text (for reference/debugging) */\n private lastCopied: { text: string; timestamp: number } | null = null;\n /**\n * Raw (structured) values for the last copy, so a same-grid paste round-trips\n * object cells losslessly instead of degrading to the copied display text.\n * `text` is the exact clipboard text; a paste whose text matches restores\n * `rawRows` (data rows only, aligned with the copied columns). Cleared on\n * detach; a non-matching external paste simply ignores it.\n */\n #internalClipboard: { text: string; rawRows: unknown[][] } | null = null;\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n // Listen for native paste events to get clipboard data synchronously\n // This is more reliable than the async Clipboard API in iframe contexts\n grid.addEventListener('paste', (e) => this.#handleNativePaste(e), {\n signal: this.disconnectSignal,\n });\n }\n\n /** @internal */\n override detach(): void {\n this.lastCopied = null;\n this.#internalClipboard = null;\n }\n\n /** @internal */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'clipboard:copy') {\n this.copy();\n return true;\n }\n return undefined;\n }\n // #endregion\n\n // #region Event Handlers\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean {\n const isCopy = (event.ctrlKey || event.metaKey) && event.key === 'c';\n\n if (isCopy) {\n // Prevent the browser's default copy action so it doesn't overwrite\n // our clipboard write with whatever text is selected in the DOM.\n event.preventDefault();\n this.#handleCopy(event.target as HTMLElement);\n return true;\n }\n\n // For paste, we do NOT return true - let the native paste event fire\n // so we can access clipboardData synchronously in #handleNativePaste\n return false;\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Handle copy operation from keyboard shortcut.\n *\n * For keyboard-triggered copies, respects the current selection or\n * falls back to the focused cell from the DOM.\n */\n #handleCopy(target: HTMLElement): void {\n const selection = this.#getSelection();\n\n // Selection plugin exists but has no ranges → resolve the single active cell.\n // Range mode CLEARS `ranges` on plain keyboard navigation (arrow/Tab without\n // Shift), keeping the focused cell only as the selection `anchor`, and DOM\n // focus is NOT on a cell element after keyboard nav — so prefer the anchor\n // (a visible-column index) and only fall back to the focused DOM cell for\n // the mouse-focus / no-anchor edge. Without the anchor path, keyboard-nav\n // copy silently no-ops.\n if (selection && selection.ranges.length === 0) {\n let row: number | undefined;\n let field: string | undefined;\n if (selection.anchor) {\n row = selection.anchor.row;\n field = this.visibleColumns[selection.anchor.col]?.field;\n } else {\n const focused = this.#getFocusedCellFromDOM(target);\n if (focused) {\n row = focused.row;\n field = this.columns[focused.col]?.field;\n }\n }\n if (row == null || field == null) return;\n this.copy({ rowIndices: [row], columns: [field] });\n return;\n }\n\n // Delegate to the public copy() method (selection or full grid)\n this.copy();\n }\n\n /**\n * Handle native paste event (preferred method - works in iframes).\n * Uses synchronous clipboardData from the native paste event.\n *\n * Flow:\n * 1. Parse clipboard text\n * 2. Build target/fields info from selection\n * 3. Emit 'paste' event (for listeners)\n * 4. Call paste handler (if configured) to apply data to grid\n *\n * Selection behavior:\n * - Single cell: paste starts at cell, expands freely\n * - Range/row: paste is clipped to fit within selection bounds\n * - No selection: paste starts at row 0, col 0\n */\n #handleNativePaste(event: ClipboardEvent): void {\n // If the paste target is an editable form control or contenteditable element\n // (e.g. an active cell editor injected by EditingPlugin, a filter input, a\n // header search box), let the browser deliver the paste to that control\n // natively. Without this guard `event.preventDefault()` below would swallow\n // the user's keystroke and the editor input would never receive the text.\n if (this.#isEditablePasteTarget(event.target)) return;\n\n const text = event.clipboardData?.getData('text/plain');\n if (!text) return;\n\n // Prevent default to avoid pasting into contenteditable elements\n event.preventDefault();\n\n const parsed = parseClipboardText(text, this.config);\n\n // Get target cell from selection via query\n const selection = this.#getSelection();\n const ranges = selection?.ranges ?? [];\n const firstRange = ranges[0];\n\n // Aggregate the bounding box across ALL selected ranges. A multi-cell\n // selection is frequently expressed as several single-cell ranges (clicking\n // or dragging cell-by-cell), so inspecting `ranges[0]` alone would treat it\n // as a single cell and skip fill/clip — pasting into only the first cell.\n // min/max also normalizes reversed (bottom-up / right-to-left) drags.\n let selMinRow = Infinity;\n let selMinCol = Infinity;\n let selMaxRow = -Infinity;\n let selMaxCol = -Infinity;\n for (const r of ranges) {\n selMinRow = Math.min(selMinRow, r.from.row, r.to.row);\n selMaxRow = Math.max(selMaxRow, r.from.row, r.to.row);\n selMinCol = Math.min(selMinCol, r.from.col, r.to.col);\n selMaxCol = Math.max(selMaxCol, r.from.col, r.to.col);\n }\n\n // Determine target cell (top-left of the whole selection). Range mode\n // CLEARS `ranges` on plain keyboard navigation (arrow/Tab without Shift),\n // keeping the focused cell only as the selection `anchor`. So when there are\n // no ranges, fall back to the anchor before defaulting to (0,0) — otherwise\n // \"copy, arrow to another cell, paste\" always lands on cell (0,0) instead of\n // the active cell. Mirrors the focused-cell fallback in #handleCopy.\n const anchor = selection?.anchor ?? null;\n const targetRow = firstRange ? selMinRow : (anchor?.row ?? 0);\n const targetCol = firstRange ? selMinCol : (anchor?.col ?? 0);\n\n // Multi-cell when the selection spans more than one cell in total, whether\n // it's one large range or many single-cell ranges.\n const isMultiCell =\n !!firstRange &&\n (selection?.mode === 'range' || selection?.mode === 'row') &&\n (selMinRow !== selMaxRow || selMinCol !== selMaxCol);\n\n const bounds = isMultiCell ? { endRow: selMaxRow, endCol: selMaxCol } : null;\n // Selection range indices are visible-column indices (from data-col)\n const maxCol = bounds?.endCol ?? this.visibleColumns.length - 1;\n\n // Drop a copied header row so a round-trip paste writes only values into\n // cells (never the header label). Strips when this grid is configured to\n // include headers AND every cell of the first parsed row is one of this\n // grid's own column labels — independent of which column is pasted INTO, so\n // cross-column pastes (e.g. \"Source terminal\" → \"Terminal route\") strip too.\n // External pastes (first row ≠ labels) are left intact.\n this.#dropCopiedHeaderRow(parsed);\n\n // Same-grid round-trip: when the pasted text is exactly what THIS grid last\n // copied, restore the raw structured values (lossless object cells) aligned\n // with the header-stripped text rows — this path preserves exact value types\n // (e.g. Date) since it never serializes. For a cross-grid / cross-window\n // paste the in-memory buffer misses, so fall back to the structured payload\n // embedded in the clipboard's text/html. External pastes have neither and\n // use the parsed text.\n let rawRows: unknown[][] | undefined;\n if (\n this.#internalClipboard &&\n this.#internalClipboard.text === text &&\n this.#internalClipboard.rawRows.length === parsed.length\n ) {\n rawRows = this.#internalClipboard.rawRows;\n } else {\n const payload = parseClipboardHtmlPayload(event.clipboardData?.getData('text/html'));\n if (payload && payload.rows.length === parsed.length) {\n rawRows = payload.rows;\n }\n }\n\n // Build target info\n const column = this.visibleColumns[targetCol];\n const target: PasteTarget | null = column ? { row: targetRow, col: targetCol, field: column.field, bounds } : null;\n\n // Fill-selection tiling only applies to a bounded (multi-cell) selection.\n const fillSelection = this.config.fillSelection === true && bounds !== null;\n\n // Build field list for paste width (constrained by bounds if set). When\n // filling the selection, extend fields across the full selection width so\n // the source can be tiled to fill every selected column; otherwise cap at\n // the clipboard's own width.\n const fields: string[] = [];\n const pasteWidth = parsed[0]?.length ?? 0;\n const fieldWidth = fillSelection ? maxCol - targetCol + 1 : pasteWidth;\n for (let i = 0; i < fieldWidth && targetCol + i <= maxCol; i++) {\n const col = this.visibleColumns[targetCol + i];\n if (col) {\n fields.push(col.field);\n }\n }\n\n const detail: PasteDetail = { rows: parsed, text, target, fields, fillSelection, rawRows };\n\n // Emit the event for any listeners\n this.emit<PasteDetail>('paste', detail);\n\n // Apply paste data using the configured handler (or default)\n this.#applyPasteHandler(detail);\n }\n\n /**\n * Apply the paste handler to update grid data.\n *\n * Uses the configured `pasteHandler`, or the default handler if not specified.\n * Set `pasteHandler: null` in config to disable auto-paste.\n */\n #applyPasteHandler(detail: PasteDetail): void {\n if (!this.grid) return;\n\n const { pasteHandler } = this.config;\n\n // pasteHandler: null means explicitly disabled\n if (pasteHandler === null) return;\n\n // Use custom handler or default\n const handler = pasteHandler ?? defaultPasteHandler;\n handler(detail, this.grid);\n }\n\n /**\n * Drop a header row that this grid itself copied, so a round-trip paste\n * never lands a header label in a data cell.\n *\n * A row copied with `includeHeaders` consists entirely of this grid's own\n * column labels (`header || field`). Detect that by matching every cell of the\n * first parsed row against the full set of column labels — independent of\n * which column the user pastes INTO. This is essential for **cross-column**\n * paste (e.g. copying \"Source terminal\" and pasting into \"Terminal route\"):\n * the copied header carries the SOURCE column's label, which does not equal\n * the target column's label but is still one of this grid's labels. Matching\n * the label set fixes cross-column paste while leaving external pastes (whose\n * first row is data, not labels) untouched.\n *\n * Guarded to avoid eating a legitimate first data row:\n * 1. The grid is configured with `includeHeaders` (it copies headers), and\n * 2. there is more than one row (never strip the only row), and\n * 3. every cell of the first row equals some column's label.\n */\n #dropCopiedHeaderRow(parsed: string[][]): void {\n if (!this.config.includeHeaders) return;\n // Never strip away the only row — that would make paste a no-op.\n if (parsed.length <= 1) return;\n\n const headerRow = parsed[0];\n if (headerRow.length === 0) return;\n\n const labels = this.#columnLabels();\n for (const cell of headerRow) {\n // A cell that isn't a known column label → this is real data, keep the row.\n if (!labels.has(cell)) return;\n }\n\n parsed.shift();\n }\n\n /**\n * Set of this grid's column labels (`header || field`), matching how\n * {@link buildClipboardText} serializes the copied header row. Used to detect\n * a header row this grid copied, regardless of paste position.\n */\n #columnLabels(): Set<string> {\n const labels = new Set<string>();\n for (const col of this.columns) {\n const label = col.header || col.field;\n if (label) labels.add(label);\n }\n return labels;\n }\n\n /**\n * True when the paste event originated from a focusable form control or\n * contenteditable element — i.e. somewhere the user expects the browser's\n * native paste behavior (typing into an editor input). Lets EditingPlugin\n * editors, filter inputs, and other text fields receive paste normally\n * instead of having ClipboardPlugin swallow the event.\n */\n #isEditablePasteTarget(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false;\n if (target.closest('input, textarea, select, [contenteditable=\"\"], [contenteditable=\"true\"]')) {\n return true;\n }\n return false;\n }\n\n /**\n * Get the current selection via Query System.\n * Returns undefined if no selection plugin is loaded or nothing is selected.\n */\n #getSelection(): SelectionQueryResult | undefined {\n const responses = this.grid?.query<SelectionQueryResult>('getSelection');\n return responses?.[0];\n }\n\n /**\n * Resolve columns and rows to include based on options and/or current selection.\n *\n * Priority for columns:\n * 1. `options.columns` (explicit field list)\n * 2. Selection range column bounds (range/cell mode only)\n * 3. All visible non-utility columns\n *\n * Priority for rows:\n * 1. `options.rowIndices` (explicit indices)\n * 2. Selection range row bounds\n * 3. All rows\n */\n #resolveData(options?: CopyOptions): { columns: ColumnConfig[]; rows: Record<string, unknown>[] } {\n const selection = this.#getSelection();\n\n // --- Columns ---\n let columns: ColumnConfig[];\n if (options?.columns) {\n // Caller specified exact fields\n columns = resolveColumns(this.columns, options.columns);\n } else if (selection?.ranges.length && selection.mode !== 'row') {\n // Range/cell selection: restrict to selection column bounds\n // Selection indices are visible-column indices (from data-col)\n const range = selection.ranges[selection.ranges.length - 1];\n const minCol = Math.min(range.from.col, range.to.col);\n const maxCol = Math.max(range.from.col, range.to.col);\n columns = resolveColumns(this.visibleColumns.slice(minCol, maxCol + 1));\n } else {\n // Row selection or no selection: all visible columns\n columns = resolveColumns(this.columns);\n }\n\n // --- Rows ---\n let rows: Record<string, unknown>[];\n if (options?.rowIndices) {\n // Caller specified exact row indices\n rows = resolveRows(this.rows as Record<string, unknown>[], options.rowIndices);\n } else if (selection?.ranges.length) {\n // Selection range: extract contiguous row range\n const range = selection.ranges[selection.ranges.length - 1];\n const minRow = Math.min(range.from.row, range.to.row);\n const maxRow = Math.max(range.from.row, range.to.row);\n rows = [];\n for (let r = minRow; r <= maxRow; r++) {\n const row = this.rows[r] as Record<string, unknown> | undefined;\n if (row) rows.push(row);\n }\n } else {\n // No selection: all rows\n rows = this.rows as Record<string, unknown>[];\n }\n\n return { columns, rows };\n }\n\n /**\n * Build delimited text from resolved columns and rows.\n *\n * When no `processCell` callback is configured, uses \"copy what you see\" logic:\n * 1. Column `format` function (includes typeDefaults merged at config time)\n * 2. DOM cell textContent for columns with custom renderers (visible rows only)\n * 3. Raw value via `formatValueAsText` as final fallback\n */\n #buildText(columns: ColumnConfig[], rows: Record<string, unknown>[], options?: CopyOptions): string {\n return this.#joinDisplayGrid(this.#buildDisplayGrid(columns, rows, options), options);\n }\n\n /**\n * Build the 2D grid of DISPLAY strings for the given columns/rows (the same\n * WYSIWYG text used for `text/plain` and the `text/html` table). Header is\n * included only when configured.\n */\n #buildDisplayGrid(\n columns: ColumnConfig[],\n rows: Record<string, unknown>[],\n options?: CopyOptions,\n ): { header: string[] | null; rows: string[][] } {\n const includeHeaders = options?.includeHeaders ?? this.config.includeHeaders ?? false;\n const processCell = options?.processCell ?? this.config.processCell;\n const header = includeHeaders ? columns.map((c) => c.header || c.field) : null;\n const dataRows = rows.map((row) =>\n columns.map((col) => {\n const value = resolveCellValue(row, col);\n if (processCell) return processCell(value, col.field, row);\n return this.#formatCellAsDisplayed(col, value, row);\n }),\n );\n return { header, rows: dataRows };\n }\n\n /** Join a display grid into delimited text (header row first when present). */\n #joinDisplayGrid(display: { header: string[] | null; rows: string[][] }, options?: CopyOptions): string {\n const delimiter = options?.delimiter ?? this.config.delimiter ?? '\\t';\n const newline = options?.newline ?? this.config.newline ?? '\\n';\n const lines: string[] = [];\n if (display.header) lines.push(display.header.join(delimiter));\n for (const cells of display.rows) lines.push(cells.join(delimiter));\n return lines.join(newline);\n }\n\n /**\n * Format a cell value the way the grid displays it.\n *\n * Priority:\n * 1. Column `format` function (includes typeDefaults applied at config merge time)\n * 2. DOM textContent for columns with a custom renderer (virtualized rows only)\n * 3. Raw value via `formatValueAsText`\n */\n #formatCellAsDisplayed(col: ColumnConfig, value: unknown, row: Record<string, unknown>): string {\n // 1. Column format function (covers col.format and typeDefaults)\n if (col.format) {\n try {\n const formatted = col.format(value, row);\n return formatted == null ? '' : String(formatted);\n } catch {\n // Format failed — fall through to next strategy\n }\n }\n\n // 2. DOM textContent for columns with custom renderers\n if (col.renderer || col.viewRenderer) {\n const text = this.#readCellTextFromDOM(col.field, row);\n if (text != null) return text;\n }\n\n // 3. Fallback to raw value formatting\n return formatValueAsText(value);\n }\n\n /**\n * Try to read a cell's displayed text from the DOM.\n *\n * Only works for rows currently rendered in the virtualized viewport.\n * Returns `null` if the row is not in the DOM (caller should fall back).\n */\n #readCellTextFromDOM(field: string, row: Record<string, unknown>): string | null {\n const host = this.gridElement;\n if (!host) return null;\n\n // Find the row index in the grid's current row array\n const rowIndex = this.rows.indexOf(row);\n if (rowIndex === -1) return null;\n\n // Query the rendered cell by row index and field name\n const cell = host.querySelector<HTMLElement>(`.cell[data-row=\"${rowIndex}\"][data-field=\"${field}\"]`);\n if (!cell) return null;\n\n return cell.textContent?.trim() ?? null;\n }\n\n /**\n * Get focused cell coordinates from DOM.\n * Used as fallback when SelectionPlugin has no selection.\n */\n #getFocusedCellFromDOM(target: HTMLElement): { row: number; col: number } | null {\n // Cells carry `data-field` + `data-row` (see `data-field-cache` was never a\n // real attribute). Body cells have `data-row`; header cells don't, so the\n // `data-row` guard below excludes headers.\n const cell = target.closest('.cell[data-field]') as HTMLElement | null;\n if (!cell) return null;\n\n const field = cell.dataset.field;\n const rowIndexStr = cell.dataset.row;\n if (!field || !rowIndexStr) return null;\n\n const row = parseInt(rowIndexStr, 10);\n if (isNaN(row)) return null;\n\n const col = this.columns.findIndex((c) => c.field === field);\n if (col === -1) return null;\n\n return { row, col };\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the text representation of the current selection (or specified data)\n * without writing to the system clipboard.\n *\n * Useful for previewing what would be copied, or for feeding the text into\n * a custom UI (e.g., a \"copy with column picker\" dialog).\n *\n * @param options - Control which columns/rows to include\n * @returns Delimited text, or empty string if nothing to copy\n *\n * @example Get text for specific columns\n * ```ts\n * const clipboard = grid.getPluginByName('clipboard');\n * const text = clipboard.getSelectionAsText({\n * columns: ['name', 'email'],\n * includeHeaders: true,\n * });\n * console.log(text);\n * // \"Name\\tEmail\\nAlice\\talice@example.com\\n...\"\n * ```\n */\n getSelectionAsText(options?: CopyOptions): string {\n const { columns, rows } = this.#resolveData(options);\n if (columns.length === 0 || rows.length === 0) return '';\n return this.#buildText(columns, rows, options);\n }\n\n /**\n * Copy data to the system clipboard.\n *\n * Without options, copies the current selection (or entire grid if no selection).\n * With options, copies exactly the specified columns and/or rows — ideal for\n * \"copy with column picker\" workflows where the user selects rows first,\n * then chooses which columns to include via a dialog.\n *\n * @param options - Control which columns/rows to include\n * @returns The copied text\n *\n * @example Copy current selection (default)\n * ```ts\n * const clipboard = grid.getPluginByName('clipboard');\n * await clipboard.copy();\n * ```\n *\n * @example Copy specific columns from specific rows\n * ```ts\n * // User selected rows in the grid, then picked columns in a dialog\n * const selectedRowIndices = [0, 3, 7];\n * const chosenColumns = ['name', 'department', 'salary'];\n * await clipboard.copy({\n * rowIndices: selectedRowIndices,\n * columns: chosenColumns,\n * includeHeaders: true,\n * });\n * ```\n */\n async copy(options?: CopyOptions): Promise<string> {\n const { columns, rows } = this.#resolveData(options);\n if (columns.length === 0 || rows.length === 0) {\n return '';\n }\n\n const display = this.#buildDisplayGrid(columns, rows, options);\n const text = this.#joinDisplayGrid(display, options);\n // Snapshot the RAW cell values (same column order as the text) for a\n // structured round-trip. Cloned so later row mutations don't change it.\n const rawRows = rows.map((row) => columns.map((col) => cloneStructured(resolveCellValue(row, col))));\n const fields = columns.map((col) => col.field);\n // Write BOTH text/plain (WYSIWYG for external targets) and text/html (a\n // table that also embeds the structured payload) so a same-app paste — even\n // cross-grid or cross-window — restores object cells losslessly.\n await copyToClipboard(text, buildClipboardHtml(display.header, display.rows, { v: 1, fields, rows: rawRows }));\n this.lastCopied = { text, timestamp: Date.now() };\n this.#internalClipboard = { text, rawRows };\n this.emit<CopyDetail>('copy', {\n text,\n rowCount: rows.length,\n columnCount: columns.length,\n });\n return text;\n }\n\n /**\n * Copy specific rows by index to clipboard.\n *\n * Convenience wrapper around {@link copy} for row-based workflows.\n * Supports non-contiguous row indices (e.g., `[0, 3, 7]`).\n *\n * @param indices - Array of row indices to copy\n * @param options - Additional copy options (columns, headers, etc.)\n * @returns The copied text\n *\n * @example\n * ```ts\n * const clipboard = grid.getPluginByName('clipboard');\n * // Copy only rows 0 and 5, including just name and email columns\n * await clipboard.copyRows([0, 5], { columns: ['name', 'email'] });\n * ```\n */\n async copyRows(indices: number[], options?: Omit<CopyOptions, 'rowIndices'>): Promise<string> {\n if (indices.length === 0) return '';\n return this.copy({ ...options, rowIndices: indices });\n }\n\n /**\n * Read and parse clipboard content.\n * @returns Parsed 2D array of cell values, or null if clipboard is empty\n */\n async paste(): Promise<string[][] | null> {\n const text = await readFromClipboard();\n if (!text) return null;\n return parseClipboardText(text, this.config);\n }\n\n /**\n * Get the last copied text and timestamp.\n * @returns The last copied info or null\n */\n getLastCopied(): { text: string; timestamp: number } | null {\n return this.lastCopied;\n }\n // #endregion\n}\n\n// #region Internal Types\n\n/**\n * Range representation for clipboard operations.\n */\ninterface CellRange {\n from: { row: number; col: number };\n to: { row: number; col: number };\n}\n\n/**\n * Selection result returned by the Query System.\n * Matches the SelectionResult type from SelectionPlugin.\n */\ninterface SelectionQueryResult {\n mode: 'cell' | 'row' | 'range';\n ranges: CellRange[];\n anchor: { row: number; col: number } | null;\n}\n// #endregion\n\n// Re-export types\nexport type { ClipboardConfig, CopyDetail, CopyOptions, PasteDetail } from './types';\n","/**\n * Clipboard Copy Logic\n *\n * Pure functions for copying grid data to clipboard.\n */\n\nimport { CLIPBOARD_FAILED, warnDiagnostic } from '../../core/internal/diagnostics';\nimport { resolveCellValue } from '../../core/internal/value-accessor';\nimport type { ColumnConfig } from '../../core/types';\nimport type { ClipboardConfig } from './types';\n\n/** Parameters for building clipboard text */\nexport interface CopyParams {\n /** All grid rows */\n rows: unknown[];\n /** Column configurations */\n columns: ColumnConfig[];\n /** Selected row indices */\n selectedIndices: Set<number> | number[];\n /** Clipboard configuration */\n config: ClipboardConfig;\n}\n\n/**\n * Format a cell value for clipboard output.\n *\n * Uses custom processCell if provided, otherwise applies default formatting:\n * - null/undefined → empty string\n * - Date → ISO string\n * - Object → JSON string\n * - Other → String conversion with optional quoting\n *\n * Dispatch order is `typeof`-first: string + number cover the vast majority\n * of cell values, so the cheap typeof check short-circuits before the\n * `instanceof Date` probe (which V8 can't fold into a fast path).\n *\n * @param value - The cell value to format\n * @param field - The field name\n * @param row - The full row object\n * @param config - Clipboard configuration\n * @returns Formatted string value\n */\nexport function formatCellValue(value: unknown, field: string, row: unknown, config: ClipboardConfig): string {\n if (config.processCell) {\n return config.processCell(value, field, row);\n }\n\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Hot path: strings.\n if (typeof value === 'string') {\n if (config.quoteStrings || value.includes(delimiter) || value.includes(newline) || value.includes('\"')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n }\n if (typeof value === 'number') return String(value);\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n return String(value);\n}\n\n/**\n * Build clipboard text from selected rows and columns.\n *\n * @param params - Copy parameters including rows, columns, selection, and config\n * @returns Tab-separated (or custom delimiter) text ready for clipboard\n */\nexport function buildClipboardText(params: CopyParams): string {\n const { rows, columns, selectedIndices, config } = params;\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Filter to visible columns (not hidden, not internal __ prefixed)\n const visibleColumns = columns.filter((c) => !c.hidden && !c.field.startsWith('__'));\n\n const lines: string[] = [];\n\n // Add header row if configured\n if (config.includeHeaders) {\n const headerCells = visibleColumns.map((c) => {\n const header = c.header || c.field;\n // Quote headers if they contain special characters\n if (header.includes(delimiter) || header.includes(newline) || header.includes('\"')) {\n return `\"${header.replace(/\"/g, '\"\"')}\"`;\n }\n return header;\n });\n lines.push(headerCells.join(delimiter));\n }\n\n // Convert indices to sorted array\n const indices = selectedIndices instanceof Set ? [...selectedIndices] : selectedIndices;\n const sortedIndices = [...indices].sort((a, b) => a - b);\n\n // Build data rows\n for (const idx of sortedIndices) {\n const row = rows[idx];\n if (!row) continue;\n\n const cells = visibleColumns.map((col) => formatCellValue(resolveCellValue(row, col), col.field, row, config));\n lines.push(cells.join(delimiter));\n }\n\n return lines.join(newline);\n}\n\n/**\n * Copy text to the system clipboard.\n *\n * Uses the modern Clipboard API when available, with fallback\n * to execCommand for older browsers.\n *\n * When `html` is supplied, writes BOTH `text/plain` and `text/html` via\n * `ClipboardItem` so a same-app paste (even cross-grid or cross-window) can\n * recover a structured payload embedded in the HTML, while external targets\n * (Excel, editors) still get the plain text. Silently falls back to text-only\n * when `ClipboardItem` / `clipboard.write` is unavailable or rejects.\n *\n * @param text - The text to copy (`text/plain`)\n * @param html - Optional rich `text/html` representation (may embed a payload)\n * @returns Promise resolving to true if successful, false otherwise\n */\nexport async function copyToClipboard(text: string, html?: string): Promise<boolean> {\n if (html && typeof ClipboardItem !== 'undefined' && navigator.clipboard?.write) {\n try {\n await navigator.clipboard.write([\n new ClipboardItem({\n 'text/plain': new Blob([text], { type: 'text/plain' }),\n 'text/html': new Blob([html], { type: 'text/html' }),\n }),\n ]);\n return true;\n } catch {\n // Fall through to text-only write below.\n }\n }\n try {\n await navigator.clipboard.writeText(text);\n return true;\n } catch (err) {\n warnDiagnostic(CLIPBOARD_FAILED, `Clipboard API failed: ${err}`);\n // Fallback for older browsers or when Clipboard API is not available\n const textarea = document.createElement('textarea');\n textarea.value = text;\n textarea.style.position = 'fixed';\n textarea.style.opacity = '0';\n textarea.style.pointerEvents = 'none';\n document.body.appendChild(textarea);\n textarea.select();\n const success = document.execCommand('copy');\n document.body.removeChild(textarea);\n return success;\n }\n}\n"],"names":["resolveColumns","columns","fields","onlyVisible","result","filter","c","hidden","field","startsWith","utility","length","fieldSet","Set","has","CLIPBOARD_PAYLOAD_ATTR","escapeHtml","s","replace","buildClipboardHtml","header","displayRows","payload","thead","map","h","join","tbody","r","btoa","encodeURIComponent","JSON","stringify","encodePayload","parseClipboardText","text","config","delimiter","newline","normalized","len","rows","currentRow","cellStart","inQuotes","cellHasQuotes","finalizeCell","endIdx","raw","slice","out","i","unquoteCell","pushRow","some","trim","push","char","defaultPasteHandler","detail","grid","pastedRows","rawRows","target","fillSelection","valueRows","currentRows","allFields","effectiveConfig","col","canEditCell","query","editsByRow","Map","rowCountAfter","addEdit","rowIndex","value","evalRow","changes","get","set","cloneStructured","bounds","srcRows","row","endRow","sourceRow","srcCols","forEach","colOffset","maxPasteRow","Infinity","rowData","rowOffset","targetRowIndex","cellValue","size","grown","emptyRow","f","rowsNow","updates","directWrote","id","getRowId","Object","entries","invalidateAccessorCache","updateRows","structuredClone","ClipboardPlugin","BaseGridPlugin","static","queries","type","description","name","required","reason","defaultConfig","includeHeaders","quoteStrings","lastCopied","internalClipboard","attach","super","addEventListener","e","this","handleNativePaste","signal","disconnectSignal","detach","handleQuery","copy","onKeyDown","event","ctrlKey","metaKey","key","preventDefault","handleCopy","selection","getSelection","ranges","anchor","visibleColumns","focused","getFocusedCellFromDOM","rowIndices","isEditablePasteTarget","clipboardData","getData","parsed","firstRange","selMinRow","selMinCol","selMaxRow","selMaxCol","Math","min","from","to","max","targetRow","targetCol","mode","endCol","maxCol","dropCopiedHeaderRow","html","indexOf","doc","DOMParser","parseFromString","encoded","querySelector","getAttribute","parse","decodeURIComponent","atob","Array","isArray","parseClipboardHtmlPayload","column","pasteWidth","fieldWidth","emit","applyPasteHandler","pasteHandler","headerRow","labels","columnLabels","cell","shift","label","add","Element","closest","responses","resolveData","options","range","minCol","indices","sort","a","b","resolveRows","minRow","maxRow","buildText","joinDisplayGrid","buildDisplayGrid","processCell","resolveCellValue","formatCellAsDisplayed","display","lines","cells","format","formatted","String","renderer","viewRenderer","readCellTextFromDOM","Date","toISOString","formatValueAsText","host","gridElement","textContent","dataset","rowIndexStr","parseInt","isNaN","findIndex","getSelectionAsText","async","ClipboardItem","navigator","clipboard","write","Blob","writeText","err","warnDiagnostic","CLIPBOARD_FAILED","textarea","document","createElement","style","position","opacity","pointerEvents","body","appendChild","select","success","execCommand","removeChild","copyToClipboard","v","timestamp","now","rowCount","columnCount","copyRows","paste","readText","readFromClipboard","getLastCopied"],"mappings":"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"}
|