vue2-excel-grid 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -0
- package/dist/vue2-excel-grid.esm.js +5883 -0
- package/dist/vue2-excel-grid.umd.js +6 -0
- package/package.json +74 -0
- package/src/components/ExcelGrid.vue +3552 -0
- package/src/index.js +9 -0
- package/src/utils/clipboard.js +141 -0
- package/src/utils/excelExport.js +144 -0
- package/src/utils/excelExportWorker.worker.js +140 -0
- package/src/utils/excelImport.js +118 -0
- package/src/utils/excelImportCore.js +247 -0
- package/src/utils/excelImportWorker.worker.js +104 -0
- package/src/utils/fillHandle.js +119 -0
- package/src/utils/headerTree.js +108 -0
- package/src/utils/i18n.js +101 -0
- package/src/utils/merge.js +77 -0
- package/src/utils/outline.js +109 -0
- package/src/utils/selection.js +76 -0
- package/src/utils/virtualScroll.js +148 -0
- package/src/utils/workerFactory.js +4 -0
- package/src/utils/workerFactory.webpack.js +7 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* vue2-excel-grid v1.0.0
|
|
3
|
+
* (c) 2026
|
|
4
|
+
* Released under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("xlsx")):"function"==typeof define&&define.amd?define(["exports","xlsx"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Vue2ExcelGrid={},e.XLSX)}(this,function(e,t){"use strict";function n(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach(function(n){if("default"!==n){var i=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,i.get?i:{enumerable:!0,get:function(){return e[n]}})}}),t.default=e,Object.freeze(t)}var i=n(t);function o(e){return{startRow:Math.min(e.startRow,e.endRow),endRow:Math.max(e.startRow,e.endRow),startCol:Math.min(e.startCol,e.endCol),endCol:Math.max(e.startCol,e.endCol)}}function l(e,t,n){const i=o(n);return e>=i.startRow&&e<=i.endRow&&t>=i.startCol&&t<=i.endCol}function s(e,t){return{startRow:e,endRow:e,startCol:t,endCol:t}}function r(e){const t=[];return function e(n){for(const i of n)i.children&&i.children.length?e(i.children):t.push({...i,_flatIndex:t.length})}(e),t}function a(e){return function e(t){let n=0;for(const i of t)n=i.children&&i.children.length?Math.max(n,1+e(i.children)):Math.max(n,1);return n}(e)}function c(e,t){const n=Array.from({length:t},()=>[]);let i=0;return function e(o,l){for(const s of o)if(s.children&&s.children.length){const t=d(s),o=i;n[l].push({column:s,colspan:t,rowspan:1,isGroup:!0,_firstLeafFlatIndex:o}),e(s.children,l+1)}else{const e=t-l;n[l].push({column:{...s,_flatIndex:i},colspan:1,rowspan:e,isGroup:!1,_firstLeafFlatIndex:i}),i++}}(e,0),n}function d(e){return e.children&&0!==e.children.length?e.children.reduce((e,t)=>e+d(t),0):1}function h(e,t,n=[]){let i=0;for(let o=0;o<e;o++)i+=void 0!==n[o]?n[o]:t;return i}const u=["Dushanba","Seshanba","Chorshanba","Payshanba","Juma","Shanba","Yakshanba"],p=["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],g=["January","February","March","April","May","June","July","August","September","October","November","December"],f=/^\d{4}[-/]\d{2}[-/]\d{2}$/;function A(e){return"string"==typeof e&&f.test(e.trim())}function C(e){if("boolean"==typeof e)return!1;const t=String(e).trim();return""!==t&&(!A(t)&&!isNaN(Number(t)))}function w(e,t){const n=function(e){if(0===e.length)return{type:"copy",step:0,base:null};if(1===e.length)return{type:"copy",step:0,base:e[0]};const t=e.map(e=>u.indexOf(e));if(t.every(e=>e>=0))return{type:"weekday_uz",step:e.length>=2&&(t[1]-t[0]+7)%7||1,base:t[0]};const n=e.map(e=>p.indexOf(e));if(n.every(e=>e>=0))return{type:"weekday_en",step:e.length>=2&&(n[1]-n[0]+7)%7||1,base:n[0]};const i=e.map(e=>g.indexOf(e));if(i.every(e=>e>=0))return{type:"month",step:e.length>=2&&(i[1]-i[0]+12)%12||1,base:i[0]};if(e.every(A)){const t=e.map(e=>new Date(e).getTime());if(t.every(e=>!isNaN(e))){const e=t.slice(1).map((e,n)=>e-t[n]);if(e.every(t=>Math.abs(t-e[0])<864e5))return{type:"date",step:e[0],base:t[0]}}}if(e.every(C)){const t=e.map(e=>Number(e)),n=t.slice(1).map((e,n)=>e-t[n]);if(n.every(e=>Math.abs(e-n[0])<1e-10))return{type:"number",step:n[0],base:t[0]}}return{type:"copy",step:0,base:e[0]}}(e),i=e.length,o=[];for(let l=0;l<t;l++){const t=i+l;switch(n.type){case"number":o.push(n.base+n.step*t);break;case"weekday_uz":o.push(u[(n.base+n.step*t)%7]);break;case"weekday_en":o.push(p[(n.base+n.step*t)%7]);break;case"month":o.push(g[(n.base+n.step*t)%12]);break;case"date":{const e=new Date(n.base+n.step*t);o.push(e.toISOString().split("T")[0]);break}default:o.push(e[t%i])}}return o}function m(e,t,n){const i=[];for(let o=n.startRow;o<=n.endRow;o++){const l=[];for(let i=n.startCol;i<=n.endCol;i++){const n=t[i],s=n&&e[o]?e[o][n.key]:"";l.push(void 0!==s?s:"")}i.push(l)}return i}function v(e,t,n,i,o,l="all",s=null,r=null){const a=e.slice(),c=Math.min(n+o.length-1,e.length-1),d=Math.min(i+(o[0]?o[0].length-1:0),t.length-1),h=[];for(let u=n;u<=c;u++){const c=o[u-n];if(!c)continue;let p=null;for(let n=i;n<=d;n++){const o=t[n];if(o&&((s?s(u,n):!1!==o.editable)&&("all"===l||"values"===l))){const t=c[n-i],l=void 0!==t?t:"";if(r&&!r(u,n,l)){h.push({row:u,col:n,value:l});continue}p||(p={...e[u]||{}},a[u]=p),p[o.key]=l}}}return{newData:a,affectedRange:{startRow:n,endRow:c,startCol:i,endCol:d},skipped:h}}function x(e,t){const n=o(e);return t.some(e=>function(e,t){const n=o(e),i=o(t);return!(n.endRow<i.startRow||n.startRow>i.endRow||n.endCol<i.startCol||n.startCol>i.endCol)}(n,o(e)))}function y(e,t,n){return n.find(n=>{const i=o(n);return e>=i.startRow&&e<=i.endRow&&t>=i.startCol&&t<=i.endCol})||null}function R(e,t,n,i){if(!function(e,t){return Number.isInteger(e)&&Number.isInteger(t)&&e<=t&&e>=0}(t,n))return i;return i.find(i=>i.type===e&&i.startIndex===t&&i.endIndex===n)?i:[...i,{type:e,startIndex:t,endIndex:n}]}function b(e,t,n,i){return i.filter(i=>!(i.type===e&&i.startIndex===t&&i.endIndex===n))}function k(e,t){return t.some(t=>t.type===e.type&&t.startIndex===e.startIndex&&t.endIndex===e.endIndex)}function E(e){const t=[];for(const n of e||[])n.children&&n.children.length?t.push(...E(n.children)):n.key&&t.push(n);return t}function S(e,t,n,i,o){for(let l=t;l<=n;l++)for(let t=i;t<=o;t++)if(""!==((e[l]||[])[t]||"").toString().trim())return!0;return!1}function _(e,t,n){return String((e[t]||[])[n]||"").trim().replace(/\n/g," ")}function D(){return new Promise(e=>setTimeout(e,0))}async function M(e,t,n={},o){const l=(e,t)=>o&&o(e,t);l(5,"Fayl o'qilmoqda..."),await D();const s=await e.arrayBuffer();let r;l(20,"Excel tahlil qilinmoqda..."),await D();try{r=i.read(s,{type:"array",cellDates:!0})}catch(e){throw new Error("Excel fayl o'qib bo'lmadi: "+e.message)}const{sheetName:a}=n;let c;if(a){if(c=r.Sheets[a],!c)throw new Error(`Sheet "${a}" topilmadi`)}else{const e=r.SheetNames[0];if(!e)throw new Error("Workbook bo'sh");c=r.Sheets[e]}l(40,"Ma'lumotlar tayyorlanmoqda..."),await D();const d=i.utils.sheet_to_json(c,{header:1,raw:!1,defval:""});if(!d||0===d.length)throw new Error("Sheet bo'sh");const h=i.utils.decode_range(c["!ref"]||"A1:A1"),u=h.s.r,p=h.s.c,g=(c["!merges"]||[]).map(e=>({s:{r:e.s.r-u,c:e.s.c-p},e:{r:e.e.r-u,c:e.e.c-p}})),f=function(e,t){if(!e||0===e.length)return{headerRow:0,headerEndRow:0,dataStartRow:1};const n=e.map(e=>e.filter(e=>""!==e).length);t=t||[];let i,o,l,s=-1;for(let t=0;t<Math.min(e.length,20);t++){const n=e[t].filter(e=>""!==e);if(n.length<2)continue;const i=n.map(e=>{const t=parseInt(String(e).trim(),10);return isNaN(t)?null:t});if(!i.some(e=>null===e)&&i.every((e,t)=>e===t+1)){s=t;break}}if(s>=0){for(l=s+1,o=s-1;o>=0&&0===n[o];)o--;for(o<0&&(o=0),i=o;i>0&&n[i-1]>1;)i--}else{i=0;for(let t=0;t<Math.min(e.length,20);t++)if(n[t]>1){i=t;break}let s=1;for(const e of t)e.s.r===i&&e.e.r>e.s.r&&(s=Math.max(s,e.e.r-e.s.r+1));o=i+s-1,l=o+1}for(;l<e.length&&0===n[l];)l++;return{headerRow:i,headerEndRow:o,dataStartRow:l}}(d,g),A=null!=n.headerRow?n.headerRow:f.headerRow,C=null!=n.headerEndRow?n.headerEndRow:f.headerEndRow,w=null!=n.dataStartRow?n.dataStartRow:f.dataStartRow,m=E(t),v=new Set,{columns:x,colMap:y}=function(e,t,n,i,o,l){let s=-1;for(let i=t;i<=n;i++)for(let t=(e[i]||[]).length-1;t>=0;t--)if(""!==((e[i]||[])[t]||"")){s=Math.max(s,t);break}const r=(i||[]).filter(e=>e.s.r>=t&&e.s.r<=n);for(const e of r)s=Math.max(s,e.e.c);if(s<0)return{columns:[],colMap:[]};const a=[];function c(e,t){const n=o.find(t=>t.title&&t.title.toLowerCase()===e.toLowerCase());if(n)return{key:n.key,title:n.title,width:n.width||100};let i=String(e||"").toLowerCase().replace(/\s+/g,"_").replace(/[^a-z0-9_]/g,"").replace(/^_+|_+$/g,"").replace(/_+/g,"_").replace(/^(\d)/,"col_$1")||`col_${t}`,s=i,r=1;for(;l.has(s)||o.some(e=>e.key===s);)s=`${i}_${r++}`;return l.add(s),{key:s,title:e||s,width:100}}const d=function t(n,i,o,l){if(n>i||o>l)return[];const s=[];let d=o;for(;d<=l;){if(r.some(e=>e.s.r<=n&&e.e.r>=n&&e.s.c<d&&e.e.c>=d)){d++;continue}const o=r.find(e=>e.s.r===n&&e.s.c===d),h=o?_(e,o.s.r,o.s.c):_(e,n,d);if(o&&o.e.c>d){const n=Math.min(o.e.c,l),r=o.e.r+1;if(r<=i&&S(e,r,i,d,n)){const e=t(r,i,d,n);s.push({title:h,children:e})}else for(let e=d;e<=n;e++){const t=c(h,e);s.push(t),a.push({key:t.key,colIndex:e})}d=n+1}else if(o&&o.e.r>=i){const e=c(h,d);s.push(e),a.push({key:e.key,colIndex:d}),d++}else if(o){const n=o.e.r+1;if(n<=i&&S(e,n,i,d,d)){const e=t(n,i,d,d);s.push({title:h,children:e})}else{const e=c(h,d);s.push(e),a.push({key:e.key,colIndex:d})}d++}else if(n===i){const e=c(h,d);s.push(e),a.push({key:e.key,colIndex:d}),d++}else{if(S(e,n+1,i,d,d)){const e=t(n+1,i,d,d);s.push({title:h,children:e})}else{const e=c(h,d);s.push(e),a.push({key:e.key,colIndex:d})}d++}}return s}(t,n,0,s);return{columns:d,colMap:a}}(d,A,C,g,m,v),R=Math.max(d.length-w,0);l(55,`Qatorlar qayta ishlanmoqda (0 / ${R})...`),await D();const b=[];for(let e=w;e<d.length;e+=2e3){const t=Math.min(e+2e3,d.length);for(let n=e;n<t;n++){const e=d[n],t={};for(const{key:n,colIndex:i}of y)t[n]=void 0!==e[i]?e[i]:"";b.push(t)}const n=Math.min(t-w,R);l(55+Math.round(40*n/Math.max(R,1)),`Qatorlar: ${n} / ${R}`),await D()}const k=g.filter(e=>e.s.r>=w).map(e=>({startRow:e.s.r-w,startCol:e.s.c,endRow:e.e.r-w,endCol:e.e.c})).filter(e=>e.startRow>=0&&e.startCol>=0);return l(100,"Tayyor!"),{data:b,columns:x,merges:k}}const H={en:{copy:"Copy",cut:"Cut",paste:"Paste",pasteSpecial:"Paste Special",pasteAll:"All (Value + Format)",pasteValues:"Values Only",pasteFormats:"Formats Only",transpose:"Transpose",mergeCells:"Merge Cells",unmergeCells:"Unmerge Cells",hideRows:"Hide Row(s)",hideColumns:"Hide Column(s)",unhideRows:"Unhide All Rows",unhideColumns:"Unhide All Columns",pinLeft:"Pin Left",pinRight:"Pin Right",unpin:"Unpin",invalidValue:"Invalid value"},ru:{copy:"Копировать",cut:"Вырезать",paste:"Вставить",pasteSpecial:"Специальная вставка",pasteAll:"Всё (значение + формат)",pasteValues:"Только значения",pasteFormats:"Только форматы",transpose:"Транспонировать",mergeCells:"Объединить ячейки",unmergeCells:"Разъединить ячейки",hideRows:"Скрыть строку(и)",hideColumns:"Скрыть столбец(цы)",unhideRows:"Показать все строки",unhideColumns:"Показать все столбцы",pinLeft:"Закрепить слева",pinRight:"Закрепить справа",unpin:"Открепить",invalidValue:"Недопустимое значение"},uz:{copy:"Nusxalash",cut:"Kesib olish",paste:"Joylashtirish",pasteSpecial:"Maxsus joylashtirish",pasteAll:"Hammasi (qiymat + format)",pasteValues:"Faqat qiymatlar",pasteFormats:"Faqat formatlar",transpose:"Transpozitsiya",mergeCells:"Katakchalarni birlashtirish",unmergeCells:"Katakchalarni ajratish",hideRows:"Qatorlarni yashirish",hideColumns:"Ustunlarni yashirish",unhideRows:"Barcha qatorlarni ko'rsatish",unhideColumns:"Barcha ustunlarni ko'rsatish",pinLeft:"Chapga pin qilish",pinRight:"O'ngga pin qilish",unpin:"Pindan olish",invalidValue:"Noto'g'ri qiymat"},"uz-Cyrl":{copy:"Нусхалаш",cut:"Кесиб олиш",paste:"Жойлаштириш",pasteSpecial:"Махсус жойлаштириш",pasteAll:"Ҳаммаси (қиймат + формат)",pasteValues:"Фақат қийматлар",pasteFormats:"Фақат форматлар",transpose:"Транспозиция",mergeCells:"Катакчаларни бирлаштириш",unmergeCells:"Катакчаларни ажратиш",hideRows:"Қаторларни яшириш",hideColumns:"Устунларни яшириш",unhideRows:"Барча қаторларни кўрсатиш",unhideColumns:"Барча устунларни кўрсатиш",pinLeft:"Чапга пин қилиш",pinRight:"Ўнгга пин қилиш",unpin:"Пиндан олиш",invalidValue:"Нотўғри қиймат"}};const I=28,z=28,V=20;var B={name:"ExcelGrid",model:{prop:"data",event:"input"},props:{data:{type:Array,default:()=>[]},columns:{type:Array,default:()=>[]},hiddenColumns:{type:Array,default:()=>[]},hiddenRows:{type:Array,default:()=>[]},merges:{type:Array,default:()=>[]},collapsedGroups:{type:Array,default:()=>[]},height:{type:Number,default:400},width:{type:[Number,String],default:"100%"},beforeDrop:{type:Function,default:null},locale:{type:String,default:"en"},pinnedColumns:{type:Object,default:()=>({})},cellEditable:{type:Function,default:null},cellValidator:{type:Function,default:null},cellValidationMessage:{type:[Function,String],default:null},strictBulkValidation:{type:Boolean,default:!1}},data:()=>({activeCell:{row:0,col:0},selectionRanges:[],isMouseDown:!1,mouseDownRange:null,isCtrlDrag:!1,editingCell:null,editValue:"",editOriginalValue:"",scrollTop:0,scrollLeft:0,headerViewportWidth:null,virtualStartIndex:0,virtualEndIndex:0,virtualOffsetTop:0,totalVirtualHeight:0,colWidths:{},rowHeights:{},importState:{active:!1,percent:0,message:""},exportState:{active:!1},resizing:{active:!1,type:null,index:-1,startX:0,startY:0,startSize:0,currentPos:0},fillDrag:{active:!1,sourceRange:null,currentRow:-1,currentCol:-1},dragMove:{active:!1,sourceRange:null,startX:0,startY:0,targetRow:-1,targetCol:-1,copyMode:!1},clipboardMatrix:null,clipboardRange:null,clipboardMode:null,contextMenu:{visible:!1,x:0,y:0,row:-1,col:-1},selectDropdown:null,invalidCell:null,internalMerges:[],internalGroups:[],internalCollapsedGroups:[],headerDepth:1,headerRows:[],leafCols:[]}),computed:{t(){return e=this.locale,H[e]||H.en;var e},pinnedLeftSet(){const e=new Set;return this.leafCols.forEach(t=>{"left"===this.pinnedColumns[t.key]&&e.add(t._flatIndex)}),e},pinnedRightSet(){const e=new Set;return this.leafCols.forEach(t=>{"right"===this.pinnedColumns[t.key]&&e.add(t._flatIndex)}),e},pinnedLeftCols(){return this.leafCols.filter(e=>this.pinnedLeftSet.has(e._flatIndex))},pinnedRightCols(){return this.leafCols.filter(e=>this.pinnedRightSet.has(e._flatIndex))},hasPinnedLeft(){return this.pinnedLeftSet.size>0},hasPinnedRight(){return this.pinnedRightSet.size>0},pinnedLeftWidth(){let e=0;for(const t of this.pinnedLeftCols)this.isColHidden(t._flatIndex)||(e+=this.getColW(t._flatIndex));return e},pinnedRightWidth(){let e=0;for(const t of this.pinnedRightCols)this.isColHidden(t._flatIndex)||(e+=this.getColW(t._flatIndex));return e},totalRows(){return this.data.length},headerRowHeight:()=>z,cumulativeHeights(){return function(e,t,n={},i=new Set){const o=new Array(e+1);o[0]=0;for(let l=0;l<e;l++){const e=i.has(l)?0:void 0!==n[l]?n[l]:t;o[l+1]=o[l]+e}return o}(this.totalRows,I,this.rowHeights,this.hiddenRowSet)},hiddenColSet(){return new Set(this.hiddenColumns)},hiddenRowSet(){const e=new Set(this.hiddenRows);return function(e,t){const n=new Set;for(const i of e)if("row"===i.type&&k(i,t))for(let e=i.startIndex;e<=i.endIndex;e++)n.add(e);return n}(this.internalGroups,this.internalCollapsedGroups).forEach(t=>e.add(t)),e},hiddenColFromGroupSet(){return function(e,t){const n=new Set;for(const i of e)if("column"===i.type&&k(i,t))for(let e=i.startIndex;e<=i.endIndex;e++)n.add(e);return n}(this.internalGroups,this.internalCollapsedGroups)},effectiveHiddenColSet(){const e=new Set(this.hiddenColSet);return this.hiddenColFromGroupSet.forEach(t=>e.add(t)),e},rowGroups(){return this.internalGroups.filter(e=>"row"===e.type)},colGroups(){return this.internalGroups.filter(e=>"column"===e.type)},visibleRowRange(){const e=[];for(let t=this.virtualStartIndex;t<=this.virtualEndIndex;t++)e.push(t);return e},primaryRange(){return function(e,t){if(!e||0===e.length)return null;return o(e.find(e=>l(t.row,t.col,e))||e[0])}(this.selectionRanges,this.activeCell)},fillHandleVisible(){return this.primaryRange&&!this.editingCell},fillHandleStyle(){if(!this.primaryRange)return{};const e=o(this.primaryRange),t=this.cumulativeHeights[e.endRow+1]||0,n=this.getColLeft(e.endCol+1);return{position:"absolute",width:"6px",height:"6px",background:"#1a73e8",border:"1px solid white",bottom:this.totalVirtualHeight-t+1+"px",right:this.scrollContentWidth-n+1+"px",cursor:"crosshair",zIndex:10}},dropdownStyle(){if(!this.selectDropdown)return{};const{rect:e,options:t}=this.selectDropdown,n=Math.min(32*t.length+8,200);return{position:"fixed",top:(window.innerHeight-e.bottom>=n?e.bottom:e.top-n)+"px",left:e.left+"px",minWidth:Math.max(e.width,120)+"px",zIndex:9999}},invalidTooltipStyle(){if(!this.invalidCell||!this.invalidCell.rect)return{};const{rect:e}=this.invalidCell;return{position:"fixed",top:e.bottom+4+"px",left:e.left+"px",zIndex:9999}},dragGhostStyle(){if(!this.dragMove.active||this.dragMove.targetRow<0)return{};const e=o(this.dragMove.sourceRange),t=e.endRow-e.startRow,n=e.endCol-e.startCol;return{position:"absolute",top:(this.cumulativeHeights[this.dragMove.targetRow]||0)+"px",left:this.getColLeft(this.dragMove.targetCol)+"px",width:this.getRangeWidth(this.dragMove.targetCol,this.dragMove.targetCol+n)+"px",height:this.getRangeHeight(this.dragMove.targetRow,this.dragMove.targetRow+t)+"px",border:"2px dashed #1a73e8",pointerEvents:"none",zIndex:20}},wrapperStyle(){return{width:"number"==typeof this.width?this.width+"px":this.width,height:this.height+"px",position:"relative",outline:"none",userSelect:"none"}},headerAreaStyle:()=>({display:"flex",flexDirection:"row",position:"sticky",top:0,zIndex:30,background:"#f8f9fa"}),cornerStyle(){const e=this.rowGroups.length?V:0;return{width:50+e+"px",minWidth:50+e+"px",height:z*this.headerDepth+"px",background:"#f0f0f0",borderRight:"1px solid #ccc",borderBottom:"1px solid #ccc",boxSizing:"border-box",flexShrink:0}},rowOutlineGutterStyle:()=>({width:"20px",flexShrink:0}),frozenColHeaderStyle(){const e=this.getFrozenWidth();return{width:e+"px",minWidth:e+"px",overflow:"hidden",flexShrink:0}},scrollColHeaderStyle(){return null!=this.headerViewportWidth?{flex:"0 0 "+this.headerViewportWidth+"px",overflow:"hidden"}:{flex:1,overflow:"hidden"}},bodyAreaStyle:()=>({display:"flex",flexDirection:"row",flex:1,position:"relative"}),rowHeaderColStyle:()=>({width:"50px",minWidth:"50px",flexShrink:0,display:"flex",flexDirection:"column",position:"sticky",left:0,zIndex:20,background:"#f0f0f0"}),rowOutlineColStyle:()=>({width:"20px",minWidth:"20px",flexShrink:0,position:"relative",background:"#fafafa"}),scrollBodyStyle:()=>({flex:1,overflow:"auto",position:"relative"}),frozenColBodyStyle(){return{position:"sticky",left:0,width:this.getFrozenWidth()+"px",zIndex:10,background:"white",overflow:"hidden"}},mainCellsStyle(){return{position:"absolute",left:this.getFrozenWidth()+"px",top:0,bottom:0}},scrollContentWidth(){let e=0;for(let t=0;t<this.leafCols.length;t++)this.isColHidden(t)||this.pinnedRightSet.has(t)||(e+=this.getColW(t));return this.hasPinnedRight&&(e+=this.pinnedRightWidth),e},scrollableContentWidth(){let e=0;for(let t=0;t<this.leafCols.length;t++)!this.isColHidden(t)&&this.isColumnScrollable(t)&&(e+=this.getColW(t));return e},pinnedRightBodyStyle(){return{position:"sticky",right:0,width:this.pinnedRightWidth+"px",zIndex:10,background:"white",flexShrink:0}},pinnedRightHeaderStyle(){return{width:this.pinnedRightWidth+"px",minWidth:this.pinnedRightWidth+"px",overflow:"hidden",flexShrink:0,borderLeft:"1px solid #ccc"}},scrollRowHeadersStyle:()=>({flex:1,overflow:"hidden",position:"relative"}),colOutlineRowStyle:()=>({position:"absolute",top:"-20px",left:0,right:0,height:"20px"}),resizeLineStyle(){return this.resizing.active?"col"===this.resizing.type?{position:"fixed",top:0,left:this.resizing.currentPos+"px",width:"2px",height:"100%",background:"#1a73e8",pointerEvents:"none",zIndex:9999}:{position:"fixed",left:0,top:this.resizing.currentPos+"px",height:"2px",width:"100%",background:"#1a73e8",pointerEvents:"none",zIndex:9999}:{}}},watch:{columns:{immediate:!0,handler(e){this.leafCols=r(e),this.headerDepth=a(e),this.headerRows=c(e,this.headerDepth),this.leafCols.forEach(e=>{void 0===this.colWidths[e._flatIndex]&&(this.colWidths[e._flatIndex]=e.width||100)})}},merges:{immediate:!0,handler(e){this.internalMerges=e?[...e]:[]}},collapsedGroups:{immediate:!0,handler(e){this.internalCollapsedGroups=e?[...e]:[]}},data:{immediate:!0,handler(e){this._dataRaw=e||[],this.updateVirtualScroll(),this.$forceUpdate()}},totalRows(){this.updateVirtualScroll()},hiddenRowSet(){this.updateVirtualScroll()},totalVirtualHeight(){this.$nextTick(this.syncHeaderViewportWidth)}},created(){this._dataRaw=this.data||[],this._cellStyleCache=Object.create(null),this._rowStyleCache=Object.create(null),this._rowHeaderStyleCache=Object.create(null),this._scrollRAF=null,this._resizeRAF=null,this._history=[],this._historyIndex=-1,this._historySkip=!1},mounted(){this.updateVirtualScroll(),this.$nextTick(this.syncHeaderViewportWidth),document.addEventListener("mousemove",this.onGlobalMouseMove),document.addEventListener("mouseup",this.onGlobalMouseUp),document.addEventListener("keydown",this.onGlobalKeyDown),document.addEventListener("mousedown",this._onDocMouseDownForDropdown,!0),window.addEventListener("resize",this.syncHeaderViewportWidth)},beforeDestroy(){document.removeEventListener("mousemove",this.onGlobalMouseMove),document.removeEventListener("mouseup",this.onGlobalMouseUp),document.removeEventListener("keydown",this.onGlobalKeyDown),document.removeEventListener("mousedown",this._onDocMouseDownForDropdown,!0),window.removeEventListener("resize",this.syncHeaderViewportWidth)},methods:{updateVirtualScroll(){const e=this.height-z*this.headerDepth,t=function(e,t,n,i,o=5){if(0===n)return{startIndex:0,endIndex:-1,offsetTop:0,totalHeight:0};const l=i[n],s=e+t;let r=0,a=n-1;for(;r<a;){const t=r+a>>1;i[t+1]<=e?r=t+1:a=t}let c=r,d=c;for(;d<n-1&&i[d+1]<s;)d++;return c=Math.max(0,c-o),d=Math.min(n-1,d+o),{startIndex:c,endIndex:d,offsetTop:i[c],totalHeight:l}}(this.scrollTop,Math.max(e,100),this.totalRows,this.cumulativeHeights);this.virtualStartIndex=t.startIndex,this.virtualEndIndex=t.endIndex,this.virtualOffsetTop=t.offsetTop,this.totalVirtualHeight=t.totalHeight},onBodyScroll(e){const t=e.target;this.$refs.scrollColHeader&&(this.$refs.scrollColHeader.scrollLeft=t.scrollLeft),this.$refs.scrollRowHeaders&&(this.$refs.scrollRowHeaders.scrollTop=t.scrollTop),this._scrollRAF||(this._scrollRAF=requestAnimationFrame(()=>{this._scrollRAF=null,this.scrollTop=t.scrollTop,this.scrollLeft=t.scrollLeft,this.updateVirtualScroll()}))},onScroll(e){},syncHeaderViewportWidth(){const e=this.$refs.scrollBody;e&&this.headerViewportWidth!==e.clientWidth&&(this.headerViewportWidth=e.clientWidth)},isRowHidden(e){return this.hiddenRowSet.has(e)},isColHidden(e){return this.effectiveHiddenColSet.has(e)},getColW(e){return void 0!==this.colWidths[e]?this.colWidths[e]:this.leafCols[e]&&this.leafCols[e].width||100},getRowH(e){return this.isRowHidden(e)?0:this.rowHeights[e]||I},getColLeft(e){let t=0;for(let n=0;n<e&&n<this.leafCols.length;n++)this.isColHidden(n)||(t+=this.getColW(n));return t},getFrozenWidth(){return this.hasPinnedLeft?this.pinnedLeftWidth:0},isColumnPinnedLeft(e){return this.pinnedLeftSet.has(e)},isColumnPinnedRight(e){return this.pinnedRightSet.has(e)},isColumnScrollable(e){return!this.isColumnPinnedLeft(e)&&!this.isColumnPinnedRight(e)},getZoneColLeft(e,t){let n=0;if("left"===t)for(const t of this.pinnedLeftCols){if(t._flatIndex===e)break;this.isColHidden(t._flatIndex)||(n+=this.getColW(t._flatIndex))}else if("right"===t)for(const t of this.pinnedRightCols){if(t._flatIndex===e)break;this.isColHidden(t._flatIndex)||(n+=this.getColW(t._flatIndex))}else for(let t=0;t<this.leafCols.length;t++){const i=this.leafCols[t];if(i._flatIndex===e)break;!this.isColHidden(i._flatIndex)&&this.isColumnScrollable(i._flatIndex)&&(n+=this.getColW(i._flatIndex))}return n},isCellInZone(e,t,n){if(e.isGroup){const t=e._firstLeafFlatIndex,i=t+e.colspan-1;for(let e=t;e<=i;e++){if(this.isColHidden(e))continue;if((this.isColumnPinnedLeft(e)?"left":this.isColumnPinnedRight(e)?"right":"scroll")===n)return!0}return!1}const i=e.column._flatIndex;if(this.isColHidden(i))return!1;return(this.isColumnPinnedLeft(i)?"left":this.isColumnPinnedRight(i)?"right":"scroll")===n},getHeaderCellStyleForZone(e,t,n){let i=e._firstLeafFlatIndex;if(e.isGroup){const t=i+e.colspan-1;for(let e=i;e<=t;e++){if(this.isColHidden(e))continue;if((this.isColumnPinnedLeft(e)?"left":this.isColumnPinnedRight(e)?"right":"scroll")===n){i=e;break}}}const o=this.getZoneColLeft(i,n);let l=0;if(e.isGroup){const t=e._firstLeafFlatIndex,i=t+e.colspan-1;for(let e=t;e<=i;e++){if(this.isColHidden(e))continue;(this.isColumnPinnedLeft(e)?"left":this.isColumnPinnedRight(e)?"right":"scroll")===n&&(l+=this.getColW(e))}}else l=this.getColW(e.column._flatIndex);return{position:"absolute",top:z*t+"px",left:o+"px",width:l+"px",height:z*e.rowspan+"px",display:"flex",alignItems:"center",justifyContent:"center",boxSizing:"border-box",borderRight:"1px solid #ccc",borderBottom:"1px solid #ccc",background:"#f0f0f0",fontSize:"12px",fontWeight:600,overflow:"hidden",whiteSpace:"nowrap",userSelect:"none",cursor:"pointer"}},getRangeWidth(e,t){let n=0;for(let i=e;i<=t&&i<this.leafCols.length;i++)this.isColHidden(i)||(n+=this.getColW(i));return n},getRangeHeight(e,t){let n=0;for(let i=e;i<=t&&i<this.totalRows;i++)this.isRowHidden(i)||(n+=this.getRowH(i));return n},getCellValue(e,t){const n=this.leafCols[t];if(!n)return"";const i=this._dataRaw[e];if(!i)return"";const o=i[n.key];return void 0!==o?o:""},isEditing(e,t){return this.editingCell&&this.editingCell.row===e&&this.editingCell.col===t},isMergedAway(e,t){const n=y(e,t,this.internalMerges);if(!n)return!1;const i=o(n);return!(i.startRow===e&&i.startCol===t)},getMergeSpan(e,t){const n=y(e,t,this.internalMerges);if(!n)return{rowspan:1,colspan:1};const i=o(n);return{rowspan:i.endRow-i.startRow+1,colspan:i.endCol-i.startCol+1}},isSelected(e,t){return n=e,i=t,this.selectionRanges.some(e=>l(n,i,e));var n,i},isActiveCell(e,t){return this.activeCell.row===e&&this.activeCell.col===t},isCopyHighlighted(e,t){return!(!this.clipboardRange||"copy"!==this.clipboardMode)&&l(e,t,this.clipboardRange)},getRowStyle(e){const t=this.getRowH(e),n=t;if(this._rowStyleCache[n])return this._rowStyleCache[n];const i={height:t+"px",minHeight:t+"px"};return this._rowStyleCache[n]=i,i},getRowHeaderStyle(e){const t=this.getRowH(e),n=t;if(this._rowHeaderStyleCache[n])return this._rowHeaderStyleCache[n];const i={height:t+"px"};return this._rowHeaderStyleCache[n]=i,i},getCellStyle(e,t){const n=y(e,t,this.internalMerges),i=n?o(n):null;let l=this.getColW(t),s=this.getRowH(e);i&&i.startRow===e&&i.startCol===t&&(l=this.getRangeWidth(i.startCol,i.endCol),s=this.getRangeHeight(i.startRow,i.endRow));const r=this.isColumnPinnedLeft(t)?"left":this.isColumnPinnedRight(t)?"right":"scroll",a=this.getZoneColLeft(t,r),c=l+"_"+s+"_"+a;if(this._cellStyleCache[c])return this._cellStyleCache[c];const d={position:"absolute",left:a+"px",top:"0px",width:l+"px",minWidth:l+"px",height:s+"px"};return this._cellStyleCache[c]=d,d},getCellClasses(e,t){const n=this.leafCols[t];return{"eg-cell-active":this.isActiveCell(e,t),"eg-cell-selected":this.isSelected(e,t)&&!this.isActiveCell(e,t),"eg-cell-fill-preview":this.isInFillPreview(e,t),"eg-cell-copy":this.isCopyHighlighted(e,t),"eg-cell-merged-hidden":this.isMergedAway(e,t),"eg-cell-select":!(!n||"select"!==n.type),"eg-cell-checkbox":!(!n||"checkbox"!==n.type),"eg-cell-readonly":!this.isCellEditable(e,t),"eg-cell-invalid":!(!this.invalidCell||this.invalidCell.row!==e||this.invalidCell.col!==t)}},isInFillPreview(e,t){if(!this.fillDrag.active||!this.fillDrag.sourceRange)return!1;const n=this.fillDrag.sourceRange,i=this.fillDrag.currentRow,o=this.fillDrag.currentCol;return i>n.endRow?e>n.endRow&&e<=i&&t>=n.startCol&&t<=n.endCol:o>n.endCol&&(t>n.endCol&&t<=o&&e>=n.startRow&&e<=n.endRow)},getHeaderCellStyle(e,t,n){const i=n?"left":"scroll";let o=e._firstLeafFlatIndex;if(e.isGroup){const t=o+e.colspan-1;for(let e=o;e<=t;e++)if(!this.isColHidden(e)&&(n?this.isColumnPinnedLeft(e):this.isColumnScrollable(e))){o=e;break}}const l=this.getZoneColLeft(o,i);let s=0;if(e.isGroup){const t=e._firstLeafFlatIndex,i=t+e.colspan-1;for(let e=t;e<=i;e++){if(this.isColHidden(e))continue;(n?this.isColumnPinnedLeft(e):this.isColumnScrollable(e))&&(s+=this.getColW(e))}}else s=this.getColW(e.column._flatIndex);return{position:"absolute",top:z*t+"px",left:l+"px",width:s+"px",height:z*e.rowspan+"px",display:"flex",alignItems:"center",justifyContent:"center",boxSizing:"border-box",borderRight:"1px solid #ccc",borderBottom:"1px solid #ccc",background:"#f0f0f0",fontSize:"12px",fontWeight:600,overflow:"hidden",whiteSpace:"nowrap",userSelect:"none",cursor:"pointer"}},isCellVisible(e,t,n){if(e.isGroup){const t=e._firstLeafFlatIndex,i=t+e.colspan-1;for(let e=t;e<=i;e++){if(this.isColHidden(e))continue;if(n?this.isColumnPinnedLeft(e):this.isColumnScrollable(e))return!0}return!1}const i=e.column._flatIndex;return!this.isColHidden(i)&&(n?this.isColumnPinnedLeft(i):this.isColumnScrollable(i))},getRowGroupBtnStyle(e){return{position:"absolute",top:h(e.startIndex,I,this.rowHeights)-this.scrollTop+"px",left:"2px",width:"16px",height:"16px",lineHeight:"16px",textAlign:"center",fontSize:"10px",cursor:"pointer",background:"#e8eaf6",border:"1px solid #9fa8da",borderRadius:"2px",zIndex:5}},getColGroupBtnStyle(e){return{position:"absolute",left:this.getColLeft(e.startIndex)-this.scrollLeft+"px",top:"2px",width:"16px",height:"16px",lineHeight:"16px",textAlign:"center",fontSize:"10px",cursor:"pointer",background:"#e8eaf6",border:"1px solid #9fa8da",borderRadius:"2px"}},selectCell(e,t,n=!1){e<0||e>=this.totalRows||t<0||t>=this.leafCols.length||(this.activeCell={row:e,col:t},n?this.selectionRanges.push(s(e,t)):this.selectionRanges=[s(e,t)],this.emitSelectionChange())},selectRange(e,t,n,i,o=!0){const l={startRow:e,startCol:t,endRow:n,endCol:i};o?this.selectionRanges=[l]:this.selectionRanges.push(l),this.emitSelectionChange()},selectRow(e){this.activeCell={row:e,col:0},this.selectionRanges=[{startRow:e,endRow:e,startCol:0,endCol:this.leafCols.length-1}],this.emitSelectionChange()},selectColumn(e){this.activeCell={row:0,col:e},this.selectionRanges=[{startRow:0,endRow:this.totalRows-1,startCol:e,endCol:e}],this.emitSelectionChange()},selectAll(){this.activeCell={row:0,col:0},this.selectionRanges=[{startRow:0,endRow:this.totalRows-1,startCol:0,endCol:this.leafCols.length-1}],this.emitSelectionChange()},emitSelectionChange(){this.$emit("selection-change",{activeCell:{...this.activeCell},ranges:this.selectionRanges.map(e=>o(e))})},onCellMouseDown(e,t,n){if(this.invalidCell=null,this.editingCell&&this.commitEdit(),this.hideContextMenu(),this.$refs.wrapper&&this.$refs.wrapper.focus(),e.ctrlKey||e.metaKey)this.selectDropdown&&this.closeSelectDropdown(),this.activeCell={row:t,col:n},this.mouseDownRange={startRow:t,startCol:n,endRow:t,endCol:n},this.selectionRanges.push(this.mouseDownRange),this.isCtrlDrag=!0;else if(e.shiftKey&&this.selectionRanges.length>0)this.selectDropdown&&this.closeSelectDropdown(),this.selectionRanges=[(i=this.activeCell,o={row:t,col:n},{startRow:i.row,startCol:i.col,endRow:o.row,endCol:o.col})];else{const i=this.activeCell.row===t&&this.activeCell.col===n;this.activeCell={row:t,col:n},this.mouseDownRange={startRow:t,startCol:n,endRow:t,endCol:n},this.selectionRanges=[this.mouseDownRange],this.isCtrlDrag=!1;const o=this.leafCols[n];if(i&&o&&"select"===o.type&&this.isCellEditable(t,n))return this.openSelectDropdown(t,n,e),e.preventDefault(),this.isMouseDown=!0,void this.emitSelectionChange();this.selectDropdown&&this.closeSelectDropdown()}var i,o;this.isMouseDown=!0,this.emitSelectionChange(),e.preventDefault()},onCellMouseOver(e,t,n){if(this.isMouseDown&&!this.fillDrag.active)return this.dragMove.active?(this.dragMove.targetRow=t,void(this.dragMove.targetCol=n)):void(this.mouseDownRange&&(this.mouseDownRange.endRow=t,this.mouseDownRange.endCol=n,this.emitSelectionChange()))},onCellMouseUp(){this.isMouseDown=!1},onCellDblClick(e,t){const n=this.leafCols[t];this.isCellEditable(e,t)&&(n&&"select"===n.type?this.openSelectDropdownForActive():n&&"checkbox"===n.type||this.startEditing(e,t,null,!0))},onRowHeaderMouseDown(e,t){const n=this.leafCols.length-1,i={startRow:t,endRow:t,startCol:0,endCol:n};if(e.ctrlKey||e.metaKey)if(e.shiftKey&&this.selectionRanges.length>0){const e=Math.min(this.activeCell.row,t),i=Math.max(this.activeCell.row,t);this.selectionRanges=[...this.selectionRanges,{startRow:e,endRow:i,startCol:0,endCol:n}]}else{const e=this.selectionRanges.findIndex(e=>e.startRow===t&&e.endRow===t&&0===e.startCol&&e.endCol===n);if(e>=0){const t=this.selectionRanges.filter((t,n)=>n!==e);this.selectionRanges=t.length?t:[i]}else this.activeCell={row:t,col:0},this.selectionRanges=[...this.selectionRanges,i]}else if(e.shiftKey&&this.selectionRanges.length>0){const e=Math.min(this.activeCell.row,t),i=Math.max(this.activeCell.row,t);this.selectionRanges=[{startRow:e,endRow:i,startCol:0,endCol:n}]}else this.activeCell={row:t,col:0},this.selectionRanges=[i];this.mouseDownRange=this.selectionRanges[this.selectionRanges.length-1],this.isMouseDown=!0,this.emitSelectionChange(),e.preventDefault()},onColHeaderMouseDown(e,t){if(t.isGroup)return;const n=t.column._flatIndex;if(e.shiftKey&&this.selectionRanges.length>0){const e=Math.min(this.activeCell.col,n),t=Math.max(this.activeCell.col,n);this.selectionRanges=[{startRow:0,endRow:this.totalRows-1,startCol:e,endCol:t}]}else this.activeCell={row:0,col:n},this.selectionRanges=[{startRow:0,endRow:this.totalRows-1,startCol:n,endCol:n}];this.isMouseDown=!0,this.mouseDownRange=this.selectionRanges[0],this.emitSelectionChange(),e.preventDefault()},onColHeaderDblClick(e,t){t.isGroup||this.autoFitColumn(t.column._flatIndex)},onColHeaderContextMenu(e,t){t.isGroup||(this.contextMenu={visible:!0,x:e.clientX,y:e.clientY,row:-1,col:t.column._flatIndex})},onRowContextMenu(e,t){this.contextMenu={visible:!0,x:e.clientX,y:e.clientY,row:t,col:-1}},onCellContextMenu(e,t,n){this.isSelected(t,n)||this.selectCell(t,n),this.contextMenu={visible:!0,x:e.clientX,y:e.clientY,row:t,col:n}},hideContextMenu(){this.contextMenu.visible=!1},onKeyDown(e){if(this.selectDropdown){const t=e.key;if("Escape"===t)return e.preventDefault(),void this.closeSelectDropdown();if("Enter"===t||" "===t){e.preventDefault();const t=this.selectDropdown.highlight;return void(t>=0&&this.selectDropdown.options[t]?this.commitSelectValue(this.selectDropdown.options[t].value):this.closeSelectDropdown())}return"ArrowDown"===t?(e.preventDefault(),void(this.selectDropdown.highlight=Math.min(this.selectDropdown.highlight+1,this.selectDropdown.options.length-1))):"ArrowUp"===t?(e.preventDefault(),void(this.selectDropdown.highlight=Math.max(this.selectDropdown.highlight-1,0))):void 0}if(this.editingCell)return;const t=e.key;if((e.ctrlKey||e.metaKey)&&"z"===t&&!e.shiftKey)return e.preventDefault(),void this.undo();if((e.ctrlKey||e.metaKey)&&("y"===t||"z"===t&&e.shiftKey))return e.preventDefault(),void this.redo();if("a"===t&&(e.ctrlKey||e.metaKey))return e.preventDefault(),void this.selectAll();if("c"===t&&(e.ctrlKey||e.metaKey))return e.preventDefault(),void this.copySelection();if("x"===t&&(e.ctrlKey||e.metaKey))return e.preventDefault(),void this.cutSelection();if("v"===t&&(e.ctrlKey||e.metaKey))return e.preventDefault(),void this.pasteAt(this.activeCell.row,this.activeCell.col);if("Delete"===t||"Backspace"===t)return e.preventDefault(),void this.clearSelection();if("F2"===t){e.preventDefault();const t=this.leafCols[this.activeCell.col];return void(t&&"select"===t.type?this.openSelectDropdownForActive():t&&"checkbox"===t.type?this.toggleCheckbox(this.activeCell.row,this.activeCell.col):this.startEditing(this.activeCell.row,this.activeCell.col,null,!0))}if(" "===t){const t=this.leafCols[this.activeCell.col];if(t&&"checkbox"===t.type)return e.preventDefault(),void this.toggleCheckbox(this.activeCell.row,this.activeCell.col)}if("Escape"===t)return this.clipboardRange=null,this.clipboardMatrix=null,void(this.clipboardMode=null);if("Tab"===t)return e.preventDefault(),void this.moveActive(0,e.shiftKey?-1:1,e);if("Enter"===t)return e.preventDefault(),void this.moveActive(e.shiftKey?-1:1,0,e);const n={ArrowUp:[-1,0],ArrowDown:[1,0],ArrowLeft:[0,-1],ArrowRight:[0,1]};if(n[t]){e.preventDefault();const[i,l]=n[t];if(e.shiftKey){const e=this.primaryRange||s(this.activeCell.row,this.activeCell.col),t=o(e);-1===i&&(e.startRow=Math.max(0,t.startRow-1)),1===i&&(e.endRow=Math.min(this.totalRows-1,t.endRow+1)),-1===l&&(e.startCol=Math.max(0,t.startCol-1)),1===l&&(e.endCol=Math.min(this.leafCols.length-1,t.endCol+1)),this.selectionRanges=[e],this.emitSelectionChange()}else this.moveActive(i,l,e);return}if("Home"===t)return e.preventDefault(),void(e.ctrlKey?this.selectCell(0,0):this.selectCell(this.activeCell.row,0));if("End"===t)return e.preventDefault(),void(e.ctrlKey?this.selectCell(this.totalRows-1,this.leafCols.length-1):this.selectCell(this.activeCell.row,this.leafCols.length-1));if("PageUp"===t){e.preventDefault();const t=Math.floor((this.height-z*this.headerDepth)/I);return void this.moveActive(-t,0,e)}if("PageDown"===t){e.preventDefault();const t=Math.floor((this.height-z*this.headerDepth)/I);return void this.moveActive(t,0,e)}},onKeyPress(e){if(!this.editingCell&&!this.selectDropdown&&!(e.ctrlKey||e.metaKey||e.altKey)&&e.key&&1===e.key.length){const t=this.leafCols[this.activeCell.col];if(t&&"select"===t.type)return e.preventDefault(),void this.openSelectDropdownForActive();if(t&&"checkbox"===t.type)return void e.preventDefault();this.startEditing(this.activeCell.row,this.activeCell.col,e.key,!1)}},onGlobalKeyDown(e){},moveActive(e,t,n){let i=this.activeCell.row+e,l=this.activeCell.col+t;for(n&&"Tab"===n.key&&(l>=this.leafCols.length&&(l=0,i++),l<0&&(l=this.leafCols.length-1,i--)),i=Math.max(0,Math.min(this.totalRows-1,i)),l=Math.max(0,Math.min(this.leafCols.length-1,l));this.isColHidden(l)&&l>0&&l<this.leafCols.length-1;)l+=t>=0?1:-1;for(;this.isRowHidden(i)&&i>0&&i<this.totalRows-1;)i+=e>=0?1:-1;const r=function(e,t,n){const i=y(e,t,n);if(!i)return{row:e,col:t};const l=o(i);return{row:l.startRow,col:l.startCol}}(i,l,this.internalMerges);i=r.row,l=r.col,this.activeCell={row:i,col:l},this.selectionRanges=[s(i,l)],this.emitSelectionChange(),this.scrollToCell(i,l)},scrollToCell(e,t){const n=this.$refs.scrollBody;if(!n)return;const i=h(e,I,this.rowHeights),o=i+this.getRowH(e),l=this.getColLeft(t)-this.getFrozenWidth(),s=l+this.getColW(t);i<n.scrollTop&&(n.scrollTop=i),o>n.scrollTop+n.clientHeight&&(n.scrollTop=o-n.clientHeight),l<n.scrollLeft&&(n.scrollLeft=l),s>n.scrollLeft+n.clientWidth&&(n.scrollLeft=s-n.clientWidth)},startEditing(e,t,n=null,i=!1){const o=this.leafCols[t];o&&this.isCellEditable(e,t)&&(e<0||e>=this.totalRows||("select"!==o.type?"checkbox"!==o.type&&(this.invalidCell=null,this.editingCell={row:e,col:t},this.editValue=null!==n?n:i?String(this.getCellValue(e,t)||""):"",this.editOriginalValue=String(this.getCellValue(e,t)||""),this.$nextTick(()=>{const e=this.$refs.editInput;if(e){const t=Array.isArray(e)?e[0]:e;t&&(t.focus(),i&&t.setSelectionRange(t.value.length,t.value.length))}})):this.openSelectDropdownForActive()))},commitEdit(){if(!this.editingCell)return;const{row:e,col:t}=this.editingCell,n=this.leafCols[t];if(!n)return void(this.editingCell=null);if(!this.isCellValueValid(e,t,this.editValue))return this._showInvalidCell(e,t,this.editValue),this.editingCell=null,void(this.$refs.wrapper&&this.$refs.wrapper.focus());this.invalidCell=null;const i=this.data,o=i.slice();o[e]={...i[e],[n.key]:this.editValue},this._emitDataChange(i,o),this.editingCell=null,this.$refs.wrapper&&this.$refs.wrapper.focus()},cancelEdit(){this.editingCell=null,this.$refs.wrapper&&this.$refs.wrapper.focus()},isCheckboxChecked(e,t){const n=this.getCellValue(e,t);return!0===n||1===n||"string"==typeof n&&["true","1"].includes(n.trim().toLowerCase())},setCheckboxValue(e,t,n){const i=this.leafCols[t];if(!i||!this.isCellEditable(e,t))return;if(!this.isCellValueValid(e,t,n))return void this._showInvalidCell(e,t,n);this.invalidCell=null;const o=this.data,l=o.slice();l[e]={...o[e],[i.key]:n},this._emitDataChange(o,l)},toggleCheckbox(e,t){this.setCheckboxValue(e,t,!this.isCheckboxChecked(e,t))},onCheckboxChange(e,t,n){this.setCheckboxValue(t,n,e.target.checked)},isCellEditable(e,t){const n=this.leafCols[t];if(!n)return!1;const i=this._dataRaw[e]||{};return"function"==typeof this.cellEditable?!!this.cellEditable(i,e,n,t):"function"==typeof n.editable?!!n.editable(i,e):!1!==n.editable},isCellValueValid(e,t,n){if("function"!=typeof this.cellValidator)return!0;const i=this.leafCols[t];if(!i)return!0;const o=this._dataRaw[e]||{};return!!this.cellValidator(n,o,e,i,t)},_resolveValidationMessage(e,t,n){const i=this.leafCols[t],o=this._dataRaw[e]||{};return"function"==typeof this.cellValidationMessage?this.cellValidationMessage(n,o,e,i,t):"string"==typeof this.cellValidationMessage&&this.cellValidationMessage?this.cellValidationMessage:this.t.invalidValue},_invalidCellRect(){const e=this.$refs.editInput,t=Array.isArray(e)?e[0]:e;if(t)return t.getBoundingClientRect();const n=this.$el?this.$el.querySelector(".eg-cell-active"):null;return n?n.getBoundingClientRect():null},_showInvalidCell(e,t,n){const i=this._resolveValidationMessage(e,t,n);this.invalidCell={row:e,col:t,message:i,rect:this._invalidCellRect()},this.$emit("cell-validation-error",{row:e,col:t,value:n,message:i})},_emitBulkValidationError(e,t){const n=e.map(e=>({row:e.row,col:e.col,value:e.value,message:this._resolveValidationMessage(e.row,e.col,e.value)}));this.$emit("bulk-validation-error",{blocked:t,cells:n})},colOptions(e){const t=this.leafCols[e];return t&&Array.isArray(t.options)?t.options.map(e=>"string"==typeof e?{value:e,label:e}:{value:e.value,label:null!=e.label?e.label:String(e.value),color:e.color}):[]},getSelectLabel(e,t){const n=this.colOptions(e);if(!n.length)return null==t?"":String(t);const i=null==t?"":String(t),o=n.find(e=>String(e.value)===i);return o?o.label:i},openSelectDropdown(e,t,n){const i=this.leafCols[t];if(!i||!Array.isArray(i.options))return;const o=n.currentTarget.getBoundingClientRect(),l=String(null!=this.getCellValue(e,t)?this.getCellValue(e,t):""),s=this.colOptions(t);this.selectDropdown={row:e,col:t,options:s,currentValue:l,highlight:s.findIndex(e=>String(e.value)===l),rect:{top:o.top,bottom:o.bottom,left:o.left,width:o.width}}},openSelectDropdownForActive(){const{row:e,col:t}=this.activeCell,n=this.leafCols[t];n&&Array.isArray(n.options)&&this.$nextTick(()=>{const n=this.$el?this.$el.querySelector(".eg-cell-active"):null;if(!n)return;const i=n.getBoundingClientRect(),o=String(null!=this.getCellValue(e,t)?this.getCellValue(e,t):""),l=this.colOptions(t);this.selectDropdown={row:e,col:t,options:l,currentValue:o,highlight:l.findIndex(e=>String(e.value)===o),rect:{top:i.top,bottom:i.bottom,left:i.left,width:i.width}}})},commitSelectValue(e){if(!this.selectDropdown)return;const{row:t,col:n}=this.selectDropdown,i=this.leafCols[n];if(i){if(!this.isCellValueValid(t,n,e))return this._showInvalidCell(t,n,e),this.closeSelectDropdown(),void(this.$refs.wrapper&&this.$refs.wrapper.focus());this.invalidCell=null;const o=this.data,l=o.slice();l[t]={...o[t],[i.key]:e},this._emitDataChange(o,l)}this.closeSelectDropdown(),this.$refs.wrapper&&this.$refs.wrapper.focus()},closeSelectDropdown(){this.selectDropdown=null},_onDocMouseDownForDropdown(e){if(!this.selectDropdown)return;const t=this.$el?this.$el.querySelector(".eg-select-dropdown"):null;t&&t.contains(e.target)||this.closeSelectDropdown()},onEditKeyDown(e){"Escape"===e.key?(e.preventDefault(),this.cancelEdit()):"Enter"!==e.key||e.shiftKey?"Tab"===e.key&&(e.preventDefault(),this.commitEdit(),this.moveActive(0,e.shiftKey?-1:1,e)):(e.preventDefault(),this.commitEdit(),this.moveActive(1,0,e))},clearSelection(){if(!this.selectionRanges.length)return;const e=this.data,t=e.slice();let n=!1;const i=[];for(const e of this.selectionRanges){const o={startRow:Math.min(e.startRow,e.endRow),endRow:Math.max(e.startRow,e.endRow),startCol:Math.min(e.startCol,e.endCol),endCol:Math.max(e.startCol,e.endCol)};for(let e=o.startRow;e<=o.endRow&&e<t.length;e++){let l=null;for(let s=o.startCol;s<=o.endCol;s++){const o=this.leafCols[s];if(o&&this.isCellEditable(e,s)){if(!this.isCellValueValid(e,s,"")){i.push({row:e,col:s,value:""});continue}l||(l={...t[e]}),l[o.key]="",n=!0}}l&&(t[e]=l)}}this.strictBulkValidation&&i.length>0?this._emitBulkValidationError(i,!0):(n&&this._emitDataChange(e,t),i.length>0&&this._emitBulkValidationError(i,!1))},startFillDrag(e){const t=this.primaryRange;t&&(this.fillDrag.active=!0,this.fillDrag.sourceRange=o(t),this.fillDrag.currentRow=t.endRow,this.fillDrag.currentCol=t.endCol,e.preventDefault())},onGlobalMouseMove(e){this.resizing.active?this.handleResizeMove(e):this.fillDrag.active?this.handleFillDragMove(e):this.dragMove.active&&this.handleDragMoveMove(e)},onGlobalMouseUp(e){this.resizing.active?this.commitResize():this.fillDrag.active?this.commitFillDrag():this.dragMove.active?this.commitDragMove(e):this.isMouseDown=!1},handleFillDragMove(e){const t=this.$refs.scrollBody;if(!t)return;const n=t.getBoundingClientRect(),i=e.clientY-n.top+t.scrollTop,o=e.clientX-n.left+t.scrollLeft,l=this.cumulativeHeights,s=this.totalRows;let r=0,a=s-1;for(;r<a;){const e=r+a>>1;l[e+1]<=i?r=e+1:a=e}const c=Math.min(r,s-1);let d=this.getFrozenWidth(),h=0;for(let e=0;e<this.leafCols.length;e++)if(this.isColumnScrollable(e)){if(d+=this.getColW(e),o<d){h=e;break}h=e}this.fillDrag.currentRow=c,this.fillDrag.currentCol=h},commitFillDrag(){const e=this.fillDrag.sourceRange;if(!e)return void(this.fillDrag.active=!1);const t=this.fillDrag.currentRow,n=this.fillDrag.currentCol,i=this.data,o=i.slice();let l;const s=[];if(t>e.endRow){const n=t-e.endRow;for(let t=e.startCol;t<=e.endCol;t++){const l=this.leafCols[t];if(!l)continue;const r=[];for(let t=e.startRow;t<=e.endRow;t++)r.push(i[t]?i[t][l.key]:"");const a=w(r,n);for(let r=0;r<n;r++){const n=e.endRow+1+r;if(n<this.totalRows&&this.isCellEditable(n,t)){if(!this.isCellValueValid(n,t,a[r])){s.push({row:n,col:t,value:a[r]});continue}o[n]===i[n]&&(o[n]={...i[n]}),o[n][l.key]=a[r]}}}l={startRow:e.endRow+1,endRow:t,startCol:e.startCol,endCol:e.endCol}}else if(n>e.endCol){const t=n-e.endCol;for(let n=e.startRow;n<=e.endRow;n++){const l=[];for(let t=e.startCol;t<=e.endCol;t++){const e=this.leafCols[t];l.push(e&&i[n]?i[n][e.key]:"")}const r=w(l,t);for(let l=0;l<t;l++){const t=e.endCol+1+l;if(t<this.leafCols.length){const e=this.leafCols[t];if(e&&this.isCellEditable(n,t)){if(!this.isCellValueValid(n,t,r[l])){s.push({row:n,col:t,value:r[l]});continue}o[n]===i[n]&&(o[n]={...i[n]}),o[n][e.key]=r[l]}}}}l={startRow:e.startRow,endRow:e.endRow,startCol:e.endCol+1,endCol:n}}l&&(this.strictBulkValidation&&s.length>0?this._emitBulkValidationError(s,!0):(this._emitDataChange(i,o),this.$emit("fill",{sourceRange:e,targetRange:l,values:o}),s.length>0&&this._emitBulkValidationError(s,!1))),this.fillDrag.active=!1,this.fillDrag.sourceRange=null},doubleFillDown(){const e=this.primaryRange;if(!e)return;const t=o(e),n=t.startCol>0?t.startCol-1:t.endCol+1;if(n<0||n>=this.leafCols.length)return;const i=this.leafCols[n];if(!i)return;let l=t.endRow;for(let e=t.endRow+1;e<this.totalRows&&(this.data[e]&&""!==this.data[e][i.key]&&void 0!==this.data[e][i.key]);e++)l=e;if(l<=t.endRow)return;const s=l-t.endRow,r=this.data,a=r.slice(),c=[];for(let e=t.startCol;e<=t.endCol;e++){const n=this.leafCols[e];if(!n)continue;const i=[];for(let e=t.startRow;e<=t.endRow;e++)i.push(r[e]?r[e][n.key]:"");const o=w(i,s);for(let i=0;i<s;i++){const l=t.endRow+1+i;if(l<this.totalRows&&this.isCellEditable(l,e)){if(!this.isCellValueValid(l,e,o[i])){c.push({row:l,col:e,value:o[i]});continue}a[l]===r[l]&&(a[l]={...r[l]}),a[l][n.key]=o[i]}}}this.strictBulkValidation&&c.length>0?this._emitBulkValidationError(c,!0):(this._emitDataChange(r,a),c.length>0&&this._emitBulkValidationError(c,!1))},copySelection(){const e=this.primaryRange;if(!e)return;const t=o(e);this.clipboardMatrix=m(this.data,this.leafCols,t),this.clipboardRange=t,this.clipboardMode="copy";const n=this.clipboardMatrix.map(e=>e.map(e=>(null==e?"":String(e)).replace(/\t/g," ").replace(/\r?\n/g," ")).join("\t")).join("\n");navigator.clipboard&&navigator.clipboard.writeText(n).catch(()=>{})},cutSelection(){this.copySelection(),this.clipboardMode="cut"},async pasteAt(e,t,n={}){const{mode:i="all",transpose:l=!1}=n;let s=null;if(navigator.clipboard)try{const e=await navigator.clipboard.readText();e&&(s=(r=e)?r.split("\n").map(e=>e.split("\t")):[[""]])}catch(e){}var r;if(!s&&this.clipboardMatrix&&(s=this.clipboardMatrix),!s)return;l&&(s=function(e){if(!e||0===e.length)return[];const t=e.length,n=e[0].length;return Array.from({length:n},(n,i)=>Array.from({length:t},(t,n)=>e[n][i]))}(s));if(!this.clipboardMode||"copy"===this.clipboardMode){const n=this.primaryRange;if(n){const i=o(n),l=i.endRow-i.startRow+1,r=i.endCol-i.startCol+1,a=s.length,c=s[0]?s[0].length:0;(l>a||r>c)&&l%a===0&&r%c===0&&(s=function(e,t,n){const i=e.length,o=e[0]?e[0].length:0;if(!i||!o)return e;if(t%i!==0||n%o!==0)return e;const l=[];for(let s=0;s<t;s++){const t=[];for(let l=0;l<n;l++)t.push(e[s%i][l%o]);l.push(t)}return l}(s,l,r),e=i.startRow,t=i.startCol)}}const a=this.data,{newData:c,affectedRange:d,skipped:h}=v(a,this.leafCols,e,t,s,i,(e,t)=>this.isCellEditable(e,t),(e,t,n)=>this.isCellValueValid(e,t,n));if(this.strictBulkValidation&&h.length>0)this._emitBulkValidationError(h,!0);else{if("cut"===this.clipboardMode&&this.clipboardRange){const e=this.clipboardRange;for(let t=e.startRow;t<=e.endRow;t++){c[t]===a[t]&&(c[t]={...a[t]});for(let n=e.startCol;n<=e.endCol;n++){const e=this.leafCols[n];e&&this.isCellEditable(t,n)&&this.isCellValueValid(t,n,"")&&(c[t][e.key]="")}}this.clipboardRange=null,this.clipboardMode=null}this._emitDataChange(a,c),h.length>0&&this._emitBulkValidationError(h,!1)}},ctxCopy(){this.hideContextMenu(),this.copySelection()},ctxCut(){this.hideContextMenu(),this.cutSelection()},ctxPaste(){this.hideContextMenu(),this.pasteAt(this.activeCell.row,this.activeCell.col)},ctxPasteSpecial(e,t=!1){this.hideContextMenu(),this.pasteAt(this.activeCell.row,this.activeCell.col,{mode:e,transpose:t})},startColResize(e,t){this.$refs.wrapper.getBoundingClientRect(),this.resizing={active:!0,type:"col",index:t,startX:e.clientX,startY:e.clientY,startSize:this.getColW(t),currentPos:e.clientX},e.preventDefault()},startRowResize(e,t){this.resizing={active:!0,type:"row",index:t,startX:e.clientX,startY:e.clientY,startSize:this.getRowH(t),currentPos:e.clientY},e.preventDefault()},handleResizeMove(e){if("col"===this.resizing.type){this.resizing.currentPos=e.clientX;const t=Math.max(20,this.resizing.startSize+(e.clientX-this.resizing.startX));this.$set(this.colWidths,this.resizing.index,t),this._cellStyleCache=Object.create(null)}else this.resizing.currentPos=e.clientY,this._resizePreviewHeight=Math.max(10,this.resizing.startSize+(e.clientY-this.resizing.startY)),this._resizeRAF||(this._resizeRAF=requestAnimationFrame(()=>{this._resizeRAF=null,this._rowStyleCache=Object.create(null),this._rowHeaderStyleCache=Object.create(null),this._cellStyleCache=Object.create(null),this.$set(this.rowHeights,this.resizing.index,this._resizePreviewHeight),this.updateVirtualScroll()}))},commitResize(){if(this._resizeRAF&&(cancelAnimationFrame(this._resizeRAF),this._resizeRAF=null,"row"===this.resizing.type&&void 0!==this._resizePreviewHeight&&this.$set(this.rowHeights,this.resizing.index,this._resizePreviewHeight)),"col"===this.resizing.type){this._cellStyleCache=Object.create(null);const e=this.colWidths[this.resizing.index];this.$emit("column-resize",{index:this.resizing.index,newSize:e})}else{this._rowStyleCache=Object.create(null),this._rowHeaderStyleCache=Object.create(null),this._cellStyleCache=Object.create(null);const e=this.rowHeights[this.resizing.index];this.$emit("row-resize",{index:this.resizing.index,newSize:e}),this.updateVirtualScroll()}this.resizing.active=!1},autoFitColumn(e){const t=this.leafCols[e];if(!t)return;let n=(t.title||"").length;for(let e=0;e<Math.min(this.totalRows,500);e++){const i=this.data[e]?String(this.data[e][t.key]||""):"";i.length>n&&(n=i.length)}const i=Math.max(50,8*n+16);this.$set(this.colWidths,e,i),this._cellStyleCache=Object.create(null),this.$emit("column-resize",{index:e,newSize:i})},ctxHideRow(){this.hideContextMenu();const e=this.primaryRange;if(!e)return;const t=o(e),n=[...this.hiddenRows];for(let e=t.startRow;e<=t.endRow;e++)n.includes(e)||n.push(e);this.$emit("update:hiddenRows",n)},ctxHideColumn(){this.hideContextMenu();const e=this.primaryRange;if(!e)return;const t=o(e),n=[...this.hiddenColumns];for(let e=t.startCol;e<=t.endCol;e++)n.includes(e)||n.push(e);this.$emit("update:hiddenColumns",n)},ctxUnhideRows(){this.hideContextMenu(),this.$emit("update:hiddenRows",[])},ctxUnhideColumns(){this.hideContextMenu(),this.$emit("update:hiddenColumns",[])},ctxPinLeft(){const e=this.leafCols[this.contextMenu.col];e?(this.$emit("update:pinnedColumns",{...this.pinnedColumns,[e.key]:"left"}),this.hideContextMenu()):this.hideContextMenu()},ctxPinRight(){const e=this.leafCols[this.contextMenu.col];e?(this.$emit("update:pinnedColumns",{...this.pinnedColumns,[e.key]:"right"}),this.hideContextMenu()):this.hideContextMenu()},ctxUnpin(){const e=this.leafCols[this.contextMenu.col];if(!e)return void this.hideContextMenu();const t={...this.pinnedColumns};delete t[e.key],this.$emit("update:pinnedColumns",t),this.hideContextMenu()},isColPinned(e){const t=this.leafCols[e];return!(!t||!this.pinnedColumns[t.key])},mergeCells(e){const t=this.internalMerges.slice(),{merges:n,conflict:i}=function(e,t){const n=o(e);return n.startRow===n.endRow&&n.startCol===n.endCol?{merges:t,conflict:!1}:x(n,t)?{merges:t,conflict:!0}:{merges:[...t,n],conflict:!1}}(e,this.internalMerges);return i?(this.$emit("merge-error",{range:e,reason:"Overlaps existing merge"}),!1):(this.internalMerges=n,this._emitMergeChange(t,[...n]),!0)},unmergeCells(e,t){const n=this.internalMerges.slice(),i=function(e,t,n){return n.filter(n=>{const i=o(n);return!(e>=i.startRow&&e<=i.endRow&&t>=i.startCol&&t<=i.endCol)})}(e,t,this.internalMerges);this.internalMerges=i,this._emitMergeChange(n,[...i])},ctxMerge(){this.hideContextMenu();const e=this.primaryRange;e&&this.mergeCells(o(e))},ctxUnmerge(){this.hideContextMenu(),this.unmergeCells(this.activeCell.row,this.activeCell.col)},groupRows(e,t){this.internalGroups=R("row",e,t,this.internalGroups)},groupColumns(e,t){this.internalGroups=R("column",e,t,this.internalGroups)},ungroupRows(e,t){this.internalGroups=b("row",e,t,this.internalGroups)},ungroupColumns(e,t){this.internalGroups=b("column",e,t,this.internalGroups)},toggleGroup(e){this.internalCollapsedGroups=function(e,t){return t.find(t=>t.type===e.type&&t.startIndex===e.startIndex&&t.endIndex===e.endIndex)?t.filter(t=>!(t.type===e.type&&t.startIndex===e.startIndex&&t.endIndex===e.endIndex)):[...t,{type:e.type,startIndex:e.startIndex,endIndex:e.endIndex}]}(e,this.internalCollapsedGroups),this.$emit("update:collapsedGroups",[...this.internalCollapsedGroups]),this.$emit("group-toggle",{group:e,collapsed:k(e,this.internalCollapsedGroups)}),this.updateVirtualScroll()},isGroupCollapsed(e){return k(e,this.internalCollapsedGroups)},expandAll(){this.internalCollapsedGroups=[],this.$emit("update:collapsedGroups",[]),this.updateVirtualScroll()},collapseAll(){this.internalCollapsedGroups=[...this.internalGroups],this.$emit("update:collapsedGroups",[...this.internalGroups]),this.updateVirtualScroll()},startDragMove(e,t,n){const i=this.primaryRange;i&&(this.dragMove={active:!0,sourceRange:o(i),startX:e.clientX,startY:e.clientY,targetRow:t,targetCol:n,copyMode:e.ctrlKey||e.metaKey},this.$emit("cell-drag-start",{sourceRange:this.dragMove.sourceRange}),e.preventDefault())},handleDragMoveMove(e){this.dragMove.copyMode=e.ctrlKey||e.metaKey,this.$emit("cell-drag-move",{sourceRange:this.dragMove.sourceRange,targetRow:this.dragMove.targetRow,targetCol:this.dragMove.targetCol})},async commitDragMove(e){if(!this.dragMove.active)return;const{sourceRange:t,targetRow:n,targetCol:i,copyMode:o}=this.dragMove,l={startRow:n,startCol:i,endRow:n+(t.endRow-t.startRow),endCol:i+(t.endCol-t.startCol)};let s=!0;if(this.beforeDrop&&(s=await Promise.resolve(this.beforeDrop(t,l))),s){const e=this.data,s=m(e,this.leafCols,t),{newData:r}=v(e,this.leafCols,n,i,s,"all",(e,t)=>this.isCellEditable(e,t));if(!o)for(let n=t.startRow;n<=t.endRow;n++){r[n]===e[n]&&(r[n]={...e[n]});for(let e=t.startCol;e<=t.endCol;e++){const t=this.leafCols[e];t&&this.isCellEditable(n,e)&&(r[n][t.key]="")}}this._emitDataChange(e,r),this.$emit("cell-drop",{sourceRange:t,targetRange:l})}this.dragMove={active:!1,sourceRange:null,startX:0,startY:0,targetRow:-1,targetCol:-1,copyMode:!1},this.isMouseDown=!1},async exportToExcel(e={}){const{filename:t="export.xlsx",sheetName:n="Sheet1",scope:l="all",includeHidden:s=!1}=e;this.$emit("export-start"),this.exportState={active:!0};const d={data:this.data,columns:this.columns,leafCols:this.leafCols,merges:this.internalMerges,hiddenCols:[...this.effectiveHiddenColSet],hiddenRows:[...this.hiddenRowSet],range:"selection"===l?this.primaryRange:null,filename:t,sheetName:n,scope:l,includeHidden:s};try{null||function(e){const{data:t,columns:n,leafCols:l,merges:s=[],hiddenColSet:d=new Set,hiddenRowSet:h=new Set,range:u=null,filename:p="export.xlsx",sheetName:g="Sheet1",scope:f="all",includeHidden:A=!1}=e,C=l.filter((e,t)=>{if(!A&&d.has(t))return!1;if("selection"===f&&u){const e=o(u);if(t<e.startCol||t>e.endCol)return!1}return!0});let w=0,m=t.length-1;if("selection"===f&&u){const e=o(u);w=e.startRow,m=e.endRow}const v=[];for(let e=w;e<=m;e++)!A&&h.has(e)||v.push({rowIndex:e,data:t[e]});const x=a(n),y=c(n,x),R=i.utils.book_new(),b=[];for(let e=0;e<y.length;e++){const t=[];for(const n of y[e])if(n.isGroup){const e=r(n.column.children||[]).filter(e=>C.some(t=>t.key===e.key));if(0===e.length)continue;t.push(n.column.title||"");for(let n=1;n<e.length;n++)t.push(null)}else{if(!C.some(e=>e.key===n.column.key))continue;n.rowspan,t.push(n.column.title||"")}b.push(t)}for(const{data:e}of v){const t=C.map(t=>{const n=e?e[t.key]:"";return void 0!==n?n:""});b.push(t)}const k=i.utils.aoa_to_sheet(b);k["!cols"]=C.map(e=>({wch:Math.round((e.width||100)/7)}));const E=[];for(const e of s){const t=o(e),n="selection"===f&&u?o(u):{startRow:0,startCol:0};if("selection"===f){if(t.startRow<n.startRow||t.endRow>n.endRow)continue;if(t.startCol<n.startCol||t.endCol>n.endCol)continue}const i=C.findIndex(e=>l.indexOf(e)===t.startCol),s=C.findIndex(e=>l.indexOf(e)===t.endCol);if(i<0||s<0)continue;const r=v.filter(e=>e.rowIndex<t.startRow).length,a=v.filter(e=>e.rowIndex<=t.endRow).length;E.push({s:{r:x+r,c:i},e:{r:x+a-1,c:s}})}E.length&&(k["!merges"]=E),i.utils.book_append_sheet(R,k,g),i.writeFile(R,p)}({...d,hiddenColSet:this.effectiveHiddenColSet,hiddenRowSet:this.hiddenRowSet}),this.$emit("export-success")}catch(e){this.$emit("export-error",e)}finally{this.exportState={active:!1}}},_exportViaWorker:(e,t)=>new Promise((n,i)=>{let o=!1;const l=(t,n)=>{o||(o=!0,e.terminate(),t(n))};e.onmessage=e=>{const{type:t,buffer:o,filename:s,message:r}=e.data;if("result"===t){const e=new Blob([new Uint8Array(o)],{type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}),t=URL.createObjectURL(e),i=document.createElement("a");i.href=t,i.download=s,document.body.appendChild(i),i.click(),document.body.removeChild(i),URL.revokeObjectURL(t),l(n)}else"error"===t&&l(i,new Error(r))},e.onerror=e=>l(i,new Error(e.message||"Export worker xatosi")),e.postMessage(t)}),async importFromFile(e,t={}){this.importState={active:!0,percent:5,message:"Fayl o'qilmoqda..."};try{let n;null||(n=await M(e,this.columns,t,(e,t)=>{this.importState={active:!0,percent:e,message:t}}));const{columns:i,merges:o}=n,l=n.data.map(e=>Object.freeze(e));this.$emit("input",l),this.$emit("update:columns",i);const s=o&&o.length>0;this.internalMerges=s?o:[],this.$emit("update:merges",s?o:[]),this.$emit("update:collapsedGroups",[]),this.$emit("update:hiddenRows",[]),this.$emit("import-success",{data:l,columns:i,merges:o}),this.rowHeights={},this.scrollTop=0,this.scrollLeft=0,this.$nextTick(()=>{this.$refs.scrollBody&&(this.$refs.scrollBody.scrollTop=0,this.$refs.scrollBody.scrollLeft=0),this.$refs.scrollColHeader&&(this.$refs.scrollColHeader.scrollLeft=0),this.$refs.scrollRowHeaders&&(this.$refs.scrollRowHeaders.scrollTop=0),this.updateVirtualScroll()})}catch(e){this.$emit("import-error",e)}finally{this.importState={active:!1,percent:0,message:""}}},_importViaWorker(e,t,n){return new Promise(async(i,o)=>{let l=!1;const s=(t,n)=>{l||(l=!0,e.terminate(),t(n))};e.onmessage=e=>{const{type:t,percent:n,message:l,data:r,columns:a,merges:c}=e.data;"progress"===t?this.importState={active:!0,percent:n,message:l}:"result"===t?s(i,{data:r,columns:a,merges:c}):"error"===t&&s(o,new Error(l))},e.onerror=e=>s(o,new Error(e.message||"Import worker xatosi"));try{const i=await t.arrayBuffer();e.postMessage({buffer:i,existingColumns:this.columns,options:n},[i])}catch(e){s(o,e)}})},_pushHistory(e){this._historyIndex<this._history.length-1&&this._history.splice(this._historyIndex+1),this._history.push(e),this._history.length>100?this._history.shift():this._historyIndex++},_emitDataChange(e,t){if(!this._historySkip){const n=[],i=[],o=[],l=Math.max(e.length,t.length);for(let s=0;s<l;s++)e[s]!==t[s]&&(n.push(s),i.push(e[s]),o.push(t[s]));n.length>0&&this._pushHistory({type:"data",before:{indices:n,rows:i},after:{indices:n,rows:o}})}this.$emit("input",t)},_emitMergeChange(e,t){this._historySkip||this._pushHistory({type:"merges",before:e,after:t}),this.$emit("update:merges",t)},undo(){if(this._historyIndex<0)return;const e=this._history[this._historyIndex--];this._historySkip=!0;try{if("data"===e.type){const t=this.data.slice();for(let n=0;n<e.before.indices.length;n++)t[e.before.indices[n]]=e.before.rows[n];this.$emit("input",t)}else"merges"===e.type&&(this.internalMerges=e.before.slice(),this.$emit("update:merges",e.before.slice()))}finally{this._historySkip=!1}},redo(){if(this._historyIndex>=this._history.length-1)return;const e=this._history[++this._historyIndex];this._historySkip=!0;try{if("data"===e.type){const t=this.data.slice();for(let n=0;n<e.after.indices.length;n++)t[e.after.indices[n]]=e.after.rows[n];this.$emit("input",t)}else"merges"===e.type&&(this.internalMerges=e.after.slice(),this.$emit("update:merges",e.after.slice()))}finally{this._historySkip=!1}}}};function L(e,t,n,i,o,l,s,r,a,c){"boolean"!=typeof s&&(a=r,r=s,s=!1);const d="function"==typeof n?n.options:n;let h;if(e&&e.render&&(d.render=e.render,d.staticRenderFns=e.staticRenderFns,d._compiled=!0,o&&(d.functional=!0)),i&&(d._scopeId=i),l?(h=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),t&&t.call(this,a(e)),e&&e._registeredComponents&&e._registeredComponents.add(l)},d._ssrRegister=h):t&&(h=s?function(e){t.call(this,c(e,this.$root.$options.shadowRoot))}:function(e){t.call(this,r(e))}),h)if(d.functional){const e=d.render;d.render=function(t,n){return h.call(n),e(t,n)}}else{const e=d.beforeCreate;d.beforeCreate=e?[].concat(e,h):[h]}return n}const W="undefined"!=typeof navigator&&/msie [6-9]\\b/.test(navigator.userAgent.toLowerCase());function G(e){return(e,t)=>function(e,t){const n=W?t.media||"default":e,i=T[n]||(T[n]={ids:new Set,styles:[]});if(!i.ids.has(e)){i.ids.add(e);let n=t.source;if(t.map&&(n+="\n/*# sourceURL="+t.map.sources[0]+" */",n+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(t.map))))+" */"),i.element||(i.element=document.createElement("style"),i.element.type="text/css",t.media&&i.element.setAttribute("media",t.media),void 0===$&&($=document.head||document.getElementsByTagName("head")[0]),$.appendChild(i.element)),"styleSheet"in i.element)i.styles.push(n),i.element.styleSheet.cssText=i.styles.filter(Boolean).join("\n");else{const e=i.ids.size-1,t=document.createTextNode(n),o=i.element.childNodes;o[e]&&i.element.removeChild(o[e]),o.length?i.element.insertBefore(t,o[e]):i.element.appendChild(t)}}}(e,t)}let $;const T={};const F=B;var O=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{ref:"wrapper",staticClass:"eg-wrapper",style:e.wrapperStyle,attrs:{tabindex:"0"},on:{keydown:e.onKeyDown,keypress:e.onKeyPress}},[e.contextMenu.visible?n("div",{staticClass:"eg-context-menu",style:{top:e.contextMenu.y+"px",left:e.contextMenu.x+"px"},on:{click:function(e){e.stopPropagation()}}},[n("div",{staticClass:"eg-context-item",on:{click:e.ctxCopy}},[e._v(e._s(e.t.copy))]),e._v(" "),n("div",{staticClass:"eg-context-item",on:{click:e.ctxCut}},[e._v(e._s(e.t.cut))]),e._v(" "),n("div",{staticClass:"eg-context-item",on:{click:e.ctxPaste}},[e._v(e._s(e.t.paste))]),e._v(" "),n("div",{staticClass:"eg-context-separator"}),e._v(" "),n("div",{staticClass:"eg-context-item eg-context-submenu-parent"},[e._v("\n "+e._s(e.t.pasteSpecial)+"\n "),n("div",{staticClass:"eg-context-submenu"},[n("div",{staticClass:"eg-context-item",on:{click:function(t){return e.ctxPasteSpecial("all")}}},[e._v(e._s(e.t.pasteAll))]),e._v(" "),n("div",{staticClass:"eg-context-item",on:{click:function(t){return e.ctxPasteSpecial("values")}}},[e._v(e._s(e.t.pasteValues))]),e._v(" "),n("div",{staticClass:"eg-context-item",on:{click:function(t){return e.ctxPasteSpecial("formats")}}},[e._v(e._s(e.t.pasteFormats))]),e._v(" "),n("div",{staticClass:"eg-context-item",on:{click:function(t){return e.ctxPasteSpecial("all",!0)}}},[e._v(e._s(e.t.transpose))])])]),e._v(" "),n("div",{staticClass:"eg-context-separator"}),e._v(" "),n("div",{staticClass:"eg-context-item",on:{click:e.ctxMerge}},[e._v(e._s(e.t.mergeCells))]),e._v(" "),n("div",{staticClass:"eg-context-item",on:{click:e.ctxUnmerge}},[e._v(e._s(e.t.unmergeCells))]),e._v(" "),n("div",{staticClass:"eg-context-separator"}),e._v(" "),n("div",{staticClass:"eg-context-item",on:{click:e.ctxHideRow}},[e._v(e._s(e.t.hideRows))]),e._v(" "),n("div",{staticClass:"eg-context-item",on:{click:e.ctxHideColumn}},[e._v(e._s(e.t.hideColumns))]),e._v(" "),n("div",{staticClass:"eg-context-item",on:{click:e.ctxUnhideRows}},[e._v(e._s(e.t.unhideRows))]),e._v(" "),n("div",{staticClass:"eg-context-item",on:{click:e.ctxUnhideColumns}},[e._v(e._s(e.t.unhideColumns))]),e._v(" "),e.contextMenu.row<0&&e.contextMenu.col>=0?[n("div",{staticClass:"eg-context-separator"}),e._v(" "),n("div",{staticClass:"eg-context-item",on:{click:e.ctxPinLeft}},[e._v(e._s(e.t.pinLeft))]),e._v(" "),n("div",{staticClass:"eg-context-item",on:{click:e.ctxPinRight}},[e._v(e._s(e.t.pinRight))]),e._v(" "),e.isColPinned(e.contextMenu.col)?n("div",{staticClass:"eg-context-item",on:{click:e.ctxUnpin}},[e._v(e._s(e.t.unpin))]):e._e()]:e._e()],2):e._e(),e._v(" "),n("div",{ref:"container",staticClass:"eg-container",on:{scroll:e.onScroll,click:e.hideContextMenu}},[n("div",{staticClass:"eg-header-area",style:e.headerAreaStyle},[n("div",{staticClass:"eg-corner",style:e.cornerStyle,on:{click:e.selectAll}}),e._v(" "),e.rowGroups.length?n("div",{staticClass:"eg-row-outline-gutter",style:e.rowOutlineGutterStyle}):e._e(),e._v(" "),e.getFrozenWidth()>0?n("div",{staticClass:"eg-frozen-col-header",style:e.frozenColHeaderStyle},[n("div",{style:{position:"relative",height:e.headerRowHeight*e.headerDepth+"px"}},[e._l(e.headerRows,function(t,i){return[e._l(t,function(t){return[e.isCellVisible(t,i,!0)?[n("div",{key:"fh-"+i+"-"+(t.column.key||t.column.title),staticClass:"eg-header-cell",class:{"eg-header-group":t.isGroup},style:e.getHeaderCellStyle(t,i,!0),on:{mousedown:function(n){return e.onColHeaderMouseDown(n,t)},dblclick:function(n){return e.onColHeaderDblClick(n,t)},contextmenu:function(n){return n.preventDefault(),e.onColHeaderContextMenu(n,t)}}},[t.isGroup?n("span",[e._v(e._s(t.column.title))]):e._t("header-"+t.column.key,function(){return[e._v("\n "+e._s(t.column.title)+"\n ")]},{column:t.column}),e._v(" "),t.isGroup||t.rowspan!==e.headerDepth?e._e():n("div",{staticClass:"eg-col-resize-handle",on:{mousedown:function(n){return n.stopPropagation(),e.startColResize(n,t.column._flatIndex)}}})],2)]:e._e()]})]})],2)]):e._e(),e._v(" "),n("div",{ref:"scrollColHeader",staticClass:"eg-scroll-col-header",style:e.scrollColHeaderStyle},[n("div",{staticClass:"eg-scroll-col-header-inner",style:{width:e.scrollContentWidth+"px",height:e.headerRowHeight*e.headerDepth+"px",position:"relative"}},[e._l(e.headerRows,function(t,i){return[e._l(t,function(t){return[e.isCellVisible(t,i,!1)?[n("div",{key:"sh-"+i+"-"+(t.column.key||t.column.title),staticClass:"eg-header-cell",class:{"eg-header-group":t.isGroup},style:e.getHeaderCellStyle(t,i,!1),on:{mousedown:function(n){return e.onColHeaderMouseDown(n,t)},dblclick:function(n){return e.onColHeaderDblClick(n,t)},contextmenu:function(n){return n.preventDefault(),e.onColHeaderContextMenu(n,t)}}},[t.isGroup?n("span",[e._v(e._s(t.column.title))]):e._t("header-"+t.column.key,function(){return[e._v("\n "+e._s(t.column.title)+"\n ")]},{column:t.column}),e._v(" "),t.isGroup||t.rowspan!==e.headerDepth?e._e():n("div",{staticClass:"eg-col-resize-handle",on:{mousedown:function(n){return n.stopPropagation(),e.startColResize(n,t.column._flatIndex)}}})],2)]:e._e()]})]})],2)]),e._v(" "),e.hasPinnedRight?n("div",{staticClass:"eg-pinned-right-header",style:e.pinnedRightHeaderStyle},[n("div",{style:{position:"relative",height:e.headerRowHeight*e.headerDepth+"px"}},[e._l(e.headerRows,function(t,i){return[e._l(t,function(t){return[e.isCellInZone(t,i,"right")?[n("div",{key:"prh-"+i+"-"+(t.column.key||t.column.title),staticClass:"eg-header-cell",class:{"eg-header-group":t.isGroup},style:e.getHeaderCellStyleForZone(t,i,"right"),on:{mousedown:function(n){return e.onColHeaderMouseDown(n,t)},dblclick:function(n){return e.onColHeaderDblClick(n,t)},contextmenu:function(n){return n.preventDefault(),e.onColHeaderContextMenu(n,t)}}},[t.isGroup?n("span",[e._v(e._s(t.column.title))]):e._t("header-"+t.column.key,function(){return[e._v("\n "+e._s(t.column.title)+"\n ")]},{column:t.column}),e._v(" "),t.isGroup||t.rowspan!==e.headerDepth?e._e():n("div",{staticClass:"eg-col-resize-handle",on:{mousedown:function(n){return n.stopPropagation(),e.startColResize(n,t.column._flatIndex)}}})],2)]:e._e()]})]})],2)]):e._e()]),e._v(" "),n("div",{staticClass:"eg-body-area",style:e.bodyAreaStyle},[n("div",{staticClass:"eg-row-header-col",style:e.rowHeaderColStyle},[n("div",{ref:"scrollRowHeaders",staticClass:"eg-scroll-row-headers",style:e.scrollRowHeadersStyle},[n("div",{style:{height:e.totalVirtualHeight+"px",position:"relative"}},[n("div",{style:{transform:"translateY("+e.virtualOffsetTop+"px)"}},[e._l(e.visibleRowRange,function(t){return[e.isRowHidden(t)?e._e():n("div",{key:"srh-"+t,staticClass:"eg-row-header-cell",style:e.getRowHeaderStyle(t),on:{mousedown:function(n){return e.onRowHeaderMouseDown(n,t)},contextmenu:function(n){return n.preventDefault(),e.onRowContextMenu(n,t)}}},[e._v("\n "+e._s(t+1)+"\n "),n("div",{staticClass:"eg-row-resize-handle",on:{mousedown:function(n){return n.stopPropagation(),e.startRowResize(n,t)}}})])]})],2)])])]),e._v(" "),e.rowGroups.length?n("div",{staticClass:"eg-row-outline-col",style:e.rowOutlineColStyle},[e._l(e.rowGroups,function(t){return[n("div",{key:"rg-"+t.startIndex+"-"+t.endIndex,staticClass:"eg-group-btn",class:{collapsed:e.isGroupCollapsed(t)},style:e.getRowGroupBtnStyle(t),on:{click:function(n){return e.toggleGroup(t)}}},[e._v(e._s(e.isGroupCollapsed(t)?"+":"−"))])]})],2):e._e(),e._v(" "),n("div",{ref:"scrollBody",staticClass:"eg-scroll-body",style:e.scrollBodyStyle,on:{scroll:e.onBodyScroll}},[n("div",{staticClass:"eg-scroll-body-inner",style:{width:e.scrollContentWidth+"px",height:e.totalVirtualHeight+"px",position:"relative"}},[e.getFrozenWidth()>0?n("div",{staticClass:"eg-frozen-col-body",style:e.frozenColBodyStyle},[n("div",{style:{height:e.totalVirtualHeight+"px",position:"relative"}},[n("div",{style:{transform:"translateY("+e.virtualOffsetTop+"px)"}},[e._l(e.visibleRowRange,function(t){return[e.isRowHidden(t)?e._e():[n("div",{key:"fc-row-"+t,staticClass:"eg-row",style:e.getRowStyle(t)},[e._l(e.leafCols,function(i,o){return[e.isColumnPinnedLeft(o)&&!e.isColHidden(o)?[n("div",{key:"fc-cell-"+t+"-"+o,staticClass:"eg-cell",class:e.getCellClasses(t,o),style:e.getCellStyle(t,o),on:{mousedown:function(n){return e.onCellMouseDown(n,t,o)},mouseover:function(n){return e.onCellMouseOver(n,t,o)},mouseup:e.onCellMouseUp,dblclick:function(n){return e.onCellDblClick(t,o)},contextmenu:function(n){return n.preventDefault(),e.onCellContextMenu(n,t,o)}}},[e.isMergedAway(t,o)?n("div",{staticStyle:{display:"none"}}):e.isEditing(t,o)?[n("input",{directives:[{name:"model",rawName:"v-model",value:e.editValue,expression:"editValue"}],ref:"editInput",refInFor:!0,staticClass:"eg-cell-input",domProps:{value:e.editValue},on:{blur:e.commitEdit,keydown:function(t){return t.stopPropagation(),e.onEditKeyDown.apply(null,arguments)},input:function(t){t.target.composing||(e.editValue=t.target.value)}}})]:"checkbox"===i.type?[n("input",{staticClass:"eg-cell-checkbox-input",attrs:{type:"checkbox",disabled:!e.isCellEditable(t,o)},domProps:{checked:e.isCheckboxChecked(t,o)},on:{change:function(n){return e.onCheckboxChange(n,t,o)}}})]:[e._t("cell-"+i.key,function(){return[e._v("\n "+e._s("select"===i.type?e.getSelectLabel(o,e.getCellValue(t,o)):e.getCellValue(t,o))+"\n ")]},{row:e._dataRaw[t],rowIndex:t,column:i,value:e.getCellValue(t,o)})]],2)]:e._e()]})],2)]]})],2)])]):e._e(),e._v(" "),n("div",{staticClass:"eg-main-cells",style:e.mainCellsStyle},[n("div",{style:{height:e.totalVirtualHeight+"px",position:"relative",width:e.scrollableContentWidth+"px"}},[n("div",{style:{transform:"translateY("+e.virtualOffsetTop+"px)"}},[e._l(e.visibleRowRange,function(t){return[e.isRowHidden(t)?e._e():[n("div",{key:"row-"+t,staticClass:"eg-row",style:e.getRowStyle(t),attrs:{"data-row":t}},[e._l(e.leafCols,function(i,o){return[e.isColumnScrollable(o)&&!e.isColHidden(o)?[n("div",{key:"cell-"+t+"-"+o,staticClass:"eg-cell",class:e.getCellClasses(t,o),style:e.getCellStyle(t,o),on:{mousedown:function(n){return e.onCellMouseDown(n,t,o)},mouseover:function(n){return e.onCellMouseOver(n,t,o)},mouseup:e.onCellMouseUp,dblclick:function(n){return e.onCellDblClick(t,o)},contextmenu:function(n){return n.preventDefault(),e.onCellContextMenu(n,t,o)}}},[e.isMergedAway(t,o)?n("div",{staticStyle:{display:"none"}}):e.isEditing(t,o)?[n("input",{directives:[{name:"model",rawName:"v-model",value:e.editValue,expression:"editValue"}],ref:"editInput",refInFor:!0,staticClass:"eg-cell-input",domProps:{value:e.editValue},on:{blur:e.commitEdit,keydown:function(t){return t.stopPropagation(),e.onEditKeyDown.apply(null,arguments)},input:function(t){t.target.composing||(e.editValue=t.target.value)}}})]:"checkbox"===i.type?[n("input",{staticClass:"eg-cell-checkbox-input",attrs:{type:"checkbox",disabled:!e.isCellEditable(t,o)},domProps:{checked:e.isCheckboxChecked(t,o)},on:{change:function(n){return e.onCheckboxChange(n,t,o)}}})]:[e._t("cell-"+i.key,function(){return[e._v("\n "+e._s("select"===i.type?e.getSelectLabel(o,e.getCellValue(t,o)):e.getCellValue(t,o))+"\n ")]},{row:e._dataRaw[t],rowIndex:t,column:i,value:e.getCellValue(t,o)})]],2)]:e._e()]})],2)]]})],2)])]),e._v(" "),e.hasPinnedRight?n("div",{staticClass:"eg-pinned-right-body",style:e.pinnedRightBodyStyle},[n("div",{style:{height:e.totalVirtualHeight+"px",position:"relative"}},[n("div",{style:{transform:"translateY("+e.virtualOffsetTop+"px)"}},[e._l(e.visibleRowRange,function(t){return[e.isRowHidden(t)?e._e():[n("div",{key:"pr-row-"+t,staticClass:"eg-row",style:e.getRowStyle(t)},[e._l(e.pinnedRightCols,function(i){return[n("div",{key:"pr-cell-"+t+"-"+i._flatIndex,staticClass:"eg-cell",class:e.getCellClasses(t,i._flatIndex),style:e.getCellStyle(t,i._flatIndex),on:{mousedown:function(n){return e.onCellMouseDown(n,t,i._flatIndex)},mouseover:function(n){return e.onCellMouseOver(n,t,i._flatIndex)},mouseup:e.onCellMouseUp,dblclick:function(n){return e.onCellDblClick(t,i._flatIndex)},contextmenu:function(n){return n.preventDefault(),e.onCellContextMenu(n,t,i._flatIndex)}}},[e.isMergedAway(t,i._flatIndex)?n("div",{staticStyle:{display:"none"}}):e.isEditing(t,i._flatIndex)?[n("input",{directives:[{name:"model",rawName:"v-model",value:e.editValue,expression:"editValue"}],ref:"editInput",refInFor:!0,staticClass:"eg-cell-input",domProps:{value:e.editValue},on:{blur:e.commitEdit,keydown:function(t){return t.stopPropagation(),e.onEditKeyDown.apply(null,arguments)},input:function(t){t.target.composing||(e.editValue=t.target.value)}}})]:"checkbox"===i.type?[n("input",{staticClass:"eg-cell-checkbox-input",attrs:{type:"checkbox",disabled:!e.isCellEditable(t,i._flatIndex)},domProps:{checked:e.isCheckboxChecked(t,i._flatIndex)},on:{change:function(n){return e.onCheckboxChange(n,t,i._flatIndex)}}})]:[e._t("cell-"+i.key,function(){return[e._v("\n "+e._s("select"===i.type?e.getSelectLabel(i._flatIndex,e.getCellValue(t,i._flatIndex)):e.getCellValue(t,i._flatIndex))+"\n ")]},{row:e._dataRaw[t],rowIndex:t,column:i,value:e.getCellValue(t,i._flatIndex)})]],2)]})],2)]]})],2)])]):e._e(),e._v(" "),e.fillHandleVisible?n("div",{staticClass:"eg-fill-handle",style:e.fillHandleStyle,on:{mousedown:function(t){return t.stopPropagation(),e.startFillDrag.apply(null,arguments)}}}):e._e(),e._v(" "),e.dragMove.active?n("div",{staticClass:"eg-drag-ghost",style:e.dragGhostStyle}):e._e()])]),e._v(" "),e.colGroups.length?n("div",{staticClass:"eg-col-outline-row",style:e.colOutlineRowStyle},[e._l(e.colGroups,function(t){return[n("div",{key:"cg-"+t.startIndex+"-"+t.endIndex,staticClass:"eg-group-btn",class:{collapsed:e.isGroupCollapsed(t)},style:e.getColGroupBtnStyle(t),on:{click:function(n){return e.toggleGroup(t)}}},[e._v(e._s(e.isGroupCollapsed(t)?"+":"−"))])]})],2):e._e()]),e._v(" "),e.resizing.active?n("div",{staticClass:"eg-resize-line",style:e.resizeLineStyle}):e._e()]),e._v(" "),e.importState.active?n("div",{staticClass:"eg-import-overlay"},[n("div",{staticClass:"eg-import-dialog"},[n("div",{staticClass:"eg-import-title"},[e._v("Fayl import qilinmoqda")]),e._v(" "),n("div",{staticClass:"eg-import-bar-wrap"},[n("div",{staticClass:"eg-import-bar",style:{width:e.importState.percent+"%"}})]),e._v(" "),n("div",{staticClass:"eg-import-msg"},[e._v(e._s(e.importState.message))]),e._v(" "),n("div",{staticClass:"eg-import-pct"},[e._v(e._s(e.importState.percent)+"%")])])]):e._e(),e._v(" "),e.exportState.active?n("div",{staticClass:"eg-import-overlay"},[e._m(0)]):e._e(),e._v(" "),e.selectDropdown?n("div",{staticClass:"eg-select-dropdown",style:e.dropdownStyle,on:{mousedown:function(e){e.preventDefault()}}},e._l(e.selectDropdown.options,function(t,i){return n("div",{key:t.value,staticClass:"eg-select-option",class:{"eg-select-option--active":t.value===e.selectDropdown.currentValue,"eg-select-option--highlighted":i===e.selectDropdown.highlight},on:{mousedown:function(n){return n.preventDefault(),e.commitSelectValue(t.value)},mouseover:function(t){e.selectDropdown.highlight=i}}},[t.color?n("span",{staticClass:"eg-select-dot",style:{background:t.color}}):e._e(),e._v("\n "+e._s(t.label)+"\n ")])}),0):e._e(),e._v(" "),e.invalidCell&&e.invalidCell.rect?n("div",{staticClass:"eg-validation-tooltip",style:e.invalidTooltipStyle},[e._v(e._s(e.invalidCell.message))]):e._e()])};O._withStripped=!0;const P=L({render:O,staticRenderFns:[function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"eg-import-dialog"},[n("div",{staticClass:"eg-import-title"},[e._v("Fayl export qilinmoqda...")]),e._v(" "),n("div",{staticClass:"eg-export-spinner"})])}]},function(e){e&&e("data-v-6f4d610c_0",{source:"\n.eg-wrapper[data-v-6f4d610c] {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n box-sizing: border-box;\n border: 1px solid #ccc;\n border-radius: 4px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;\n}\n.eg-container[data-v-6f4d610c] {\n display: flex;\n flex-direction: column;\n flex: 1;\n overflow: auto;\n position: relative;\n}\n.eg-header-area[data-v-6f4d610c] {\n display: flex;\n flex-shrink: 0;\n box-shadow: 0 2px 0 0 #d0d0d0;\n position: relative;\n z-index: 31;\n}\n.eg-header-cell[data-v-6f4d610c] {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n border-right: 1px solid #ccc;\n border-bottom: 1px solid #ccc;\n background: #f0f0f0;\n font-size: 12px;\n font-weight: 600;\n overflow: hidden;\n white-space: nowrap;\n position: absolute;\n user-select: none;\n cursor: pointer;\n}\n.eg-header-cell[data-v-6f4d610c]:hover { background: #e0e0e0;\n}\n.eg-header-group[data-v-6f4d610c] { background: #e8eaf6;\n}\n.eg-col-resize-handle[data-v-6f4d610c] {\n position: absolute;\n right: 0;\n top: 0;\n bottom: 0;\n width: 5px;\n cursor: col-resize;\n z-index: 5;\n}\n.eg-body-area[data-v-6f4d610c] {\n display: flex;\n flex: 1;\n overflow: hidden;\n}\n.eg-row[data-v-6f4d610c] {\n position: relative;\n}\n.eg-cell[data-v-6f4d610c] {\n box-sizing: border-box;\n border-right: 1px solid #e0e0e0;\n border-bottom: 1px solid #e0e0e0;\n overflow: hidden;\n white-space: nowrap;\n font-size: 13px;\n padding: 0 4px;\n position: absolute;\n top: 0;\n background: white;\n cursor: default;\n display: flex;\n align-items: center;\n}\n.eg-cell-active[data-v-6f4d610c] {\n outline: 2px solid #1a73e8 !important;\n outline-offset: -2px;\n z-index: 3;\n}\n.eg-cell-selected[data-v-6f4d610c] {\n background: #e3f2fd !important;\n}\n.eg-cell-fill-preview[data-v-6f4d610c] {\n background: #bbdefb !important;\n outline: 1px dashed #1565c0;\n outline-offset: -1px;\n}\n.eg-cell-copy[data-v-6f4d610c] {\n outline: 1px dashed #1a73e8;\n outline-offset: -1px;\n}\n.eg-cell-merged-hidden[data-v-6f4d610c] {\n display: none !important;\n}\n.eg-cell-invalid[data-v-6f4d610c] {\n outline: 2px solid #e53935 !important;\n outline-offset: -2px;\n z-index: 4;\n}\n.eg-cell-input[data-v-6f4d610c] {\n width: 100%;\n height: 100%;\n border: none;\n outline: none;\n background: white;\n font-size: 13px;\n font-family: inherit;\n padding: 0;\n}\n.eg-row-header-cell[data-v-6f4d610c] {\n box-sizing: border-box;\n background: #f0f0f0;\n border-bottom: 1px solid #e0e0e0;\n border-right: 2px solid #bbb;\n text-align: center;\n font-size: 11px;\n color: #666;\n cursor: pointer;\n user-select: none;\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.eg-row-resize-handle[data-v-6f4d610c] {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 4px;\n cursor: row-resize;\n z-index: 5;\n}\n.eg-fill-handle[data-v-6f4d610c] {\n position: absolute;\n width: 6px;\n height: 6px;\n background: #1a73e8;\n border: 1px solid white;\n cursor: crosshair;\n z-index: 10;\n}\n.eg-context-menu[data-v-6f4d610c] {\n position: fixed;\n background: white;\n border: 1px solid #ccc;\n border-radius: 4px;\n box-shadow: 2px 4px 12px rgba(0,0,0,0.15);\n z-index: 9999;\n min-width: 160px;\n font-size: 13px;\n padding: 4px 0;\n}\n.eg-context-item[data-v-6f4d610c] {\n padding: 6px 16px;\n cursor: pointer;\n position: relative;\n}\n.eg-context-item[data-v-6f4d610c]:hover { background: #f0f0f0;\n}\n.eg-context-separator[data-v-6f4d610c] {\n height: 1px;\n background: #e0e0e0;\n margin: 4px 0;\n}\n.eg-context-submenu-parent[data-v-6f4d610c]::after { content: ' ▶'; float: right;\n}\n.eg-context-submenu[data-v-6f4d610c] {\n display: none;\n position: absolute;\n left: 100%;\n top: -4px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 4px;\n box-shadow: 2px 4px 12px rgba(0,0,0,0.15);\n min-width: 160px;\n padding: 4px 0;\n z-index: 10000;\n}\n.eg-context-submenu-parent:hover .eg-context-submenu[data-v-6f4d610c] { display: block;\n}\n.eg-group-btn[data-v-6f4d610c] {\n position: absolute;\n width: 16px;\n height: 16px;\n line-height: 14px;\n text-align: center;\n font-size: 10px;\n cursor: pointer;\n background: #e8eaf6;\n border: 1px solid #9fa8da;\n border-radius: 2px;\n z-index: 5;\n user-select: none;\n}\n.eg-group-btn.collapsed[data-v-6f4d610c] { background: #c5cae9;\n}\n.eg-drag-ghost[data-v-6f4d610c] {\n position: absolute;\n border: 2px dashed #1a73e8;\n pointer-events: none;\n z-index: 20;\n}\n.eg-resize-line[data-v-6f4d610c] {\n pointer-events: none;\n z-index: 9999;\n}\n\n/* Pin left separator shadow */\n.eg-frozen-col-body[data-v-6f4d610c]::after {\n content: '';\n position: absolute;\n top: 0;\n right: -4px;\n width: 4px;\n height: 100%;\n background: linear-gradient(to right, rgba(0,0,0,0.1), transparent);\n pointer-events: none;\n}\n.eg-pinned-right-body[data-v-6f4d610c] {\n position: sticky;\n right: 0;\n z-index: 10;\n background: white;\n flex-shrink: 0;\n}\n.eg-pinned-right-body[data-v-6f4d610c]::before {\n content: '';\n position: absolute;\n top: 0;\n left: -4px;\n bottom: 0;\n width: 4px;\n background: linear-gradient(to left, rgba(0,0,0,0.10), transparent);\n pointer-events: none;\n}\n.eg-pinned-right-header[data-v-6f4d610c] {\n overflow: hidden;\n flex-shrink: 0;\n border-left: 2px solid #bbb;\n background: #f0f0f0;\n}\n.eg-import-overlay[data-v-6f4d610c] {\n position: absolute;\n inset: 0;\n background: rgba(255, 255, 255, 0.85);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 100;\n border-radius: 4px;\n}\n.eg-import-dialog[data-v-6f4d610c] {\n background: white;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n padding: 24px 32px;\n min-width: 280px;\n box-shadow: 0 4px 20px rgba(0,0,0,0.15);\n text-align: center;\n}\n.eg-import-title[data-v-6f4d610c] {\n font-size: 14px;\n font-weight: 600;\n color: #333;\n margin-bottom: 16px;\n}\n.eg-import-bar-wrap[data-v-6f4d610c] {\n height: 8px;\n background: #e0e0e0;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 10px;\n}\n.eg-import-bar[data-v-6f4d610c] {\n height: 100%;\n background: linear-gradient(to right, #1a73e8, #4caf50);\n border-radius: 4px;\n transition: width 0.15s ease;\n}\n.eg-import-msg[data-v-6f4d610c] {\n font-size: 12px;\n color: #666;\n margin-bottom: 4px;\n}\n.eg-import-pct[data-v-6f4d610c] {\n font-size: 13px;\n font-weight: 600;\n color: #1a73e8;\n}\n.eg-export-spinner[data-v-6f4d610c] {\n width: 32px;\n height: 32px;\n margin: 12px auto 0;\n border: 3px solid #e0e0e0;\n border-top-color: #1a73e8;\n border-radius: 50%;\n animation: eg-spin-data-v-6f4d610c 0.8s linear infinite;\n}\n@keyframes eg-spin-data-v-6f4d610c {\nto { transform: rotate(360deg);\n}\n}\n\n/* ===== READONLY CELL ===== */\n.eg-cell-readonly[data-v-6f4d610c] {\n background: #f8f8f8;\n cursor: default;\n}\n.eg-cell-readonly.eg-cell-selected[data-v-6f4d610c] {\n background: #dce8f5;\n}\n.eg-cell-readonly.eg-cell-active[data-v-6f4d610c] {\n outline-color: #aaa;\n}\n\n/* ===== SELECT DROPDOWN ===== */\n.eg-cell-select[data-v-6f4d610c] {\n cursor: pointer;\n}\n.eg-cell-select[data-v-6f4d610c]::after {\n content: '▾';\n position: absolute;\n right: 4px;\n top: 50%;\n transform: translateY(-50%);\n color: #aaa;\n font-size: 11px;\n pointer-events: none;\n line-height: 1;\n}\n\n/* ===== CHECKBOX CELL ===== */\n.eg-cell-checkbox[data-v-6f4d610c] {\n justify-content: center;\n}\n.eg-cell-checkbox-input[data-v-6f4d610c] {\n width: 15px;\n height: 15px;\n margin: 0;\n cursor: pointer;\n}\n.eg-cell-checkbox-input[data-v-6f4d610c]:disabled {\n cursor: default;\n}\n.eg-select-dropdown[data-v-6f4d610c] {\n background: white;\n border: 1px solid #d0d0d0;\n border-radius: 4px;\n box-shadow: 0 4px 16px rgba(0,0,0,0.15);\n overflow-y: auto;\n max-height: 200px;\n padding: 4px 0;\n}\n.eg-select-option[data-v-6f4d610c] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n white-space: nowrap;\n user-select: none;\n color: #333;\n}\n.eg-select-option[data-v-6f4d610c]:hover,\n.eg-select-option--highlighted[data-v-6f4d610c] {\n background: #f0f4ff;\n}\n.eg-select-option--active[data-v-6f4d610c] {\n font-weight: 600;\n color: #1a73e8;\n}\n.eg-select-option--active.eg-select-option--highlighted[data-v-6f4d610c],\n.eg-select-option--active[data-v-6f4d610c]:hover {\n background: #e8f0fe;\n}\n.eg-validation-tooltip[data-v-6f4d610c] {\n background: #fff0f0;\n border: 1px solid #e53935;\n color: #c62828;\n font-size: 12px;\n padding: 4px 8px;\n border-radius: 4px;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);\n white-space: nowrap;\n pointer-events: none;\n}\n.eg-select-dot[data-v-6f4d610c] {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n}\n",map:{version:3,sources:["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/ExcelGrid.vue"],names:[],mappings:";AA8iGA;EACA,aAAA;EACA,sBAAA;EACA,gBAAA;EACA,sBAAA;EACA,sBAAA;EACA,kBAAA;EACA,6EAAA;AACA;AAEA;EACA,aAAA;EACA,sBAAA;EACA,OAAA;EACA,cAAA;EACA,kBAAA;AACA;AAEA;EACA,aAAA;EACA,cAAA;EACA,6BAAA;EACA,kBAAA;EACA,WAAA;AACA;AAEA;EACA,aAAA;EACA,mBAAA;EACA,uBAAA;EACA,sBAAA;EACA,4BAAA;EACA,6BAAA;EACA,mBAAA;EACA,eAAA;EACA,gBAAA;EACA,gBAAA;EACA,mBAAA;EACA,kBAAA;EACA,iBAAA;EACA,eAAA;AACA;AAEA,yCAAA,mBAAA;AAAA;AACA,oCAAA,mBAAA;AAAA;AAEA;EACA,kBAAA;EACA,QAAA;EACA,MAAA;EACA,SAAA;EACA,UAAA;EACA,kBAAA;EACA,UAAA;AACA;AAEA;EACA,aAAA;EACA,OAAA;EACA,gBAAA;AACA;AAEA;EACA,kBAAA;AACA;AAEA;EACA,sBAAA;EACA,+BAAA;EACA,gCAAA;EACA,gBAAA;EACA,mBAAA;EACA,eAAA;EACA,cAAA;EACA,kBAAA;EACA,MAAA;EACA,iBAAA;EACA,eAAA;EACA,aAAA;EACA,mBAAA;AACA;AAEA;EACA,qCAAA;EACA,oBAAA;EACA,UAAA;AACA;AAEA;EACA,8BAAA;AACA;AAEA;EACA,8BAAA;EACA,2BAAA;EACA,oBAAA;AACA;AAEA;EACA,2BAAA;EACA,oBAAA;AACA;AAEA;EACA,wBAAA;AACA;AAEA;EACA,qCAAA;EACA,oBAAA;EACA,UAAA;AACA;AAEA;EACA,WAAA;EACA,YAAA;EACA,YAAA;EACA,aAAA;EACA,iBAAA;EACA,eAAA;EACA,oBAAA;EACA,UAAA;AACA;AAEA;EACA,sBAAA;EACA,mBAAA;EACA,gCAAA;EACA,4BAAA;EACA,kBAAA;EACA,eAAA;EACA,WAAA;EACA,eAAA;EACA,iBAAA;EACA,kBAAA;EACA,aAAA;EACA,mBAAA;EACA,uBAAA;AACA;AAEA;EACA,kBAAA;EACA,SAAA;EACA,OAAA;EACA,QAAA;EACA,WAAA;EACA,kBAAA;EACA,UAAA;AACA;AAEA;EACA,kBAAA;EACA,UAAA;EACA,WAAA;EACA,mBAAA;EACA,uBAAA;EACA,iBAAA;EACA,WAAA;AACA;AAEA;EACA,eAAA;EACA,iBAAA;EACA,sBAAA;EACA,kBAAA;EACA,yCAAA;EACA,aAAA;EACA,gBAAA;EACA,eAAA;EACA,cAAA;AACA;AAEA;EACA,iBAAA;EACA,eAAA;EACA,kBAAA;AACA;AAEA,0CAAA,mBAAA;AAAA;AAEA;EACA,WAAA;EACA,mBAAA;EACA,aAAA;AACA;AAEA,qDAAA,aAAA,EAAA,YAAA;AAAA;AAEA;EACA,aAAA;EACA,kBAAA;EACA,UAAA;EACA,SAAA;EACA,iBAAA;EACA,sBAAA;EACA,kBAAA;EACA,yCAAA;EACA,gBAAA;EACA,cAAA;EACA,cAAA;AACA;AAEA,wEAAA,cAAA;AAAA;AAEA;EACA,kBAAA;EACA,WAAA;EACA,YAAA;EACA,iBAAA;EACA,kBAAA;EACA,eAAA;EACA,eAAA;EACA,mBAAA;EACA,yBAAA;EACA,kBAAA;EACA,UAAA;EACA,iBAAA;AACA;AAEA,2CAAA,mBAAA;AAAA;AAEA;EACA,kBAAA;EACA,0BAAA;EACA,oBAAA;EACA,WAAA;AACA;AAEA;EACA,oBAAA;EACA,aAAA;AACA;;AAEA,8BAAA;AACA;EACA,WAAA;EACA,kBAAA;EACA,MAAA;EACA,WAAA;EACA,UAAA;EACA,YAAA;EACA,mEAAA;EACA,oBAAA;AACA;AAEA;EACA,gBAAA;EACA,QAAA;EACA,WAAA;EACA,iBAAA;EACA,cAAA;AACA;AAEA;EACA,WAAA;EACA,kBAAA;EACA,MAAA;EACA,UAAA;EACA,SAAA;EACA,UAAA;EACA,mEAAA;EACA,oBAAA;AACA;AAEA;EACA,gBAAA;EACA,cAAA;EACA,2BAAA;EACA,mBAAA;AACA;AAEA;EACA,kBAAA;EACA,QAAA;EACA,qCAAA;EACA,aAAA;EACA,mBAAA;EACA,uBAAA;EACA,YAAA;EACA,kBAAA;AACA;AAEA;EACA,iBAAA;EACA,yBAAA;EACA,kBAAA;EACA,kBAAA;EACA,gBAAA;EACA,uCAAA;EACA,kBAAA;AACA;AAEA;EACA,eAAA;EACA,gBAAA;EACA,WAAA;EACA,mBAAA;AACA;AAEA;EACA,WAAA;EACA,mBAAA;EACA,kBAAA;EACA,gBAAA;EACA,mBAAA;AACA;AAEA;EACA,YAAA;EACA,uDAAA;EACA,kBAAA;EACA,4BAAA;AACA;AAEA;EACA,eAAA;EACA,WAAA;EACA,kBAAA;AACA;AAEA;EACA,eAAA;EACA,gBAAA;EACA,cAAA;AACA;AACA;EACA,WAAA;EACA,YAAA;EACA,mBAAA;EACA,yBAAA;EACA,yBAAA;EACA,kBAAA;EACA,uDAAA;AACA;AACA;AACA,KAAA,yBAAA;AAAA;AACA;;AAEA,8BAAA;AACA;EACA,mBAAA;EACA,eAAA;AACA;AACA;EACA,mBAAA;AACA;AACA;EACA,mBAAA;AACA;;AAEA,gCAAA;AACA;EACA,eAAA;AACA;AACA;EACA,YAAA;EACA,kBAAA;EACA,UAAA;EACA,QAAA;EACA,2BAAA;EACA,WAAA;EACA,eAAA;EACA,oBAAA;EACA,cAAA;AACA;;AAEA,8BAAA;AACA;EACA,uBAAA;AACA;AACA;EACA,WAAA;EACA,YAAA;EACA,SAAA;EACA,eAAA;AACA;AACA;EACA,eAAA;AACA;AAEA;EACA,iBAAA;EACA,yBAAA;EACA,kBAAA;EACA,uCAAA;EACA,gBAAA;EACA,iBAAA;EACA,cAAA;AACA;AAEA;EACA,aAAA;EACA,mBAAA;EACA,QAAA;EACA,iBAAA;EACA,eAAA;EACA,eAAA;EACA,mBAAA;EACA,iBAAA;EACA,WAAA;AACA;AACA;;EAEA,mBAAA;AACA;AACA;EACA,gBAAA;EACA,cAAA;AACA;AACA;;EAEA,mBAAA;AACA;AAEA;EACA,mBAAA;EACA,yBAAA;EACA,cAAA;EACA,eAAA;EACA,gBAAA;EACA,kBAAA;EACA,yCAAA;EACA,mBAAA;EACA,oBAAA;AACA;AAEA;EACA,UAAA;EACA,WAAA;EACA,kBAAA;EACA,cAAA;EACA,qBAAA;AACA",file:"ExcelGrid.vue",sourcesContent:["<template>\n <div\n class=\"eg-wrapper\"\n :style=\"wrapperStyle\"\n tabindex=\"0\"\n @keydown=\"onKeyDown\"\n @keypress=\"onKeyPress\"\n ref=\"wrapper\"\n >\n \x3c!-- Context menu --\x3e\n <div\n v-if=\"contextMenu.visible\"\n class=\"eg-context-menu\"\n :style=\"{ top: contextMenu.y + 'px', left: contextMenu.x + 'px' }\"\n @click.stop\n >\n <div class=\"eg-context-item\" @click=\"ctxCopy\">{{ t.copy }}</div>\n <div class=\"eg-context-item\" @click=\"ctxCut\">{{ t.cut }}</div>\n <div class=\"eg-context-item\" @click=\"ctxPaste\">{{ t.paste }}</div>\n <div class=\"eg-context-separator\"></div>\n <div class=\"eg-context-item eg-context-submenu-parent\">\n {{ t.pasteSpecial }}\n <div class=\"eg-context-submenu\">\n <div class=\"eg-context-item\" @click=\"ctxPasteSpecial('all')\">{{ t.pasteAll }}</div>\n <div class=\"eg-context-item\" @click=\"ctxPasteSpecial('values')\">{{ t.pasteValues }}</div>\n <div class=\"eg-context-item\" @click=\"ctxPasteSpecial('formats')\">{{ t.pasteFormats }}</div>\n <div class=\"eg-context-item\" @click=\"ctxPasteSpecial('all', true)\">{{ t.transpose }}</div>\n </div>\n </div>\n <div class=\"eg-context-separator\"></div>\n <div class=\"eg-context-item\" @click=\"ctxMerge\">{{ t.mergeCells }}</div>\n <div class=\"eg-context-item\" @click=\"ctxUnmerge\">{{ t.unmergeCells }}</div>\n <div class=\"eg-context-separator\"></div>\n <div class=\"eg-context-item\" @click=\"ctxHideRow\">{{ t.hideRows }}</div>\n <div class=\"eg-context-item\" @click=\"ctxHideColumn\">{{ t.hideColumns }}</div>\n <div class=\"eg-context-item\" @click=\"ctxUnhideRows\">{{ t.unhideRows }}</div>\n <div class=\"eg-context-item\" @click=\"ctxUnhideColumns\">{{ t.unhideColumns }}</div>\n <template v-if=\"contextMenu.row < 0 && contextMenu.col >= 0\">\n <div class=\"eg-context-separator\"></div>\n <div class=\"eg-context-item\" @click=\"ctxPinLeft\">{{ t.pinLeft }}</div>\n <div class=\"eg-context-item\" @click=\"ctxPinRight\">{{ t.pinRight }}</div>\n <div class=\"eg-context-item\" v-if=\"isColPinned(contextMenu.col)\" @click=\"ctxUnpin\">{{ t.unpin }}</div>\n </template>\n </div>\n\n \x3c!-- Scroll container --\x3e\n <div class=\"eg-container\" ref=\"container\" @scroll=\"onScroll\" @click=\"hideContextMenu\">\n\n \x3c!-- ===== HEADER AREA ===== --\x3e\n <div class=\"eg-header-area\" :style=\"headerAreaStyle\">\n\n \x3c!-- Corner cell --\x3e\n <div\n class=\"eg-corner\"\n :style=\"cornerStyle\"\n @click=\"selectAll\"\n ></div>\n\n \x3c!-- Row group outline gutter (left) --\x3e\n <div v-if=\"rowGroups.length\" class=\"eg-row-outline-gutter\" :style=\"rowOutlineGutterStyle\"></div>\n\n \x3c!-- Frozen columns header --\x3e\n <div v-if=\"getFrozenWidth() > 0\" class=\"eg-frozen-col-header\" :style=\"frozenColHeaderStyle\">\n <div :style=\"{ position: 'relative', height: (headerRowHeight * headerDepth) + 'px' }\">\n <template v-for=\"(row, rowIdx) in headerRows\">\n <template v-for=\"cell in row\">\n <template v-if=\"isCellVisible(cell, rowIdx, true)\">\n <div\n :key=\"'fh-' + rowIdx + '-' + (cell.column.key || cell.column.title)\"\n class=\"eg-header-cell\"\n :class=\"{ 'eg-header-group': cell.isGroup }\"\n :style=\"getHeaderCellStyle(cell, rowIdx, true)\"\n @mousedown=\"onColHeaderMouseDown($event, cell)\"\n @dblclick=\"onColHeaderDblClick($event, cell)\"\n @contextmenu.prevent=\"onColHeaderContextMenu($event, cell)\"\n >\n <slot v-if=\"!cell.isGroup\" :name=\"'header-' + cell.column.key\" :column=\"cell.column\">\n {{ cell.column.title }}\n </slot>\n <span v-else>{{ cell.column.title }}</span>\n <div\n v-if=\"!cell.isGroup && cell.rowspan === headerDepth\"\n class=\"eg-col-resize-handle\"\n @mousedown.stop=\"startColResize($event, cell.column._flatIndex)\"\n ></div>\n </div>\n </template>\n </template>\n </template>\n </div>\n </div>\n\n \x3c!-- Scrollable columns header --\x3e\n <div class=\"eg-scroll-col-header\" ref=\"scrollColHeader\" :style=\"scrollColHeaderStyle\">\n <div class=\"eg-scroll-col-header-inner\" :style=\"{ width: scrollContentWidth + 'px', height: (headerRowHeight * headerDepth) + 'px', position: 'relative' }\">\n <template v-for=\"(row, rowIdx) in headerRows\">\n <template v-for=\"cell in row\">\n <template v-if=\"isCellVisible(cell, rowIdx, false)\">\n <div\n :key=\"'sh-' + rowIdx + '-' + (cell.column.key || cell.column.title)\"\n class=\"eg-header-cell\"\n :class=\"{ 'eg-header-group': cell.isGroup }\"\n :style=\"getHeaderCellStyle(cell, rowIdx, false)\"\n @mousedown=\"onColHeaderMouseDown($event, cell)\"\n @dblclick=\"onColHeaderDblClick($event, cell)\"\n @contextmenu.prevent=\"onColHeaderContextMenu($event, cell)\"\n >\n <slot v-if=\"!cell.isGroup\" :name=\"'header-' + cell.column.key\" :column=\"cell.column\">\n {{ cell.column.title }}\n </slot>\n <span v-else>{{ cell.column.title }}</span>\n <div\n v-if=\"!cell.isGroup && cell.rowspan === headerDepth\"\n class=\"eg-col-resize-handle\"\n @mousedown.stop=\"startColResize($event, cell.column._flatIndex)\"\n ></div>\n </div>\n </template>\n </template>\n </template>\n </div>\n </div>\n\n \x3c!-- Pinned Right columns header --\x3e\n <div v-if=\"hasPinnedRight\" class=\"eg-pinned-right-header\" :style=\"pinnedRightHeaderStyle\">\n <div :style=\"{ position: 'relative', height: (headerRowHeight * headerDepth) + 'px' }\">\n <template v-for=\"(row, rowIdx) in headerRows\">\n <template v-for=\"cell in row\">\n <template v-if=\"isCellInZone(cell, rowIdx, 'right')\">\n <div\n :key=\"'prh-' + rowIdx + '-' + (cell.column.key || cell.column.title)\"\n class=\"eg-header-cell\"\n :class=\"{ 'eg-header-group': cell.isGroup }\"\n :style=\"getHeaderCellStyleForZone(cell, rowIdx, 'right')\"\n @mousedown=\"onColHeaderMouseDown($event, cell)\"\n @dblclick=\"onColHeaderDblClick($event, cell)\"\n @contextmenu.prevent=\"onColHeaderContextMenu($event, cell)\"\n >\n <slot v-if=\"!cell.isGroup\" :name=\"'header-' + cell.column.key\" :column=\"cell.column\">\n {{ cell.column.title }}\n </slot>\n <span v-else>{{ cell.column.title }}</span>\n <div\n v-if=\"!cell.isGroup && cell.rowspan === headerDepth\"\n class=\"eg-col-resize-handle\"\n @mousedown.stop=\"startColResize($event, cell.column._flatIndex)\"\n ></div>\n </div>\n </template>\n </template>\n </template>\n </div>\n </div>\n </div>\n\n \x3c!-- ===== BODY AREA ===== --\x3e\n <div class=\"eg-body-area\" :style=\"bodyAreaStyle\">\n\n \x3c!-- Row number header column --\x3e\n <div class=\"eg-row-header-col\" :style=\"rowHeaderColStyle\">\n \x3c!-- Scrollable row headers --\x3e\n <div class=\"eg-scroll-row-headers\" :style=\"scrollRowHeadersStyle\" ref=\"scrollRowHeaders\">\n <div :style=\"{ height: totalVirtualHeight + 'px', position: 'relative' }\">\n <div :style=\"{ transform: 'translateY(' + virtualOffsetTop + 'px)' }\">\n <template v-for=\"ri in visibleRowRange\">\n <div\n v-if=\"!isRowHidden(ri)\"\n :key=\"'srh-' + ri\"\n class=\"eg-row-header-cell\"\n :style=\"getRowHeaderStyle(ri)\"\n @mousedown=\"onRowHeaderMouseDown($event, ri)\"\n @contextmenu.prevent=\"onRowContextMenu($event, ri)\"\n >\n {{ ri + 1 }}\n <div class=\"eg-row-resize-handle\" @mousedown.stop=\"startRowResize($event, ri)\"></div>\n </div>\n </template>\n </div>\n </div>\n </div>\n </div>\n\n \x3c!-- Row group outline gutter (left side of rows) --\x3e\n <div v-if=\"rowGroups.length\" class=\"eg-row-outline-col\" :style=\"rowOutlineColStyle\">\n <template v-for=\"group in rowGroups\">\n <div\n :key=\"'rg-' + group.startIndex + '-' + group.endIndex\"\n class=\"eg-group-btn\"\n :class=\"{ collapsed: isGroupCollapsed(group) }\"\n :style=\"getRowGroupBtnStyle(group)\"\n @click=\"toggleGroup(group)\"\n >{{ isGroupCollapsed(group) ? '+' : '−' }}</div>\n </template>\n </div>\n\n \x3c!-- Main scroll body --\x3e\n <div class=\"eg-scroll-body\" ref=\"scrollBody\" :style=\"scrollBodyStyle\" @scroll=\"onBodyScroll\">\n <div class=\"eg-scroll-body-inner\" :style=\"{ width: scrollContentWidth + 'px', height: totalVirtualHeight + 'px', position: 'relative' }\">\n\n \x3c!-- Pinned left column body --\x3e\n <div v-if=\"getFrozenWidth() > 0\" class=\"eg-frozen-col-body\" :style=\"frozenColBodyStyle\">\n <div :style=\"{ height: totalVirtualHeight + 'px', position: 'relative' }\">\n <div :style=\"{ transform: 'translateY(' + virtualOffsetTop + 'px)' }\">\n <template v-for=\"ri in visibleRowRange\">\n <template v-if=\"!isRowHidden(ri)\">\n <div :key=\"'fc-row-' + ri\" class=\"eg-row\" :style=\"getRowStyle(ri)\">\n <template v-for=\"(col, ci) in leafCols\">\n <template v-if=\"isColumnPinnedLeft(ci) && !isColHidden(ci)\">\n <div\n :key=\"'fc-cell-' + ri + '-' + ci\"\n class=\"eg-cell\"\n :class=\"getCellClasses(ri, ci)\"\n :style=\"getCellStyle(ri, ci)\"\n @mousedown=\"onCellMouseDown($event, ri, ci)\"\n @mouseover=\"onCellMouseOver($event, ri, ci)\"\n @mouseup=\"onCellMouseUp\"\n @dblclick=\"onCellDblClick(ri, ci)\"\n @contextmenu.prevent=\"onCellContextMenu($event, ri, ci)\"\n >\n <div v-if=\"isMergedAway(ri, ci)\" style=\"display:none\"></div>\n <template v-else-if=\"isEditing(ri, ci)\">\n <input ref=\"editInput\" class=\"eg-cell-input\" v-model=\"editValue\" @blur=\"commitEdit\" @keydown.stop=\"onEditKeyDown\" />\n </template>\n <template v-else-if=\"col.type === 'checkbox'\">\n <input\n type=\"checkbox\"\n class=\"eg-cell-checkbox-input\"\n :checked=\"isCheckboxChecked(ri, ci)\"\n :disabled=\"!isCellEditable(ri, ci)\"\n @change=\"onCheckboxChange($event, ri, ci)\"\n />\n </template>\n <template v-else>\n <slot :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\">\n {{ col.type === 'select' ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}\n </slot>\n </template>\n </div>\n </template>\n </template>\n </div>\n </template>\n </template>\n </div>\n </div>\n </div>\n\n \x3c!-- Main data cells --\x3e\n <div class=\"eg-main-cells\" :style=\"mainCellsStyle\">\n <div :style=\"{ height: totalVirtualHeight + 'px', position: 'relative', width: scrollableContentWidth + 'px' }\">\n <div :style=\"{ transform: 'translateY(' + virtualOffsetTop + 'px)' }\">\n <template v-for=\"ri in visibleRowRange\">\n <template v-if=\"!isRowHidden(ri)\">\n <div\n :key=\"'row-' + ri\"\n class=\"eg-row\"\n :style=\"getRowStyle(ri)\"\n :data-row=\"ri\"\n >\n <template v-for=\"(col, ci) in leafCols\">\n <template v-if=\"isColumnScrollable(ci) && !isColHidden(ci)\">\n <div\n :key=\"'cell-' + ri + '-' + ci\"\n class=\"eg-cell\"\n :class=\"getCellClasses(ri, ci)\"\n :style=\"getCellStyle(ri, ci)\"\n @mousedown=\"onCellMouseDown($event, ri, ci)\"\n @mouseover=\"onCellMouseOver($event, ri, ci)\"\n @mouseup=\"onCellMouseUp\"\n @dblclick=\"onCellDblClick(ri, ci)\"\n @contextmenu.prevent=\"onCellContextMenu($event, ri, ci)\"\n >\n <div v-if=\"isMergedAway(ri, ci)\" style=\"display:none\"></div>\n <template v-else-if=\"isEditing(ri, ci)\">\n <input\n ref=\"editInput\"\n class=\"eg-cell-input\"\n v-model=\"editValue\"\n @blur=\"commitEdit\"\n @keydown.stop=\"onEditKeyDown\"\n />\n </template>\n <template v-else-if=\"col.type === 'checkbox'\">\n <input\n type=\"checkbox\"\n class=\"eg-cell-checkbox-input\"\n :checked=\"isCheckboxChecked(ri, ci)\"\n :disabled=\"!isCellEditable(ri, ci)\"\n @change=\"onCheckboxChange($event, ri, ci)\"\n />\n </template>\n <template v-else>\n <slot :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\">\n {{ col.type === 'select' ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}\n </slot>\n </template>\n </div>\n </template>\n </template>\n </div>\n </template>\n </template>\n </div>\n </div>\n </div>\n\n \x3c!-- Pinned Right column body --\x3e\n <div v-if=\"hasPinnedRight\" class=\"eg-pinned-right-body\" :style=\"pinnedRightBodyStyle\">\n <div :style=\"{ height: totalVirtualHeight + 'px', position: 'relative' }\">\n <div :style=\"{ transform: 'translateY(' + virtualOffsetTop + 'px)' }\">\n <template v-for=\"ri in visibleRowRange\">\n <template v-if=\"!isRowHidden(ri)\">\n <div :key=\"'pr-row-' + ri\" class=\"eg-row\" :style=\"getRowStyle(ri)\">\n <template v-for=\"col in pinnedRightCols\">\n <div\n :key=\"'pr-cell-' + ri + '-' + col._flatIndex\"\n class=\"eg-cell\"\n :class=\"getCellClasses(ri, col._flatIndex)\"\n :style=\"getCellStyle(ri, col._flatIndex)\"\n @mousedown=\"onCellMouseDown($event, ri, col._flatIndex)\"\n @mouseover=\"onCellMouseOver($event, ri, col._flatIndex)\"\n @mouseup=\"onCellMouseUp\"\n @dblclick=\"onCellDblClick(ri, col._flatIndex)\"\n @contextmenu.prevent=\"onCellContextMenu($event, ri, col._flatIndex)\"\n >\n <div v-if=\"isMergedAway(ri, col._flatIndex)\" style=\"display:none\"></div>\n <template v-else-if=\"isEditing(ri, col._flatIndex)\">\n <input ref=\"editInput\" class=\"eg-cell-input\" v-model=\"editValue\" @blur=\"commitEdit\" @keydown.stop=\"onEditKeyDown\" />\n </template>\n <template v-else-if=\"col.type === 'checkbox'\">\n <input\n type=\"checkbox\"\n class=\"eg-cell-checkbox-input\"\n :checked=\"isCheckboxChecked(ri, col._flatIndex)\"\n :disabled=\"!isCellEditable(ri, col._flatIndex)\"\n @change=\"onCheckboxChange($event, ri, col._flatIndex)\"\n />\n </template>\n <template v-else>\n <slot :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\">\n {{ col.type === 'select' ? getSelectLabel(col._flatIndex, getCellValue(ri, col._flatIndex)) : getCellValue(ri, col._flatIndex) }}\n </slot>\n </template>\n </div>\n </template>\n </div>\n </template>\n </template>\n </div>\n </div>\n </div>\n\n \x3c!-- Fill handle (shown on primary range) --\x3e\n <div\n v-if=\"fillHandleVisible\"\n class=\"eg-fill-handle\"\n :style=\"fillHandleStyle\"\n @mousedown.stop=\"startFillDrag\"\n ></div>\n\n \x3c!-- Drag move ghost border --\x3e\n <div\n v-if=\"dragMove.active\"\n class=\"eg-drag-ghost\"\n :style=\"dragGhostStyle\"\n ></div>\n </div>\n </div>\n\n \x3c!-- Column group outline row (top, above body) --\x3e\n <div v-if=\"colGroups.length\" class=\"eg-col-outline-row\" :style=\"colOutlineRowStyle\">\n <template v-for=\"group in colGroups\">\n <div\n :key=\"'cg-' + group.startIndex + '-' + group.endIndex\"\n class=\"eg-group-btn\"\n :class=\"{ collapsed: isGroupCollapsed(group) }\"\n :style=\"getColGroupBtnStyle(group)\"\n @click=\"toggleGroup(group)\"\n >{{ isGroupCollapsed(group) ? '+' : '−' }}</div>\n </template>\n </div>\n\n </div>\x3c!-- end body-area --\x3e\n\n \x3c!-- Resize indicator line --\x3e\n <div v-if=\"resizing.active\" class=\"eg-resize-line\" :style=\"resizeLineStyle\"></div>\n </div>\x3c!-- end container --\x3e\n\n \x3c!-- Import progress overlay --\x3e\n <div v-if=\"importState.active\" class=\"eg-import-overlay\">\n <div class=\"eg-import-dialog\">\n <div class=\"eg-import-title\">Fayl import qilinmoqda</div>\n <div class=\"eg-import-bar-wrap\">\n <div class=\"eg-import-bar\" :style=\"{ width: importState.percent + '%' }\"></div>\n </div>\n <div class=\"eg-import-msg\">{{ importState.message }}</div>\n <div class=\"eg-import-pct\">{{ importState.percent }}%</div>\n </div>\n </div>\n\n \x3c!-- Export progress overlay --\x3e\n <div v-if=\"exportState.active\" class=\"eg-import-overlay\">\n <div class=\"eg-import-dialog\">\n <div class=\"eg-import-title\">Fayl export qilinmoqda...</div>\n <div class=\"eg-export-spinner\"></div>\n </div>\n </div>\n\n \x3c!-- Select dropdown overlay --\x3e\n <div\n v-if=\"selectDropdown\"\n class=\"eg-select-dropdown\"\n :style=\"dropdownStyle\"\n @mousedown.prevent\n >\n <div\n v-for=\"(opt, idx) in selectDropdown.options\"\n :key=\"opt.value\"\n class=\"eg-select-option\"\n :class=\"{\n 'eg-select-option--active': opt.value === selectDropdown.currentValue,\n 'eg-select-option--highlighted': idx === selectDropdown.highlight\n }\"\n @mousedown.prevent=\"commitSelectValue(opt.value)\"\n @mouseover=\"selectDropdown.highlight = idx\"\n >\n <span v-if=\"opt.color\" class=\"eg-select-dot\" :style=\"{ background: opt.color }\"></span>\n {{ opt.label }}\n </div>\n </div>\n\n \x3c!-- Validation error tooltip (cellValidator) --\x3e\n <div\n v-if=\"invalidCell && invalidCell.rect\"\n class=\"eg-validation-tooltip\"\n :style=\"invalidTooltipStyle\"\n >{{ invalidCell.message }}</div>\n </div>\n</template>\n\n<script>\nimport {\n normalizeRange, isCellInRange, isCellInAnyRange,\n buildRange, singleCellRange, getPrimaryRange\n} from '../utils/selection.js'\nimport { flattenColumns, buildHeaderRows, columnTreeDepth } from '../utils/headerTree.js'\nimport { calcVisibleRows, buildCumulativeHeights, findVisibleRange, getRowTop, getRowHeight } from '../utils/virtualScroll.js'\nimport { generateFillValues } from '../utils/fillHandle.js'\nimport {\n matrixToTSV, tsvToMatrix, extractMatrix, transposeMatrix, applyMatrix, tileMatrix\n} from '../utils/clipboard.js'\nimport {\n mergeHasConflict, findMergeAtCell, isMergeAnchor, addMerge,\n removeMergeAt, resolveMergeAnchor\n} from '../utils/merge.js'\nimport {\n addGroup, removeGroup, toggleGroupCollapse, isGroupCollapsed as _isGroupCollapsed,\n getCollapsedRowIndices, getCollapsedColIndices\n} from '../utils/outline.js'\nimport { exportToExcel as _exportToExcel } from '../utils/excelExport.js'\nimport { importFromFile as _importFromFile } from '../utils/excelImport.js'\nimport { createExcelImportWorker, createExcelExportWorker } from '../utils/workerFactory'\nimport { getMessages } from '../utils/i18n.js'\n\nconst ROW_HEIGHT = 28\nconst HEADER_ROW_HEIGHT = 28\nconst ROW_HEADER_WIDTH = 50\nconst DEFAULT_COL_WIDTH = 100\nconst OUTLINE_GUTTER = 20\n\nexport default {\n name: 'ExcelGrid',\n\n // v-model → :data + @input\n model: { prop: 'data', event: 'input' },\n\n props: {\n /** Array of row objects keyed by columns[].key */\n data: { type: Array, default: () => [] },\n\n /** Column definitions (may be nested via children[]) */\n columns: { type: Array, default: () => [] },\n\n /** Indices of hidden leaf columns */\n hiddenColumns: { type: Array, default: () => [] },\n\n /** Indices of hidden rows */\n hiddenRows: { type: Array, default: () => [] },\n\n /** Initial merge descriptors [{ startRow, startCol, endRow, endCol }] */\n merges: { type: Array, default: () => [] },\n\n /** Collapsed group descriptors [{ type, startIndex, endIndex }] */\n collapsedGroups: { type: Array, default: () => [] },\n\n /** Height of the grid container in px */\n height: { type: Number, default: 400 },\n\n /** Width of the grid container (px or 'auto') */\n width: { type: [Number, String], default: '100%' },\n\n /**\n * Called before drag-drop: (sourceRange, targetRange) => bool\n * Return false to cancel the drop.\n */\n beforeDrop: { type: Function, default: null },\n\n /**\n * UI locale for the context menu.\n * Supported: 'en' | 'ru' | 'uz' | 'uz-Cyrl'\n */\n locale: { type: String, default: 'en' },\n\n /**\n * Pinned columns map: { [colKey]: 'left' | 'right' }\n * Pinned-left columns render as a fixed zone on the left.\n * Pinned-right columns render as a fixed zone on the right.\n * Emit 'update:pinnedColumns' to support .sync modifier.\n */\n pinnedColumns: { type: Object, default: () => ({}) },\n\n /**\n * Dynamic cell editability: (row, rowIndex, col, colIndex) => boolean\n * Overrides column-level editable when provided.\n * row = plain data object, col = column definition.\n */\n cellEditable: { type: Function, default: null },\n\n /**\n * Cell value validation: (value, row, rowIndex, col, colIndex) => boolean\n * Return false to reject the write — the cell keeps its old value and\n * an error tooltip is shown anchored to that cell.\n */\n cellValidator: { type: Function, default: null },\n\n /**\n * Error tooltip text for a rejected value.\n * Function: (value, row, rowIndex, col, colIndex) => string\n * String: same message for every rejected cell.\n * Default: locale's \"invalidValue\" message.\n */\n cellValidationMessage: { type: [Function, String], default: null },\n\n /**\n * Bulk-write strictness (paste / fill handle / Delete).\n * false (default): invalid cells in the affected range are skipped,\n * the rest of the operation still applies.\n * true: if ANY cell in the affected range fails validation, the whole\n * bulk operation is rejected — nothing is written.\n */\n strictBulkValidation: { type: Boolean, default: false }\n },\n\n data() {\n return {\n // Selection\n activeCell: { row: 0, col: 0 },\n selectionRanges: [], // [{ startRow, startCol, endRow, endCol }]\n isMouseDown: false,\n mouseDownRange: null,\n isCtrlDrag: false,\n\n // Edit\n editingCell: null, // { row, col }\n editValue: '',\n editOriginalValue: '',\n\n // Scroll\n scrollTop: 0,\n scrollLeft: 0,\n headerViewportWidth: null,\n\n // Virtual scroll\n virtualStartIndex: 0,\n virtualEndIndex: 0,\n virtualOffsetTop: 0,\n totalVirtualHeight: 0,\n\n // Column widths (flat index → px)\n colWidths: {},\n\n // Row heights (row index → px)\n rowHeights: {},\n\n // Import/export progress\n importState: { active: false, percent: 0, message: '' },\n exportState: { active: false },\n\n // Resize state\n resizing: {\n active: false,\n type: null, // 'col' | 'row'\n index: -1,\n startX: 0,\n startY: 0,\n startSize: 0,\n currentPos: 0\n },\n\n // Fill handle\n fillDrag: {\n active: false,\n sourceRange: null,\n currentRow: -1,\n currentCol: -1\n },\n\n // Drag-move\n dragMove: {\n active: false,\n sourceRange: null,\n startX: 0,\n startY: 0,\n targetRow: -1,\n targetCol: -1,\n copyMode: false\n },\n\n // Clipboard (internal)\n clipboardMatrix: null,\n clipboardRange: null,\n clipboardMode: null, // 'copy' | 'cut'\n\n // Context menu\n contextMenu: { visible: false, x: 0, y: 0, row: -1, col: -1 },\n\n // Select dropdown\n selectDropdown: null, // { row, col, options, currentValue, rect }\n\n // Cell value validation (cellValidator)\n invalidCell: null, // { row, col, message, rect }\n\n // Merges (controlled via prop + update:merges)\n internalMerges: [],\n\n // Groups\n internalGroups: [], // [{ type, startIndex, endIndex }]\n internalCollapsedGroups: [],\n\n // Header\n headerDepth: 1,\n headerRows: [],\n\n // Leaf columns cache\n leafCols: []\n }\n },\n\n computed: {\n t() { return getMessages(this.locale) },\n\n pinnedLeftSet() {\n const s = new Set()\n this.leafCols.forEach(col => { if (this.pinnedColumns[col.key] === 'left') s.add(col._flatIndex) })\n return s\n },\n\n pinnedRightSet() {\n const s = new Set()\n this.leafCols.forEach(col => { if (this.pinnedColumns[col.key] === 'right') s.add(col._flatIndex) })\n return s\n },\n\n pinnedLeftCols() { return this.leafCols.filter(col => this.pinnedLeftSet.has(col._flatIndex)) },\n pinnedRightCols() { return this.leafCols.filter(col => this.pinnedRightSet.has(col._flatIndex)) },\n\n hasPinnedLeft() { return this.pinnedLeftSet.size > 0 },\n hasPinnedRight() { return this.pinnedRightSet.size > 0 },\n\n pinnedLeftWidth() {\n let w = 0\n for (const col of this.pinnedLeftCols) {\n if (!this.isColHidden(col._flatIndex)) w += this.getColW(col._flatIndex)\n }\n return w\n },\n\n pinnedRightWidth() {\n let w = 0\n for (const col of this.pinnedRightCols) {\n if (!this.isColHidden(col._flatIndex)) w += this.getColW(col._flatIndex)\n }\n return w\n },\n\n totalRows() { return this.data.length },\n\n headerRowHeight() { return HEADER_ROW_HEIGHT },\n\n // Pre-built cumulative height array — O(n) once, then cached by Vue.\n // findVisibleRange() uses binary search against this cached array → O(log n) per scroll.\n // Invalidated only when row count, custom heights, or hidden rows change.\n cumulativeHeights() {\n return buildCumulativeHeights(this.totalRows, ROW_HEIGHT, this.rowHeights, this.hiddenRowSet)\n },\n\n hiddenColSet() { return new Set(this.hiddenColumns) },\n\n hiddenRowSet() {\n const s = new Set(this.hiddenRows)\n // Also add rows hidden by collapsed row groups\n const collapsed = getCollapsedRowIndices(this.internalGroups, this.internalCollapsedGroups)\n collapsed.forEach(i => s.add(i))\n return s\n },\n\n hiddenColFromGroupSet() {\n return getCollapsedColIndices(this.internalGroups, this.internalCollapsedGroups)\n },\n\n effectiveHiddenColSet() {\n const s = new Set(this.hiddenColSet)\n this.hiddenColFromGroupSet.forEach(i => s.add(i))\n return s\n },\n\n rowGroups() { return this.internalGroups.filter(g => g.type === 'row') },\n colGroups() { return this.internalGroups.filter(g => g.type === 'column') },\n\n visibleRowRange() {\n const rows = []\n for (let i = this.virtualStartIndex; i <= this.virtualEndIndex; i++) rows.push(i)\n return rows\n },\n\n primaryRange() {\n return getPrimaryRange(this.selectionRanges, this.activeCell)\n },\n\n fillHandleVisible() {\n return this.primaryRange && !this.editingCell\n },\n\n fillHandleStyle() {\n if (!this.primaryRange) return {}\n const r = normalizeRange(this.primaryRange)\n // O(1) lookup via cumulativeHeights instead of O(endRow) getRowTop loop\n const bottom = this.cumulativeHeights[r.endRow + 1] || 0\n const right = this.getColLeft(r.endCol + 1)\n return {\n position: 'absolute',\n width: '6px',\n height: '6px',\n background: '#1a73e8',\n border: '1px solid white',\n bottom: (this.totalVirtualHeight - bottom + 1) + 'px',\n right: (this.scrollContentWidth - right + 1) + 'px',\n cursor: 'crosshair',\n zIndex: 10\n }\n },\n\n dropdownStyle() {\n if (!this.selectDropdown) return {}\n const { rect, options } = this.selectDropdown\n const approxHeight = Math.min(options.length * 32 + 8, 200)\n const spaceBelow = window.innerHeight - rect.bottom\n const top = spaceBelow >= approxHeight ? rect.bottom : rect.top - approxHeight\n return {\n position: 'fixed',\n top: top + 'px',\n left: rect.left + 'px',\n minWidth: Math.max(rect.width, 120) + 'px',\n zIndex: 9999\n }\n },\n\n invalidTooltipStyle() {\n if (!this.invalidCell || !this.invalidCell.rect) return {}\n const { rect } = this.invalidCell\n return {\n position: 'fixed',\n top: (rect.bottom + 4) + 'px',\n left: rect.left + 'px',\n zIndex: 9999\n }\n },\n\n dragGhostStyle() {\n if (!this.dragMove.active || this.dragMove.targetRow < 0) return {}\n const r = normalizeRange(this.dragMove.sourceRange)\n const rows = r.endRow - r.startRow\n const cols = r.endCol - r.startCol\n // O(1) lookup via cumulativeHeights instead of O(targetRow) getRowTop loop\n const top = this.cumulativeHeights[this.dragMove.targetRow] || 0\n const left = this.getColLeft(this.dragMove.targetCol)\n const width = this.getRangeWidth(this.dragMove.targetCol, this.dragMove.targetCol + cols)\n const height = this.getRangeHeight(this.dragMove.targetRow, this.dragMove.targetRow + rows)\n return {\n position: 'absolute',\n top: top + 'px',\n left: left + 'px',\n width: width + 'px',\n height: height + 'px',\n border: '2px dashed #1a73e8',\n pointerEvents: 'none',\n zIndex: 20\n }\n },\n\n wrapperStyle() {\n return {\n width: typeof this.width === 'number' ? this.width + 'px' : this.width,\n height: this.height + 'px',\n position: 'relative',\n outline: 'none',\n userSelect: 'none'\n }\n },\n\n headerAreaStyle() {\n return {\n display: 'flex',\n flexDirection: 'row',\n position: 'sticky',\n top: 0,\n zIndex: 30,\n background: '#f8f9fa'\n }\n },\n\n cornerStyle() {\n const outlineW = this.rowGroups.length ? OUTLINE_GUTTER : 0\n return {\n width: (ROW_HEADER_WIDTH + outlineW) + 'px',\n minWidth: (ROW_HEADER_WIDTH + outlineW) + 'px',\n height: (HEADER_ROW_HEIGHT * this.headerDepth) + 'px',\n background: '#f0f0f0',\n borderRight: '1px solid #ccc',\n borderBottom: '1px solid #ccc',\n boxSizing: 'border-box',\n flexShrink: 0\n }\n },\n\n rowOutlineGutterStyle() {\n return { width: OUTLINE_GUTTER + 'px', flexShrink: 0 }\n },\n\n frozenColHeaderStyle() {\n const w = this.getFrozenWidth()\n return {\n width: w + 'px',\n minWidth: w + 'px',\n overflow: 'hidden',\n flexShrink: 0\n }\n },\n\n scrollColHeaderStyle() {\n // Once measured, lock the header to the body's exact clientWidth (flex: 0 0 <px>)\n // instead of growing independently via flex:1 — this guarantees identical\n // scrollWidth-clientWidth (max scrollLeft) on both, so syncing scrollLeft never clamps\n // one before the other. (padding-right can't do this: clientWidth always includes\n // padding, so padding never actually shrinks it.)\n if (this.headerViewportWidth != null) {\n return { flex: '0 0 ' + this.headerViewportWidth + 'px', overflow: 'hidden' }\n }\n return { flex: 1, overflow: 'hidden' }\n },\n\n bodyAreaStyle() {\n return {\n display: 'flex',\n flexDirection: 'row',\n flex: 1,\n position: 'relative'\n }\n },\n\n rowHeaderColStyle() {\n return {\n width: ROW_HEADER_WIDTH + 'px',\n minWidth: ROW_HEADER_WIDTH + 'px',\n flexShrink: 0,\n display: 'flex',\n flexDirection: 'column',\n position: 'sticky',\n left: 0,\n zIndex: 20,\n background: '#f0f0f0'\n }\n },\n\n rowOutlineColStyle() {\n return {\n width: OUTLINE_GUTTER + 'px',\n minWidth: OUTLINE_GUTTER + 'px',\n flexShrink: 0,\n position: 'relative',\n background: '#fafafa'\n }\n },\n\n scrollBodyStyle() {\n return {\n flex: 1,\n overflow: 'auto',\n position: 'relative'\n }\n },\n\n frozenColBodyStyle() {\n const w = this.getFrozenWidth()\n return {\n position: 'sticky',\n left: 0,\n width: w + 'px',\n zIndex: 10,\n background: 'white',\n overflow: 'hidden'\n }\n },\n\n mainCellsStyle() {\n const left = this.getFrozenWidth()\n return {\n position: 'absolute',\n left: left + 'px',\n top: 0,\n bottom: 0\n }\n },\n\n scrollContentWidth() {\n let w = 0\n for (let i = 0; i < this.leafCols.length; i++) {\n if (!this.isColHidden(i) && !this.pinnedRightSet.has(i)) w += this.getColW(i)\n }\n // Extra right padding so pinned-right zone doesn't cover the last scrollable column\n if (this.hasPinnedRight) w += this.pinnedRightWidth\n return w\n },\n\n scrollableContentWidth() {\n let w = 0\n for (let i = 0; i < this.leafCols.length; i++) {\n if (!this.isColHidden(i) && this.isColumnScrollable(i)) w += this.getColW(i)\n }\n return w\n },\n\n pinnedRightBodyStyle() {\n return {\n position: 'sticky',\n right: 0,\n width: this.pinnedRightWidth + 'px',\n zIndex: 10,\n background: 'white',\n flexShrink: 0\n }\n },\n\n pinnedRightHeaderStyle() {\n return {\n width: this.pinnedRightWidth + 'px',\n minWidth: this.pinnedRightWidth + 'px',\n overflow: 'hidden',\n flexShrink: 0,\n borderLeft: '1px solid #ccc'\n }\n },\n\n scrollRowHeadersStyle() {\n return { flex: 1, overflow: 'hidden', position: 'relative' }\n },\n\n colOutlineRowStyle() {\n return {\n position: 'absolute',\n top: -OUTLINE_GUTTER + 'px',\n left: 0,\n right: 0,\n height: OUTLINE_GUTTER + 'px'\n }\n },\n\n resizeLineStyle() {\n if (!this.resizing.active) return {}\n if (this.resizing.type === 'col') {\n return {\n position: 'fixed',\n top: 0,\n left: this.resizing.currentPos + 'px',\n width: '2px',\n height: '100%',\n background: '#1a73e8',\n pointerEvents: 'none',\n zIndex: 9999\n }\n } else {\n return {\n position: 'fixed',\n left: 0,\n top: this.resizing.currentPos + 'px',\n height: '2px',\n width: '100%',\n background: '#1a73e8',\n pointerEvents: 'none',\n zIndex: 9999\n }\n }\n }\n },\n\n watch: {\n columns: {\n immediate: true,\n handler(cols) {\n this.leafCols = flattenColumns(cols)\n this.headerDepth = columnTreeDepth(cols)\n this.headerRows = buildHeaderRows(cols, this.headerDepth)\n // Init colWidths\n this.leafCols.forEach(col => {\n if (this.colWidths[col._flatIndex] === undefined) {\n this.colWidths[col._flatIndex] = col.width || DEFAULT_COL_WIDTH\n }\n })\n }\n },\n merges: {\n immediate: true,\n handler(v) { this.internalMerges = v ? [...v] : [] }\n },\n collapsedGroups: {\n immediate: true,\n handler(v) { this.internalCollapsedGroups = v ? [...v] : [] }\n },\n data: {\n immediate: true,\n handler(newData) {\n // Keep _dataRaw in sync so template uses fresh data without triggering dependArray\n this._dataRaw = newData || []\n this.updateVirtualScroll()\n // Force re-render so cell values refresh even when virtualScroll indices don't change\n // (e.g. editing a cell: same row count, same scroll position, only content changed)\n this.$forceUpdate()\n }\n },\n totalRows() { this.updateVirtualScroll() },\n hiddenRowSet() { this.updateVirtualScroll() },\n totalVirtualHeight() { this.$nextTick(this.syncHeaderViewportWidth) }\n },\n\n created() {\n // Non-reactive row data reference — avoids Vue 2 dependArray(data) O(n) loop.\n // Vue 2 calls dependArray() for EVERY `data[ri]` access in the template,\n // iterating all 50,000 rows each time. With 1200 cells per render that's\n // 60 million iterations per render (~600ms). _dataRaw bypasses this.\n this._dataRaw = this.data || []\n\n // Non-reactive caches: same (w,h) → same style object → Vue 2 skips DOM style writes\n this._cellStyleCache = Object.create(null)\n this._rowStyleCache = Object.create(null)\n this._rowHeaderStyleCache = Object.create(null)\n // rAF handles for scroll/resize throttle\n this._scrollRAF = null\n this._resizeRAF = null\n\n // Undo/redo history — non-reactive to avoid Vue overhead.\n // Each entry: { type: 'data'|'merges', before: {...}, after: {...} }\n // 'data' stores only diff rows: { indices: number[], rows: object[] }\n this._history = []\n this._historyIndex = -1\n this._historySkip = false // true while undo/redo is in progress\n\n },\n\n mounted() {\n this.updateVirtualScroll()\n this.$nextTick(this.syncHeaderViewportWidth)\n document.addEventListener('mousemove', this.onGlobalMouseMove)\n document.addEventListener('mouseup', this.onGlobalMouseUp)\n document.addEventListener('keydown', this.onGlobalKeyDown)\n document.addEventListener('mousedown', this._onDocMouseDownForDropdown, true)\n window.addEventListener('resize', this.syncHeaderViewportWidth)\n },\n\n beforeDestroy() {\n document.removeEventListener('mousemove', this.onGlobalMouseMove)\n document.removeEventListener('mouseup', this.onGlobalMouseUp)\n document.removeEventListener('keydown', this.onGlobalKeyDown)\n document.removeEventListener('mousedown', this._onDocMouseDownForDropdown, true)\n window.removeEventListener('resize', this.syncHeaderViewportWidth)\n },\n\n methods: {\n // =========================================================\n // VIRTUAL SCROLL\n // =========================================================\n updateVirtualScroll() {\n const containerH = this.height - (HEADER_ROW_HEIGHT * this.headerDepth)\n // Use binary search against the Vue-cached cumulativeHeights → O(log n) per scroll\n const result = findVisibleRange(\n this.scrollTop, Math.max(containerH, 100), this.totalRows, this.cumulativeHeights\n )\n this.virtualStartIndex = result.startIndex\n this.virtualEndIndex = result.endIndex\n this.virtualOffsetTop = result.offsetTop\n this.totalVirtualHeight = result.totalHeight\n },\n\n onBodyScroll(e) {\n const target = e.target\n // Sync sibling scroll containers immediately (visual alignment, no layout trigger)\n if (this.$refs.scrollColHeader) this.$refs.scrollColHeader.scrollLeft = target.scrollLeft\n if (this.$refs.scrollRowHeaders) this.$refs.scrollRowHeaders.scrollTop = target.scrollTop\n\n // Throttle reactive state updates + virtual scroll recalc to one rAF per frame\n if (!this._scrollRAF) {\n this._scrollRAF = requestAnimationFrame(() => {\n this._scrollRAF = null\n this.scrollTop = target.scrollTop\n this.scrollLeft = target.scrollLeft\n this.updateVirtualScroll()\n })\n }\n },\n\n onScroll(e) {},\n\n // .eg-scroll-col-header (overflow:hidden, synced via JS) has no native scrollbar, but\n // .eg-scroll-body (overflow:auto) does — its real vertical scrollbar eats into its\n // clientWidth. If both containers just grow via flex:1, the header ends up wider than\n // the body, so its max scrollLeft is smaller and syncing clamps it before the body\n // reaches its rightmost scroll position — column borders desync right at the scroll\n // end. Locking the header to the body's exact measured clientWidth (scrollColHeaderStyle)\n // makes both max scrollLeft values identical.\n syncHeaderViewportWidth() {\n const el = this.$refs.scrollBody\n if (!el) return\n if (this.headerViewportWidth !== el.clientWidth) this.headerViewportWidth = el.clientWidth\n },\n\n // =========================================================\n // HELPERS\n // =========================================================\n isRowHidden(ri) { return this.hiddenRowSet.has(ri) },\n isColHidden(ci) { return this.effectiveHiddenColSet.has(ci) },\n\n getColW(ci) {\n return this.colWidths[ci] !== undefined ? this.colWidths[ci] : (this.leafCols[ci] ? this.leafCols[ci].width || DEFAULT_COL_WIDTH : DEFAULT_COL_WIDTH)\n },\n\n getRowH(ri) {\n if (this.isRowHidden(ri)) return 0\n return this.rowHeights[ri] || ROW_HEIGHT\n },\n\n getColLeft(ci) {\n let left = 0\n for (let i = 0; i < ci && i < this.leafCols.length; i++) {\n if (!this.isColHidden(i)) left += this.getColW(i)\n }\n return left\n },\n\n getFrozenWidth() {\n return this.hasPinnedLeft ? this.pinnedLeftWidth : 0\n },\n\n isColumnPinnedLeft(ci) {\n return this.pinnedLeftSet.has(ci)\n },\n\n isColumnPinnedRight(ci) {\n return this.pinnedRightSet.has(ci)\n },\n\n isColumnScrollable(ci) {\n return !this.isColumnPinnedLeft(ci) && !this.isColumnPinnedRight(ci)\n },\n\n getZoneColLeft(ci, zone) {\n let left = 0\n if (zone === 'left') {\n for (const col of this.pinnedLeftCols) {\n if (col._flatIndex === ci) break\n if (!this.isColHidden(col._flatIndex)) left += this.getColW(col._flatIndex)\n }\n } else if (zone === 'right') {\n for (const col of this.pinnedRightCols) {\n if (col._flatIndex === ci) break\n if (!this.isColHidden(col._flatIndex)) left += this.getColW(col._flatIndex)\n }\n } else {\n for (let i = 0; i < this.leafCols.length; i++) {\n const col = this.leafCols[i]\n if (col._flatIndex === ci) break\n if (!this.isColHidden(col._flatIndex) && this.isColumnScrollable(col._flatIndex)) {\n left += this.getColW(col._flatIndex)\n }\n }\n }\n return left\n },\n\n isCellInZone(cell, rowIdx, zone) {\n if (cell.isGroup) {\n const start = cell._firstLeafFlatIndex\n const end = start + cell.colspan - 1\n for (let i = start; i <= end; i++) {\n if (this.isColHidden(i)) continue\n const z = this.isColumnPinnedLeft(i) ? 'left' : (this.isColumnPinnedRight(i) ? 'right' : 'scroll')\n if (z === zone) return true\n }\n return false\n }\n const ci = cell.column._flatIndex\n if (this.isColHidden(ci)) return false\n const z = this.isColumnPinnedLeft(ci) ? 'left' : (this.isColumnPinnedRight(ci) ? 'right' : 'scroll')\n return z === zone\n },\n\n getHeaderCellStyleForZone(cell, rowIndex, zone) {\n let leftLeafIdx = cell._firstLeafFlatIndex\n if (cell.isGroup) {\n const end = leftLeafIdx + cell.colspan - 1\n for (let i = leftLeafIdx; i <= end; i++) {\n if (this.isColHidden(i)) continue\n const z = this.isColumnPinnedLeft(i) ? 'left' : (this.isColumnPinnedRight(i) ? 'right' : 'scroll')\n if (z === zone) { leftLeafIdx = i; break }\n }\n }\n const left = this.getZoneColLeft(leftLeafIdx, zone)\n let width = 0\n if (cell.isGroup) {\n const start = cell._firstLeafFlatIndex\n const end = start + cell.colspan - 1\n for (let i = start; i <= end; i++) {\n if (this.isColHidden(i)) continue\n const z = this.isColumnPinnedLeft(i) ? 'left' : (this.isColumnPinnedRight(i) ? 'right' : 'scroll')\n if (z !== zone) continue\n width += this.getColW(i)\n }\n } else {\n width = this.getColW(cell.column._flatIndex)\n }\n return {\n position: 'absolute',\n top: (HEADER_ROW_HEIGHT * rowIndex) + 'px',\n left: left + 'px',\n width: width + 'px',\n height: (HEADER_ROW_HEIGHT * cell.rowspan) + 'px',\n display: 'flex', alignItems: 'center', justifyContent: 'center',\n boxSizing: 'border-box',\n borderRight: '1px solid #ccc', borderBottom: '1px solid #ccc',\n background: '#f0f0f0', fontSize: '12px', fontWeight: 600,\n overflow: 'hidden', whiteSpace: 'nowrap', userSelect: 'none', cursor: 'pointer'\n }\n },\n\n getRangeWidth(startCol, endCol) {\n let w = 0\n for (let i = startCol; i <= endCol && i < this.leafCols.length; i++) {\n if (!this.isColHidden(i)) w += this.getColW(i)\n }\n return w\n },\n\n getRangeHeight(startRow, endRow) {\n let h = 0\n for (let i = startRow; i <= endRow && i < this.totalRows; i++) {\n if (!this.isRowHidden(i)) h += this.getRowH(i)\n }\n return h\n },\n\n getCellValue(ri, ci) {\n const col = this.leafCols[ci]\n if (!col) return ''\n const row = this._dataRaw[ri]\n if (!row) return ''\n const v = row[col.key]\n return v !== undefined ? v : ''\n },\n\n isEditing(ri, ci) {\n return this.editingCell && this.editingCell.row === ri && this.editingCell.col === ci\n },\n\n isMergedAway(ri, ci) {\n const merge = findMergeAtCell(ri, ci, this.internalMerges)\n if (!merge) return false\n const nm = normalizeRange(merge)\n return !(nm.startRow === ri && nm.startCol === ci)\n },\n\n getMergeSpan(ri, ci) {\n const merge = findMergeAtCell(ri, ci, this.internalMerges)\n if (!merge) return { rowspan: 1, colspan: 1 }\n const nm = normalizeRange(merge)\n return {\n rowspan: nm.endRow - nm.startRow + 1,\n colspan: nm.endCol - nm.startCol + 1\n }\n },\n\n isSelected(ri, ci) {\n return isCellInAnyRange(ri, ci, this.selectionRanges)\n },\n\n isActiveCell(ri, ci) {\n return this.activeCell.row === ri && this.activeCell.col === ci\n },\n\n isCopyHighlighted(ri, ci) {\n if (!this.clipboardRange || this.clipboardMode !== 'copy') return false\n return isCellInRange(ri, ci, this.clipboardRange)\n },\n\n // =========================================================\n // STYLES\n // =========================================================\n getRowStyle(ri) {\n const h = this.getRowH(ri)\n const key = h\n if (this._rowStyleCache[key]) return this._rowStyleCache[key]\n const style = { height: h + 'px', minHeight: h + 'px' }\n this._rowStyleCache[key] = style\n return style\n },\n\n getRowHeaderStyle(ri) {\n const h = this.getRowH(ri)\n const key = h\n if (this._rowHeaderStyleCache[key]) return this._rowHeaderStyleCache[key]\n const style = { height: h + 'px' }\n this._rowHeaderStyleCache[key] = style\n return style\n },\n\n getCellStyle(ri, ci) {\n const merge = findMergeAtCell(ri, ci, this.internalMerges)\n const nm = merge ? normalizeRange(merge) : null\n let w = this.getColW(ci)\n let h = this.getRowH(ri)\n\n if (nm && nm.startRow === ri && nm.startCol === ci) {\n w = this.getRangeWidth(nm.startCol, nm.endCol)\n h = this.getRangeHeight(nm.startRow, nm.endRow)\n }\n\n // Position cells with the same absolute-left arithmetic the header uses\n // (getZoneColLeft), instead of relying on flex layout to accumulate widths —\n // flex and a JS integer sum can drift apart by a few px over many columns,\n // desyncing the body's column borders from the header's.\n const zone = this.isColumnPinnedLeft(ci) ? 'left' : (this.isColumnPinnedRight(ci) ? 'right' : 'scroll')\n const left = this.getZoneColLeft(ci, zone)\n\n // Cache by dimensions+position — same (w,h,left) → same object ref → Vue 2 skips DOM style writes\n const key = w + '_' + h + '_' + left\n if (this._cellStyleCache[key]) return this._cellStyleCache[key]\n const style = { position: 'absolute', left: left + 'px', top: '0px', width: w + 'px', minWidth: w + 'px', height: h + 'px' }\n this._cellStyleCache[key] = style\n return style\n },\n\n getCellClasses(ri, ci) {\n const col = this.leafCols[ci]\n return {\n 'eg-cell-active': this.isActiveCell(ri, ci),\n 'eg-cell-selected': this.isSelected(ri, ci) && !this.isActiveCell(ri, ci),\n 'eg-cell-fill-preview': this.isInFillPreview(ri, ci),\n 'eg-cell-copy': this.isCopyHighlighted(ri, ci),\n 'eg-cell-merged-hidden': this.isMergedAway(ri, ci),\n 'eg-cell-select': !!(col && col.type === 'select'),\n 'eg-cell-checkbox': !!(col && col.type === 'checkbox'),\n 'eg-cell-readonly': !this.isCellEditable(ri, ci),\n 'eg-cell-invalid': !!(this.invalidCell && this.invalidCell.row === ri && this.invalidCell.col === ci)\n }\n },\n\n isInFillPreview(ri, ci) {\n if (!this.fillDrag.active || !this.fillDrag.sourceRange) return false\n const src = this.fillDrag.sourceRange\n const targetRow = this.fillDrag.currentRow\n const targetCol = this.fillDrag.currentCol\n if (targetRow > src.endRow) {\n return ri > src.endRow && ri <= targetRow && ci >= src.startCol && ci <= src.endCol\n }\n if (targetCol > src.endCol) {\n return ci > src.endCol && ci <= targetCol && ri >= src.startRow && ri <= src.endRow\n }\n return false\n },\n\n getHeaderCellStyle(cell, rowIndex, isFrozen) {\n const zone = isFrozen ? 'left' : 'scroll'\n let leftLeafIdx = cell._firstLeafFlatIndex\n if (cell.isGroup) {\n const end = leftLeafIdx + cell.colspan - 1\n for (let i = leftLeafIdx; i <= end; i++) {\n if (this.isColHidden(i)) continue\n if (isFrozen ? this.isColumnPinnedLeft(i) : this.isColumnScrollable(i)) { leftLeafIdx = i; break }\n }\n }\n const left = this.getZoneColLeft(leftLeafIdx, zone)\n\n let width = 0\n if (cell.isGroup) {\n const start = cell._firstLeafFlatIndex\n const end = start + cell.colspan - 1\n for (let i = start; i <= end; i++) {\n if (this.isColHidden(i)) continue\n const inZone = isFrozen ? this.isColumnPinnedLeft(i) : this.isColumnScrollable(i)\n if (!inZone) continue\n width += this.getColW(i)\n }\n } else {\n width = this.getColW(cell.column._flatIndex)\n }\n\n return {\n position: 'absolute',\n top: (HEADER_ROW_HEIGHT * rowIndex) + 'px',\n left: left + 'px',\n width: width + 'px',\n height: (HEADER_ROW_HEIGHT * cell.rowspan) + 'px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n boxSizing: 'border-box',\n borderRight: '1px solid #ccc',\n borderBottom: '1px solid #ccc',\n background: '#f0f0f0',\n fontSize: '12px',\n fontWeight: 600,\n overflow: 'hidden',\n whiteSpace: 'nowrap',\n userSelect: 'none',\n cursor: 'pointer'\n }\n },\n\n isCellVisible(cell, rowIndex, isFrozen) {\n if (cell.isGroup) {\n const start = cell._firstLeafFlatIndex\n const end = start + cell.colspan - 1\n for (let i = start; i <= end; i++) {\n if (this.isColHidden(i)) continue\n const inZone = isFrozen ? this.isColumnPinnedLeft(i) : this.isColumnScrollable(i)\n if (inZone) return true\n }\n return false\n }\n const ci = cell.column._flatIndex\n if (this.isColHidden(ci)) return false\n return isFrozen ? this.isColumnPinnedLeft(ci) : this.isColumnScrollable(ci)\n },\n\n getRowGroupBtnStyle(group) {\n const top = getRowTop(group.startIndex, ROW_HEIGHT, this.rowHeights) - this.scrollTop\n return {\n position: 'absolute',\n top: top + 'px',\n left: '2px',\n width: '16px',\n height: '16px',\n lineHeight: '16px',\n textAlign: 'center',\n fontSize: '10px',\n cursor: 'pointer',\n background: '#e8eaf6',\n border: '1px solid #9fa8da',\n borderRadius: '2px',\n zIndex: 5\n }\n },\n\n getColGroupBtnStyle(group) {\n const left = this.getColLeft(group.startIndex) - this.scrollLeft\n return {\n position: 'absolute',\n left: left + 'px',\n top: '2px',\n width: '16px',\n height: '16px',\n lineHeight: '16px',\n textAlign: 'center',\n fontSize: '10px',\n cursor: 'pointer',\n background: '#e8eaf6',\n border: '1px solid #9fa8da',\n borderRadius: '2px'\n }\n },\n\n // =========================================================\n // CELL SELECTION (3.1)\n // =========================================================\n selectCell(row, col, addToSelection = false) {\n if (row < 0 || row >= this.totalRows || col < 0 || col >= this.leafCols.length) return\n this.activeCell = { row, col }\n if (addToSelection) {\n this.selectionRanges.push(singleCellRange(row, col))\n } else {\n this.selectionRanges = [singleCellRange(row, col)]\n }\n this.emitSelectionChange()\n },\n\n selectRange(startRow, startCol, endRow, endCol, replace = true) {\n const range = { startRow, startCol, endRow, endCol }\n if (replace) {\n this.selectionRanges = [range]\n } else {\n this.selectionRanges.push(range)\n }\n this.emitSelectionChange()\n },\n\n selectRow(ri) {\n this.activeCell = { row: ri, col: 0 }\n this.selectionRanges = [{ startRow: ri, endRow: ri, startCol: 0, endCol: this.leafCols.length - 1 }]\n this.emitSelectionChange()\n },\n\n selectColumn(ci) {\n this.activeCell = { row: 0, col: ci }\n this.selectionRanges = [{ startRow: 0, endRow: this.totalRows - 1, startCol: ci, endCol: ci }]\n this.emitSelectionChange()\n },\n\n selectAll() {\n this.activeCell = { row: 0, col: 0 }\n this.selectionRanges = [{ startRow: 0, endRow: this.totalRows - 1, startCol: 0, endCol: this.leafCols.length - 1 }]\n this.emitSelectionChange()\n },\n\n emitSelectionChange() {\n this.$emit('selection-change', {\n activeCell: { ...this.activeCell },\n ranges: this.selectionRanges.map(r => normalizeRange(r))\n })\n },\n\n onCellMouseDown(e, ri, ci) {\n this.invalidCell = null\n if (this.editingCell) {\n this.commitEdit()\n }\n this.hideContextMenu()\n this.$refs.wrapper && this.$refs.wrapper.focus()\n\n if (e.ctrlKey || e.metaKey) {\n if (this.selectDropdown) this.closeSelectDropdown()\n // Ctrl+click: start new independent range\n this.activeCell = { row: ri, col: ci }\n this.mouseDownRange = { startRow: ri, startCol: ci, endRow: ri, endCol: ci }\n this.selectionRanges.push(this.mouseDownRange)\n this.isCtrlDrag = true\n } else if (e.shiftKey && this.selectionRanges.length > 0) {\n if (this.selectDropdown) this.closeSelectDropdown()\n // Shift+click: extend primary range to this cell\n this.selectionRanges = [buildRange(this.activeCell, { row: ri, col: ci })]\n } else {\n const wasActive = this.activeCell.row === ri && this.activeCell.col === ci\n this.activeCell = { row: ri, col: ci }\n this.mouseDownRange = { startRow: ri, startCol: ci, endRow: ri, endCol: ci }\n this.selectionRanges = [this.mouseDownRange]\n this.isCtrlDrag = false\n\n // Open select dropdown on click if cell was already active (2nd click)\n const col = this.leafCols[ci]\n if (wasActive && col && col.type === 'select' && this.isCellEditable(ri, ci)) {\n this.openSelectDropdown(ri, ci, e)\n e.preventDefault()\n this.isMouseDown = true\n this.emitSelectionChange()\n return\n }\n if (this.selectDropdown) this.closeSelectDropdown()\n }\n\n this.isMouseDown = true\n this.emitSelectionChange()\n e.preventDefault()\n },\n\n onCellMouseOver(e, ri, ci) {\n if (!this.isMouseDown) return\n if (this.fillDrag.active) return\n if (this.dragMove.active) {\n this.dragMove.targetRow = ri\n this.dragMove.targetCol = ci\n return\n }\n\n // Extend current drag range\n if (this.mouseDownRange) {\n this.mouseDownRange.endRow = ri\n this.mouseDownRange.endCol = ci\n this.emitSelectionChange()\n }\n },\n\n onCellMouseUp() {\n this.isMouseDown = false\n },\n\n onCellDblClick(ri, ci) {\n const col = this.leafCols[ci]\n if (!this.isCellEditable(ri, ci)) return\n if (col && col.type === 'select') {\n this.openSelectDropdownForActive()\n return\n }\n if (col && col.type === 'checkbox') return\n this.startEditing(ri, ci, null, true)\n },\n\n onRowHeaderMouseDown(e, ri) {\n const endCol = this.leafCols.length - 1\n const fullRow = { startRow: ri, endRow: ri, startCol: 0, endCol }\n\n if (e.ctrlKey || e.metaKey) {\n if (e.shiftKey && this.selectionRanges.length > 0) {\n // Ctrl+Shift: active qatordan ri gacha yangi range qo'sh\n const sr = Math.min(this.activeCell.row, ri)\n const er = Math.max(this.activeCell.row, ri)\n this.selectionRanges = [...this.selectionRanges, { startRow: sr, endRow: er, startCol: 0, endCol }]\n } else {\n // Ctrl+click: qatorni toggle qil\n const idx = this.selectionRanges.findIndex(r =>\n r.startRow === ri && r.endRow === ri && r.startCol === 0 && r.endCol === endCol\n )\n if (idx >= 0) {\n const next = this.selectionRanges.filter((_, i) => i !== idx)\n this.selectionRanges = next.length ? next : [fullRow]\n } else {\n this.activeCell = { row: ri, col: 0 }\n this.selectionRanges = [...this.selectionRanges, fullRow]\n }\n }\n } else if (e.shiftKey && this.selectionRanges.length > 0) {\n const sr = Math.min(this.activeCell.row, ri)\n const er = Math.max(this.activeCell.row, ri)\n this.selectionRanges = [{ startRow: sr, endRow: er, startCol: 0, endCol }]\n } else {\n this.activeCell = { row: ri, col: 0 }\n this.selectionRanges = [fullRow]\n }\n\n this.mouseDownRange = this.selectionRanges[this.selectionRanges.length - 1]\n this.isMouseDown = true\n this.emitSelectionChange()\n e.preventDefault()\n },\n\n onColHeaderMouseDown(e, cell) {\n if (cell.isGroup) return\n const ci = cell.column._flatIndex\n if (e.shiftKey && this.selectionRanges.length > 0) {\n const sc = Math.min(this.activeCell.col, ci)\n const ec = Math.max(this.activeCell.col, ci)\n this.selectionRanges = [{ startRow: 0, endRow: this.totalRows - 1, startCol: sc, endCol: ec }]\n } else {\n this.activeCell = { row: 0, col: ci }\n this.selectionRanges = [{ startRow: 0, endRow: this.totalRows - 1, startCol: ci, endCol: ci }]\n }\n this.isMouseDown = true\n this.mouseDownRange = this.selectionRanges[0]\n this.emitSelectionChange()\n e.preventDefault()\n },\n\n onColHeaderDblClick(e, cell) {\n if (cell.isGroup) return\n this.autoFitColumn(cell.column._flatIndex)\n },\n\n onColHeaderContextMenu(e, cell) {\n if (!cell.isGroup) {\n this.contextMenu = { visible: true, x: e.clientX, y: e.clientY, row: -1, col: cell.column._flatIndex }\n }\n },\n\n onRowContextMenu(e, ri) {\n this.contextMenu = { visible: true, x: e.clientX, y: e.clientY, row: ri, col: -1 }\n },\n\n onCellContextMenu(e, ri, ci) {\n if (!this.isSelected(ri, ci)) {\n this.selectCell(ri, ci)\n }\n this.contextMenu = { visible: true, x: e.clientX, y: e.clientY, row: ri, col: ci }\n },\n\n hideContextMenu() {\n this.contextMenu.visible = false\n },\n\n // =========================================================\n // KEYBOARD NAVIGATION (3.2)\n // =========================================================\n onKeyDown(e) {\n // Handle open select dropdown keyboard navigation\n if (this.selectDropdown) {\n const key = e.key\n if (key === 'Escape') {\n e.preventDefault()\n this.closeSelectDropdown()\n return\n }\n if (key === 'Enter' || key === ' ') {\n e.preventDefault()\n const h = this.selectDropdown.highlight\n if (h >= 0 && this.selectDropdown.options[h]) {\n this.commitSelectValue(this.selectDropdown.options[h].value)\n } else {\n this.closeSelectDropdown()\n }\n return\n }\n if (key === 'ArrowDown') {\n e.preventDefault()\n this.selectDropdown.highlight = Math.min(this.selectDropdown.highlight + 1, this.selectDropdown.options.length - 1)\n return\n }\n if (key === 'ArrowUp') {\n e.preventDefault()\n this.selectDropdown.highlight = Math.max(this.selectDropdown.highlight - 1, 0)\n return\n }\n return // block other keys while dropdown open\n }\n\n if (this.editingCell) return // handled by onEditKeyDown\n const key = e.key\n\n if ((e.ctrlKey || e.metaKey) && key === 'z' && !e.shiftKey) {\n e.preventDefault()\n this.undo()\n return\n }\n if ((e.ctrlKey || e.metaKey) && (key === 'y' || (key === 'z' && e.shiftKey))) {\n e.preventDefault()\n this.redo()\n return\n }\n\n if (key === 'a' && (e.ctrlKey || e.metaKey)) {\n e.preventDefault()\n this.selectAll()\n return\n }\n\n if (key === 'c' && (e.ctrlKey || e.metaKey)) {\n e.preventDefault()\n this.copySelection()\n return\n }\n if (key === 'x' && (e.ctrlKey || e.metaKey)) {\n e.preventDefault()\n this.cutSelection()\n return\n }\n if (key === 'v' && (e.ctrlKey || e.metaKey)) {\n e.preventDefault()\n this.pasteAt(this.activeCell.row, this.activeCell.col)\n return\n }\n\n if (key === 'Delete' || key === 'Backspace') {\n e.preventDefault()\n this.clearSelection()\n return\n }\n\n if (key === 'F2') {\n e.preventDefault()\n const activeCol = this.leafCols[this.activeCell.col]\n if (activeCol && activeCol.type === 'select') {\n this.openSelectDropdownForActive()\n } else if (activeCol && activeCol.type === 'checkbox') {\n this.toggleCheckbox(this.activeCell.row, this.activeCell.col)\n } else {\n this.startEditing(this.activeCell.row, this.activeCell.col, null, true)\n }\n return\n }\n\n if (key === ' ') {\n const activeCol = this.leafCols[this.activeCell.col]\n if (activeCol && activeCol.type === 'checkbox') {\n e.preventDefault()\n this.toggleCheckbox(this.activeCell.row, this.activeCell.col)\n return\n }\n }\n\n if (key === 'Escape') {\n this.clipboardRange = null\n this.clipboardMatrix = null\n this.clipboardMode = null\n return\n }\n\n if (key === 'Tab') {\n e.preventDefault()\n this.moveActive(0, e.shiftKey ? -1 : 1, e)\n return\n }\n if (key === 'Enter') {\n e.preventDefault()\n this.moveActive(e.shiftKey ? -1 : 1, 0, e)\n return\n }\n\n const arrows = { ArrowUp: [-1,0], ArrowDown: [1,0], ArrowLeft: [0,-1], ArrowRight: [0,1] }\n if (arrows[key]) {\n e.preventDefault()\n const [dr, dc] = arrows[key]\n if (e.shiftKey) {\n // Extend selection\n const pr = this.primaryRange || singleCellRange(this.activeCell.row, this.activeCell.col)\n const nm = normalizeRange(pr)\n // Determine which edge to extend\n if (dr === -1) pr.startRow = Math.max(0, nm.startRow - 1)\n if (dr === 1) pr.endRow = Math.min(this.totalRows - 1, nm.endRow + 1)\n if (dc === -1) pr.startCol = Math.max(0, nm.startCol - 1)\n if (dc === 1) pr.endCol = Math.min(this.leafCols.length - 1, nm.endCol + 1)\n this.selectionRanges = [pr]\n this.emitSelectionChange()\n } else {\n this.moveActive(dr, dc, e)\n }\n return\n }\n\n if (key === 'Home') {\n e.preventDefault()\n if (e.ctrlKey) {\n this.selectCell(0, 0)\n } else {\n this.selectCell(this.activeCell.row, 0)\n }\n return\n }\n if (key === 'End') {\n e.preventDefault()\n if (e.ctrlKey) {\n this.selectCell(this.totalRows - 1, this.leafCols.length - 1)\n } else {\n this.selectCell(this.activeCell.row, this.leafCols.length - 1)\n }\n return\n }\n if (key === 'PageUp') {\n e.preventDefault()\n const visibleRows = Math.floor((this.height - HEADER_ROW_HEIGHT * this.headerDepth) / ROW_HEIGHT)\n this.moveActive(-visibleRows, 0, e)\n return\n }\n if (key === 'PageDown') {\n e.preventDefault()\n const visibleRows = Math.floor((this.height - HEADER_ROW_HEIGHT * this.headerDepth) / ROW_HEIGHT)\n this.moveActive(visibleRows, 0, e)\n return\n }\n },\n\n onKeyPress(e) {\n if (this.editingCell) return\n if (this.selectDropdown) return\n if (e.ctrlKey || e.metaKey || e.altKey) return\n if (e.key && e.key.length === 1) {\n const activeCol = this.leafCols[this.activeCell.col]\n if (activeCol && activeCol.type === 'select') {\n e.preventDefault()\n this.openSelectDropdownForActive()\n return\n }\n if (activeCol && activeCol.type === 'checkbox') {\n e.preventDefault()\n return\n }\n this.startEditing(this.activeCell.row, this.activeCell.col, e.key, false)\n }\n },\n\n onGlobalKeyDown(e) {\n // Handled by focused wrapper\n },\n\n moveActive(dr, dc, e) {\n let row = this.activeCell.row + dr\n let col = this.activeCell.col + dc\n\n // Tab: wrap around rows\n if (e && e.key === 'Tab') {\n if (col >= this.leafCols.length) { col = 0; row++ }\n if (col < 0) { col = this.leafCols.length - 1; row-- }\n }\n\n row = Math.max(0, Math.min(this.totalRows - 1, row))\n col = Math.max(0, Math.min(this.leafCols.length - 1, col))\n\n // Skip hidden cols\n while (this.isColHidden(col) && col > 0 && col < this.leafCols.length - 1) {\n col += dc >= 0 ? 1 : -1\n }\n // Skip hidden rows\n while (this.isRowHidden(row) && row > 0 && row < this.totalRows - 1) {\n row += dr >= 0 ? 1 : -1\n }\n\n // Handle merge navigation\n const resolved = resolveMergeAnchor(row, col, this.internalMerges)\n row = resolved.row\n col = resolved.col\n\n this.activeCell = { row, col }\n this.selectionRanges = [singleCellRange(row, col)]\n this.emitSelectionChange()\n this.scrollToCell(row, col)\n },\n\n scrollToCell(ri, ci) {\n const scrollBody = this.$refs.scrollBody\n if (!scrollBody) return\n const rowTop = getRowTop(ri, ROW_HEIGHT, this.rowHeights)\n const rowBottom = rowTop + this.getRowH(ri)\n const colLeft = this.getColLeft(ci) - this.getFrozenWidth()\n const colRight = colLeft + this.getColW(ci)\n\n if (rowTop < scrollBody.scrollTop) scrollBody.scrollTop = rowTop\n if (rowBottom > scrollBody.scrollTop + scrollBody.clientHeight) {\n scrollBody.scrollTop = rowBottom - scrollBody.clientHeight\n }\n if (colLeft < scrollBody.scrollLeft) scrollBody.scrollLeft = colLeft\n if (colRight > scrollBody.scrollLeft + scrollBody.clientWidth) {\n scrollBody.scrollLeft = colRight - scrollBody.clientWidth\n }\n },\n\n // =========================================================\n // EDITING (used by 3.1 + 3.2)\n // =========================================================\n startEditing(ri, ci, initialChar = null, keepValue = false) {\n const col = this.leafCols[ci]\n if (!col || !this.isCellEditable(ri, ci)) return\n if (ri < 0 || ri >= this.totalRows) return\n if (col.type === 'select') { this.openSelectDropdownForActive(); return }\n if (col.type === 'checkbox') return\n\n this.invalidCell = null\n this.editingCell = { row: ri, col: ci }\n if (initialChar !== null) {\n this.editValue = initialChar\n } else if (keepValue) {\n this.editValue = String(this.getCellValue(ri, ci) || '')\n } else {\n this.editValue = ''\n }\n this.editOriginalValue = String(this.getCellValue(ri, ci) || '')\n\n this.$nextTick(() => {\n const inputs = this.$refs.editInput\n if (inputs) {\n const input = Array.isArray(inputs) ? inputs[0] : inputs\n if (input) {\n input.focus()\n if (keepValue) {\n input.setSelectionRange(input.value.length, input.value.length)\n }\n }\n }\n })\n },\n\n commitEdit() {\n if (!this.editingCell) return\n const { row, col } = this.editingCell\n const colDef = this.leafCols[col]\n if (!colDef) { this.editingCell = null; return }\n\n if (!this.isCellValueValid(row, col, this.editValue)) {\n this._showInvalidCell(row, col, this.editValue)\n this.editingCell = null\n this.$refs.wrapper && this.$refs.wrapper.focus()\n return\n }\n\n this.invalidCell = null\n const oldData = this.data\n const newData = oldData.slice()\n newData[row] = { ...oldData[row], [colDef.key]: this.editValue }\n this._emitDataChange(oldData, newData)\n this.editingCell = null\n this.$refs.wrapper && this.$refs.wrapper.focus()\n },\n\n cancelEdit() {\n this.editingCell = null\n this.$refs.wrapper && this.$refs.wrapper.focus()\n },\n\n // =========================================================\n // CHECKBOX CELLS\n // =========================================================\n\n isCheckboxChecked(ri, ci) {\n const v = this.getCellValue(ri, ci)\n if (v === true || v === 1) return true\n if (typeof v === 'string') return ['true', '1'].includes(v.trim().toLowerCase())\n return false\n },\n\n setCheckboxValue(ri, ci, value) {\n const col = this.leafCols[ci]\n if (!col || !this.isCellEditable(ri, ci)) return\n if (!this.isCellValueValid(ri, ci, value)) {\n this._showInvalidCell(ri, ci, value)\n return\n }\n this.invalidCell = null\n const oldData = this.data\n const newData = oldData.slice()\n newData[ri] = { ...oldData[ri], [col.key]: value }\n this._emitDataChange(oldData, newData)\n },\n\n toggleCheckbox(ri, ci) {\n this.setCheckboxValue(ri, ci, !this.isCheckboxChecked(ri, ci))\n },\n\n onCheckboxChange(e, ri, ci) {\n this.setCheckboxValue(ri, ci, e.target.checked)\n },\n\n // =========================================================\n // CELL EDITABILITY (3.17)\n // =========================================================\n\n isCellEditable(ri, ci) {\n const col = this.leafCols[ci]\n if (!col) return false\n const row = this._dataRaw[ri] || {}\n if (typeof this.cellEditable === 'function') return !!this.cellEditable(row, ri, col, ci)\n if (typeof col.editable === 'function') return !!col.editable(row, ri)\n if (col.editable === false) return false\n return true\n },\n\n // =========================================================\n // CELL VALUE VALIDATION (cellValidator)\n // =========================================================\n\n isCellValueValid(ri, ci, value) {\n if (typeof this.cellValidator !== 'function') return true\n const col = this.leafCols[ci]\n if (!col) return true\n const row = this._dataRaw[ri] || {}\n return !!this.cellValidator(value, row, ri, col, ci)\n },\n\n _resolveValidationMessage(ri, ci, value) {\n const col = this.leafCols[ci]\n const row = this._dataRaw[ri] || {}\n if (typeof this.cellValidationMessage === 'function') return this.cellValidationMessage(value, row, ri, col, ci)\n if (typeof this.cellValidationMessage === 'string' && this.cellValidationMessage) return this.cellValidationMessage\n return this.t.invalidValue\n },\n\n // Find the on-screen rect of the cell currently being committed — the edit\n // input if still mounted (commitEdit, before editingCell is cleared), else\n // the active cell element (checkbox/select commits, which never enter edit mode).\n _invalidCellRect() {\n const inputs = this.$refs.editInput\n const input = Array.isArray(inputs) ? inputs[0] : inputs\n if (input) return input.getBoundingClientRect()\n const el = this.$el ? this.$el.querySelector('.eg-cell-active') : null\n return el ? el.getBoundingClientRect() : null\n },\n\n _showInvalidCell(ri, ci, value) {\n const message = this._resolveValidationMessage(ri, ci, value)\n this.invalidCell = { row: ri, col: ci, message, rect: this._invalidCellRect() }\n this.$emit('cell-validation-error', { row: ri, col: ci, value, message })\n },\n\n // Resolve+emit messages for a batch of rejected { row, col, value } entries\n // from a bulk write (paste / fill / clear). blocked=true means the whole\n // operation was aborted (strictBulkValidation); false means these cells\n // were skipped but the rest of the write still applied.\n _emitBulkValidationError(skipped, blocked) {\n const cells = skipped.map(c => ({\n row: c.row, col: c.col, value: c.value,\n message: this._resolveValidationMessage(c.row, c.col, c.value)\n }))\n this.$emit('bulk-validation-error', { blocked, cells })\n },\n\n // =========================================================\n // SELECT DROPDOWN (3.16)\n // =========================================================\n\n colOptions(ci) {\n const col = this.leafCols[ci]\n if (!col || !Array.isArray(col.options)) return []\n return col.options.map(o =>\n typeof o === 'string' ? { value: o, label: o } : { value: o.value, label: o.label != null ? o.label : String(o.value), color: o.color }\n )\n },\n\n getSelectLabel(ci, value) {\n const opts = this.colOptions(ci)\n if (!opts.length) return value == null ? '' : String(value)\n const v = value == null ? '' : String(value)\n const found = opts.find(o => String(o.value) === v)\n return found ? found.label : v\n },\n\n openSelectDropdown(ri, ci, e) {\n const col = this.leafCols[ci]\n if (!col || !Array.isArray(col.options)) return\n const el = e.currentTarget\n const rect = el.getBoundingClientRect()\n const currentValue = String(this.getCellValue(ri, ci) != null ? this.getCellValue(ri, ci) : '')\n const opts = this.colOptions(ci)\n this.selectDropdown = {\n row: ri, col: ci,\n options: opts,\n currentValue,\n highlight: opts.findIndex(o => String(o.value) === currentValue),\n rect: { top: rect.top, bottom: rect.bottom, left: rect.left, width: rect.width }\n }\n },\n\n openSelectDropdownForActive() {\n const { row, col } = this.activeCell\n const colDef = this.leafCols[col]\n if (!colDef || !Array.isArray(colDef.options)) return\n this.$nextTick(() => {\n const el = this.$el ? this.$el.querySelector('.eg-cell-active') : null\n if (!el) return\n const rect = el.getBoundingClientRect()\n const currentValue = String(this.getCellValue(row, col) != null ? this.getCellValue(row, col) : '')\n const opts = this.colOptions(col)\n this.selectDropdown = {\n row, col,\n options: opts,\n currentValue,\n highlight: opts.findIndex(o => String(o.value) === currentValue),\n rect: { top: rect.top, bottom: rect.bottom, left: rect.left, width: rect.width }\n }\n })\n },\n\n commitSelectValue(value) {\n if (!this.selectDropdown) return\n const { row, col } = this.selectDropdown\n const colDef = this.leafCols[col]\n if (colDef) {\n if (!this.isCellValueValid(row, col, value)) {\n this._showInvalidCell(row, col, value)\n this.closeSelectDropdown()\n this.$refs.wrapper && this.$refs.wrapper.focus()\n return\n }\n this.invalidCell = null\n const oldData = this.data\n const newData = oldData.slice()\n newData[row] = { ...oldData[row], [colDef.key]: value }\n this._emitDataChange(oldData, newData)\n }\n this.closeSelectDropdown()\n this.$refs.wrapper && this.$refs.wrapper.focus()\n },\n\n closeSelectDropdown() {\n this.selectDropdown = null\n },\n\n _onDocMouseDownForDropdown(e) {\n if (!this.selectDropdown) return\n const dd = this.$el ? this.$el.querySelector('.eg-select-dropdown') : null\n if (dd && dd.contains(e.target)) return\n // Click outside dropdown — close it\n this.closeSelectDropdown()\n },\n\n onEditKeyDown(e) {\n if (e.key === 'Escape') {\n e.preventDefault()\n this.cancelEdit()\n } else if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n this.commitEdit()\n this.moveActive(1, 0, e)\n } else if (e.key === 'Tab') {\n e.preventDefault()\n this.commitEdit()\n this.moveActive(0, e.shiftKey ? -1 : 1, e)\n }\n },\n\n clearSelection() {\n if (!this.selectionRanges.length) return\n const oldData = this.data\n const newData = oldData.slice()\n let hasChanges = false\n const skipped = []\n for (const range of this.selectionRanges) {\n const r = {\n startRow: Math.min(range.startRow, range.endRow),\n endRow: Math.max(range.startRow, range.endRow),\n startCol: Math.min(range.startCol, range.endCol),\n endCol: Math.max(range.startCol, range.endCol)\n }\n for (let ri = r.startRow; ri <= r.endRow && ri < newData.length; ri++) {\n let rowUpdated = null\n for (let ci = r.startCol; ci <= r.endCol; ci++) {\n const col = this.leafCols[ci]\n if (col && this.isCellEditable(ri, ci)) {\n if (!this.isCellValueValid(ri, ci, '')) { skipped.push({ row: ri, col: ci, value: '' }); continue }\n if (!rowUpdated) rowUpdated = { ...newData[ri] }\n rowUpdated[col.key] = ''\n hasChanges = true\n }\n }\n if (rowUpdated) newData[ri] = rowUpdated\n }\n }\n if (this.strictBulkValidation && skipped.length > 0) {\n this._emitBulkValidationError(skipped, true)\n return\n }\n if (hasChanges) this._emitDataChange(oldData, newData)\n if (skipped.length > 0) this._emitBulkValidationError(skipped, false)\n },\n\n // =========================================================\n // FILL HANDLE (3.3)\n // =========================================================\n startFillDrag(e) {\n const pr = this.primaryRange\n if (!pr) return\n this.fillDrag.active = true\n this.fillDrag.sourceRange = normalizeRange(pr)\n this.fillDrag.currentRow = pr.endRow\n this.fillDrag.currentCol = pr.endCol\n e.preventDefault()\n },\n\n onGlobalMouseMove(e) {\n if (this.resizing.active) {\n this.handleResizeMove(e)\n return\n }\n if (this.fillDrag.active) {\n this.handleFillDragMove(e)\n return\n }\n if (this.dragMove.active) {\n this.handleDragMoveMove(e)\n return\n }\n },\n\n onGlobalMouseUp(e) {\n if (this.resizing.active) {\n this.commitResize()\n return\n }\n if (this.fillDrag.active) {\n this.commitFillDrag()\n return\n }\n if (this.dragMove.active) {\n this.commitDragMove(e)\n return\n }\n this.isMouseDown = false\n },\n\n handleFillDragMove(e) {\n const scrollBody = this.$refs.scrollBody\n if (!scrollBody) return\n const rect = scrollBody.getBoundingClientRect()\n const relY = e.clientY - rect.top + scrollBody.scrollTop\n const relX = e.clientX - rect.left + scrollBody.scrollLeft\n\n // Binary search on cumulativeHeights — O(log n) instead of O(n)\n const cum = this.cumulativeHeights\n const total = this.totalRows\n let lo = 0, hi = total - 1\n while (lo < hi) {\n const mid = (lo + hi) >> 1\n if (cum[mid + 1] <= relY) lo = mid + 1\n else hi = mid\n }\n const row = Math.min(lo, total - 1)\n\n // Columns are few — O(columns) is fast\n let left = this.getFrozenWidth()\n let col = 0\n for (let i = 0; i < this.leafCols.length; i++) {\n if (!this.isColumnScrollable(i)) continue\n left += this.getColW(i)\n if (relX < left) { col = i; break }\n col = i\n }\n\n this.fillDrag.currentRow = row\n this.fillDrag.currentCol = col\n },\n\n commitFillDrag() {\n const src = this.fillDrag.sourceRange\n if (!src) { this.fillDrag.active = false; return }\n\n const targetRow = this.fillDrag.currentRow\n const targetCol = this.fillDrag.currentCol\n\n const oldData = this.data\n const newData = oldData.slice() // only affected rows get new objects\n let targetRange\n const skipped = []\n\n if (targetRow > src.endRow) {\n // Fill down\n const count = targetRow - src.endRow\n for (let ci = src.startCol; ci <= src.endCol; ci++) {\n const col = this.leafCols[ci]\n if (!col) continue\n const srcValues = []\n for (let ri = src.startRow; ri <= src.endRow; ri++) {\n srcValues.push(oldData[ri] ? oldData[ri][col.key] : '')\n }\n const filled = generateFillValues(srcValues, count)\n for (let k = 0; k < count; k++) {\n const ri = src.endRow + 1 + k\n if (ri < this.totalRows && this.isCellEditable(ri, ci)) {\n if (!this.isCellValueValid(ri, ci, filled[k])) { skipped.push({ row: ri, col: ci, value: filled[k] }); continue }\n if (newData[ri] === oldData[ri]) newData[ri] = { ...oldData[ri] }\n newData[ri][col.key] = filled[k]\n }\n }\n }\n targetRange = { startRow: src.endRow + 1, endRow: targetRow, startCol: src.startCol, endCol: src.endCol }\n } else if (targetCol > src.endCol) {\n // Fill right\n const count = targetCol - src.endCol\n for (let ri = src.startRow; ri <= src.endRow; ri++) {\n const srcValues = []\n for (let ci = src.startCol; ci <= src.endCol; ci++) {\n const col = this.leafCols[ci]\n srcValues.push(col && oldData[ri] ? oldData[ri][col.key] : '')\n }\n const filled = generateFillValues(srcValues, count)\n for (let k = 0; k < count; k++) {\n const ci = src.endCol + 1 + k\n if (ci < this.leafCols.length) {\n const col = this.leafCols[ci]\n if (col && this.isCellEditable(ri, ci)) {\n if (!this.isCellValueValid(ri, ci, filled[k])) { skipped.push({ row: ri, col: ci, value: filled[k] }); continue }\n if (newData[ri] === oldData[ri]) newData[ri] = { ...oldData[ri] }\n newData[ri][col.key] = filled[k]\n }\n }\n }\n }\n targetRange = { startRow: src.startRow, endRow: src.endRow, startCol: src.endCol + 1, endCol: targetCol }\n }\n\n if (targetRange) {\n if (this.strictBulkValidation && skipped.length > 0) {\n this._emitBulkValidationError(skipped, true)\n } else {\n this._emitDataChange(oldData, newData)\n this.$emit('fill', { sourceRange: src, targetRange, values: newData })\n if (skipped.length > 0) this._emitBulkValidationError(skipped, false)\n }\n }\n\n this.fillDrag.active = false\n this.fillDrag.sourceRange = null\n },\n\n doubleFillDown() {\n const pr = this.primaryRange\n if (!pr) return\n const src = normalizeRange(pr)\n // Find adjacent column filled length\n const adjCol = src.startCol > 0 ? src.startCol - 1 : src.endCol + 1\n if (adjCol < 0 || adjCol >= this.leafCols.length) return\n const adjColDef = this.leafCols[adjCol]\n if (!adjColDef) return\n let lastFilledRow = src.endRow\n for (let ri = src.endRow + 1; ri < this.totalRows; ri++) {\n if (this.data[ri] && this.data[ri][adjColDef.key] !== '' && this.data[ri][adjColDef.key] !== undefined) {\n lastFilledRow = ri\n } else break\n }\n if (lastFilledRow <= src.endRow) return\n\n const count = lastFilledRow - src.endRow\n const oldData = this.data\n const newData = oldData.slice()\n const skipped = []\n for (let ci = src.startCol; ci <= src.endCol; ci++) {\n const col = this.leafCols[ci]\n if (!col) continue\n const srcValues = []\n for (let ri = src.startRow; ri <= src.endRow; ri++) {\n srcValues.push(oldData[ri] ? oldData[ri][col.key] : '')\n }\n const filled = generateFillValues(srcValues, count)\n for (let k = 0; k < count; k++) {\n const ri = src.endRow + 1 + k\n if (ri < this.totalRows && this.isCellEditable(ri, ci)) {\n if (!this.isCellValueValid(ri, ci, filled[k])) { skipped.push({ row: ri, col: ci, value: filled[k] }); continue }\n if (newData[ri] === oldData[ri]) newData[ri] = { ...oldData[ri] }\n newData[ri][col.key] = filled[k]\n }\n }\n }\n if (this.strictBulkValidation && skipped.length > 0) {\n this._emitBulkValidationError(skipped, true)\n return\n }\n this._emitDataChange(oldData, newData)\n if (skipped.length > 0) this._emitBulkValidationError(skipped, false)\n },\n\n // =========================================================\n // COPY / PASTE (3.4)\n // =========================================================\n copySelection() {\n const pr = this.primaryRange\n if (!pr) return\n const r = normalizeRange(pr)\n this.clipboardMatrix = extractMatrix(this.data, this.leafCols, r)\n this.clipboardRange = r\n this.clipboardMode = 'copy'\n\n // Write to system clipboard as TSV\n const tsv = matrixToTSV(this.clipboardMatrix)\n if (navigator.clipboard) {\n navigator.clipboard.writeText(tsv).catch(() => {})\n }\n },\n\n cutSelection() {\n this.copySelection()\n this.clipboardMode = 'cut'\n },\n\n async pasteAt(targetRow, targetCol, options = {}) {\n const { mode = 'all', transpose = false } = options\n\n let matrix = null\n\n // Try system clipboard first\n if (navigator.clipboard) {\n try {\n const text = await navigator.clipboard.readText()\n if (text) matrix = tsvToMatrix(text)\n } catch (_) {}\n }\n\n // Fall back to internal clipboard\n if (!matrix && this.clipboardMatrix) {\n matrix = this.clipboardMatrix\n }\n\n if (!matrix) return\n\n if (transpose) matrix = transposeMatrix(matrix)\n\n // ── Excel-style selection broadcast / tiling ──────────────────────────\n // When pasting over a multi-cell selection and the clipboard matrix is\n // smaller than the selection, tile the matrix to fill the selection —\n // provided the selection dimensions are exact multiples of the matrix\n // dimensions (matches Excel's tiling rule exactly).\n // Cut-paste is never tiled (source must be cleared exactly once).\n const isCopyPaste = !this.clipboardMode || this.clipboardMode === 'copy'\n if (isCopyPaste) {\n const pr = this.primaryRange\n if (pr) {\n const sel = normalizeRange(pr)\n const selRows = sel.endRow - sel.startRow + 1\n const selCols = sel.endCol - sel.startCol + 1\n const matRows = matrix.length\n const matCols = matrix[0] ? matrix[0].length : 0\n\n // Only tile when selection is strictly larger than clipboard\n if ((selRows > matRows || selCols > matCols) &&\n selRows % matRows === 0 && selCols % matCols === 0) {\n matrix = tileMatrix(matrix, selRows, selCols)\n // Paste at selection origin, not just at the active cell\n targetRow = sel.startRow\n targetCol = sel.startCol\n }\n }\n }\n\n const oldData = this.data\n const { newData, affectedRange, skipped } = applyMatrix(\n oldData, this.leafCols, targetRow, targetCol, matrix, mode,\n (ri, ci) => this.isCellEditable(ri, ci),\n (ri, ci, v) => this.isCellValueValid(ri, ci, v)\n )\n\n if (this.strictBulkValidation && skipped.length > 0) {\n this._emitBulkValidationError(skipped, true)\n return\n }\n\n if (this.clipboardMode === 'cut' && this.clipboardRange) {\n // Clear source — ensure each source row is a new copy before mutation\n const src = this.clipboardRange\n for (let ri = src.startRow; ri <= src.endRow; ri++) {\n if (newData[ri] === oldData[ri]) newData[ri] = { ...oldData[ri] }\n for (let ci = src.startCol; ci <= src.endCol; ci++) {\n const col = this.leafCols[ci]\n if (col && this.isCellEditable(ri, ci) && this.isCellValueValid(ri, ci, '')) {\n newData[ri][col.key] = ''\n }\n }\n }\n this.clipboardRange = null\n this.clipboardMode = null\n }\n\n this._emitDataChange(oldData, newData)\n if (skipped.length > 0) this._emitBulkValidationError(skipped, false)\n },\n\n ctxCopy() { this.hideContextMenu(); this.copySelection() },\n ctxCut() { this.hideContextMenu(); this.cutSelection() },\n ctxPaste() { this.hideContextMenu(); this.pasteAt(this.activeCell.row, this.activeCell.col) },\n ctxPasteSpecial(mode, transpose = false) {\n this.hideContextMenu()\n this.pasteAt(this.activeCell.row, this.activeCell.col, { mode, transpose })\n },\n\n // =========================================================\n // RESIZE (3.5)\n // =========================================================\n startColResize(e, ci) {\n const rect = this.$refs.wrapper.getBoundingClientRect()\n this.resizing = {\n active: true,\n type: 'col',\n index: ci,\n startX: e.clientX,\n startY: e.clientY,\n startSize: this.getColW(ci),\n currentPos: e.clientX\n }\n e.preventDefault()\n },\n\n startRowResize(e, ri) {\n this.resizing = {\n active: true,\n type: 'row',\n index: ri,\n startX: e.clientX,\n startY: e.clientY,\n startSize: this.getRowH(ri),\n currentPos: e.clientY\n }\n e.preventDefault()\n },\n\n handleResizeMove(e) {\n if (this.resizing.type === 'col') {\n this.resizing.currentPos = e.clientX\n const newWidth = Math.max(20, this.resizing.startSize + (e.clientX - this.resizing.startX))\n this.$set(this.colWidths, this.resizing.index, newWidth)\n this._cellStyleCache = Object.create(null)\n } else {\n this.resizing.currentPos = e.clientY\n this._resizePreviewHeight = Math.max(10, this.resizing.startSize + (e.clientY - this.resizing.startY))\n // Batch $set + updateVirtualScroll to rAF — prevents O(n) cumulativeHeights rebuild\n // on every mousemove (200+ Hz). At most 60 rebuilds/sec instead of 200+.\n if (!this._resizeRAF) {\n this._resizeRAF = requestAnimationFrame(() => {\n this._resizeRAF = null\n this._rowStyleCache = Object.create(null)\n this._rowHeaderStyleCache = Object.create(null)\n this._cellStyleCache = Object.create(null)\n this.$set(this.rowHeights, this.resizing.index, this._resizePreviewHeight)\n this.updateVirtualScroll()\n })\n }\n }\n },\n\n commitResize() {\n if (this._resizeRAF) {\n cancelAnimationFrame(this._resizeRAF)\n this._resizeRAF = null\n // Apply the last pending preview height immediately on commit\n if (this.resizing.type === 'row' && this._resizePreviewHeight !== undefined) {\n this.$set(this.rowHeights, this.resizing.index, this._resizePreviewHeight)\n }\n }\n if (this.resizing.type === 'col') {\n this._cellStyleCache = Object.create(null)\n const newSize = this.colWidths[this.resizing.index]\n this.$emit('column-resize', { index: this.resizing.index, newSize })\n } else {\n this._rowStyleCache = Object.create(null)\n this._rowHeaderStyleCache = Object.create(null)\n this._cellStyleCache = Object.create(null)\n const newSize = this.rowHeights[this.resizing.index]\n this.$emit('row-resize', { index: this.resizing.index, newSize })\n this.updateVirtualScroll()\n }\n this.resizing.active = false\n },\n\n autoFitColumn(ci) {\n const col = this.leafCols[ci]\n if (!col) return\n let maxLen = (col.title || '').length\n for (let ri = 0; ri < Math.min(this.totalRows, 500); ri++) {\n const v = this.data[ri] ? String(this.data[ri][col.key] || '') : ''\n if (v.length > maxLen) maxLen = v.length\n }\n const newWidth = Math.max(50, maxLen * 8 + 16)\n this.$set(this.colWidths, ci, newWidth)\n this._cellStyleCache = Object.create(null)\n this.$emit('column-resize', { index: ci, newSize: newWidth })\n },\n\n ctxHideRow() {\n this.hideContextMenu()\n const pr = this.primaryRange\n if (!pr) return\n const r = normalizeRange(pr)\n const newHidden = [...this.hiddenRows]\n for (let ri = r.startRow; ri <= r.endRow; ri++) {\n if (!newHidden.includes(ri)) newHidden.push(ri)\n }\n this.$emit('update:hiddenRows', newHidden)\n },\n\n ctxHideColumn() {\n this.hideContextMenu()\n const pr = this.primaryRange\n if (!pr) return\n const r = normalizeRange(pr)\n const newHidden = [...this.hiddenColumns]\n for (let ci = r.startCol; ci <= r.endCol; ci++) {\n if (!newHidden.includes(ci)) newHidden.push(ci)\n }\n this.$emit('update:hiddenColumns', newHidden)\n },\n\n ctxUnhideRows() {\n this.hideContextMenu()\n this.$emit('update:hiddenRows', [])\n },\n\n ctxUnhideColumns() {\n this.hideContextMenu()\n this.$emit('update:hiddenColumns', [])\n },\n\n ctxPinLeft() {\n const col = this.leafCols[this.contextMenu.col]\n if (!col) { this.hideContextMenu(); return }\n this.$emit('update:pinnedColumns', { ...this.pinnedColumns, [col.key]: 'left' })\n this.hideContextMenu()\n },\n\n ctxPinRight() {\n const col = this.leafCols[this.contextMenu.col]\n if (!col) { this.hideContextMenu(); return }\n this.$emit('update:pinnedColumns', { ...this.pinnedColumns, [col.key]: 'right' })\n this.hideContextMenu()\n },\n\n ctxUnpin() {\n const col = this.leafCols[this.contextMenu.col]\n if (!col) { this.hideContextMenu(); return }\n const next = { ...this.pinnedColumns }\n delete next[col.key]\n this.$emit('update:pinnedColumns', next)\n this.hideContextMenu()\n },\n\n isColPinned(ci) {\n const col = this.leafCols[ci]\n return !!(col && this.pinnedColumns[col.key])\n },\n\n // =========================================================\n // MERGE CELLS (3.8)\n // =========================================================\n mergeCells(range) {\n const oldMerges = this.internalMerges.slice()\n const { merges: newMerges, conflict } = addMerge(range, this.internalMerges)\n if (conflict) {\n this.$emit('merge-error', { range, reason: 'Overlaps existing merge' })\n return false\n }\n this.internalMerges = newMerges\n this._emitMergeChange(oldMerges, [...newMerges])\n return true\n },\n\n unmergeCells(row, col) {\n const oldMerges = this.internalMerges.slice()\n const newMerges = removeMergeAt(row, col, this.internalMerges)\n this.internalMerges = newMerges\n this._emitMergeChange(oldMerges, [...newMerges])\n },\n\n ctxMerge() {\n this.hideContextMenu()\n const pr = this.primaryRange\n if (!pr) return\n this.mergeCells(normalizeRange(pr))\n },\n\n ctxUnmerge() {\n this.hideContextMenu()\n this.unmergeCells(this.activeCell.row, this.activeCell.col)\n },\n\n // =========================================================\n // GROUPING (3.7)\n // =========================================================\n groupRows(startIndex, endIndex) {\n this.internalGroups = addGroup('row', startIndex, endIndex, this.internalGroups)\n },\n\n groupColumns(startIndex, endIndex) {\n this.internalGroups = addGroup('column', startIndex, endIndex, this.internalGroups)\n },\n\n ungroupRows(startIndex, endIndex) {\n this.internalGroups = removeGroup('row', startIndex, endIndex, this.internalGroups)\n },\n\n ungroupColumns(startIndex, endIndex) {\n this.internalGroups = removeGroup('column', startIndex, endIndex, this.internalGroups)\n },\n\n toggleGroup(group) {\n this.internalCollapsedGroups = toggleGroupCollapse(group, this.internalCollapsedGroups)\n this.$emit('update:collapsedGroups', [...this.internalCollapsedGroups])\n this.$emit('group-toggle', { group, collapsed: _isGroupCollapsed(group, this.internalCollapsedGroups) })\n this.updateVirtualScroll()\n },\n\n isGroupCollapsed(group) {\n return _isGroupCollapsed(group, this.internalCollapsedGroups)\n },\n\n expandAll() {\n this.internalCollapsedGroups = []\n this.$emit('update:collapsedGroups', [])\n this.updateVirtualScroll()\n },\n\n collapseAll() {\n this.internalCollapsedGroups = [...this.internalGroups]\n this.$emit('update:collapsedGroups', [...this.internalGroups])\n this.updateVirtualScroll()\n },\n\n // =========================================================\n // DRAG AND DROP (3.9)\n // =========================================================\n startDragMove(e, ri, ci) {\n const pr = this.primaryRange\n if (!pr) return\n this.dragMove = {\n active: true,\n sourceRange: normalizeRange(pr),\n startX: e.clientX,\n startY: e.clientY,\n targetRow: ri,\n targetCol: ci,\n copyMode: e.ctrlKey || e.metaKey\n }\n this.$emit('cell-drag-start', { sourceRange: this.dragMove.sourceRange })\n e.preventDefault()\n },\n\n handleDragMoveMove(e) {\n this.dragMove.copyMode = e.ctrlKey || e.metaKey\n this.$emit('cell-drag-move', {\n sourceRange: this.dragMove.sourceRange,\n targetRow: this.dragMove.targetRow,\n targetCol: this.dragMove.targetCol\n })\n },\n\n async commitDragMove(e) {\n if (!this.dragMove.active) return\n const { sourceRange, targetRow, targetCol, copyMode } = this.dragMove\n\n const targetRange = {\n startRow: targetRow,\n startCol: targetCol,\n endRow: targetRow + (sourceRange.endRow - sourceRange.startRow),\n endCol: targetCol + (sourceRange.endCol - sourceRange.startCol)\n }\n\n let proceed = true\n if (this.beforeDrop) {\n proceed = await Promise.resolve(this.beforeDrop(sourceRange, targetRange))\n }\n\n if (proceed) {\n const oldData = this.data\n const matrix = extractMatrix(oldData, this.leafCols, sourceRange)\n const { newData } = applyMatrix(oldData, this.leafCols, targetRow, targetCol, matrix, 'all', (ri, ci) => this.isCellEditable(ri, ci))\n\n if (!copyMode) {\n // Clear source — ensure each source row is a new copy before mutation\n for (let ri = sourceRange.startRow; ri <= sourceRange.endRow; ri++) {\n if (newData[ri] === oldData[ri]) newData[ri] = { ...oldData[ri] }\n for (let ci = sourceRange.startCol; ci <= sourceRange.endCol; ci++) {\n const col = this.leafCols[ci]\n if (col && this.isCellEditable(ri, ci)) newData[ri][col.key] = ''\n }\n }\n }\n this._emitDataChange(oldData, newData)\n this.$emit('cell-drop', { sourceRange, targetRange })\n }\n\n this.dragMove = { active: false, sourceRange: null, startX: 0, startY: 0, targetRow: -1, targetCol: -1, copyMode: false }\n this.isMouseDown = false\n },\n\n // =========================================================\n // EXCEL EXPORT (3.11)\n // =========================================================\n async exportToExcel(options = {}) {\n const {\n filename = 'export.xlsx',\n sheetName = 'Sheet1',\n scope = 'all',\n includeHidden = false\n } = options\n\n this.$emit('export-start')\n this.exportState = { active: true }\n\n const workerPayload = {\n data: this.data,\n columns: this.columns,\n leafCols: this.leafCols,\n merges: this.internalMerges,\n hiddenCols: [...this.effectiveHiddenColSet],\n hiddenRows: [...this.hiddenRowSet],\n range: scope === 'selection' ? this.primaryRange : null,\n filename,\n sheetName,\n scope,\n includeHidden\n }\n\n try {\n const worker = createExcelExportWorker()\n if (worker) {\n await this._exportViaWorker(worker, workerPayload)\n } else {\n // Fallback: synchronous export on main thread\n _exportToExcel({\n ...workerPayload,\n hiddenColSet: this.effectiveHiddenColSet,\n hiddenRowSet: this.hiddenRowSet\n })\n }\n this.$emit('export-success')\n } catch (err) {\n this.$emit('export-error', err)\n } finally {\n this.exportState = { active: false }\n }\n },\n\n _exportViaWorker(worker, payload) {\n return new Promise((resolve, reject) => {\n let done = false\n const finish = (fn, val) => {\n if (done) return\n done = true\n worker.terminate()\n fn(val)\n }\n\n worker.onmessage = (e) => {\n const { type, buffer, filename, message } = e.data\n if (type === 'result') {\n // Trigger browser download from the main thread (DOM required)\n const blob = new Blob([new Uint8Array(buffer)], {\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'\n })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n document.body.appendChild(a)\n a.click()\n document.body.removeChild(a)\n URL.revokeObjectURL(url)\n finish(resolve)\n } else if (type === 'error') {\n finish(reject, new Error(message))\n }\n }\n\n worker.onerror = (err) => finish(reject, new Error(err.message || 'Export worker xatosi'))\n\n worker.postMessage(payload)\n })\n },\n\n // =========================================================\n // EXCEL IMPORT (3.12)\n // =========================================================\n async importFromFile(file, options = {}) {\n this.importState = { active: true, percent: 5, message: \"Fayl o'qilmoqda...\" }\n\n try {\n let result\n const worker = createExcelImportWorker()\n\n if (worker) {\n result = await this._importViaWorker(worker, file, options)\n } else {\n // Fallback: chunked async on main thread\n result = await _importFromFile(\n file,\n this.columns,\n options,\n (percent, message) => { this.importState = { active: true, percent, message } }\n )\n }\n\n const { columns, merges } = result\n // Freeze each row so Vue 2 skips deep Object.defineProperty observation.\n // Vue 2 checks Object.isExtensible() — frozen objects are skipped entirely.\n // All edit paths already use spread ({ ...row }) so frozen rows are safe.\n const data = result.data.map(row => Object.freeze(row))\n this.$emit('input', data)\n this.$emit('update:columns', columns)\n\n const hasMerges = merges && merges.length > 0\n this.internalMerges = hasMerges ? merges : []\n this.$emit('update:merges', hasMerges ? merges : [])\n this.$emit('update:collapsedGroups', [])\n this.$emit('update:hiddenRows', [])\n this.$emit('import-success', { data, columns, merges })\n\n // Reset scroll state and re-derive virtual scroll after DOM settles.\n // Without this, stale scrollTop + old rowHeights cause blank bands on scroll.\n this.rowHeights = {}\n this.scrollTop = 0\n this.scrollLeft = 0\n this.$nextTick(() => {\n if (this.$refs.scrollBody) {\n this.$refs.scrollBody.scrollTop = 0\n this.$refs.scrollBody.scrollLeft = 0\n }\n if (this.$refs.scrollColHeader) this.$refs.scrollColHeader.scrollLeft = 0\n if (this.$refs.scrollRowHeaders) this.$refs.scrollRowHeaders.scrollTop = 0\n this.updateVirtualScroll()\n })\n } catch (err) {\n this.$emit('import-error', err)\n } finally {\n this.importState = { active: false, percent: 0, message: '' }\n }\n },\n\n _importViaWorker(worker, file, options) {\n return new Promise(async (resolve, reject) => {\n let done = false\n const finish = (fn, val) => {\n if (done) return\n done = true\n worker.terminate()\n fn(val)\n }\n\n worker.onmessage = (e) => {\n const { type, percent, message, data, columns, merges } = e.data\n if (type === 'progress') {\n this.importState = { active: true, percent, message }\n } else if (type === 'result') {\n finish(resolve, { data, columns, merges })\n } else if (type === 'error') {\n finish(reject, new Error(message))\n }\n }\n\n worker.onerror = (err) => finish(reject, new Error(err.message || 'Import worker xatosi'))\n\n try {\n // Transfer the ArrayBuffer zero-copy into the worker\n const buffer = await file.arrayBuffer()\n worker.postMessage(\n { buffer, existingColumns: this.columns, options },\n [buffer]\n )\n } catch (err) {\n finish(reject, err)\n }\n })\n },\n\n // =========================================================\n // UNDO / REDO (3.13)\n // =========================================================\n\n // Push one entry onto the non-reactive history stack.\n // Truncates the redo tail, caps at 100 entries.\n _pushHistory(entry) {\n if (this._historyIndex < this._history.length - 1) {\n this._history.splice(this._historyIndex + 1)\n }\n this._history.push(entry)\n if (this._history.length > 100) {\n this._history.shift()\n // historyIndex stays the same — it now points to the new last entry\n } else {\n this._historyIndex++\n }\n },\n\n // Emit 'input' and record a data diff entry (only changed row indices).\n // Use slice() + per-row spread in callers so unchanged rows keep the\n // same reference — then the identity check here catches only real changes.\n _emitDataChange(oldData, newData) {\n if (!this._historySkip) {\n const indices = []\n const beforeRows = []\n const afterRows = []\n const len = Math.max(oldData.length, newData.length)\n for (let i = 0; i < len; i++) {\n if (oldData[i] !== newData[i]) {\n indices.push(i)\n beforeRows.push(oldData[i])\n afterRows.push(newData[i])\n }\n }\n if (indices.length > 0) {\n this._pushHistory({\n type: 'data',\n before: { indices, rows: beforeRows },\n after: { indices, rows: afterRows }\n })\n }\n }\n this.$emit('input', newData)\n },\n\n // Emit 'update:merges' and record a merges diff entry.\n _emitMergeChange(oldMerges, newMerges) {\n if (!this._historySkip) {\n this._pushHistory({\n type: 'merges',\n before: oldMerges,\n after: newMerges\n })\n }\n this.$emit('update:merges', newMerges)\n },\n\n undo() {\n if (this._historyIndex < 0) return\n const entry = this._history[this._historyIndex--]\n this._historySkip = true\n try {\n if (entry.type === 'data') {\n const newData = this.data.slice()\n for (let i = 0; i < entry.before.indices.length; i++) {\n newData[entry.before.indices[i]] = entry.before.rows[i]\n }\n this.$emit('input', newData)\n } else if (entry.type === 'merges') {\n this.internalMerges = entry.before.slice()\n this.$emit('update:merges', entry.before.slice())\n }\n } finally {\n this._historySkip = false\n }\n },\n\n redo() {\n if (this._historyIndex >= this._history.length - 1) return\n const entry = this._history[++this._historyIndex]\n this._historySkip = true\n try {\n if (entry.type === 'data') {\n const newData = this.data.slice()\n for (let i = 0; i < entry.after.indices.length; i++) {\n newData[entry.after.indices[i]] = entry.after.rows[i]\n }\n this.$emit('input', newData)\n } else if (entry.type === 'merges') {\n this.internalMerges = entry.after.slice()\n this.$emit('update:merges', entry.after.slice())\n }\n } finally {\n this._historySkip = false\n }\n }\n }\n}\n<\/script>\n\n<style scoped>\n.eg-wrapper {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n box-sizing: border-box;\n border: 1px solid #ccc;\n border-radius: 4px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;\n}\n\n.eg-container {\n display: flex;\n flex-direction: column;\n flex: 1;\n overflow: auto;\n position: relative;\n}\n\n.eg-header-area {\n display: flex;\n flex-shrink: 0;\n box-shadow: 0 2px 0 0 #d0d0d0;\n position: relative;\n z-index: 31;\n}\n\n.eg-header-cell {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n border-right: 1px solid #ccc;\n border-bottom: 1px solid #ccc;\n background: #f0f0f0;\n font-size: 12px;\n font-weight: 600;\n overflow: hidden;\n white-space: nowrap;\n position: absolute;\n user-select: none;\n cursor: pointer;\n}\n\n.eg-header-cell:hover { background: #e0e0e0; }\n.eg-header-group { background: #e8eaf6; }\n\n.eg-col-resize-handle {\n position: absolute;\n right: 0;\n top: 0;\n bottom: 0;\n width: 5px;\n cursor: col-resize;\n z-index: 5;\n}\n\n.eg-body-area {\n display: flex;\n flex: 1;\n overflow: hidden;\n}\n\n.eg-row {\n position: relative;\n}\n\n.eg-cell {\n box-sizing: border-box;\n border-right: 1px solid #e0e0e0;\n border-bottom: 1px solid #e0e0e0;\n overflow: hidden;\n white-space: nowrap;\n font-size: 13px;\n padding: 0 4px;\n position: absolute;\n top: 0;\n background: white;\n cursor: default;\n display: flex;\n align-items: center;\n}\n\n.eg-cell-active {\n outline: 2px solid #1a73e8 !important;\n outline-offset: -2px;\n z-index: 3;\n}\n\n.eg-cell-selected {\n background: #e3f2fd !important;\n}\n\n.eg-cell-fill-preview {\n background: #bbdefb !important;\n outline: 1px dashed #1565c0;\n outline-offset: -1px;\n}\n\n.eg-cell-copy {\n outline: 1px dashed #1a73e8;\n outline-offset: -1px;\n}\n\n.eg-cell-merged-hidden {\n display: none !important;\n}\n\n.eg-cell-invalid {\n outline: 2px solid #e53935 !important;\n outline-offset: -2px;\n z-index: 4;\n}\n\n.eg-cell-input {\n width: 100%;\n height: 100%;\n border: none;\n outline: none;\n background: white;\n font-size: 13px;\n font-family: inherit;\n padding: 0;\n}\n\n.eg-row-header-cell {\n box-sizing: border-box;\n background: #f0f0f0;\n border-bottom: 1px solid #e0e0e0;\n border-right: 2px solid #bbb;\n text-align: center;\n font-size: 11px;\n color: #666;\n cursor: pointer;\n user-select: none;\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.eg-row-resize-handle {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 4px;\n cursor: row-resize;\n z-index: 5;\n}\n\n.eg-fill-handle {\n position: absolute;\n width: 6px;\n height: 6px;\n background: #1a73e8;\n border: 1px solid white;\n cursor: crosshair;\n z-index: 10;\n}\n\n.eg-context-menu {\n position: fixed;\n background: white;\n border: 1px solid #ccc;\n border-radius: 4px;\n box-shadow: 2px 4px 12px rgba(0,0,0,0.15);\n z-index: 9999;\n min-width: 160px;\n font-size: 13px;\n padding: 4px 0;\n}\n\n.eg-context-item {\n padding: 6px 16px;\n cursor: pointer;\n position: relative;\n}\n\n.eg-context-item:hover { background: #f0f0f0; }\n\n.eg-context-separator {\n height: 1px;\n background: #e0e0e0;\n margin: 4px 0;\n}\n\n.eg-context-submenu-parent::after { content: ' ▶'; float: right; }\n\n.eg-context-submenu {\n display: none;\n position: absolute;\n left: 100%;\n top: -4px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 4px;\n box-shadow: 2px 4px 12px rgba(0,0,0,0.15);\n min-width: 160px;\n padding: 4px 0;\n z-index: 10000;\n}\n\n.eg-context-submenu-parent:hover .eg-context-submenu { display: block; }\n\n.eg-group-btn {\n position: absolute;\n width: 16px;\n height: 16px;\n line-height: 14px;\n text-align: center;\n font-size: 10px;\n cursor: pointer;\n background: #e8eaf6;\n border: 1px solid #9fa8da;\n border-radius: 2px;\n z-index: 5;\n user-select: none;\n}\n\n.eg-group-btn.collapsed { background: #c5cae9; }\n\n.eg-drag-ghost {\n position: absolute;\n border: 2px dashed #1a73e8;\n pointer-events: none;\n z-index: 20;\n}\n\n.eg-resize-line {\n pointer-events: none;\n z-index: 9999;\n}\n\n/* Pin left separator shadow */\n.eg-frozen-col-body::after {\n content: '';\n position: absolute;\n top: 0;\n right: -4px;\n width: 4px;\n height: 100%;\n background: linear-gradient(to right, rgba(0,0,0,0.1), transparent);\n pointer-events: none;\n}\n\n.eg-pinned-right-body {\n position: sticky;\n right: 0;\n z-index: 10;\n background: white;\n flex-shrink: 0;\n}\n\n.eg-pinned-right-body::before {\n content: '';\n position: absolute;\n top: 0;\n left: -4px;\n bottom: 0;\n width: 4px;\n background: linear-gradient(to left, rgba(0,0,0,0.10), transparent);\n pointer-events: none;\n}\n\n.eg-pinned-right-header {\n overflow: hidden;\n flex-shrink: 0;\n border-left: 2px solid #bbb;\n background: #f0f0f0;\n}\n\n.eg-import-overlay {\n position: absolute;\n inset: 0;\n background: rgba(255, 255, 255, 0.85);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 100;\n border-radius: 4px;\n}\n\n.eg-import-dialog {\n background: white;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n padding: 24px 32px;\n min-width: 280px;\n box-shadow: 0 4px 20px rgba(0,0,0,0.15);\n text-align: center;\n}\n\n.eg-import-title {\n font-size: 14px;\n font-weight: 600;\n color: #333;\n margin-bottom: 16px;\n}\n\n.eg-import-bar-wrap {\n height: 8px;\n background: #e0e0e0;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 10px;\n}\n\n.eg-import-bar {\n height: 100%;\n background: linear-gradient(to right, #1a73e8, #4caf50);\n border-radius: 4px;\n transition: width 0.15s ease;\n}\n\n.eg-import-msg {\n font-size: 12px;\n color: #666;\n margin-bottom: 4px;\n}\n\n.eg-import-pct {\n font-size: 13px;\n font-weight: 600;\n color: #1a73e8;\n}\n.eg-export-spinner {\n width: 32px;\n height: 32px;\n margin: 12px auto 0;\n border: 3px solid #e0e0e0;\n border-top-color: #1a73e8;\n border-radius: 50%;\n animation: eg-spin 0.8s linear infinite;\n}\n@keyframes eg-spin {\n to { transform: rotate(360deg); }\n}\n\n/* ===== READONLY CELL ===== */\n.eg-cell-readonly {\n background: #f8f8f8;\n cursor: default;\n}\n.eg-cell-readonly.eg-cell-selected {\n background: #dce8f5;\n}\n.eg-cell-readonly.eg-cell-active {\n outline-color: #aaa;\n}\n\n/* ===== SELECT DROPDOWN ===== */\n.eg-cell-select {\n cursor: pointer;\n}\n.eg-cell-select::after {\n content: '▾';\n position: absolute;\n right: 4px;\n top: 50%;\n transform: translateY(-50%);\n color: #aaa;\n font-size: 11px;\n pointer-events: none;\n line-height: 1;\n}\n\n/* ===== CHECKBOX CELL ===== */\n.eg-cell-checkbox {\n justify-content: center;\n}\n.eg-cell-checkbox-input {\n width: 15px;\n height: 15px;\n margin: 0;\n cursor: pointer;\n}\n.eg-cell-checkbox-input:disabled {\n cursor: default;\n}\n\n.eg-select-dropdown {\n background: white;\n border: 1px solid #d0d0d0;\n border-radius: 4px;\n box-shadow: 0 4px 16px rgba(0,0,0,0.15);\n overflow-y: auto;\n max-height: 200px;\n padding: 4px 0;\n}\n\n.eg-select-option {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n white-space: nowrap;\n user-select: none;\n color: #333;\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted {\n background: #f0f4ff;\n}\n.eg-select-option--active {\n font-weight: 600;\n color: #1a73e8;\n}\n.eg-select-option--active.eg-select-option--highlighted,\n.eg-select-option--active:hover {\n background: #e8f0fe;\n}\n\n.eg-validation-tooltip {\n background: #fff0f0;\n border: 1px solid #e53935;\n color: #c62828;\n font-size: 12px;\n padding: 4px 8px;\n border-radius: 4px;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);\n white-space: nowrap;\n pointer-events: none;\n}\n\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n}\n</style>\n"]},media:void 0})},F,"data-v-6f4d610c",false,void 0,!1,G,void 0,void 0);var U={install(e){e.component("ExcelGrid",P)}};e.ExcelGrid=P,e.default=U,Object.defineProperty(e,"__esModule",{value:!0})});
|