vue2-excel-grid 1.1.21 → 1.1.22
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.
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* (c) 2026
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
6
|
-
!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("xlsx")):"function"==typeof define&&define.amd?define(["exports","xlsx"],e):e((n="undefined"!=typeof globalThis?globalThis:n||self).Vue2ExcelGrid={},n.XLSX)}(this,function(n,e){"use strict";function t(n){if(n&&n.__esModule)return n;var e=Object.create(null);return n&&Object.keys(n).forEach(function(t){if("default"!==t){var i=Object.getOwnPropertyDescriptor(n,t);Object.defineProperty(e,t,i.get?i:{enumerable:!0,get:function(){return n[t]}})}}),e.default=n,Object.freeze(e)}var i=t(e);function o(n){return{startRow:Math.min(n.startRow,n.endRow),endRow:Math.max(n.startRow,n.endRow),startCol:Math.min(n.startCol,n.endCol),endCol:Math.max(n.startCol,n.endCol)}}function l(n,e,t){const i=o(t);return n>=i.startRow&&n<=i.endRow&&e>=i.startCol&&e<=i.endCol}function s(n,e){return{startRow:n,endRow:n,startCol:e,endCol:e}}function r(n){const e=[];return function n(t){for(const i of t)i.children&&i.children.length?n(i.children):e.push({...i,_flatIndex:e.length})}(n),e}function a(n){return function n(e){let t=0;for(const i of e)t=i.children&&i.children.length?Math.max(t,1+n(i.children)):Math.max(t,1);return t}(n)}function c(n,e){const t=Array.from({length:e},()=>[]);let i=0;return function n(o,l){for(const s of o)if(s.children&&s.children.length){const e=d(s),o=i;t[l].push({column:s,colspan:e,rowspan:1,isGroup:!0,_firstLeafFlatIndex:o}),n(s.children,l+1)}else{const n=e-l;t[l].push({column:{...s,_flatIndex:i},colspan:1,rowspan:n,isGroup:!1,_firstLeafFlatIndex:i}),i++}}(n,0),t}function d(n){return n.children&&0!==n.children.length?n.children.reduce((n,e)=>n+d(e),0):1}function h(n,e,t=[]){let i=0;for(let o=0;o<n;o++)i+=void 0!==t[o]?t[o]:e;return i}const p=["Dushanba","Seshanba","Chorshanba","Payshanba","Juma","Shanba","Yakshanba"],u=["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(n){return"string"==typeof n&&f.test(n.trim())}function C(n){if("boolean"==typeof n)return!1;const e=String(n).trim();return""!==e&&(!A(e)&&!isNaN(Number(e)))}function w(n,e){const t=function(n){if(0===n.length)return{type:"copy",step:0,base:null};if(1===n.length)return{type:"copy",step:0,base:n[0]};const e=n.map(n=>p.indexOf(n));if(e.every(n=>n>=0))return{type:"weekday_uz",step:n.length>=2&&(e[1]-e[0]+7)%7||1,base:e[0]};const t=n.map(n=>u.indexOf(n));if(t.every(n=>n>=0))return{type:"weekday_en",step:n.length>=2&&(t[1]-t[0]+7)%7||1,base:t[0]};const i=n.map(n=>g.indexOf(n));if(i.every(n=>n>=0))return{type:"month",step:n.length>=2&&(i[1]-i[0]+12)%12||1,base:i[0]};if(n.every(A)){const e=n.map(n=>new Date(n).getTime());if(e.every(n=>!isNaN(n))){const n=e.slice(1).map((n,t)=>n-e[t]);if(n.every(e=>Math.abs(e-n[0])<864e5))return{type:"date",step:n[0],base:e[0]}}}if(n.every(C)){const e=n.map(n=>Number(n)),t=e.slice(1).map((n,t)=>n-e[t]);if(t.every(n=>Math.abs(n-t[0])<1e-10))return{type:"number",step:t[0],base:e[0]}}return{type:"copy",step:0,base:n[0]}}(n),i=n.length,o=[];for(let l=0;l<e;l++){const e=i+l;switch(t.type){case"number":o.push(t.base+t.step*e);break;case"weekday_uz":o.push(p[(t.base+t.step*e)%7]);break;case"weekday_en":o.push(u[(t.base+t.step*e)%7]);break;case"month":o.push(g[(t.base+t.step*e)%12]);break;case"date":{const n=new Date(t.base+t.step*e);o.push(n.toISOString().split("T")[0]);break}default:o.push(n[e%i])}}return o}function m(n,e,t){const i=[];for(let o=t.startRow;o<=t.endRow;o++){const l=[];for(let i=t.startCol;i<=t.endCol;i++){const t=e[i],s=t&&n[o]?n[o][t.key]:"";l.push(void 0!==s?s:"")}i.push(l)}return i}function x(n,e,t,i,o,l="all",s=null,r=null){const a=n.slice(),c=Math.min(t+o.length-1,n.length-1),d=Math.min(i+(o[0]?o[0].length-1:0),e.length-1),h=[];for(let p=t;p<=c;p++){const c=o[p-t];if(!c)continue;let u=null;for(let t=i;t<=d;t++){const o=e[t];if(o&&((s?s(p,t):!1!==o.editable)&&("all"===l||"values"===l))){const e=c[t-i],l=void 0!==e?e:"";if(r&&!r(p,t,l)){h.push({row:p,col:t,value:l});continue}u||(u={...n[p]||{}},a[p]=u),u[o.key]=l}}}return{newData:a,affectedRange:{startRow:t,endRow:c,startCol:i,endCol:d},skipped:h}}function v(n,e){const t=o(n);return e.some(n=>function(n,e){const t=o(n),i=o(e);return!(t.endRow<i.startRow||t.startRow>i.endRow||t.endCol<i.startCol||t.startCol>i.endCol)}(t,o(n)))}function y(n,e,t){return t.find(t=>{const i=o(t);return n>=i.startRow&&n<=i.endRow&&e>=i.startCol&&e<=i.endCol})||null}function b(n,e,t,i){if(!function(n,e){return Number.isInteger(n)&&Number.isInteger(e)&&n<=e&&n>=0}(e,t))return i;return i.find(i=>i.type===n&&i.startIndex===e&&i.endIndex===t)?i:[...i,{type:n,startIndex:e,endIndex:t}]}function R(n,e,t,i){return i.filter(i=>!(i.type===n&&i.startIndex===e&&i.endIndex===t))}function k(n,e){return e.some(e=>e.type===n.type&&e.startIndex===n.startIndex&&e.endIndex===n.endIndex)}function S(n){const e=[];for(const t of n||[])t.children&&t.children.length?e.push(...S(t.children)):t.key&&e.push(t);return e}function E(n,e,t,i,o){for(let l=e;l<=t;l++)for(let e=i;e<=o;e++)if(""!==((n[l]||[])[e]||"").toString().trim())return!0;return!1}function _(n,e,t){return String((n[e]||[])[t]||"").trim().replace(/\n/g," ")}function D(){return new Promise(n=>setTimeout(n,0))}async function M(n,e,t={},o){const l=(n,e)=>o&&o(n,e);l(5,"Fayl o'qilmoqda..."),await D();const s=await n.arrayBuffer();let r;l(20,"Excel tahlil qilinmoqda..."),await D();try{r=i.read(s,{type:"array",cellDates:!0})}catch(n){throw new Error("Excel fayl o'qib bo'lmadi: "+n.message)}const{sheetName:a}=t;let c;if(a){if(c=r.Sheets[a],!c)throw new Error(`Sheet "${a}" topilmadi`)}else{const n=r.SheetNames[0];if(!n)throw new Error("Workbook bo'sh");c=r.Sheets[n]}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"),p=h.s.r,u=h.s.c,g=(c["!merges"]||[]).map(n=>({s:{r:n.s.r-p,c:n.s.c-u},e:{r:n.e.r-p,c:n.e.c-u}})),f=function(n,e){if(!n||0===n.length)return{headerRow:0,headerEndRow:0,dataStartRow:1};const t=n.map(n=>n.filter(n=>""!==n).length);e=e||[];let i,o,l,s=-1;for(let e=0;e<Math.min(n.length,20);e++){const t=n[e].filter(n=>""!==n);if(t.length<2)continue;const i=t.map(n=>{const e=parseInt(String(n).trim(),10);return isNaN(e)?null:e});if(!i.some(n=>null===n)&&i.every((n,e)=>n===e+1)){s=e;break}}if(s>=0){for(l=s+1,o=s-1;o>=0&&0===t[o];)o--;for(o<0&&(o=0),i=o;i>0&&t[i-1]>1;)i--}else{i=0;for(let e=0;e<Math.min(n.length,20);e++)if(t[e]>1){i=e;break}let s=1;for(const n of e)n.s.r===i&&n.e.r>n.s.r&&(s=Math.max(s,n.e.r-n.s.r+1));o=i+s-1,l=o+1}for(;l<n.length&&0===t[l];)l++;return{headerRow:i,headerEndRow:o,dataStartRow:l}}(d,g),A=null!=t.headerRow?t.headerRow:f.headerRow,C=null!=t.headerEndRow?t.headerEndRow:f.headerEndRow,w=null!=t.dataStartRow?t.dataStartRow:f.dataStartRow,m=S(e),x=new Set,{columns:v,colMap:y}=function(n,e,t,i,o,l){let s=-1;for(let i=e;i<=t;i++)for(let e=(n[i]||[]).length-1;e>=0;e--)if(""!==((n[i]||[])[e]||"")){s=Math.max(s,e);break}const r=(i||[]).filter(n=>n.s.r>=e&&n.s.r<=t);for(const n of r)s=Math.max(s,n.e.c);if(s<0)return{columns:[],colMap:[]};const a=[];function c(n,e){const t=o.find(e=>e.title&&e.title.toLowerCase()===n.toLowerCase());if(t)return{key:t.key,title:t.title,width:t.width||100};let i=String(n||"").toLowerCase().replace(/\s+/g,"_").replace(/[^a-z0-9_]/g,"").replace(/^_+|_+$/g,"").replace(/_+/g,"_").replace(/^(\d)/,"col_$1")||`col_${e}`,s=i,r=1;for(;l.has(s)||o.some(n=>n.key===s);)s=`${i}_${r++}`;return l.add(s),{key:s,title:n||s,width:100}}const d=function e(t,i,o,l){if(t>i||o>l)return[];const s=[];let d=o;for(;d<=l;){if(r.some(n=>n.s.r<=t&&n.e.r>=t&&n.s.c<d&&n.e.c>=d)){d++;continue}const o=r.find(n=>n.s.r===t&&n.s.c===d),h=o?_(n,o.s.r,o.s.c):_(n,t,d);if(o&&o.e.c>d){const t=Math.min(o.e.c,l),r=o.e.r+1;if(r<=i&&E(n,r,i,d,t)){const n=e(r,i,d,t);s.push({title:h,children:n})}else for(let n=d;n<=t;n++){const e=c(h,n);s.push(e),a.push({key:e.key,colIndex:n})}d=t+1}else if(o&&o.e.r>=i){const n=c(h,d);s.push(n),a.push({key:n.key,colIndex:d}),d++}else if(o){const t=o.e.r+1;if(t<=i&&E(n,t,i,d,d)){const n=e(t,i,d,d);s.push({title:h,children:n})}else{const n=c(h,d);s.push(n),a.push({key:n.key,colIndex:d})}d++}else if(t===i){const n=c(h,d);s.push(n),a.push({key:n.key,colIndex:d}),d++}else{if(E(n,t+1,i,d,d)){const n=e(t+1,i,d,d);s.push({title:h,children:n})}else{const n=c(h,d);s.push(n),a.push({key:n.key,colIndex:d})}d++}}return s}(e,t,0,s);return{columns:d,colMap:a}}(d,A,C,g,m,x),b=Math.max(d.length-w,0);l(55,`Qatorlar qayta ishlanmoqda (0 / ${b})...`),await D();const R=[];for(let n=w;n<d.length;n+=2e3){const e=Math.min(n+2e3,d.length);for(let t=n;t<e;t++){const n=d[t],e={};for(const{key:t,colIndex:i}of y)e[t]=void 0!==n[i]?n[i]:"";R.push(e)}const t=Math.min(e-w,b);l(55+Math.round(40*t/Math.max(b,1)),`Qatorlar: ${t} / ${b}`),await D()}const k=g.filter(n=>n.s.r>=w).map(n=>({startRow:n.s.r-w,startCol:n.s.c,endRow:n.e.r-w,endCol:n.e.c})).filter(n=>n.startRow>=0&&n.startCol>=0);return l(100,"Tayyor!"),{data:R,columns:v,merges:k}}const z={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",total:"Total"},ru:{copy:"Копировать",cut:"Вырезать",paste:"Вставить",pasteSpecial:"Специальная вставка",pasteAll:"Всё (значение + формат)",pasteValues:"Только значения",pasteFormats:"Только форматы",transpose:"Транспонировать",mergeCells:"Объединить ячейки",unmergeCells:"Разъединить ячейки",hideRows:"Скрыть строку(и)",hideColumns:"Скрыть столбец(цы)",unhideRows:"Показать все строки",unhideColumns:"Показать все столбцы",pinLeft:"Закрепить слева",pinRight:"Закрепить справа",unpin:"Открепить",invalidValue:"Недопустимое значение",total:"Итого"},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",total:"Jami"},"uz-Cyrl":{copy:"Нусхалаш",cut:"Кесиб олиш",paste:"Жойлаштириш",pasteSpecial:"Махсус жойлаштириш",pasteAll:"Ҳаммаси (қиймат + формат)",pasteValues:"Фақат қийматлар",pasteFormats:"Фақат форматлар",transpose:"Транспозиция",mergeCells:"Катакчаларни бирлаштириш",unmergeCells:"Катакчаларни ажратиш",hideRows:"Қаторларни яшириш",hideColumns:"Устунларни яшириш",unhideRows:"Барча қаторларни кўрсатиш",unhideColumns:"Барча устунларни кўрсатиш",pinLeft:"Чапга пин қилиш",pinRight:"Ўнгга пин қилиш",unpin:"Пиндан олиш",invalidValue:"Нотўғри қиймат",total:"Жами"}};var I={name:"EgCheckbox",model:{prop:"value",event:"input"},props:{value:{default:!1},trueValue:{default:!0},falseValue:{default:!1},disabled:{type:Boolean,default:!1}},computed:{isChecked(){return this.value===this.trueValue}},methods:{onChange(n){const e=n.target.checked?this.trueValue:this.falseValue;this.$emit("input",e),this.$emit("change",e)}}};function H(n,e,t,i,o,l,s,r,a,c){"boolean"!=typeof s&&(a=r,r=s,s=!1);const d="function"==typeof t?t.options:t;let h;if(n&&n.render&&(d.render=n.render,d.staticRenderFns=n.staticRenderFns,d._compiled=!0,o&&(d.functional=!0)),i&&(d._scopeId=i),l?(h=function(n){(n=n||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(n=__VUE_SSR_CONTEXT__),e&&e.call(this,a(n)),n&&n._registeredComponents&&n._registeredComponents.add(l)},d._ssrRegister=h):e&&(h=s?function(n){e.call(this,c(n,this.$root.$options.shadowRoot))}:function(n){e.call(this,r(n))}),h)if(d.functional){const n=d.render;d.render=function(e,t){return h.call(t),n(e,t)}}else{const n=d.beforeCreate;d.beforeCreate=n?[].concat(n,h):[h]}return t}const V="undefined"!=typeof navigator&&/msie [6-9]\\b/.test(navigator.userAgent.toLowerCase());function L(n){return(n,e)=>function(n,e){const t=V?e.media||"default":n,i=W[t]||(W[t]={ids:new Set,styles:[]});if(!i.ids.has(n)){i.ids.add(n);let t=e.source;if(e.map&&(t+="\n/*# sourceURL="+e.map.sources[0]+" */",t+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(e.map))))+" */"),i.element||(i.element=document.createElement("style"),i.element.type="text/css",e.media&&i.element.setAttribute("media",e.media),void 0===B&&(B=document.head||document.getElementsByTagName("head")[0]),B.appendChild(i.element)),"styleSheet"in i.element)i.styles.push(t),i.element.styleSheet.cssText=i.styles.filter(Boolean).join("\n");else{const n=i.ids.size-1,e=document.createTextNode(t),o=i.element.childNodes;o[n]&&i.element.removeChild(o[n]),o.length?i.element.insertBefore(e,o[n]):i.element.appendChild(e)}}}(n,e)}let B;const W={};const O=I;var $=function(){var n=this,e=n.$createElement,t=n._self._c||e;return t("label",{staticClass:"eg-checkbox",class:{"eg-checkbox--disabled":n.disabled}},[t("input",{staticClass:"eg-checkbox-input",attrs:{type:"checkbox",disabled:n.disabled},domProps:{checked:n.isChecked},on:{change:n.onChange}}),n._v(" "),t("span",{staticClass:"eg-checkbox-box"})])};$._withStripped=!0;const T=H({render:$,staticRenderFns:[]},function(n){n&&n("data-v-48d91ddc_0",{source:'\n.eg-checkbox {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n cursor: pointer;\n}\n.eg-checkbox-input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\n.eg-checkbox-box {\n position: relative;\n display: block;\n width: 19px;\n height: 19px;\n box-sizing: border-box;\n background-color: #fff;\n background-repeat: no-repeat;\n background-position: center;\n background-size: 0 0;\n border: 1.5px solid #c2c8d1;\n border-radius: 4px;\n transition: background-color .12s ease, border-color .12s ease, background-size .12s ease;\n}\n.eg-checkbox:hover .eg-checkbox-box {\n border-color: #1a73e8;\n}\n.eg-checkbox-input:checked + .eg-checkbox-box {\n background-color: #1a73e8;\n background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZD0iTTMuNSA4LjNsMyAzIDYtNi42IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==");\n background-size: 70% 70%;\n border-color: #1a73e8;\n}\n.eg-checkbox-input:focus-visible + .eg-checkbox-box {\n box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.2);\n}\n.eg-checkbox--disabled,\n.eg-checkbox--disabled .eg-checkbox-input {\n cursor: default;\n}\n.eg-checkbox-input:disabled + .eg-checkbox-box {\n background-color: #f0f1f3;\n border-color: #dadde2;\n}\n.eg-checkbox-input:disabled:checked + .eg-checkbox-box {\n background-color: #b9c2ce;\n border-color: #b9c2ce;\n}\n',map:{version:3,sources:["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/EgCheckbox.vue"],names:[],mappings:";AAuCA;EACA,kBAAA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;EACA,eAAA;AACA;AACA;EACA,kBAAA;EACA,QAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA;EACA,UAAA;EACA,eAAA;AACA;AACA;EACA,kBAAA;EACA,cAAA;EACA,WAAA;EACA,YAAA;EACA,sBAAA;EACA,sBAAA;EACA,4BAAA;EACA,2BAAA;EACA,oBAAA;EACA,2BAAA;EACA,kBAAA;EACA,yFAAA;AACA;AACA;EACA,qBAAA;AACA;AACA;EACA,yBAAA;EACA,+SAAA;EACA,wBAAA;EACA,qBAAA;AACA;AACA;EACA,6CAAA;AACA;AACA;;EAEA,eAAA;AACA;AACA;EACA,yBAAA;EACA,qBAAA;AACA;AACA;EACA,yBAAA;EACA,qBAAA;AACA",file:"EgCheckbox.vue",sourcesContent:['<template>\n <label class="eg-checkbox" :class="{ \'eg-checkbox--disabled\': disabled }">\n <input\n type="checkbox"\n class="eg-checkbox-input"\n :checked="isChecked"\n :disabled="disabled"\n @change="onChange"\n />\n <span class="eg-checkbox-box"></span>\n </label>\n</template>\n\n<script>\nexport default {\n name: \'EgCheckbox\',\n model: { prop: \'value\', event: \'input\' },\n props: {\n value: { default: false },\n trueValue: { default: true },\n falseValue: { default: false },\n disabled: { type: Boolean, default: false }\n },\n computed: {\n isChecked() {\n return this.value === this.trueValue\n }\n },\n methods: {\n onChange(e) {\n const val = e.target.checked ? this.trueValue : this.falseValue\n this.$emit(\'input\', val)\n this.$emit(\'change\', val)\n }\n }\n}\n<\/script>\n\n<style>\n.eg-checkbox {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n cursor: pointer;\n}\n.eg-checkbox-input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\n.eg-checkbox-box {\n position: relative;\n display: block;\n width: 19px;\n height: 19px;\n box-sizing: border-box;\n background-color: #fff;\n background-repeat: no-repeat;\n background-position: center;\n background-size: 0 0;\n border: 1.5px solid #c2c8d1;\n border-radius: 4px;\n transition: background-color .12s ease, border-color .12s ease, background-size .12s ease;\n}\n.eg-checkbox:hover .eg-checkbox-box {\n border-color: #1a73e8;\n}\n.eg-checkbox-input:checked + .eg-checkbox-box {\n background-color: #1a73e8;\n background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZD0iTTMuNSA4LjNsMyAzIDYtNi42IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==");\n background-size: 70% 70%;\n border-color: #1a73e8;\n}\n.eg-checkbox-input:focus-visible + .eg-checkbox-box {\n box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.2);\n}\n.eg-checkbox--disabled,\n.eg-checkbox--disabled .eg-checkbox-input {\n cursor: default;\n}\n.eg-checkbox-input:disabled + .eg-checkbox-box {\n background-color: #f0f1f3;\n border-color: #dadde2;\n}\n.eg-checkbox-input:disabled:checked + .eg-checkbox-box {\n background-color: #b9c2ce;\n border-color: #b9c2ce;\n}\n</style>\n']},media:void 0})},O,void 0,false,void 0,!1,L,void 0,void 0);const F={name:"EgSelect",model:{prop:"value",event:"input"},props:{value:{default:null},options:{type:Array,default:()=>[]},enableSearch:{type:Boolean,default:!1},placeholder:{type:String,default:""},disabled:{type:Boolean,default:!1},dropdownOnly:{type:Boolean,default:!1},triggerRect:{type:Object,default:null}},data:()=>({open:!1,searchQuery:"",highlight:-1}),computed:{normalizedOptions(){return(this.options||[]).map(n=>"string"==typeof n?{value:n,label:n}:{value:n.value,label:null!=n.label?n.label:String(n.value),color:n.color})},filteredOptions(){if(!this.enableSearch||!this.searchQuery.trim())return this.normalizedOptions;const n=this.searchQuery.trim().toLowerCase();return this.normalizedOptions.filter(e=>String(e.label||"").toLowerCase().includes(n))},selectedOpt(){const n=String(null==this.value?"":this.value);return this.normalizedOptions.find(e=>String(e.value)===n)||null},overlayStyle(){if(!this.dropdownOnly||!this.triggerRect)return{position:"fixed",zIndex:9999};const n=this.triggerRect,e=this.enableSearch?42:0,t=Math.min(28*this.filteredOptions.length+8+e,320);return{position:"fixed",top:(window.innerHeight-n.bottom>=t?n.bottom:n.top-t)+"px",left:n.left+"px",minWidth:Math.max(n.width,120)+"px",maxWidth:Math.min(360,window.innerWidth-n.left-16)+"px",zIndex:9999}}},watch:{open(n){n&&this.enableSearch&&this.$nextTick(()=>{this.$refs.searchInput&&this.$refs.searchInput.focus()})}},mounted(){if(this.dropdownOnly){const n=String(null==this.value?"":this.value);this.highlight=this.normalizedOptions.findIndex(e=>String(e.value)===n),this.enableSearch&&this.$nextTick(()=>{this.$refs.searchInput&&this.$refs.searchInput.focus()})}else document.addEventListener("click",this._onDocClick)},beforeDestroy(){this.dropdownOnly||document.removeEventListener("click",this._onDocClick)},methods:{toggle(){if(!this.disabled&&(this.open=!this.open,this.open)){const n=String(null==this.value?"":this.value);this.highlight=this.normalizedOptions.findIndex(e=>String(e.value)===n),this.searchQuery=""}},pickOption(n){this.$emit("input",n.value),this.$emit("select",{value:n.value,label:n.label}),this.dropdownOnly||(this.open=!1,this.searchQuery="",this.highlight=-1)},onSearch(n){this.searchQuery=n.target.value,this.highlight=0},onSearchKey(n){const e=n.key;if("Escape"===e)n.preventDefault(),this.dropdownOnly?this.$emit("close"):(this.open=!1,this.searchQuery="");else if("ArrowDown"===e)n.preventDefault(),this.highlight=Math.min((this.highlight||0)+1,this.filteredOptions.length-1);else if("ArrowUp"===e)n.preventDefault(),this.highlight=Math.max((this.highlight||0)-1,0);else if("Enter"===e){n.preventDefault();const e=this.filteredOptions[this.highlight];e&&this.pickOption(e)}},handleKey(n){const e=n.key;if("Escape"===e)n.preventDefault(),this.$emit("close");else if("Enter"===e||" "===e){n.preventDefault();const e=this.filteredOptions[this.highlight];e?this.pickOption(e):this.$emit("close")}else"ArrowDown"===e?(n.preventDefault(),this.highlight=Math.min((this.highlight||0)+1,this.filteredOptions.length-1)):"ArrowUp"===e&&(n.preventDefault(),this.highlight=Math.max((this.highlight||0)-1,0))},onRootMouseDown(n){this.dropdownOnly&&n.preventDefault()},_onDocClick(n){this.open&&(this.$el&&this.$el.contains(n.target)||(this.open=!1))}}};var P=function(){var n=this,e=n.$createElement,t=n._self._c||e;return t("div",{class:n.dropdownOnly?"eg-select-dropdown":"eg-select-wrapper",style:n.dropdownOnly?n.overlayStyle:null,on:{mousedown:n.onRootMouseDown}},[n.dropdownOnly?n._e():t("div",{staticClass:"eg-select-trigger",class:{"eg-select-trigger--disabled":n.disabled,"eg-select-trigger--open":n.open},on:{click:function(e){return e.stopPropagation(),n.toggle.apply(null,arguments)}}},[n.selectedOpt&&n.selectedOpt.color?t("span",{staticClass:"eg-select-dot",style:{background:n.selectedOpt.color}}):n._e(),n._v(" "),t("span",{staticClass:"eg-select-trigger-label",class:{"eg-select-placeholder":!n.selectedOpt}},[n._v("\n "+n._s(n.selectedOpt?n.selectedOpt.label:n.placeholder)+"\n ")]),n._v(" "),t("span",{staticClass:"eg-select-caret"},[n._v("▾")])]),n._v(" "),n.dropdownOnly?[n.enableSearch?t("div",{staticClass:"eg-select-search",on:{mousedown:function(n){n.stopPropagation()}}},[t("input",{ref:"searchInput",staticClass:"eg-select-search-input",attrs:{placeholder:"Qidirish...",autocomplete:"off",autocorrect:"off",autocapitalize:"off",spellcheck:"false"},domProps:{value:n.searchQuery},on:{input:n.onSearch,keydown:function(e){return e.stopPropagation(),n.onSearchKey.apply(null,arguments)}}})]):n._e(),n._v(" "),n._l(n.filteredOptions,function(e,i){return t("div",{key:String(e.value),staticClass:"eg-select-option",class:{"eg-select-option--active":String(e.value)===String(n.value),"eg-select-option--highlighted":i===n.highlight},on:{mousedown:function(t){return t.preventDefault(),n.pickOption(e)},mouseover:function(e){n.highlight=i}}},[e.color?t("span",{staticClass:"eg-select-dot",style:{background:e.color}}):n._e(),n._v("\n "+n._s(e.label)+"\n ")])})]:n.open?t("div",{staticClass:"eg-select-dropdown eg-select-dropdown--floating",on:{mousedown:function(n){n.stopPropagation(),n.preventDefault()}}},[n.enableSearch?t("div",{staticClass:"eg-select-search",on:{mousedown:function(n){n.stopPropagation()}}},[t("input",{ref:"searchInput",staticClass:"eg-select-search-input",attrs:{placeholder:"Qidirish...",autocomplete:"off",autocorrect:"off",autocapitalize:"off",spellcheck:"false"},domProps:{value:n.searchQuery},on:{input:n.onSearch,keydown:function(e){return e.stopPropagation(),n.onSearchKey.apply(null,arguments)}}})]):n._e(),n._v(" "),n._l(n.filteredOptions,function(e,i){return t("div",{key:String(e.value),staticClass:"eg-select-option",class:{"eg-select-option--active":String(e.value)===String(n.value),"eg-select-option--highlighted":i===n.highlight},on:{mousedown:function(t){return t.preventDefault(),n.pickOption(e)},mouseover:function(e){n.highlight=i}}},[e.color?t("span",{staticClass:"eg-select-dot",style:{background:e.color}}):n._e(),n._v("\n "+n._s(e.label)+"\n ")])})],2):n._e()],2)};P._withStripped=!0;const G=H({render:P,staticRenderFns:[]},function(n){n&&n("data-v-76c9c032_0",{source:"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* Standalone wrapper */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: 13px;\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n background: #fff;\n cursor: pointer;\n user-select: none;\n color: #333;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover {\n border-color: #1a73e8;\n}\n.eg-select-trigger--open {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-trigger--disabled {\n background: #f5f5f5;\n cursor: default;\n color: #aaa;\n}\n.eg-select-trigger-label {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.eg-select-placeholder {\n color: #aaa;\n}\n.eg-select-caret {\n flex-shrink: 0;\n color: #888;\n font-size: 12px;\n}\n\n/* Dropdown panel */\n.eg-select-dropdown {\n background: #fff;\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: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n\n/* Search input inside dropdown */\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid #ebebeb;\n position: sticky;\n top: 0;\n background: #fff;\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 13px;\n outline: none;\n color: #333;\n background: #fff;\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n\n/* Option rows */\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\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/* Color dot */\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n",map:{version:3,sources:["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/EgSelect.vue"],names:[],mappings:";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+OA,uBAAA;AACA;EACA,kBAAA;EACA,qBAAA;EACA,gBAAA;EACA,eAAA;EACA,oBAAA;AACA;AACA;EACA,aAAA;EACA,mBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;EACA,gBAAA;EACA,sBAAA;EACA,yBAAA;EACA,kBAAA;EACA,gBAAA;EACA,eAAA;EACA,iBAAA;EACA,WAAA;EACA,wDAAA;AACA;AACA;EACA,qBAAA;AACA;AACA;EACA,qBAAA;EACA,2CAAA;AACA;AACA;EACA,mBAAA;EACA,eAAA;EACA,WAAA;AACA;AACA;EACA,OAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;AACA;AACA;EACA,WAAA;AACA;AACA;EACA,cAAA;EACA,WAAA;EACA,eAAA;AACA;;AAEA,mBAAA;AACA;EACA,gBAAA;EACA,yBAAA;EACA,kBAAA;EACA,uCAAA;EACA,gBAAA;EACA,iBAAA;EACA,cAAA;AACA;AACA;EACA,kBAAA;EACA,SAAA;EACA,OAAA;EACA,eAAA;EACA,YAAA;EACA,eAAA;AACA;;AAEA,iCAAA;AACA;EACA,gBAAA;EACA,gCAAA;EACA,gBAAA;EACA,MAAA;EACA,gBAAA;EACA,UAAA;AACA;AACA;EACA,WAAA;EACA,sBAAA;EACA,yBAAA;EACA,kBAAA;EACA,gBAAA;EACA,eAAA;EACA,aAAA;EACA,WAAA;EACA,gBAAA;EACA,oBAAA;AACA;AACA;EACA,qBAAA;EACA,2CAAA;AACA;;AAEA,gBAAA;AACA;EACA,aAAA;EACA,uBAAA;EACA,QAAA;EACA,iBAAA;EACA,eAAA;EACA,eAAA;EACA,mBAAA;EACA,sBAAA;EACA,iBAAA;EACA,WAAA;AACA;AACA;;EAEA,mBAAA;AACA;AACA;EACA,gBAAA;EACA,cAAA;AACA;AACA;;EAEA,mBAAA;AACA;;AAEA,cAAA;AACA;EACA,UAAA;EACA,WAAA;EACA,kBAAA;EACA,cAAA;EACA,qBAAA;EACA,eAAA;AACA",file:"EgSelect.vue",sourcesContent:['<template>\n <div\n :class="dropdownOnly ? \'eg-select-dropdown\' : \'eg-select-wrapper\'"\n :style="dropdownOnly ? overlayStyle : null"\n @mousedown="onRootMouseDown"\n >\n \x3c!-- Standalone mode: trigger button --\x3e\n <div\n v-if="!dropdownOnly"\n class="eg-select-trigger"\n :class="{ \'eg-select-trigger--disabled\': disabled, \'eg-select-trigger--open\': open }"\n @click.stop="toggle"\n >\n <span v-if="selectedOpt && selectedOpt.color" class="eg-select-dot" :style="{ background: selectedOpt.color }"></span>\n <span class="eg-select-trigger-label" :class="{ \'eg-select-placeholder\': !selectedOpt }">\n {{ selectedOpt ? selectedOpt.label : placeholder }}\n </span>\n <span class="eg-select-caret">▾</span>\n </div>\n\n \x3c!-- Options panel in grid dropdown-only mode (root IS the dropdown panel) --\x3e\n <template v-if="dropdownOnly">\n <div v-if="enableSearch" class="eg-select-search" @mousedown.stop>\n <input\n ref="searchInput"\n class="eg-select-search-input"\n :value="searchQuery"\n placeholder="Qidirish..."\n autocomplete="off"\n autocorrect="off"\n autocapitalize="off"\n spellcheck="false"\n @input="onSearch"\n @keydown.stop="onSearchKey"\n />\n </div>\n <div\n v-for="(opt, idx) in filteredOptions"\n :key="String(opt.value)"\n class="eg-select-option"\n :class="{\n \'eg-select-option--active\': String(opt.value) === String(value),\n \'eg-select-option--highlighted\': idx === highlight\n }"\n @mousedown.prevent="pickOption(opt)"\n @mouseover="highlight = idx"\n >\n <span v-if="opt.color" class="eg-select-dot" :style="{ background: opt.color }"></span>\n {{ opt.label }}\n </div>\n </template>\n\n \x3c!-- Options panel in standalone mode (floating dropdown below trigger) --\x3e\n <div\n v-else-if="open"\n class="eg-select-dropdown eg-select-dropdown--floating"\n @mousedown.stop.prevent\n >\n <div v-if="enableSearch" class="eg-select-search" @mousedown.stop>\n <input\n ref="searchInput"\n class="eg-select-search-input"\n :value="searchQuery"\n placeholder="Qidirish..."\n autocomplete="off"\n autocorrect="off"\n autocapitalize="off"\n spellcheck="false"\n @input="onSearch"\n @keydown.stop="onSearchKey"\n />\n </div>\n <div\n v-for="(opt, idx) in filteredOptions"\n :key="String(opt.value)"\n class="eg-select-option"\n :class="{\n \'eg-select-option--active\': String(opt.value) === String(value),\n \'eg-select-option--highlighted\': idx === highlight\n }"\n @mousedown.prevent="pickOption(opt)"\n @mouseover="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 </div>\n</template>\n\n<script>\nexport default {\n name: \'EgSelect\',\n model: { prop: \'value\', event: \'input\' },\n props: {\n value: { default: null },\n options: { type: Array, default: () => [] },\n enableSearch: { type: Boolean, default: false },\n placeholder: { type: String, default: \'\' },\n disabled: { type: Boolean, default: false },\n dropdownOnly: { type: Boolean, default: false },\n triggerRect: { type: Object, default: null }\n },\n data() {\n return {\n open: false,\n searchQuery: \'\',\n highlight: -1\n }\n },\n computed: {\n normalizedOptions() {\n return (this.options || []).map(o =>\n typeof o === \'string\'\n ? { value: o, label: o }\n : { value: o.value, label: o.label != null ? o.label : String(o.value), color: o.color }\n )\n },\n filteredOptions() {\n if (!this.enableSearch || !this.searchQuery.trim()) return this.normalizedOptions\n const q = this.searchQuery.trim().toLowerCase()\n return this.normalizedOptions.filter(o => String(o.label || \'\').toLowerCase().includes(q))\n },\n selectedOpt() {\n const v = String(this.value == null ? \'\' : this.value)\n return this.normalizedOptions.find(o => String(o.value) === v) || null\n },\n overlayStyle() {\n if (!this.dropdownOnly || !this.triggerRect) return { position: \'fixed\', zIndex: 9999 }\n const rect = this.triggerRect\n const searchH = this.enableSearch ? 42 : 0\n const approxH = Math.min(this.filteredOptions.length * 28 + 8 + searchH, 320)\n const spaceBelow = window.innerHeight - rect.bottom\n const top = spaceBelow >= approxH ? rect.bottom : rect.top - approxH\n return {\n position: \'fixed\',\n top: top + \'px\',\n left: rect.left + \'px\',\n minWidth: Math.max(rect.width, 120) + \'px\',\n maxWidth: Math.min(360, window.innerWidth - rect.left - 16) + \'px\',\n zIndex: 9999\n }\n }\n },\n watch: {\n open(val) {\n if (val && this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n }\n },\n mounted() {\n if (this.dropdownOnly) {\n const v = String(this.value == null ? \'\' : this.value)\n this.highlight = this.normalizedOptions.findIndex(o => String(o.value) === v)\n if (this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n } else {\n document.addEventListener(\'click\', this._onDocClick)\n }\n },\n beforeDestroy() {\n if (!this.dropdownOnly) {\n document.removeEventListener(\'click\', this._onDocClick)\n }\n },\n methods: {\n toggle() {\n if (this.disabled) return\n this.open = !this.open\n if (this.open) {\n const v = String(this.value == null ? \'\' : this.value)\n this.highlight = this.normalizedOptions.findIndex(o => String(o.value) === v)\n this.searchQuery = \'\'\n }\n },\n pickOption(opt) {\n this.$emit(\'input\', opt.value)\n this.$emit(\'select\', { value: opt.value, label: opt.label })\n if (!this.dropdownOnly) {\n this.open = false\n this.searchQuery = \'\'\n this.highlight = -1\n }\n },\n onSearch(e) {\n this.searchQuery = e.target.value\n this.highlight = 0\n },\n onSearchKey(e) {\n const key = e.key\n if (key === \'Escape\') {\n e.preventDefault()\n if (this.dropdownOnly) this.$emit(\'close\')\n else { this.open = false; this.searchQuery = \'\' }\n } else if (key === \'ArrowDown\') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight || 0) + 1, this.filteredOptions.length - 1)\n } else if (key === \'ArrowUp\') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n } else if (key === \'Enter\') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n }\n },\n handleKey(e) {\n const key = e.key\n if (key === \'Escape\') {\n e.preventDefault()\n this.$emit(\'close\')\n } else if (key === \'Enter\' || key === \' \') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n else this.$emit(\'close\')\n } else if (key === \'ArrowDown\') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight || 0) + 1, this.filteredOptions.length - 1)\n } else if (key === \'ArrowUp\') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n }\n },\n onRootMouseDown(e) {\n if (this.dropdownOnly) e.preventDefault()\n },\n _onDocClick(e) {\n if (!this.open) return\n if (this.$el && this.$el.contains(e.target)) return\n this.open = false\n }\n }\n}\n<\/script>\n\n<style>\n/* Standalone wrapper */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: 13px;\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n background: #fff;\n cursor: pointer;\n user-select: none;\n color: #333;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover {\n border-color: #1a73e8;\n}\n.eg-select-trigger--open {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-trigger--disabled {\n background: #f5f5f5;\n cursor: default;\n color: #aaa;\n}\n.eg-select-trigger-label {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.eg-select-placeholder {\n color: #aaa;\n}\n.eg-select-caret {\n flex-shrink: 0;\n color: #888;\n font-size: 12px;\n}\n\n/* Dropdown panel */\n.eg-select-dropdown {\n background: #fff;\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: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n\n/* Search input inside dropdown */\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid #ebebeb;\n position: sticky;\n top: 0;\n background: #fff;\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 13px;\n outline: none;\n color: #333;\n background: #fff;\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n\n/* Option rows */\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\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/* Color dot */\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n</style>\n']},media:void 0})},F,void 0,false,void 0,!1,L,void 0,void 0);const K={name:"EgTreeSelect",model:{prop:"value",event:"input"},props:{value:{default:null},options:{type:Array,default:()=>[]},valueKey:{type:String,default:"value"},labelKey:{type:String,default:"label"},childrenKey:{type:String,default:"children"},enableSearch:{type:Boolean,default:!1},placeholder:{type:String,default:""},disabled:{type:Boolean,default:!1},dropdownOnly:{type:Boolean,default:!1},triggerRect:{type:Object,default:null}},data:()=>({open:!1,searchQuery:"",highlight:-1,expandedMap:{}}),computed:{keys(){return{valueKey:this.valueKey,labelKey:this.labelKey,childrenKey:this.childrenKey}},flatOptions(){return this._treeFlatten(this.options||[],this.keys,this.expandedMap,0,[])},allFlatOptions(){return this.enableSearch?this._treeFlattenAll(this.options||[],this.keys):null},filteredOptions(){if(!this.enableSearch||!this.searchQuery.trim())return this.flatOptions;const n=this.searchQuery.trim().toLowerCase();return(this.allFlatOptions||this.flatOptions).filter(e=>String(e.label||"").toLowerCase().includes(n))},selectedLabel(){const n=String(null==this.value?"":this.value);if(!n)return null;const e=this._treeFindLabel(this.options||[],this.keys,n);return null!=e?e:null},overlayStyle(){if(!this.dropdownOnly||!this.triggerRect)return{position:"fixed",zIndex:9999};const n=this.triggerRect,e=this.enableSearch?42:0,t=Math.min(28*this.filteredOptions.length+8+e,320);return{position:"fixed",top:(window.innerHeight-n.bottom>=t?n.bottom:n.top-t)+"px",left:n.left+"px",minWidth:Math.max(n.width,120)+"px",maxWidth:Math.min(360,window.innerWidth-n.left-16)+"px",zIndex:9999}}},watch:{open(n){n&&this.enableSearch&&this.$nextTick(()=>{this.$refs.searchInput&&this.$refs.searchInput.focus()})}},mounted(){this.dropdownOnly?(this._initExpanded(),this.enableSearch&&this.$nextTick(()=>{this.$refs.searchInput&&this.$refs.searchInput.focus()})):document.addEventListener("click",this._onDocClick)},beforeDestroy(){this.dropdownOnly||document.removeEventListener("click",this._onDocClick)},methods:{_initExpanded(){const n=String(null==this.value?"":this.value),e=this._treeFindPath(this.options||[],this.keys,n,[])||[],t={};e.forEach(n=>{t[n]=!0}),this.expandedMap=t;const i=this.flatOptions;this.highlight=i.findIndex(e=>String(e.value)===n)},toggle(){this.disabled||(this.open=!this.open,this.open&&(this.searchQuery="",this._initExpanded()))},pickOption(n){this.$emit("input",n.value),this.$emit("select",{value:n.value,label:n.label}),this.dropdownOnly||(this.open=!1,this.searchQuery="",this.highlight=-1)},toggleNode(n){const e=this.flatOptions[n];if(!e||!e.hasChildren)return;const t={...this.expandedMap};t[e.id]?delete t[e.id]:t[e.id]=!0,this.expandedMap=t;const i=this.flatOptions.findIndex(n=>n.id===e.id);this.highlight=i>=0?i:this.highlight},onSearch(n){this.searchQuery=n.target.value,this.highlight=0},onSearchKey(n){const e=n.key;if("Escape"===e)n.preventDefault(),this.dropdownOnly?this.$emit("close"):(this.open=!1,this.searchQuery="");else if("ArrowDown"===e)n.preventDefault(),this.highlight=Math.min((this.highlight||0)+1,this.filteredOptions.length-1);else if("ArrowUp"===e)n.preventDefault(),this.highlight=Math.max((this.highlight||0)-1,0);else if("Enter"===e){n.preventDefault();const e=this.filteredOptions[this.highlight];e&&this.pickOption(e)}},handleKey(n){const e=n.key;if("Escape"===e)n.preventDefault(),this.$emit("close");else if("Enter"===e||" "===e){n.preventDefault();const e=this.filteredOptions[this.highlight];e?this.pickOption(e):this.$emit("close")}else if("ArrowDown"===e)n.preventDefault(),this.highlight=Math.min((this.highlight||0)+1,this.filteredOptions.length-1);else if("ArrowUp"===e)n.preventDefault(),this.highlight=Math.max((this.highlight||0)-1,0);else if(!this.searchQuery&&("ArrowRight"===e||"ArrowLeft"===e)){n.preventDefault();const t=this.flatOptions[this.highlight];t&&t.hasChildren&&("ArrowRight"!==e||t.expanded||this.toggleNode(this.highlight),"ArrowLeft"===e&&t.expanded&&this.toggleNode(this.highlight))}},onRootMouseDown(n){this.dropdownOnly&&n.preventDefault()},_onDocClick(n){this.open&&(this.$el&&this.$el.contains(n.target)||(this.open=!1))},_treeFlatten(n,e,t,i,o){let l=[];return(n||[]).forEach((n,s)=>{const r=o.concat(s),a=r.join("-"),c=Array.isArray(n[e.childrenKey])?n[e.childrenKey]:[],d=c.length>0,h=d&&!!t[a];l.push({value:n[e.valueKey],label:null!=n[e.labelKey]?n[e.labelKey]:String(n[e.valueKey]),color:n.color,id:a,depth:i,hasChildren:d,expanded:h}),h&&(l=l.concat(this._treeFlatten(c,e,t,i+1,r)))}),l},_treeFlattenAll(n,e){const t=[],i=n=>{(n||[]).forEach(n=>{const o=Array.isArray(n[e.childrenKey])?n[e.childrenKey]:[];t.push({value:n[e.valueKey],label:null!=n[e.labelKey]?n[e.labelKey]:String(n[e.valueKey]),color:n.color,id:String(n[e.valueKey]),depth:0,hasChildren:!1,expanded:!1}),o.length&&i(o)})};return i(n),t},_treeFindLabel(n,e,t){for(const i of n||[]){if(String(i[e.valueKey])===t)return null!=i[e.labelKey]?i[e.labelKey]:String(i[e.valueKey]);const n=this._treeFindLabel(i[e.childrenKey],e,t);if(null!=n)return n}return null},_treeFindPath(n,e,t,i){for(let o=0;o<(n||[]).length;o++){const l=n[o],s=i.concat(o);if(String(l[e.valueKey])===t)return[s.join("-")];const r=l[e.childrenKey];if(Array.isArray(r)){const n=this._treeFindPath(r,e,t,s);if(n)return[s.join("-")].concat(n)}}return null}}};var j=function(){var n=this,e=n.$createElement,t=n._self._c||e;return t("div",{class:n.dropdownOnly?"eg-select-dropdown":"eg-select-wrapper",style:n.dropdownOnly?n.overlayStyle:null,on:{mousedown:n.onRootMouseDown}},[n.dropdownOnly?n._e():t("div",{staticClass:"eg-select-trigger",class:{"eg-select-trigger--disabled":n.disabled,"eg-select-trigger--open":n.open},on:{click:function(e){return e.stopPropagation(),n.toggle.apply(null,arguments)}}},[t("span",{staticClass:"eg-select-trigger-label",class:{"eg-select-placeholder":null==n.selectedLabel}},[n._v("\n "+n._s(null!=n.selectedLabel?n.selectedLabel:n.placeholder)+"\n ")]),n._v(" "),t("span",{staticClass:"eg-select-caret"},[n._v("▾")])]),n._v(" "),n.dropdownOnly?[n.enableSearch?t("div",{staticClass:"eg-select-search",on:{mousedown:function(n){n.stopPropagation()}}},[t("input",{ref:"searchInput",staticClass:"eg-select-search-input",attrs:{placeholder:"Qidirish...",autocomplete:"off",autocorrect:"off",autocapitalize:"off",spellcheck:"false"},domProps:{value:n.searchQuery},on:{input:n.onSearch,keydown:function(e){return e.stopPropagation(),n.onSearchKey.apply(null,arguments)}}})]):n._e(),n._v(" "),n._l(n.filteredOptions,function(e,i){return t("div",{key:null!=e.id?e.id:String(e.value),staticClass:"eg-select-option",class:{"eg-select-option--active":String(e.value)===String(n.value),"eg-select-option--highlighted":i===n.highlight},style:n.searchQuery?null:{paddingLeft:12+16*e.depth+"px"},on:{mousedown:function(t){return t.preventDefault(),n.pickOption(e)},mouseover:function(e){n.highlight=i}}},[n.searchQuery?n._e():t("span",{staticClass:"eg-tree-toggle",class:{"eg-tree-toggle--leaf":!e.hasChildren},on:{mousedown:function(e){return e.preventDefault(),e.stopPropagation(),n.toggleNode(i)}}},[n._v(n._s(e.hasChildren?e.expanded?"▾":"▸":""))]),n._v(" "),e.color?t("span",{staticClass:"eg-select-dot",style:{background:e.color}}):n._e(),n._v("\n "+n._s(e.label)+"\n ")])})]:n.open?t("div",{staticClass:"eg-select-dropdown eg-select-dropdown--floating",on:{mousedown:function(n){n.stopPropagation(),n.preventDefault()}}},[n.enableSearch?t("div",{staticClass:"eg-select-search",on:{mousedown:function(n){n.stopPropagation()}}},[t("input",{ref:"searchInput",staticClass:"eg-select-search-input",attrs:{placeholder:"Qidirish...",autocomplete:"off",autocorrect:"off",autocapitalize:"off",spellcheck:"false"},domProps:{value:n.searchQuery},on:{input:n.onSearch,keydown:function(e){return e.stopPropagation(),n.onSearchKey.apply(null,arguments)}}})]):n._e(),n._v(" "),n._l(n.filteredOptions,function(e,i){return t("div",{key:null!=e.id?e.id:String(e.value),staticClass:"eg-select-option",class:{"eg-select-option--active":String(e.value)===String(n.value),"eg-select-option--highlighted":i===n.highlight},style:n.searchQuery?null:{paddingLeft:12+16*e.depth+"px"},on:{mousedown:function(t){return t.preventDefault(),n.pickOption(e)},mouseover:function(e){n.highlight=i}}},[n.searchQuery?n._e():t("span",{staticClass:"eg-tree-toggle",class:{"eg-tree-toggle--leaf":!e.hasChildren},on:{mousedown:function(e){return e.preventDefault(),e.stopPropagation(),n.toggleNode(i)}}},[n._v(n._s(e.hasChildren?e.expanded?"▾":"▸":""))]),n._v(" "),e.color?t("span",{staticClass:"eg-select-dot",style:{background:e.color}}):n._e(),n._v("\n "+n._s(e.label)+"\n ")])})],2):n._e()],2)};j._withStripped=!0;const U=H({render:j,staticRenderFns:[]},function(n){n&&n("data-v-63651711_0",{source:"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: #888;\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf {\n visibility: hidden;\n}\n",map:{version:3,sources:["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/EgTreeSelect.vue"],names:[],mappings:";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+UA,gBAAA;AACA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;EACA,cAAA;EACA,eAAA;EACA,WAAA;EACA,eAAA;AACA;AACA;EACA,kBAAA;AACA",file:"EgTreeSelect.vue",sourcesContent:['<template>\n <div\n :class="dropdownOnly ? \'eg-select-dropdown\' : \'eg-select-wrapper\'"\n :style="dropdownOnly ? overlayStyle : null"\n @mousedown="onRootMouseDown"\n >\n \x3c!-- Standalone mode: trigger button --\x3e\n <div\n v-if="!dropdownOnly"\n class="eg-select-trigger"\n :class="{ \'eg-select-trigger--disabled\': disabled, \'eg-select-trigger--open\': open }"\n @click.stop="toggle"\n >\n <span class="eg-select-trigger-label" :class="{ \'eg-select-placeholder\': selectedLabel == null }">\n {{ selectedLabel != null ? selectedLabel : placeholder }}\n </span>\n <span class="eg-select-caret">▾</span>\n </div>\n\n \x3c!-- Options panel in grid dropdown-only mode --\x3e\n <template v-if="dropdownOnly">\n <div v-if="enableSearch" class="eg-select-search" @mousedown.stop>\n <input\n ref="searchInput"\n class="eg-select-search-input"\n :value="searchQuery"\n placeholder="Qidirish..."\n autocomplete="off"\n autocorrect="off"\n autocapitalize="off"\n spellcheck="false"\n @input="onSearch"\n @keydown.stop="onSearchKey"\n />\n </div>\n <div\n v-for="(opt, idx) in filteredOptions"\n :key="opt.id != null ? opt.id : String(opt.value)"\n class="eg-select-option"\n :class="{\n \'eg-select-option--active\': String(opt.value) === String(value),\n \'eg-select-option--highlighted\': idx === highlight\n }"\n :style="!searchQuery ? { paddingLeft: (12 + opt.depth * 16) + \'px\' } : null"\n @mousedown.prevent="pickOption(opt)"\n @mouseover="highlight = idx"\n >\n <span\n v-if="!searchQuery"\n class="eg-tree-toggle"\n :class="{ \'eg-tree-toggle--leaf\': !opt.hasChildren }"\n @mousedown.prevent.stop="toggleNode(idx)"\n >{{ opt.hasChildren ? (opt.expanded ? \'▾\' : \'▸\') : \'\' }}</span>\n <span v-if="opt.color" class="eg-select-dot" :style="{ background: opt.color }"></span>\n {{ opt.label }}\n </div>\n </template>\n\n \x3c!-- Options panel in standalone mode --\x3e\n <div\n v-else-if="open"\n class="eg-select-dropdown eg-select-dropdown--floating"\n @mousedown.stop.prevent\n >\n <div v-if="enableSearch" class="eg-select-search" @mousedown.stop>\n <input\n ref="searchInput"\n class="eg-select-search-input"\n :value="searchQuery"\n placeholder="Qidirish..."\n autocomplete="off"\n autocorrect="off"\n autocapitalize="off"\n spellcheck="false"\n @input="onSearch"\n @keydown.stop="onSearchKey"\n />\n </div>\n <div\n v-for="(opt, idx) in filteredOptions"\n :key="opt.id != null ? opt.id : String(opt.value)"\n class="eg-select-option"\n :class="{\n \'eg-select-option--active\': String(opt.value) === String(value),\n \'eg-select-option--highlighted\': idx === highlight\n }"\n :style="!searchQuery ? { paddingLeft: (12 + opt.depth * 16) + \'px\' } : null"\n @mousedown.prevent="pickOption(opt)"\n @mouseover="highlight = idx"\n >\n <span\n v-if="!searchQuery"\n class="eg-tree-toggle"\n :class="{ \'eg-tree-toggle--leaf\': !opt.hasChildren }"\n @mousedown.prevent.stop="toggleNode(idx)"\n >{{ opt.hasChildren ? (opt.expanded ? \'▾\' : \'▸\') : \'\' }}</span>\n <span v-if="opt.color" class="eg-select-dot" :style="{ background: opt.color }"></span>\n {{ opt.label }}\n </div>\n </div>\n </div>\n</template>\n\n<script>\nexport default {\n name: \'EgTreeSelect\',\n model: { prop: \'value\', event: \'input\' },\n props: {\n value: { default: null },\n options: { type: Array, default: () => [] },\n valueKey: { type: String, default: \'value\' },\n labelKey: { type: String, default: \'label\' },\n childrenKey: { type: String, default: \'children\' },\n enableSearch: { type: Boolean, default: false },\n placeholder: { type: String, default: \'\' },\n disabled: { type: Boolean, default: false },\n dropdownOnly: { type: Boolean, default: false },\n triggerRect: { type: Object, default: null }\n },\n data() {\n return {\n open: false,\n searchQuery: \'\',\n highlight: -1,\n expandedMap: {}\n }\n },\n computed: {\n keys() {\n return { valueKey: this.valueKey, labelKey: this.labelKey, childrenKey: this.childrenKey }\n },\n flatOptions() {\n return this._treeFlatten(this.options || [], this.keys, this.expandedMap, 0, [])\n },\n allFlatOptions() {\n if (!this.enableSearch) return null\n return this._treeFlattenAll(this.options || [], this.keys)\n },\n filteredOptions() {\n if (!this.enableSearch || !this.searchQuery.trim()) return this.flatOptions\n const q = this.searchQuery.trim().toLowerCase()\n const pool = this.allFlatOptions || this.flatOptions\n return pool.filter(o => String(o.label || \'\').toLowerCase().includes(q))\n },\n selectedLabel() {\n const v = String(this.value == null ? \'\' : this.value)\n if (!v) return null\n const found = this._treeFindLabel(this.options || [], this.keys, v)\n return found != null ? found : null\n },\n overlayStyle() {\n if (!this.dropdownOnly || !this.triggerRect) return { position: \'fixed\', zIndex: 9999 }\n const rect = this.triggerRect\n const searchH = this.enableSearch ? 42 : 0\n const approxH = Math.min(this.filteredOptions.length * 28 + 8 + searchH, 320)\n const spaceBelow = window.innerHeight - rect.bottom\n const top = spaceBelow >= approxH ? rect.bottom : rect.top - approxH\n return {\n position: \'fixed\',\n top: top + \'px\',\n left: rect.left + \'px\',\n minWidth: Math.max(rect.width, 120) + \'px\',\n maxWidth: Math.min(360, window.innerWidth - rect.left - 16) + \'px\',\n zIndex: 9999\n }\n }\n },\n watch: {\n open(val) {\n if (val && this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n }\n },\n mounted() {\n if (this.dropdownOnly) {\n this._initExpanded()\n if (this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n } else {\n document.addEventListener(\'click\', this._onDocClick)\n }\n },\n beforeDestroy() {\n if (!this.dropdownOnly) {\n document.removeEventListener(\'click\', this._onDocClick)\n }\n },\n methods: {\n _initExpanded() {\n const v = String(this.value == null ? \'\' : this.value)\n const path = this._treeFindPath(this.options || [], this.keys, v, []) || []\n const expandedMap = {}\n path.forEach(id => { expandedMap[id] = true })\n this.expandedMap = expandedMap\n const flat = this.flatOptions\n this.highlight = flat.findIndex(o => String(o.value) === v)\n },\n toggle() {\n if (this.disabled) return\n this.open = !this.open\n if (this.open) {\n this.searchQuery = \'\'\n this._initExpanded()\n }\n },\n pickOption(opt) {\n this.$emit(\'input\', opt.value)\n this.$emit(\'select\', { value: opt.value, label: opt.label })\n if (!this.dropdownOnly) {\n this.open = false\n this.searchQuery = \'\'\n this.highlight = -1\n }\n },\n toggleNode(idx) {\n const opt = this.flatOptions[idx]\n if (!opt || !opt.hasChildren) return\n const next = { ...this.expandedMap }\n if (next[opt.id]) delete next[opt.id]\n else next[opt.id] = true\n this.expandedMap = next\n const newFlat = this.flatOptions\n const newIdx = newFlat.findIndex(o => o.id === opt.id)\n this.highlight = newIdx >= 0 ? newIdx : this.highlight\n },\n onSearch(e) {\n this.searchQuery = e.target.value\n this.highlight = 0\n },\n onSearchKey(e) {\n const key = e.key\n if (key === \'Escape\') {\n e.preventDefault()\n if (this.dropdownOnly) this.$emit(\'close\')\n else { this.open = false; this.searchQuery = \'\' }\n } else if (key === \'ArrowDown\') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight || 0) + 1, this.filteredOptions.length - 1)\n } else if (key === \'ArrowUp\') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n } else if (key === \'Enter\') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n }\n },\n handleKey(e) {\n const key = e.key\n if (key === \'Escape\') {\n e.preventDefault()\n this.$emit(\'close\')\n } else if (key === \'Enter\' || key === \' \') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n else this.$emit(\'close\')\n } else if (key === \'ArrowDown\') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight || 0) + 1, this.filteredOptions.length - 1)\n } else if (key === \'ArrowUp\') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n } else if (!this.searchQuery && (key === \'ArrowRight\' || key === \'ArrowLeft\')) {\n e.preventDefault()\n const opt = this.flatOptions[this.highlight]\n if (opt && opt.hasChildren) {\n if (key === \'ArrowRight\' && !opt.expanded) this.toggleNode(this.highlight)\n if (key === \'ArrowLeft\' && opt.expanded) this.toggleNode(this.highlight)\n }\n }\n },\n onRootMouseDown(e) {\n if (this.dropdownOnly) e.preventDefault()\n },\n _onDocClick(e) {\n if (!this.open) return\n if (this.$el && this.$el.contains(e.target)) return\n this.open = false\n },\n _treeFlatten(nodes, keys, expandedMap, depth, pathArr) {\n let out = []\n ;(nodes || []).forEach((node, idx) => {\n const path = pathArr.concat(idx)\n const id = path.join(\'-\')\n const children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : []\n const hasChildren = children.length > 0\n const expanded = hasChildren && !!expandedMap[id]\n out.push({ value: node[keys.valueKey], label: node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey]), color: node.color, id, depth, hasChildren, expanded })\n if (expanded) out = out.concat(this._treeFlatten(children, keys, expandedMap, depth + 1, path))\n })\n return out\n },\n _treeFlattenAll(nodes, keys) {\n const result = []\n const walk = (list) => {\n ;(list || []).forEach(node => {\n const children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : []\n result.push({ value: node[keys.valueKey], label: node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey]), color: node.color, id: String(node[keys.valueKey]), depth: 0, hasChildren: false, expanded: false })\n if (children.length) walk(children)\n })\n }\n walk(nodes)\n return result\n },\n _treeFindLabel(nodes, keys, value) {\n for (const node of (nodes || [])) {\n if (String(node[keys.valueKey]) === value) {\n return node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey])\n }\n const found = this._treeFindLabel(node[keys.childrenKey], keys, value)\n if (found != null) return found\n }\n return null\n },\n _treeFindPath(nodes, keys, value, pathArr) {\n for (let i = 0; i < (nodes || []).length; i++) {\n const node = nodes[i]\n const path = pathArr.concat(i)\n if (String(node[keys.valueKey]) === value) return [path.join(\'-\')]\n const children = node[keys.childrenKey]\n if (Array.isArray(children)) {\n const sub = this._treeFindPath(children, keys, value, path)\n if (sub) return [path.join(\'-\')].concat(sub)\n }\n }\n return null\n }\n }\n}\n<\/script>\n\n<style>\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: #888;\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf {\n visibility: hidden;\n}\n</style>\n']},media:void 0})},K,void 0,false,void 0,!1,L,void 0,void 0),N={small:{row:22,header:24,footer:24},middle:{row:28,header:28,footer:28},large:{row:36,header:36,footer:36}},Y=50,q=20;const Z={name:"ExcelGrid",components:{EgCheckbox:T,EgSelect:G,EgTreeSelect:U},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},size:{type:String,default:"middle"},transitionEnabled:{type:Boolean,default:!1},autoWidth:{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},_isScrolling:!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:[],columnTotals:{}}),computed:{t(){return n=this.locale,z[n]||z.en;var n},pinnedLeftSet(){const n=new Set;return this.leafCols.forEach(e=>{"left"===this.pinnedColumns[e.key]&&n.add(e._flatIndex)}),n},pinnedRightSet(){const n=new Set;return this.leafCols.forEach(e=>{"right"===this.pinnedColumns[e.key]&&n.add(e._flatIndex)}),n},pinnedLeftCols(){return this.leafCols.filter(n=>this.pinnedLeftSet.has(n._flatIndex))},pinnedRightCols(){return this.leafCols.filter(n=>this.pinnedRightSet.has(n._flatIndex))},hasPinnedLeft(){return this.pinnedLeftSet.size>0},hasPinnedRight(){return this.pinnedRightSet.size>0},hasFooter(){return this.leafCols.some(n=>n.showTotal)},pinnedLeftWidth(){let n=0;for(const e of this.pinnedLeftCols)this.isColHidden(e._flatIndex)||(n+=this.getColW(e._flatIndex));return n},pinnedRightWidth(){let n=0;for(const e of this.pinnedRightCols)this.isColHidden(e._flatIndex)||(n+=this.getColW(e._flatIndex));return n},totalRows(){return this.data.length},_rowTransitionName(){return this.transitionEnabled&&!this._isScrolling?"eg-row":""},_sizeConf(){return N[this.size]||N.middle},_rowH(){return this._sizeConf.row},_headerRowH(){return this._sizeConf.header},_footerRowH(){return this._sizeConf.footer},headerRowHeight(){return this._headerRowH},footerRowHeight(){return this._footerRowH},cumulativeHeights(){return function(n,e,t={},i=new Set){const o=new Array(n+1);o[0]=0;for(let l=0;l<n;l++){const n=i.has(l)?0:void 0!==t[l]?t[l]:e;o[l+1]=o[l]+n}return o}(this.totalRows,this._rowH,this.rowHeights,this.hiddenRowSet)},hiddenColSet(){return new Set(this.hiddenColumns)},hiddenRowSet(){const n=new Set(this.hiddenRows);return function(n,e){const t=new Set;for(const i of n)if("row"===i.type&&k(i,e))for(let n=i.startIndex;n<=i.endIndex;n++)t.add(n);return t}(this.internalGroups,this.internalCollapsedGroups).forEach(e=>n.add(e)),n},hiddenColFromGroupSet(){return function(n,e){const t=new Set;for(const i of n)if("column"===i.type&&k(i,e))for(let n=i.startIndex;n<=i.endIndex;n++)t.add(n);return t}(this.internalGroups,this.internalCollapsedGroups)},effectiveHiddenColSet(){const n=new Set(this.hiddenColSet);return this.hiddenColFromGroupSet.forEach(e=>n.add(e)),n},rowGroups(){return this.internalGroups.filter(n=>"row"===n.type)},colGroups(){return this.internalGroups.filter(n=>"column"===n.type)},visibleRowRange(){const n=[];for(let e=this.virtualStartIndex;e<=this.virtualEndIndex;e++)n.push(e);return n},primaryRange(){return function(n,e){if(!n||0===n.length)return null;return o(n.find(n=>l(e.row,e.col,n))||n[0])}(this.selectionRanges,this.activeCell)},fillHandleVisible(){return this.primaryRange&&!this.editingCell},fillHandleStyle(){if(!this.primaryRange)return{};const n=o(this.primaryRange),e=this.cumulativeHeights[n.endRow+1]||0,t=this.getColLeft(n.endCol+1);return{position:"absolute",width:"6px",height:"6px",background:"#1a73e8",border:"1px solid white",bottom:this.totalVirtualHeight-e+1+"px",right:this.scrollContentWidth-t+1+"px",cursor:"crosshair",zIndex:10}},invalidTooltipStyle(){if(!this.invalidCell||!this.invalidCell.rect)return{};const{rect:n}=this.invalidCell;return{position:"fixed",top:n.bottom+4+"px",left:n.left+"px",zIndex:9999}},dragGhostStyle(){if(!this.dragMove.active||this.dragMove.targetRow<0)return{};const n=o(this.dragMove.sourceRange),e=n.endRow-n.startRow,t=n.endCol-n.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+t)+"px",height:this.getRangeHeight(this.dragMove.targetRow,this.dragMove.targetRow+e)+"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 n=this.rowGroups.length?q:0;return{width:Y+n+"px",minWidth:Y+n+"px",height:this._headerRowH*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 n=this.getFrozenWidth();return{width:n+"px",minWidth:n+"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 n=0;for(let e=0;e<this.leafCols.length;e++)this.isColHidden(e)||this.pinnedRightSet.has(e)||(n+=this.getColW(e));return this.hasPinnedRight&&(n+=this.pinnedRightWidth),n},scrollableContentWidth(){let n=0;for(let e=0;e<this.leafCols.length;e++)!this.isColHidden(e)&&this.isColumnScrollable(e)&&(n+=this.getColW(e));return n},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"}),footerAreaStyle:()=>({display:"flex",flexDirection:"row",flexShrink:0,position:"sticky",bottom:0,zIndex:30,background:"#f8f9fa",boxShadow:"0 -2px 0 0 #d0d0d0"}),footerLabelStyle(){const n=this.rowGroups.length?q:0;return{width:Y+n+"px",minWidth:Y+n+"px",height:this._footerRowH+"px",display:"flex",alignItems:"center",justifyContent:"center",background:"#f0f0f0",borderRight:"1px solid #ccc",borderTop:"1px solid #ccc",boxSizing:"border-box",flexShrink:0,fontSize:"12px",fontWeight:600}},frozenColFooterStyle(){const n=this.getFrozenWidth();return{width:n+"px",minWidth:n+"px",height:this._footerRowH+"px",overflow:"hidden",flexShrink:0}},pinnedRightFooterStyle(){return{width:this.pinnedRightWidth+"px",minWidth:this.pinnedRightWidth+"px",height:this._footerRowH+"px",overflow:"hidden",flexShrink:0,borderLeft:"1px solid #ccc"}},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:{size(){this.rowHeights={},this.$nextTick(()=>this.updateVirtualScroll())},autoWidth(n){n&&this._applyAutoWidth()},columns:{immediate:!0,handler(n){this.leafCols=r(n),this.headerDepth=a(n),this.headerRows=c(n,this.headerDepth),this.leafCols.forEach(n=>{void 0===this.colWidths[n._flatIndex]&&(this.colWidths[n._flatIndex]=n.width||100)}),this.computeColumnTotals(),this.updateVirtualScroll(),this._applyAutoWidth()}},merges:{immediate:!0,handler(n){this.internalMerges=n?[...n]:[]}},collapsedGroups:{immediate:!0,handler(n){this.internalCollapsedGroups=n?[...n]:[]}},data:{immediate:!0,handler(n){this._dataRaw=n||[],this.updateVirtualScroll(),this.computeColumnTotals(),this.$forceUpdate(),this._applyAutoWidth()}},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),window.addEventListener("scroll",this._onWindowScrollForDropdown,!0)},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),window.removeEventListener("scroll",this._onWindowScrollForDropdown,!0),clearTimeout(this._scrollEndTimer)},methods:{updateVirtualScroll(){const n=this.hasFooter?this._footerRowH:0,e=this.height-this._headerRowH*this.headerDepth-n,t=function(n,e,t,i,o=5){if(0===t)return{startIndex:0,endIndex:-1,offsetTop:0,totalHeight:0};const l=i[t],s=n+e;let r=0,a=t-1;for(;r<a;){const e=r+a>>1;i[e+1]<=n?r=e+1:a=e}let c=r,d=c;for(;d<t-1&&i[d+1]<s;)d++;return c=Math.max(0,c-o),d=Math.min(t-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(n){const e=n.target;this.$refs.scrollColHeader&&(this.$refs.scrollColHeader.scrollLeft=e.scrollLeft),this.$refs.scrollFooter&&(this.$refs.scrollFooter.scrollLeft=e.scrollLeft),this.$refs.scrollRowHeaders&&(this.$refs.scrollRowHeaders.scrollTop=e.scrollTop),this.selectDropdown&&this.closeSelectDropdown(),this.transitionEnabled&&(this._isScrolling=!0,clearTimeout(this._scrollEndTimer),this._scrollEndTimer=setTimeout(()=>{this._isScrolling=!1},150)),this._scrollRAF||(this._scrollRAF=requestAnimationFrame(()=>{this._scrollRAF=null,this.scrollTop=e.scrollTop,this.scrollLeft=e.scrollLeft,this.updateVirtualScroll()}))},onScroll(n){},syncHeaderViewportWidth(){const n=this.$refs.scrollBody;n&&this.headerViewportWidth!==n.clientWidth&&(this.headerViewportWidth=n.clientWidth)},isRowHidden(n){return this.hiddenRowSet.has(n)},isColHidden(n){return this.effectiveHiddenColSet.has(n)},getColW(n){return void 0!==this.colWidths[n]?this.colWidths[n]:this.leafCols[n]&&this.leafCols[n].width||100},getRowH(n){return this.isRowHidden(n)?0:this.rowHeights[n]||this._rowH},getColLeft(n){let e=0;for(let t=0;t<n&&t<this.leafCols.length;t++)this.isColHidden(t)||(e+=this.getColW(t));return e},getFrozenWidth(){return this.hasPinnedLeft?this.pinnedLeftWidth:0},isColumnPinnedLeft(n){return this.pinnedLeftSet.has(n)},isColumnPinnedRight(n){return this.pinnedRightSet.has(n)},isColumnScrollable(n){return!this.isColumnPinnedLeft(n)&&!this.isColumnPinnedRight(n)},getZoneColLeft(n,e){let t=0;if("left"===e)for(const e of this.pinnedLeftCols){if(e._flatIndex===n)break;this.isColHidden(e._flatIndex)||(t+=this.getColW(e._flatIndex))}else if("right"===e)for(const e of this.pinnedRightCols){if(e._flatIndex===n)break;this.isColHidden(e._flatIndex)||(t+=this.getColW(e._flatIndex))}else for(let e=0;e<this.leafCols.length;e++){const i=this.leafCols[e];if(i._flatIndex===n)break;!this.isColHidden(i._flatIndex)&&this.isColumnScrollable(i._flatIndex)&&(t+=this.getColW(i._flatIndex))}return t},isCellInZone(n,e,t){if(n.isGroup){const e=n._firstLeafFlatIndex,i=e+n.colspan-1;for(let n=e;n<=i;n++){if(this.isColHidden(n))continue;if((this.isColumnPinnedLeft(n)?"left":this.isColumnPinnedRight(n)?"right":"scroll")===t)return!0}return!1}const i=n.column._flatIndex;if(this.isColHidden(i))return!1;return(this.isColumnPinnedLeft(i)?"left":this.isColumnPinnedRight(i)?"right":"scroll")===t},getHeaderCellStyleForZone(n,e,t){let i=n._firstLeafFlatIndex;if(n.isGroup){const e=i+n.colspan-1;for(let n=i;n<=e;n++){if(this.isColHidden(n))continue;if((this.isColumnPinnedLeft(n)?"left":this.isColumnPinnedRight(n)?"right":"scroll")===t){i=n;break}}}const o=this.getZoneColLeft(i,t);let l=0;if(n.isGroup){const e=n._firstLeafFlatIndex,i=e+n.colspan-1;for(let n=e;n<=i;n++){if(this.isColHidden(n))continue;(this.isColumnPinnedLeft(n)?"left":this.isColumnPinnedRight(n)?"right":"scroll")===t&&(l+=this.getColW(n))}}else l=this.getColW(n.column._flatIndex);return{position:"absolute",top:this._headerRowH*e+"px",left:o+"px",width:l+"px",height:this._headerRowH*n.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(n,e){let t=0;for(let i=n;i<=e&&i<this.leafCols.length;i++)this.isColHidden(i)||(t+=this.getColW(i));return t},getRangeHeight(n,e){let t=0;for(let i=n;i<=e&&i<this.totalRows;i++)this.isRowHidden(i)||(t+=this.getRowH(i));return t},getCellValue(n,e){const t=this.leafCols[e];if(!t)return"";const i=this._dataRaw[n];if(!i)return"";const o=i[t.key];return void 0!==o?o:""},isEditing(n,e){return this.editingCell&&this.editingCell.row===n&&this.editingCell.col===e},isMergedAway(n,e){const t=y(n,e,this.internalMerges);if(!t)return!1;const i=o(t);return!(i.startRow===n&&i.startCol===e)},getMergeSpan(n,e){const t=y(n,e,this.internalMerges);if(!t)return{rowspan:1,colspan:1};const i=o(t);return{rowspan:i.endRow-i.startRow+1,colspan:i.endCol-i.startCol+1}},isSelected(n,e){return t=n,i=e,this.selectionRanges.some(n=>l(t,i,n));var t,i},isActiveCell(n,e){return this.activeCell.row===n&&this.activeCell.col===e},isCopyHighlighted(n,e){return!(!this.clipboardRange||"copy"!==this.clipboardMode)&&l(n,e,this.clipboardRange)},getRowStyle(n){const e=this.getRowH(n),t=e;if(this._rowStyleCache[t])return this._rowStyleCache[t];const i={height:e+"px",minHeight:e+"px"};return this._rowStyleCache[t]=i,i},getRowHeaderStyle(n){const e=this.getRowH(n),t=e;if(this._rowHeaderStyleCache[t])return this._rowHeaderStyleCache[t];const i={height:e+"px"};return this._rowHeaderStyleCache[t]=i,i},getCellStyle(n,e){const t=y(n,e,this.internalMerges),i=t?o(t):null;let l=this.getColW(e),s=this.getRowH(n);i&&i.startRow===n&&i.startCol===e&&(l=this.getRangeWidth(i.startCol,i.endCol),s=this.getRangeHeight(i.startRow,i.endRow));const r=this.isColumnPinnedLeft(e)?"left":this.isColumnPinnedRight(e)?"right":"scroll",a=this.getZoneColLeft(e,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(n,e){const t=this.leafCols[e];return{"eg-cell-active":this.isActiveCell(n,e),"eg-cell-selected":this.isSelected(n,e)&&!this.isActiveCell(n,e),"eg-cell-fill-preview":this.isInFillPreview(n,e),"eg-cell-copy":this.isCopyHighlighted(n,e),"eg-cell-merged-hidden":this.isMergedAway(n,e),"eg-cell-select":!(!t||!this.isSelectLikeCol(t)),"eg-cell-checkbox":!(!t||"checkbox"!==t.type),"eg-cell-readonly":!this.isCellEditable(n,e),"eg-cell-invalid":!(!this.invalidCell||this.invalidCell.row!==n||this.invalidCell.col!==e)}},isInFillPreview(n,e){if(!this.fillDrag.active||!this.fillDrag.sourceRange)return!1;const t=this.fillDrag.sourceRange,i=this.fillDrag.currentRow,o=this.fillDrag.currentCol;return i>t.endRow?n>t.endRow&&n<=i&&e>=t.startCol&&e<=t.endCol:o>t.endCol&&(e>t.endCol&&e<=o&&n>=t.startRow&&n<=t.endRow)},getHeaderCellStyle(n,e,t){const i=t?"left":"scroll";let o=n._firstLeafFlatIndex;if(n.isGroup){const e=o+n.colspan-1;for(let n=o;n<=e;n++)if(!this.isColHidden(n)&&(t?this.isColumnPinnedLeft(n):this.isColumnScrollable(n))){o=n;break}}const l=this.getZoneColLeft(o,i);let s=0;if(n.isGroup){const e=n._firstLeafFlatIndex,i=e+n.colspan-1;for(let n=e;n<=i;n++){if(this.isColHidden(n))continue;(t?this.isColumnPinnedLeft(n):this.isColumnScrollable(n))&&(s+=this.getColW(n))}}else s=this.getColW(n.column._flatIndex);return{position:"absolute",top:this._headerRowH*e+"px",left:l+"px",width:s+"px",height:this._headerRowH*n.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"}},computeColumnTotals(){const n={},e=this._dataRaw||[];this.leafCols.forEach(t=>{if(!t.showTotal)return;let i=0,o=!1;for(let n=0;n<e.length;n++){const l=e[n],s=l?l[t.key]:void 0;"number"==typeof s&&isFinite(s)&&(i+=s,o=!0)}n[t._flatIndex]=o?Math.round(1e6*i)/1e6:null}),this.columnTotals=n},getColumnTotalDisplay(n){const e=this.leafCols[n];if(!e||!e.showTotal)return"";const t=this.columnTotals[n];return null==t?"-":t},getFooterCellStyle(n){const e=this.isColumnPinnedLeft(n)?"left":this.isColumnPinnedRight(n)?"right":"scroll",t=this.getZoneColLeft(n,e),i=this.getColW(n);return{position:"absolute",left:t+"px",top:"0px",width:i+"px",minWidth:i+"px",height:this._footerRowH+"px"}},isCellVisible(n,e,t){if(n.isGroup){const e=n._firstLeafFlatIndex,i=e+n.colspan-1;for(let n=e;n<=i;n++){if(this.isColHidden(n))continue;if(t?this.isColumnPinnedLeft(n):this.isColumnScrollable(n))return!0}return!1}const i=n.column._flatIndex;return!this.isColHidden(i)&&(t?this.isColumnPinnedLeft(i):this.isColumnScrollable(i))},getRowGroupBtnStyle(n){return{position:"absolute",top:h(n.startIndex,this._rowH,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(n){return{position:"absolute",left:this.getColLeft(n.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(n,e,t=!1){n<0||n>=this.totalRows||e<0||e>=this.leafCols.length||(this.activeCell={row:n,col:e},t?this.selectionRanges.push(s(n,e)):this.selectionRanges=[s(n,e)],this.emitSelectionChange())},selectRange(n,e,t,i,o=!0){const l={startRow:n,startCol:e,endRow:t,endCol:i};o?this.selectionRanges=[l]:this.selectionRanges.push(l),this.emitSelectionChange()},selectRow(n){this.activeCell={row:n,col:0},this.selectionRanges=[{startRow:n,endRow:n,startCol:0,endCol:this.leafCols.length-1}],this.emitSelectionChange()},selectColumn(n){this.activeCell={row:0,col:n},this.selectionRanges=[{startRow:0,endRow:this.totalRows-1,startCol:n,endCol:n}],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(n=>o(n))})},onCellMouseDown(n,e,t){if(this.invalidCell=null,this.editingCell){const n=this.leafCols[this.editingCell.col];n&&this._hasCustomEdit(n)?this.cancelEdit():this.commitEdit()}if(this.hideContextMenu(),this.$refs.wrapper&&this.$refs.wrapper.focus(),n.ctrlKey||n.metaKey)this.selectDropdown&&this.closeSelectDropdown(),this.activeCell={row:e,col:t},this.mouseDownRange={startRow:e,startCol:t,endRow:e,endCol:t},this.selectionRanges.push(this.mouseDownRange),this.isCtrlDrag=!0;else if(n.shiftKey&&this.selectionRanges.length>0)this.selectDropdown&&this.closeSelectDropdown(),this.selectionRanges=[(i=this.activeCell,o={row:e,col:t},{startRow:i.row,startCol:i.col,endRow:o.row,endCol:o.col})];else{const i=this.activeCell.row===e&&this.activeCell.col===t;this.activeCell={row:e,col:t},this.mouseDownRange={startRow:e,startCol:t,endRow:e,endCol:t},this.selectionRanges=[this.mouseDownRange],this.isCtrlDrag=!1;const o=this.leafCols[t];if(i&&o&&this.isSelectLikeCol(o)&&!this._hasCustomEdit(o)&&this.isCellEditable(e,t))return this.openSelectDropdown(e,t,n),n.preventDefault(),this.isMouseDown=!0,void this.emitSelectionChange();this.selectDropdown&&this.closeSelectDropdown()}var i,o;this.isMouseDown=!0,this.emitSelectionChange(),n.preventDefault()},onCellMouseOver(n,e,t){if(this.isMouseDown&&!this.fillDrag.active)return this.dragMove.active?(this.dragMove.targetRow=e,void(this.dragMove.targetCol=t)):void(this.mouseDownRange&&(this.mouseDownRange.endRow=e,this.mouseDownRange.endCol=t,this.emitSelectionChange()))},onCellMouseUp(){this.isMouseDown=!1},onCellDblClick(n,e){const t=this.leafCols[e];this.isCellEditable(n,e)&&(t&&this.isSelectLikeCol(t)&&!this._hasCustomEdit(t)?this.openSelectDropdownForActive():t&&"checkbox"===t.type||this.startEditing(n,e,null,!0))},onRowHeaderMouseDown(n,e){const t=this.leafCols.length-1,i={startRow:e,endRow:e,startCol:0,endCol:t};if(n.ctrlKey||n.metaKey)if(n.shiftKey&&this.selectionRanges.length>0){const n=Math.min(this.activeCell.row,e),i=Math.max(this.activeCell.row,e);this.selectionRanges=[...this.selectionRanges,{startRow:n,endRow:i,startCol:0,endCol:t}]}else{const n=this.selectionRanges.findIndex(n=>n.startRow===e&&n.endRow===e&&0===n.startCol&&n.endCol===t);if(n>=0){const e=this.selectionRanges.filter((e,t)=>t!==n);this.selectionRanges=e.length?e:[i]}else this.activeCell={row:e,col:0},this.selectionRanges=[...this.selectionRanges,i]}else if(n.shiftKey&&this.selectionRanges.length>0){const n=Math.min(this.activeCell.row,e),i=Math.max(this.activeCell.row,e);this.selectionRanges=[{startRow:n,endRow:i,startCol:0,endCol:t}]}else this.activeCell={row:e,col:0},this.selectionRanges=[i];this.mouseDownRange=this.selectionRanges[this.selectionRanges.length-1],this.isMouseDown=!0,this.emitSelectionChange(),n.preventDefault()},onColHeaderMouseDown(n,e){if(e.isGroup)return;const t=e.column._flatIndex;if(n.shiftKey&&this.selectionRanges.length>0){const n=Math.min(this.activeCell.col,t),e=Math.max(this.activeCell.col,t);this.selectionRanges=[{startRow:0,endRow:this.totalRows-1,startCol:n,endCol:e}]}else this.activeCell={row:0,col:t},this.selectionRanges=[{startRow:0,endRow:this.totalRows-1,startCol:t,endCol:t}];this.isMouseDown=!0,this.mouseDownRange=this.selectionRanges[0],this.emitSelectionChange(),n.preventDefault()},onColHeaderDblClick(n,e){e.isGroup||this.autoFitColumn(e.column._flatIndex)},onColHeaderContextMenu(n,e){e.isGroup||(this.contextMenu={visible:!0,x:n.clientX,y:n.clientY,row:-1,col:e.column._flatIndex})},onRowContextMenu(n,e){this.contextMenu={visible:!0,x:n.clientX,y:n.clientY,row:e,col:-1}},onCellContextMenu(n,e,t){this.isSelected(e,t)||this.selectCell(e,t),this.contextMenu={visible:!0,x:n.clientX,y:n.clientY,row:e,col:t}},hideContextMenu(){this.contextMenu.visible=!1},onKeyDown(n){if(this.selectDropdown){const e=this.$refs.selectDropdownComp;return void(e&&e.handleKey&&e.handleKey(n))}if(this.editingCell)return;const e=n.key;if((n.ctrlKey||n.metaKey)&&"z"===e&&!n.shiftKey)return n.preventDefault(),void this.undo();if((n.ctrlKey||n.metaKey)&&("y"===e||"z"===e&&n.shiftKey))return n.preventDefault(),void this.redo();if("a"===e&&(n.ctrlKey||n.metaKey))return n.preventDefault(),void this.selectAll();if("c"===e&&(n.ctrlKey||n.metaKey))return n.preventDefault(),void this.copySelection();if("x"===e&&(n.ctrlKey||n.metaKey))return n.preventDefault(),void this.cutSelection();if("v"===e&&(n.ctrlKey||n.metaKey))return n.preventDefault(),void this.pasteAt(this.activeCell.row,this.activeCell.col);if("Delete"===e||"Backspace"===e)return n.preventDefault(),void this.clearSelection();if("F2"===e){n.preventDefault();const e=this.leafCols[this.activeCell.col];return void(e&&this.isSelectLikeCol(e)&&!this._hasCustomEdit(e)?this.openSelectDropdownForActive():e&&"checkbox"===e.type?this.toggleCheckbox(this.activeCell.row,this.activeCell.col):this.startEditing(this.activeCell.row,this.activeCell.col,null,!0))}if(" "===e){const e=this.leafCols[this.activeCell.col];if(e&&"checkbox"===e.type)return n.preventDefault(),void this.toggleCheckbox(this.activeCell.row,this.activeCell.col)}if("Escape"===e)return this.clipboardRange=null,this.clipboardMatrix=null,void(this.clipboardMode=null);if("Tab"===e)return n.preventDefault(),void this.moveActive(0,n.shiftKey?-1:1,n);if("Enter"===e)return n.preventDefault(),void this.moveActive(n.shiftKey?-1:1,0,n);const t={ArrowUp:[-1,0],ArrowDown:[1,0],ArrowLeft:[0,-1],ArrowRight:[0,1]};if(t[e]){n.preventDefault();const[i,l]=t[e];if(n.shiftKey){const n=this.primaryRange||s(this.activeCell.row,this.activeCell.col),e=o(n);-1===i&&(n.startRow=Math.max(0,e.startRow-1)),1===i&&(n.endRow=Math.min(this.totalRows-1,e.endRow+1)),-1===l&&(n.startCol=Math.max(0,e.startCol-1)),1===l&&(n.endCol=Math.min(this.leafCols.length-1,e.endCol+1)),this.selectionRanges=[n],this.emitSelectionChange()}else this.moveActive(i,l,n);return}if("Home"===e)return n.preventDefault(),void(n.ctrlKey?this.selectCell(0,0):this.selectCell(this.activeCell.row,0));if("End"===e)return n.preventDefault(),void(n.ctrlKey?this.selectCell(this.totalRows-1,this.leafCols.length-1):this.selectCell(this.activeCell.row,this.leafCols.length-1));if("PageUp"===e){n.preventDefault();const e=Math.floor((this.height-this._headerRowH*this.headerDepth)/this._rowH);return void this.moveActive(-e,0,n)}if("PageDown"===e){n.preventDefault();const e=Math.floor((this.height-this._headerRowH*this.headerDepth)/this._rowH);return void this.moveActive(e,0,n)}},onKeyPress(n){if(!this.editingCell&&!this.selectDropdown&&!(n.ctrlKey||n.metaKey||n.altKey)&&n.key&&1===n.key.length){const e=this.leafCols[this.activeCell.col];if(e&&this.isSelectLikeCol(e)&&!this._hasCustomEdit(e))return n.preventDefault(),void this.openSelectDropdownForActive();if(e&&"checkbox"===e.type)return void n.preventDefault();this.startEditing(this.activeCell.row,this.activeCell.col,n.key,!1)}},onGlobalKeyDown(n){},moveActive(n,e,t){let i=this.activeCell.row+n,l=this.activeCell.col+e;for(t&&"Tab"===t.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+=e>=0?1:-1;for(;this.isRowHidden(i)&&i>0&&i<this.totalRows-1;)i+=n>=0?1:-1;const r=function(n,e,t){const i=y(n,e,t);if(!i)return{row:n,col:e};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(n,e){const t=this.$refs.scrollBody;if(!t)return;const i=h(n,this._rowH,this.rowHeights),o=i+this.getRowH(n),l=this.getColLeft(e)-this.getFrozenWidth(),s=l+this.getColW(e);i<t.scrollTop&&(t.scrollTop=i),o>t.scrollTop+t.clientHeight&&(t.scrollTop=o-t.clientHeight),l<t.scrollLeft&&(t.scrollLeft=l),s>t.scrollLeft+t.clientWidth&&(t.scrollLeft=s-t.clientWidth)},startEditing(n,e,t=null,i=!1){const o=this.leafCols[e];if(o&&this.isCellEditable(n,e)&&!(n<0||n>=this.totalRows)){if(!this._hasCustomEdit(o)){if(this.isSelectLikeCol(o))return void this.openSelectDropdownForActive();if("checkbox"===o.type)return}this.invalidCell=null,this.editingCell={row:n,col:e},this.editValue=null!==t?t:i?String(this.getCellValue(n,e)||""):"",this.editOriginalValue=String(this.getCellValue(n,e)||""),this.$nextTick(()=>{const n=this.$refs.editInput;if(n){const e=Array.isArray(n)?n[0]:n;e&&(e.focus(),i&&e.setSelectionRange(e.value.length,e.value.length))}})}},commitEdit(){if(!this.editingCell)return;const{row:n,col:e}=this.editingCell,t=this.leafCols[e];if(!t)return void(this.editingCell=null);if(!this.isCellValueValid(n,e,this.editValue))return this._showInvalidCell(n,e,this.editValue),this.editingCell=null,void(this.$refs.wrapper&&this.$refs.wrapper.focus());this.invalidCell=null;const i=this.data,o=i.slice();o[n]={...i[n],[t.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()},hasCellSlot(n){return!!(n&&n.key&&this.$scopedSlots&&this.$scopedSlots["cell-"+n.key])},hasLegacyEditSlot(n){return!!(n&&n.key&&this.$scopedSlots&&this.$scopedSlots["edit-"+n.key])},hasPrefixSlot(n){return!!(n&&n.key&&this.$scopedSlots&&this.$scopedSlots["prefix-"+n.key])},hasSuffixSlot(n){return!!(n&&n.key&&this.$scopedSlots&&this.$scopedSlots["suffix-"+n.key])},_hasCustomEdit(n){return"select"===n.type||"treeSelect"===n.type?this.hasLegacyEditSlot(n):this.hasCellSlot(n)||this.hasLegacyEditSlot(n)},commitEditSlot(n){if(!this.editingCell)return;const{row:e,col:t}=this.editingCell,i=this.leafCols[t];if(!i)return void(this.editingCell=null);if(!this.isCellValueValid(e,t,n))return this._showInvalidCell(e,t,n),this.editingCell=null,void(this.$refs.wrapper&&this.$refs.wrapper.focus());this.invalidCell=null;const o=this.data,l=o.slice();l[e]={...o[e],[i.key]:n},this._emitDataChange(o,l),this.editingCell=null,this.$refs.wrapper&&this.$refs.wrapper.focus()},isCheckboxChecked(n,e){const t=this.getCellValue(n,e);return!0===t||1===t||"string"==typeof t&&["true","1"].includes(t.trim().toLowerCase())},setCheckboxValue(n,e,t){const i=this.leafCols[e];if(!i||!this.isCellEditable(n,e))return;if(!this.isCellValueValid(n,e,t))return void this._showInvalidCell(n,e,t);this.invalidCell=null;const o=this.data,l=o.slice();l[n]={...o[n],[i.key]:t},this._emitDataChange(o,l)},toggleCheckbox(n,e){this.setCheckboxValue(n,e,!this.isCheckboxChecked(n,e))},isCellEditable(n,e){const t=this.leafCols[e];if(!t)return!1;const i=this._dataRaw[n]||{};return"function"==typeof this.cellEditable?!!this.cellEditable(i,n,t,e):"function"==typeof t.editable?!!t.editable(i,n):!1!==t.editable},isCellValueValid(n,e,t){if("function"!=typeof this.cellValidator)return!0;const i=this.leafCols[e];if(!i)return!0;const o=this._dataRaw[n]||{};return!!this.cellValidator(t,o,n,i,e)},_resolveValidationMessage(n,e,t){const i=this.leafCols[e],o=this._dataRaw[n]||{};return"function"==typeof this.cellValidationMessage?this.cellValidationMessage(t,o,n,i,e):"string"==typeof this.cellValidationMessage&&this.cellValidationMessage?this.cellValidationMessage:this.t.invalidValue},_invalidCellRect(){const n=this.$refs.editInput,e=Array.isArray(n)?n[0]:n;if(e)return e.getBoundingClientRect();const t=this.$el?this.$el.querySelector(".eg-cell-active"):null;return t?t.getBoundingClientRect():null},_showInvalidCell(n,e,t){const i=this._resolveValidationMessage(n,e,t);this.invalidCell={row:n,col:e,message:i,rect:this._invalidCellRect()},this.$emit("cell-validation-error",{row:n,col:e,value:t,message:i})},_emitBulkValidationError(n,e){const t=n.map(n=>({row:n.row,col:n.col,value:n.value,message:this._resolveValidationMessage(n.row,n.col,n.value)}));this.$emit("bulk-validation-error",{blocked:e,cells:t})},isSelectLikeCol:n=>!(!n||"select"!==n.type&&"treeSelect"!==n.type),treeOptionKeys:n=>({valueKey:n.optionValueKey||"value",labelKey:n.optionLabelKey||"label",childrenKey:n.optionChildrenKey||"children"}),_treeFindLabel(n,e,t){for(const i of n||[]){if(String(i[e.valueKey])===t)return null!=i[e.labelKey]?i[e.labelKey]:String(i[e.valueKey]);const n=this._treeFindLabel(i[e.childrenKey],e,t);if(null!=n)return n}return null},colOptions(n){const e=this.leafCols[n];return e&&Array.isArray(e.options)?e.options.map(n=>"string"==typeof n?{value:n,label:n}:{value:n.value,label:null!=n.label?n.label:String(n.value),color:n.color}):[]},getSelectLabel(n,e){const t=this.leafCols[n],i=null==e?"":String(e);if(t&&"treeSelect"===t.type){if(!Array.isArray(t.options))return i;const n=this._treeFindLabel(t.options,this.treeOptionKeys(t),i);return null!=n?n:i}const o=this.colOptions(n);if(!o.length)return i;const l=o.find(n=>String(n.value)===i);return l?l.label:i},_buildSelectDropdownState(n,e,t,i,o){const l={top:o.top,bottom:o.bottom,left:o.left,width:o.width};return{row:n,col:e,tree:"treeSelect"===t.type,keys:"treeSelect"===t.type?this.treeOptionKeys(t):null,rawOptions:t.options||[],currentValue:i,rect:l,enableSearch:!!t.enableSearch}},openSelectDropdown(n,e,t){const i=this.leafCols[e];if(!i||!Array.isArray(i.options))return;const o=t.currentTarget.getBoundingClientRect(),l=String(null!=this.getCellValue(n,e)?this.getCellValue(n,e):"");this.selectDropdown=this._buildSelectDropdownState(n,e,i,l,o)},openSelectDropdownForActive(){const{row:n,col:e}=this.activeCell,t=this.leafCols[e];t&&Array.isArray(t.options)&&this.$nextTick(()=>{const i=this.$el?this.$el.querySelector(".eg-cell-active"):null;if(!i)return;const o=i.getBoundingClientRect(),l=String(null!=this.getCellValue(n,e)?this.getCellValue(n,e):"");this.selectDropdown=this._buildSelectDropdownState(n,e,t,l,o)})},onComponentSelect({value:n,label:e}){if(!this.selectDropdown)return;const{row:t,col:i}=this.selectDropdown,o=this.leafCols[i];if(o){if(!this.isCellValueValid(t,i,n))return this._showInvalidCell(t,i,n),this.closeSelectDropdown(),void(this.$refs.wrapper&&this.$refs.wrapper.focus());this.invalidCell=null;const l=this.data,s=l.slice();s[t]={...l[t],[o.key]:n},this._emitDataChange(l,s),"function"==typeof o.onSelect&&o.onSelect({value:n,label:e},s[t],t,o)}this.closeSelectDropdown(),this.$refs.wrapper&&this.$refs.wrapper.focus()},closeSelectDropdown(){this.selectDropdown=null},_onWindowScrollForDropdown(n){if(!this.selectDropdown)return;const e=this.$refs.selectDropdownComp,t=e&&e.$el?e.$el:null;t&&t.contains(n.target)||this.closeSelectDropdown()},_onDocMouseDownForDropdown(n){if(!this.selectDropdown)return;const e=this.$refs.selectDropdownComp,t=e&&e.$el?e.$el:null;t&&t.contains(n.target)||this.closeSelectDropdown()},onEditKeyDown(n){"Escape"===n.key?(n.preventDefault(),this.cancelEdit()):"Enter"!==n.key||n.shiftKey?"Tab"===n.key&&(n.preventDefault(),this.commitEdit(),this.moveActive(0,n.shiftKey?-1:1,n)):(n.preventDefault(),this.commitEdit(),this.moveActive(1,0,n))},clearSelection(){if(!this.selectionRanges.length)return;const n=this.data,e=n.slice();let t=!1;const i=[];for(const n of this.selectionRanges){const o={startRow:Math.min(n.startRow,n.endRow),endRow:Math.max(n.startRow,n.endRow),startCol:Math.min(n.startCol,n.endCol),endCol:Math.max(n.startCol,n.endCol)};for(let n=o.startRow;n<=o.endRow&&n<e.length;n++){let l=null;for(let s=o.startCol;s<=o.endCol;s++){const o=this.leafCols[s];if(o&&this.isCellEditable(n,s)){if(!this.isCellValueValid(n,s,"")){i.push({row:n,col:s,value:""});continue}l||(l={...e[n]}),l[o.key]="",t=!0}}l&&(e[n]=l)}}this.strictBulkValidation&&i.length>0?this._emitBulkValidationError(i,!0):(t&&this._emitDataChange(n,e),i.length>0&&this._emitBulkValidationError(i,!1))},startFillDrag(n){const e=this.primaryRange;e&&(this.fillDrag.active=!0,this.fillDrag.sourceRange=o(e),this.fillDrag.currentRow=e.endRow,this.fillDrag.currentCol=e.endCol,n.preventDefault())},onGlobalMouseMove(n){this.resizing.active?this.handleResizeMove(n):this.fillDrag.active?this.handleFillDragMove(n):this.dragMove.active&&this.handleDragMoveMove(n)},onGlobalMouseUp(n){this.resizing.active?this.commitResize():this.fillDrag.active?this.commitFillDrag():this.dragMove.active?this.commitDragMove(n):this.isMouseDown=!1},handleFillDragMove(n){const e=this.$refs.scrollBody;if(!e)return;const t=e.getBoundingClientRect(),i=n.clientY-t.top+e.scrollTop,o=n.clientX-t.left+e.scrollLeft,l=this.cumulativeHeights,s=this.totalRows;let r=0,a=s-1;for(;r<a;){const n=r+a>>1;l[n+1]<=i?r=n+1:a=n}const c=Math.min(r,s-1);let d=this.getFrozenWidth(),h=0;for(let n=0;n<this.leafCols.length;n++)if(this.isColumnScrollable(n)){if(d+=this.getColW(n),o<d){h=n;break}h=n}this.fillDrag.currentRow=c,this.fillDrag.currentCol=h},commitFillDrag(){const n=this.fillDrag.sourceRange;if(!n)return void(this.fillDrag.active=!1);const e=this.fillDrag.currentRow,t=this.fillDrag.currentCol,i=this.data,o=i.slice();let l;const s=[];if(e>n.endRow){const t=e-n.endRow;for(let e=n.startCol;e<=n.endCol;e++){const l=this.leafCols[e];if(!l)continue;const r=[];for(let e=n.startRow;e<=n.endRow;e++)r.push(i[e]?i[e][l.key]:"");const a=w(r,t);for(let r=0;r<t;r++){const t=n.endRow+1+r;if(t<this.totalRows&&this.isCellEditable(t,e)){if(!this.isCellValueValid(t,e,a[r])){s.push({row:t,col:e,value:a[r]});continue}o[t]===i[t]&&(o[t]={...i[t]}),o[t][l.key]=a[r]}}}l={startRow:n.endRow+1,endRow:e,startCol:n.startCol,endCol:n.endCol}}else if(t>n.endCol){const e=t-n.endCol;for(let t=n.startRow;t<=n.endRow;t++){const l=[];for(let e=n.startCol;e<=n.endCol;e++){const n=this.leafCols[e];l.push(n&&i[t]?i[t][n.key]:"")}const r=w(l,e);for(let l=0;l<e;l++){const e=n.endCol+1+l;if(e<this.leafCols.length){const n=this.leafCols[e];if(n&&this.isCellEditable(t,e)){if(!this.isCellValueValid(t,e,r[l])){s.push({row:t,col:e,value:r[l]});continue}o[t]===i[t]&&(o[t]={...i[t]}),o[t][n.key]=r[l]}}}}l={startRow:n.startRow,endRow:n.endRow,startCol:n.endCol+1,endCol:t}}l&&(this.strictBulkValidation&&s.length>0?this._emitBulkValidationError(s,!0):(this._emitDataChange(i,o),this.$emit("fill",{sourceRange:n,targetRange:l,values:o}),s.length>0&&this._emitBulkValidationError(s,!1))),this.fillDrag.active=!1,this.fillDrag.sourceRange=null},doubleFillDown(){const n=this.primaryRange;if(!n)return;const e=o(n),t=e.startCol>0?e.startCol-1:e.endCol+1;if(t<0||t>=this.leafCols.length)return;const i=this.leafCols[t];if(!i)return;let l=e.endRow;for(let n=e.endRow+1;n<this.totalRows&&(this.data[n]&&""!==this.data[n][i.key]&&void 0!==this.data[n][i.key]);n++)l=n;if(l<=e.endRow)return;const s=l-e.endRow,r=this.data,a=r.slice(),c=[];for(let n=e.startCol;n<=e.endCol;n++){const t=this.leafCols[n];if(!t)continue;const i=[];for(let n=e.startRow;n<=e.endRow;n++)i.push(r[n]?r[n][t.key]:"");const o=w(i,s);for(let i=0;i<s;i++){const l=e.endRow+1+i;if(l<this.totalRows&&this.isCellEditable(l,n)){if(!this.isCellValueValid(l,n,o[i])){c.push({row:l,col:n,value:o[i]});continue}a[l]===r[l]&&(a[l]={...r[l]}),a[l][t.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 n=this.primaryRange;if(!n)return;const e=o(n);this.clipboardMatrix=m(this.data,this.leafCols,e),this.clipboardRange=e,this.clipboardMode="copy";const t=this.clipboardMatrix.map(n=>n.map(n=>(null==n?"":String(n)).replace(/\t/g," ").replace(/\r?\n/g," ")).join("\t")).join("\n");navigator.clipboard&&navigator.clipboard.writeText(t).catch(()=>{})},cutSelection(){this.copySelection(),this.clipboardMode="cut"},async pasteAt(n,e,t={}){const{mode:i="all",transpose:l=!1}=t;let s=null;if(navigator.clipboard)try{const n=await navigator.clipboard.readText();n&&(s=(r=n)?r.split("\n").map(n=>n.split("\t")):[[""]])}catch(n){}var r;if(!s&&this.clipboardMatrix&&(s=this.clipboardMatrix),!s)return;l&&(s=function(n){if(!n||0===n.length)return[];const e=n.length,t=n[0].length;return Array.from({length:t},(t,i)=>Array.from({length:e},(e,t)=>n[t][i]))}(s));if(!this.clipboardMode||"copy"===this.clipboardMode){const t=this.primaryRange;if(t){const i=o(t),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(n,e,t){const i=n.length,o=n[0]?n[0].length:0;if(!i||!o)return n;if(e%i!==0||t%o!==0)return n;const l=[];for(let s=0;s<e;s++){const e=[];for(let l=0;l<t;l++)e.push(n[s%i][l%o]);l.push(e)}return l}(s,l,r),n=i.startRow,e=i.startCol)}}const a=this.data,{newData:c,affectedRange:d,skipped:h}=x(a,this.leafCols,n,e,s,i,(n,e)=>this.isCellEditable(n,e),(n,e,t)=>this.isCellValueValid(n,e,t));if(this.strictBulkValidation&&h.length>0)this._emitBulkValidationError(h,!0);else{if("cut"===this.clipboardMode&&this.clipboardRange){const n=this.clipboardRange;for(let e=n.startRow;e<=n.endRow;e++){c[e]===a[e]&&(c[e]={...a[e]});for(let t=n.startCol;t<=n.endCol;t++){const n=this.leafCols[t];n&&this.isCellEditable(e,t)&&this.isCellValueValid(e,t,"")&&(c[e][n.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(n,e=!1){this.hideContextMenu(),this.pasteAt(this.activeCell.row,this.activeCell.col,{mode:n,transpose:e})},startColResize(n,e){this.$refs.wrapper.getBoundingClientRect(),this.resizing={active:!0,type:"col",index:e,startX:n.clientX,startY:n.clientY,startSize:this.getColW(e),currentPos:n.clientX},n.preventDefault()},startRowResize(n,e){this.resizing={active:!0,type:"row",index:e,startX:n.clientX,startY:n.clientY,startSize:this.getRowH(e),currentPos:n.clientY},n.preventDefault()},handleResizeMove(n){if("col"===this.resizing.type){this.resizing.currentPos=n.clientX;const e=Math.max(20,this.resizing.startSize+(n.clientX-this.resizing.startX));this.$set(this.colWidths,this.resizing.index,e),this._cellStyleCache=Object.create(null)}else this.resizing.currentPos=n.clientY,this._resizePreviewHeight=Math.max(10,this.resizing.startSize+(n.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 n=this.colWidths[this.resizing.index];this.$emit("column-resize",{index:this.resizing.index,newSize:n})}else{this._rowStyleCache=Object.create(null),this._rowHeaderStyleCache=Object.create(null),this._cellStyleCache=Object.create(null);const n=this.rowHeights[this.resizing.index];this.$emit("row-resize",{index:this.resizing.index,newSize:n}),this.updateVirtualScroll()}this.resizing.active=!1},autoFitColumn(n){const e=this.leafCols[n];if(!e)return;let t=(e.title||"").length;for(let n=0;n<Math.min(this.totalRows,500);n++){const i=this.data[n]?String(this.data[n][e.key]||""):"";i.length>t&&(t=i.length)}const i=Math.max(50,8*t+16);this.$set(this.colWidths,n,i),this._cellStyleCache=Object.create(null),this.$emit("column-resize",{index:n,newSize:i})},_applyAutoWidth(){this.autoWidth&&this.leafCols.length&&(this.leafCols.forEach((n,e)=>{let t=(n.title||"").length;for(let e=0;e<Math.min(this.totalRows,500);e++){const i=this.data[e]?String(this.data[e][n.key]||""):"";i.length>t&&(t=i.length)}this.$set(this.colWidths,e,Math.max(50,8*t+16))}),this._cellStyleCache=Object.create(null))},ctxHideRow(){this.hideContextMenu();const n=this.primaryRange;if(!n)return;const e=o(n),t=[...this.hiddenRows];for(let n=e.startRow;n<=e.endRow;n++)t.includes(n)||t.push(n);this.$emit("update:hiddenRows",t)},ctxHideColumn(){this.hideContextMenu();const n=this.primaryRange;if(!n)return;const e=o(n),t=[...this.hiddenColumns];for(let n=e.startCol;n<=e.endCol;n++)t.includes(n)||t.push(n);this.$emit("update:hiddenColumns",t)},ctxUnhideRows(){this.hideContextMenu(),this.$emit("update:hiddenRows",[])},ctxUnhideColumns(){this.hideContextMenu(),this.$emit("update:hiddenColumns",[])},ctxPinLeft(){const n=this.leafCols[this.contextMenu.col];n?(this.$emit("update:pinnedColumns",{...this.pinnedColumns,[n.key]:"left"}),this.hideContextMenu()):this.hideContextMenu()},ctxPinRight(){const n=this.leafCols[this.contextMenu.col];n?(this.$emit("update:pinnedColumns",{...this.pinnedColumns,[n.key]:"right"}),this.hideContextMenu()):this.hideContextMenu()},ctxUnpin(){const n=this.leafCols[this.contextMenu.col];if(!n)return void this.hideContextMenu();const e={...this.pinnedColumns};delete e[n.key],this.$emit("update:pinnedColumns",e),this.hideContextMenu()},isColPinned(n){const e=this.leafCols[n];return!(!e||!this.pinnedColumns[e.key])},mergeCells(n){const e=this.internalMerges.slice(),{merges:t,conflict:i}=function(n,e){const t=o(n);return t.startRow===t.endRow&&t.startCol===t.endCol?{merges:e,conflict:!1}:v(t,e)?{merges:e,conflict:!0}:{merges:[...e,t],conflict:!1}}(n,this.internalMerges);return i?(this.$emit("merge-error",{range:n,reason:"Overlaps existing merge"}),!1):(this.internalMerges=t,this._emitMergeChange(e,[...t]),!0)},unmergeCells(n,e){const t=this.internalMerges.slice(),i=function(n,e,t){return t.filter(t=>{const i=o(t);return!(n>=i.startRow&&n<=i.endRow&&e>=i.startCol&&e<=i.endCol)})}(n,e,this.internalMerges);this.internalMerges=i,this._emitMergeChange(t,[...i])},ctxMerge(){this.hideContextMenu();const n=this.primaryRange;n&&this.mergeCells(o(n))},ctxUnmerge(){this.hideContextMenu(),this.unmergeCells(this.activeCell.row,this.activeCell.col)},groupRows(n,e){this.internalGroups=b("row",n,e,this.internalGroups)},groupColumns(n,e){this.internalGroups=b("column",n,e,this.internalGroups)},ungroupRows(n,e){this.internalGroups=R("row",n,e,this.internalGroups)},ungroupColumns(n,e){this.internalGroups=R("column",n,e,this.internalGroups)},toggleGroup(n){this.internalCollapsedGroups=function(n,e){return e.find(e=>e.type===n.type&&e.startIndex===n.startIndex&&e.endIndex===n.endIndex)?e.filter(e=>!(e.type===n.type&&e.startIndex===n.startIndex&&e.endIndex===n.endIndex)):[...e,{type:n.type,startIndex:n.startIndex,endIndex:n.endIndex}]}(n,this.internalCollapsedGroups),this.$emit("update:collapsedGroups",[...this.internalCollapsedGroups]),this.$emit("group-toggle",{group:n,collapsed:k(n,this.internalCollapsedGroups)}),this.updateVirtualScroll()},isGroupCollapsed(n){return k(n,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(n,e,t){const i=this.primaryRange;i&&(this.dragMove={active:!0,sourceRange:o(i),startX:n.clientX,startY:n.clientY,targetRow:e,targetCol:t,copyMode:n.ctrlKey||n.metaKey},this.$emit("cell-drag-start",{sourceRange:this.dragMove.sourceRange}),n.preventDefault())},handleDragMoveMove(n){this.dragMove.copyMode=n.ctrlKey||n.metaKey,this.$emit("cell-drag-move",{sourceRange:this.dragMove.sourceRange,targetRow:this.dragMove.targetRow,targetCol:this.dragMove.targetCol})},async commitDragMove(n){if(!this.dragMove.active)return;const{sourceRange:e,targetRow:t,targetCol:i,copyMode:o}=this.dragMove,l={startRow:t,startCol:i,endRow:t+(e.endRow-e.startRow),endCol:i+(e.endCol-e.startCol)};let s=!0;if(this.beforeDrop&&(s=await Promise.resolve(this.beforeDrop(e,l))),s){const n=this.data,s=m(n,this.leafCols,e),{newData:r}=x(n,this.leafCols,t,i,s,"all",(n,e)=>this.isCellEditable(n,e));if(!o)for(let t=e.startRow;t<=e.endRow;t++){r[t]===n[t]&&(r[t]={...n[t]});for(let n=e.startCol;n<=e.endCol;n++){const e=this.leafCols[n];e&&this.isCellEditable(t,n)&&(r[t][e.key]="")}}this._emitDataChange(n,r),this.$emit("cell-drop",{sourceRange:e,targetRange:l})}this.dragMove={active:!1,sourceRange:null,startX:0,startY:0,targetRow:-1,targetCol:-1,copyMode:!1},this.isMouseDown=!1},async exportToExcel(n={}){const{filename:e="export.xlsx",sheetName:t="Sheet1",scope:l="all",includeHidden:s=!1}=n;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:e,sheetName:t,scope:l,includeHidden:s};try{null||function(n){const{data:e,columns:t,leafCols:l,merges:s=[],hiddenColSet:d=new Set,hiddenRowSet:h=new Set,range:p=null,filename:u="export.xlsx",sheetName:g="Sheet1",scope:f="all",includeHidden:A=!1}=n,C=l.filter((n,e)=>{if(!A&&d.has(e))return!1;if("selection"===f&&p){const n=o(p);if(e<n.startCol||e>n.endCol)return!1}return!0});let w=0,m=e.length-1;if("selection"===f&&p){const n=o(p);w=n.startRow,m=n.endRow}const x=[];for(let n=w;n<=m;n++)!A&&h.has(n)||x.push({rowIndex:n,data:e[n]});const v=a(t),y=c(t,v),b=i.utils.book_new(),R=[];for(let n=0;n<y.length;n++){const e=[];for(const t of y[n])if(t.isGroup){const n=r(t.column.children||[]).filter(n=>C.some(e=>e.key===n.key));if(0===n.length)continue;e.push(t.column.title||"");for(let t=1;t<n.length;t++)e.push(null)}else{if(!C.some(n=>n.key===t.column.key))continue;t.rowspan,e.push(t.column.title||"")}R.push(e)}for(const{data:n}of x){const e=C.map(e=>{const t=n?n[e.key]:"";return void 0!==t?t:""});R.push(e)}const k=i.utils.aoa_to_sheet(R);k["!cols"]=C.map(n=>({wch:Math.round((n.width||100)/7)}));const S=[];for(const n of s){const e=o(n),t="selection"===f&&p?o(p):{startRow:0,startCol:0};if("selection"===f){if(e.startRow<t.startRow||e.endRow>t.endRow)continue;if(e.startCol<t.startCol||e.endCol>t.endCol)continue}const i=C.findIndex(n=>l.indexOf(n)===e.startCol),s=C.findIndex(n=>l.indexOf(n)===e.endCol);if(i<0||s<0)continue;const r=x.filter(n=>n.rowIndex<e.startRow).length,a=x.filter(n=>n.rowIndex<=e.endRow).length;S.push({s:{r:v+r,c:i},e:{r:v+a-1,c:s}})}S.length&&(k["!merges"]=S),i.utils.book_append_sheet(b,k,g),i.writeFile(b,u)}({...d,hiddenColSet:this.effectiveHiddenColSet,hiddenRowSet:this.hiddenRowSet}),this.$emit("export-success")}catch(n){this.$emit("export-error",n)}finally{this.exportState={active:!1}}},_exportViaWorker:(n,e)=>new Promise((t,i)=>{let o=!1;const l=(e,t)=>{o||(o=!0,n.terminate(),e(t))};n.onmessage=n=>{const{type:e,buffer:o,filename:s,message:r}=n.data;if("result"===e){const n=new Blob([new Uint8Array(o)],{type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}),e=URL.createObjectURL(n),i=document.createElement("a");i.href=e,i.download=s,document.body.appendChild(i),i.click(),document.body.removeChild(i),URL.revokeObjectURL(e),l(t)}else"error"===e&&l(i,new Error(r))},n.onerror=n=>l(i,new Error(n.message||"Export worker xatosi")),n.postMessage(e)}),async importFromFile(n,e={}){this.importState={active:!0,percent:5,message:"Fayl o'qilmoqda..."};try{let t;null||(t=await M(n,this.columns,e,(n,e)=>{this.importState={active:!0,percent:n,message:e}}));const{columns:i,merges:o}=t,l=t.data.map(n=>Object.freeze(n));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.scrollFooter&&(this.$refs.scrollFooter.scrollLeft=0),this.$refs.scrollRowHeaders&&(this.$refs.scrollRowHeaders.scrollTop=0),this.updateVirtualScroll()})}catch(n){this.$emit("import-error",n)}finally{this.importState={active:!1,percent:0,message:""}}},_importViaWorker(n,e,t){return new Promise(async(i,o)=>{let l=!1;const s=(e,t)=>{l||(l=!0,n.terminate(),e(t))};n.onmessage=n=>{const{type:e,percent:t,message:l,data:r,columns:a,merges:c}=n.data;"progress"===e?this.importState={active:!0,percent:t,message:l}:"result"===e?s(i,{data:r,columns:a,merges:c}):"error"===e&&s(o,new Error(l))},n.onerror=n=>s(o,new Error(n.message||"Import worker xatosi"));try{const i=await e.arrayBuffer();n.postMessage({buffer:i,existingColumns:this.columns,options:t},[i])}catch(n){s(o,n)}})},_pushHistory(n){this._historyIndex<this._history.length-1&&this._history.splice(this._historyIndex+1),this._history.push(n),this._history.length>100?this._history.shift():this._historyIndex++},_emitDataChange(n,e){if(!this._historySkip){const t=[],i=[],o=[],l=Math.max(n.length,e.length);for(let s=0;s<l;s++)n[s]!==e[s]&&(t.push(s),i.push(n[s]),o.push(e[s]));t.length>0&&this._pushHistory({type:"data",before:{indices:t,rows:i},after:{indices:t,rows:o}})}this.$emit("input",e)},_emitMergeChange(n,e){this._historySkip||this._pushHistory({type:"merges",before:n,after:e}),this.$emit("update:merges",e)},undo(){if(this._historyIndex<0)return;const n=this._history[this._historyIndex--];this._historySkip=!0;try{if("data"===n.type){const e=this.data.slice();for(let t=0;t<n.before.indices.length;t++)e[n.before.indices[t]]=n.before.rows[t];this.$emit("input",e)}else"merges"===n.type&&(this.internalMerges=n.before.slice(),this.$emit("update:merges",n.before.slice()))}finally{this._historySkip=!1}},redo(){if(this._historyIndex>=this._history.length-1)return;const n=this._history[++this._historyIndex];this._historySkip=!0;try{if("data"===n.type){const e=this.data.slice();for(let t=0;t<n.after.indices.length;t++)e[n.after.indices[t]]=n.after.rows[t];this.$emit("input",e)}else"merges"===n.type&&(this.internalMerges=n.after.slice(),this.$emit("update:merges",n.after.slice()))}finally{this._historySkip=!1}}}};var X=function(){var n=this,e=n.$createElement,t=n._self._c||e;return t("div",{ref:"wrapper",staticClass:"eg-wrapper",class:"eg-size-"+n.size,style:n.wrapperStyle,attrs:{tabindex:"0"},on:{keydown:n.onKeyDown,keypress:n.onKeyPress}},[n.contextMenu.visible?t("div",{staticClass:"eg-context-menu",style:{top:n.contextMenu.y+"px",left:n.contextMenu.x+"px"},on:{click:function(n){n.stopPropagation()}}},[t("div",{staticClass:"eg-context-item",on:{click:n.ctxCopy}},[n._v(n._s(n.t.copy))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxCut}},[n._v(n._s(n.t.cut))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxPaste}},[n._v(n._s(n.t.paste))]),n._v(" "),t("div",{staticClass:"eg-context-separator"}),n._v(" "),t("div",{staticClass:"eg-context-item eg-context-submenu-parent"},[n._v("\n "+n._s(n.t.pasteSpecial)+"\n "),t("div",{staticClass:"eg-context-submenu"},[t("div",{staticClass:"eg-context-item",on:{click:function(e){return n.ctxPasteSpecial("all")}}},[n._v(n._s(n.t.pasteAll))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:function(e){return n.ctxPasteSpecial("values")}}},[n._v(n._s(n.t.pasteValues))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:function(e){return n.ctxPasteSpecial("formats")}}},[n._v(n._s(n.t.pasteFormats))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:function(e){return n.ctxPasteSpecial("all",!0)}}},[n._v(n._s(n.t.transpose))])])]),n._v(" "),t("div",{staticClass:"eg-context-separator"}),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxMerge}},[n._v(n._s(n.t.mergeCells))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxUnmerge}},[n._v(n._s(n.t.unmergeCells))]),n._v(" "),t("div",{staticClass:"eg-context-separator"}),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxHideRow}},[n._v(n._s(n.t.hideRows))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxHideColumn}},[n._v(n._s(n.t.hideColumns))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxUnhideRows}},[n._v(n._s(n.t.unhideRows))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxUnhideColumns}},[n._v(n._s(n.t.unhideColumns))]),n._v(" "),n.contextMenu.row<0&&n.contextMenu.col>=0?[t("div",{staticClass:"eg-context-separator"}),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxPinLeft}},[n._v(n._s(n.t.pinLeft))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxPinRight}},[n._v(n._s(n.t.pinRight))]),n._v(" "),n.isColPinned(n.contextMenu.col)?t("div",{staticClass:"eg-context-item",on:{click:n.ctxUnpin}},[n._v(n._s(n.t.unpin))]):n._e()]:n._e()],2):n._e(),n._v(" "),t("div",{ref:"container",staticClass:"eg-container",on:{scroll:n.onScroll,click:n.hideContextMenu}},[t("div",{staticClass:"eg-header-area",style:n.headerAreaStyle},[t("div",{staticClass:"eg-corner",style:n.cornerStyle,on:{click:n.selectAll}}),n._v(" "),n.rowGroups.length?t("div",{staticClass:"eg-row-outline-gutter",style:n.rowOutlineGutterStyle}):n._e(),n._v(" "),n.getFrozenWidth()>0?t("div",{staticClass:"eg-frozen-col-header",style:n.frozenColHeaderStyle},[t("div",{style:{position:"relative",height:n.headerRowHeight*n.headerDepth+"px"}},[n._l(n.headerRows,function(e,i){return[n._l(e,function(e){return[n.isCellVisible(e,i,!0)?[t("div",{key:"fh-"+i+"-"+(e.column.key||e.column.title),staticClass:"eg-header-cell",class:{"eg-header-group":e.isGroup},style:n.getHeaderCellStyle(e,i,!0),on:{mousedown:function(t){return n.onColHeaderMouseDown(t,e)},dblclick:function(t){return n.onColHeaderDblClick(t,e)},contextmenu:function(t){return t.preventDefault(),n.onColHeaderContextMenu(t,e)}}},[e.isGroup?t("span",[n._v(n._s(e.column.title))]):n._t("header-"+e.column.key,function(){return[n._v("\n "+n._s(e.column.title)+"\n ")]},{column:e.column}),n._v(" "),e.isGroup?n._e():t("div",{staticClass:"eg-col-resize-handle",on:{mousedown:function(t){return t.stopPropagation(),n.startColResize(t,e.column._flatIndex)}}})],2)]:n._e()]})]})],2)]):n._e(),n._v(" "),t("div",{ref:"scrollColHeader",staticClass:"eg-scroll-col-header",style:n.scrollColHeaderStyle},[t("div",{staticClass:"eg-scroll-col-header-inner",style:{width:n.scrollContentWidth+"px",height:n.headerRowHeight*n.headerDepth+"px",position:"relative"}},[n._l(n.headerRows,function(e,i){return[n._l(e,function(e){return[n.isCellVisible(e,i,!1)?[t("div",{key:"sh-"+i+"-"+(e.column.key||e.column.title),staticClass:"eg-header-cell",class:{"eg-header-group":e.isGroup},style:n.getHeaderCellStyle(e,i,!1),on:{mousedown:function(t){return n.onColHeaderMouseDown(t,e)},dblclick:function(t){return n.onColHeaderDblClick(t,e)},contextmenu:function(t){return t.preventDefault(),n.onColHeaderContextMenu(t,e)}}},[e.isGroup?t("span",[n._v(n._s(e.column.title))]):n._t("header-"+e.column.key,function(){return[n._v("\n "+n._s(e.column.title)+"\n ")]},{column:e.column}),n._v(" "),e.isGroup?n._e():t("div",{staticClass:"eg-col-resize-handle",on:{mousedown:function(t){return t.stopPropagation(),n.startColResize(t,e.column._flatIndex)}}})],2)]:n._e()]})]})],2)]),n._v(" "),n.hasPinnedRight?t("div",{staticClass:"eg-pinned-right-header",style:n.pinnedRightHeaderStyle},[t("div",{style:{position:"relative",height:n.headerRowHeight*n.headerDepth+"px"}},[n._l(n.headerRows,function(e,i){return[n._l(e,function(e){return[n.isCellInZone(e,i,"right")?[t("div",{key:"prh-"+i+"-"+(e.column.key||e.column.title),staticClass:"eg-header-cell",class:{"eg-header-group":e.isGroup},style:n.getHeaderCellStyleForZone(e,i,"right"),on:{mousedown:function(t){return n.onColHeaderMouseDown(t,e)},dblclick:function(t){return n.onColHeaderDblClick(t,e)},contextmenu:function(t){return t.preventDefault(),n.onColHeaderContextMenu(t,e)}}},[e.isGroup?t("span",[n._v(n._s(e.column.title))]):n._t("header-"+e.column.key,function(){return[n._v("\n "+n._s(e.column.title)+"\n ")]},{column:e.column}),n._v(" "),e.isGroup?n._e():t("div",{staticClass:"eg-col-resize-handle",on:{mousedown:function(t){return t.stopPropagation(),n.startColResize(t,e.column._flatIndex)}}})],2)]:n._e()]})]})],2)]):n._e()]),n._v(" "),t("div",{staticClass:"eg-body-area",style:n.bodyAreaStyle},[t("div",{staticClass:"eg-row-header-col",style:n.rowHeaderColStyle},[t("div",{ref:"scrollRowHeaders",staticClass:"eg-scroll-row-headers",style:n.scrollRowHeadersStyle},[t("div",{style:{height:n.totalVirtualHeight+"px",position:"relative"}},[t("div",{style:{transform:"translateY("+n.virtualOffsetTop+"px)"}},[n._l(n.visibleRowRange,function(e){return[n.isRowHidden(e)?n._e():t("div",{key:"srh-"+e,staticClass:"eg-row-header-cell",style:n.getRowHeaderStyle(e),on:{mousedown:function(t){return n.onRowHeaderMouseDown(t,e)},contextmenu:function(t){return t.preventDefault(),n.onRowContextMenu(t,e)}}},[n._v("\n "+n._s(e+1)+"\n "),t("div",{staticClass:"eg-row-resize-handle",on:{mousedown:function(t){return t.stopPropagation(),n.startRowResize(t,e)}}})])]})],2)])])]),n._v(" "),n.rowGroups.length?t("div",{staticClass:"eg-row-outline-col",style:n.rowOutlineColStyle},[n._l(n.rowGroups,function(e){return[t("div",{key:"rg-"+e.startIndex+"-"+e.endIndex,staticClass:"eg-group-btn",class:{collapsed:n.isGroupCollapsed(e)},style:n.getRowGroupBtnStyle(e),on:{click:function(t){return n.toggleGroup(e)}}},[n._v(n._s(n.isGroupCollapsed(e)?"+":"−"))])]})],2):n._e(),n._v(" "),t("div",{ref:"scrollBody",staticClass:"eg-scroll-body",style:n.scrollBodyStyle,on:{scroll:n.onBodyScroll}},[t("div",{staticClass:"eg-scroll-body-inner",style:{width:n.scrollContentWidth+"px",height:n.totalVirtualHeight+"px",position:"relative"}},[n.getFrozenWidth()>0?t("div",{staticClass:"eg-frozen-col-body",style:n.frozenColBodyStyle},[t("div",{style:{height:n.totalVirtualHeight+"px",position:"relative"}},[t("div",{style:{transform:"translateY("+n.virtualOffsetTop+"px)"}},[n._l(n.visibleRowRange,function(e){return[t("transition",{key:"tr-fc-"+e,attrs:{name:n._rowTransitionName}},[n.isRowHidden(e)?n._e():t("div",{key:"fc-row-"+e,staticClass:"eg-row",style:n.getRowStyle(e)},[n._l(n.leafCols,function(i,o){return[n.isColumnPinnedLeft(o)&&!n.isColHidden(o)?[t("div",{key:"fc-cell-"+e+"-"+o,staticClass:"eg-cell",class:n.getCellClasses(e,o),style:n.getCellStyle(e,o),on:{mousedown:function(t){return n.onCellMouseDown(t,e,o)},mouseover:function(t){return n.onCellMouseOver(t,e,o)},mouseup:n.onCellMouseUp,dblclick:function(t){return n.onCellDblClick(e,o)},contextmenu:function(t){return t.preventDefault(),n.onCellContextMenu(t,e,o)}}},[n.isMergedAway(e,o)?t("div",{staticStyle:{display:"none"}}):n.isEditing(e,o)?[n.hasCellSlot(i)?n._t("cell-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o),isEditing:!0,done:function(e){return n.commitEditSlot(e)},cancel:n.cancelEdit}):n.hasLegacyEditSlot(i)?n._t("edit-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o),done:function(e){return n.commitEditSlot(e)},cancel:n.cancelEdit}):t("input",{directives:[{name:"model",rawName:"v-model",value:n.editValue,expression:"editValue"}],ref:"editInput",refInFor:!0,staticClass:"eg-cell-input",domProps:{value:n.editValue},on:{blur:n.commitEdit,keydown:function(e){return e.stopPropagation(),n.onEditKeyDown.apply(null,arguments)},input:function(e){e.target.composing||(n.editValue=e.target.value)}}})]:"checkbox"===i.type?[t("eg-checkbox",{attrs:{value:n.isCheckboxChecked(e,o),disabled:!n.isCellEditable(e,o)},on:{change:function(t){return n.setCheckboxValue(e,o,t)}}})]:[n._t("cell-"+i.key,function(){return[n.hasPrefixSlot(i)?n._t("prefix-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o)}):n._e(),n.hasPrefixSlot(i)||n.hasSuffixSlot(i)?t("span",{staticClass:"eg-cell-value"},[n._v(n._s(n.isSelectLikeCol(i)?n.getSelectLabel(o,n.getCellValue(e,o)):n.getCellValue(e,o)))]):[n._v(n._s(n.isSelectLikeCol(i)?n.getSelectLabel(o,n.getCellValue(e,o)):n.getCellValue(e,o)))],n.hasSuffixSlot(i)?n._t("suffix-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o)}):n._e()]},{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o),isEditing:!1,done:null,cancel:null})]],2)]:n._e()]})],2)])]})],2)])]):n._e(),n._v(" "),t("div",{staticClass:"eg-main-cells",style:n.mainCellsStyle},[t("div",{style:{height:n.totalVirtualHeight+"px",position:"relative",width:n.scrollableContentWidth+"px"}},[t("div",{style:{transform:"translateY("+n.virtualOffsetTop+"px)"}},[n._l(n.visibleRowRange,function(e){return[t("transition",{key:"tr-m-"+e,attrs:{name:n._rowTransitionName}},[n.isRowHidden(e)?n._e():t("div",{key:"row-"+e,staticClass:"eg-row",style:n.getRowStyle(e),attrs:{"data-row":e}},[n._l(n.leafCols,function(i,o){return[n.isColumnScrollable(o)&&!n.isColHidden(o)?[t("div",{key:"cell-"+e+"-"+o,staticClass:"eg-cell",class:n.getCellClasses(e,o),style:n.getCellStyle(e,o),on:{mousedown:function(t){return n.onCellMouseDown(t,e,o)},mouseover:function(t){return n.onCellMouseOver(t,e,o)},mouseup:n.onCellMouseUp,dblclick:function(t){return n.onCellDblClick(e,o)},contextmenu:function(t){return t.preventDefault(),n.onCellContextMenu(t,e,o)}}},[n.isMergedAway(e,o)?t("div",{staticStyle:{display:"none"}}):n.isEditing(e,o)?[n.hasCellSlot(i)?n._t("cell-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o),isEditing:!0,done:function(e){return n.commitEditSlot(e)},cancel:n.cancelEdit}):n.hasLegacyEditSlot(i)?n._t("edit-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o),done:function(e){return n.commitEditSlot(e)},cancel:n.cancelEdit}):t("input",{directives:[{name:"model",rawName:"v-model",value:n.editValue,expression:"editValue"}],ref:"editInput",refInFor:!0,staticClass:"eg-cell-input",domProps:{value:n.editValue},on:{blur:n.commitEdit,keydown:function(e){return e.stopPropagation(),n.onEditKeyDown.apply(null,arguments)},input:function(e){e.target.composing||(n.editValue=e.target.value)}}})]:"checkbox"===i.type?[t("eg-checkbox",{attrs:{value:n.isCheckboxChecked(e,o),disabled:!n.isCellEditable(e,o)},on:{change:function(t){return n.setCheckboxValue(e,o,t)}}})]:[n._t("cell-"+i.key,function(){return[n.hasPrefixSlot(i)?n._t("prefix-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o)}):n._e(),n.hasPrefixSlot(i)||n.hasSuffixSlot(i)?t("span",{staticClass:"eg-cell-value"},[n._v(n._s(n.isSelectLikeCol(i)?n.getSelectLabel(o,n.getCellValue(e,o)):n.getCellValue(e,o)))]):[n._v(n._s(n.isSelectLikeCol(i)?n.getSelectLabel(o,n.getCellValue(e,o)):n.getCellValue(e,o)))],n.hasSuffixSlot(i)?n._t("suffix-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o)}):n._e()]},{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o),isEditing:!1,done:null,cancel:null})]],2)]:n._e()]})],2)])]})],2)])]),n._v(" "),n.hasPinnedRight?t("div",{staticClass:"eg-pinned-right-body",style:n.pinnedRightBodyStyle},[t("div",{style:{height:n.totalVirtualHeight+"px",position:"relative"}},[t("div",{style:{transform:"translateY("+n.virtualOffsetTop+"px)"}},[n._l(n.visibleRowRange,function(e){return[t("transition",{key:"tr-pr-"+e,attrs:{name:n._rowTransitionName}},[n.isRowHidden(e)?n._e():t("div",{key:"pr-row-"+e,staticClass:"eg-row",style:n.getRowStyle(e)},[n._l(n.pinnedRightCols,function(i){return[t("div",{key:"pr-cell-"+e+"-"+i._flatIndex,staticClass:"eg-cell",class:n.getCellClasses(e,i._flatIndex),style:n.getCellStyle(e,i._flatIndex),on:{mousedown:function(t){return n.onCellMouseDown(t,e,i._flatIndex)},mouseover:function(t){return n.onCellMouseOver(t,e,i._flatIndex)},mouseup:n.onCellMouseUp,dblclick:function(t){return n.onCellDblClick(e,i._flatIndex)},contextmenu:function(t){return t.preventDefault(),n.onCellContextMenu(t,e,i._flatIndex)}}},[n.isMergedAway(e,i._flatIndex)?t("div",{staticStyle:{display:"none"}}):n.isEditing(e,i._flatIndex)?[n.hasCellSlot(i)?n._t("cell-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,i._flatIndex),isEditing:!0,done:function(e){return n.commitEditSlot(e)},cancel:n.cancelEdit}):n.hasLegacyEditSlot(i)?n._t("edit-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,i._flatIndex),done:function(e){return n.commitEditSlot(e)},cancel:n.cancelEdit}):t("input",{directives:[{name:"model",rawName:"v-model",value:n.editValue,expression:"editValue"}],ref:"editInput",refInFor:!0,staticClass:"eg-cell-input",domProps:{value:n.editValue},on:{blur:n.commitEdit,keydown:function(e){return e.stopPropagation(),n.onEditKeyDown.apply(null,arguments)},input:function(e){e.target.composing||(n.editValue=e.target.value)}}})]:"checkbox"===i.type?[t("eg-checkbox",{attrs:{value:n.isCheckboxChecked(e,i._flatIndex),disabled:!n.isCellEditable(e,i._flatIndex)},on:{change:function(t){return n.setCheckboxValue(e,i._flatIndex,t)}}})]:[n._t("cell-"+i.key,function(){return[n.hasPrefixSlot(i)?n._t("prefix-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,i._flatIndex)}):n._e(),n.hasPrefixSlot(i)||n.hasSuffixSlot(i)?t("span",{staticClass:"eg-cell-value"},[n._v(n._s(n.isSelectLikeCol(i)?n.getSelectLabel(i._flatIndex,n.getCellValue(e,i._flatIndex)):n.getCellValue(e,i._flatIndex)))]):[n._v(n._s(n.isSelectLikeCol(i)?n.getSelectLabel(i._flatIndex,n.getCellValue(e,i._flatIndex)):n.getCellValue(e,i._flatIndex)))],n.hasSuffixSlot(i)?n._t("suffix-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,i._flatIndex)}):n._e()]},{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,i._flatIndex),isEditing:!1,done:null,cancel:null})]],2)]})],2)])]})],2)])]):n._e(),n._v(" "),n.fillHandleVisible?t("div",{staticClass:"eg-fill-handle",style:n.fillHandleStyle,on:{mousedown:function(e){return e.stopPropagation(),n.startFillDrag.apply(null,arguments)}}}):n._e(),n._v(" "),n.dragMove.active?t("div",{staticClass:"eg-drag-ghost",style:n.dragGhostStyle}):n._e()])]),n._v(" "),n.colGroups.length?t("div",{staticClass:"eg-col-outline-row",style:n.colOutlineRowStyle},[n._l(n.colGroups,function(e){return[t("div",{key:"cg-"+e.startIndex+"-"+e.endIndex,staticClass:"eg-group-btn",class:{collapsed:n.isGroupCollapsed(e)},style:n.getColGroupBtnStyle(e),on:{click:function(t){return n.toggleGroup(e)}}},[n._v(n._s(n.isGroupCollapsed(e)?"+":"−"))])]})],2):n._e()]),n._v(" "),n.hasFooter?t("div",{staticClass:"eg-footer-area",style:n.footerAreaStyle},[t("div",{staticClass:"eg-footer-label",style:n.footerLabelStyle},[n._v(n._s(n.t.total))]),n._v(" "),n.getFrozenWidth()>0?t("div",{staticClass:"eg-frozen-col-footer",style:n.frozenColFooterStyle},[t("div",{style:{position:"relative",height:n.footerRowHeight+"px"}},[n._l(n.leafCols,function(e,i){return[n.isColumnPinnedLeft(i)&&!n.isColHidden(i)?[t("div",{key:"ff-"+i,staticClass:"eg-footer-cell",style:n.getFooterCellStyle(i)},[n._v("\n "+n._s(n.getColumnTotalDisplay(i))+"\n ")])]:n._e()]})],2)]):n._e(),n._v(" "),t("div",{ref:"scrollFooter",staticClass:"eg-scroll-col-footer",style:n.scrollColHeaderStyle},[t("div",{staticClass:"eg-scroll-col-footer-inner",style:{width:n.scrollContentWidth+"px",height:n.footerRowHeight+"px",position:"relative"}},[n._l(n.leafCols,function(e,i){return[n.isColumnScrollable(i)&&!n.isColHidden(i)?[t("div",{key:"sf-"+i,staticClass:"eg-footer-cell",style:n.getFooterCellStyle(i)},[n._v("\n "+n._s(n.getColumnTotalDisplay(i))+"\n ")])]:n._e()]})],2)]),n._v(" "),n.hasPinnedRight?t("div",{staticClass:"eg-pinned-right-footer",style:n.pinnedRightFooterStyle},[t("div",{style:{position:"relative",height:n.footerRowHeight+"px"}},n._l(n.pinnedRightCols,function(e){return t("div",{key:"prf-"+e._flatIndex,staticClass:"eg-footer-cell",style:n.getFooterCellStyle(e._flatIndex)},[n._v(n._s(n.getColumnTotalDisplay(e._flatIndex)))])}),0)]):n._e()]):n._e(),n._v(" "),n.resizing.active?t("div",{staticClass:"eg-resize-line",style:n.resizeLineStyle}):n._e()]),n._v(" "),n.importState.active?t("div",{staticClass:"eg-import-overlay"},[t("div",{staticClass:"eg-import-dialog"},[t("div",{staticClass:"eg-import-title"},[n._v("Fayl import qilinmoqda")]),n._v(" "),t("div",{staticClass:"eg-import-bar-wrap"},[t("div",{staticClass:"eg-import-bar",style:{width:n.importState.percent+"%"}})]),n._v(" "),t("div",{staticClass:"eg-import-msg"},[n._v(n._s(n.importState.message))]),n._v(" "),t("div",{staticClass:"eg-import-pct"},[n._v(n._s(n.importState.percent)+"%")])])]):n._e(),n._v(" "),n.exportState.active?t("div",{staticClass:"eg-import-overlay"},[n._m(0)]):n._e(),n._v(" "),n.selectDropdown&&!n.selectDropdown.tree?t("eg-select",{ref:"selectDropdownComp",attrs:{value:n.selectDropdown.currentValue,options:n.selectDropdown.rawOptions,"enable-search":n.selectDropdown.enableSearch,"trigger-rect":n.selectDropdown.rect,"dropdown-only":!0},on:{select:n.onComponentSelect,close:n.closeSelectDropdown}}):n._e(),n._v(" "),n.selectDropdown&&n.selectDropdown.tree?t("eg-tree-select",{ref:"selectDropdownComp",attrs:{value:n.selectDropdown.currentValue,options:n.selectDropdown.rawOptions,"value-key":n.selectDropdown.keys.valueKey,"label-key":n.selectDropdown.keys.labelKey,"children-key":n.selectDropdown.keys.childrenKey,"enable-search":n.selectDropdown.enableSearch,"trigger-rect":n.selectDropdown.rect,"dropdown-only":!0},on:{select:n.onComponentSelect,close:n.closeSelectDropdown}}):n._e(),n._v(" "),n.invalidCell&&n.invalidCell.rect?t("div",{staticClass:"eg-validation-tooltip",style:n.invalidTooltipStyle},[n._v(n._s(n.invalidCell.message))]):n._e()],1)};X._withStripped=!0;const Q=H({render:X,staticRenderFns:[function(){var n=this,e=n.$createElement,t=n._self._c||e;return t("div",{staticClass:"eg-import-dialog"},[t("div",{staticClass:"eg-import-title"},[n._v("Fayl export qilinmoqda...")]),n._v(" "),t("div",{staticClass:"eg-export-spinner"})])}]},function(n){n&&(n("data-v-f65b9586_0",{source:"\n.eg-wrapper[data-v-f65b9586] {\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-f65b9586] {\n display: flex;\n flex-direction: column;\n flex: 1;\n overflow: auto;\n position: relative;\n}\n.eg-header-area[data-v-f65b9586] {\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-f65b9586] {\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-f65b9586]:hover { background: #e0e0e0;\n}\n.eg-header-group[data-v-f65b9586] { background: #e8eaf6;\n}\n.eg-col-resize-handle[data-v-f65b9586] {\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-f65b9586] {\n display: flex;\n flex: 1;\n overflow: hidden;\n}\n\n/* ===== FOOTER AREA (column totals) ===== */\n.eg-footer-area[data-v-f65b9586] {\n border-top: 1px solid #ccc;\n}\n.eg-frozen-col-footer[data-v-f65b9586],\n.eg-scroll-col-footer[data-v-f65b9586],\n.eg-pinned-right-footer[data-v-f65b9586] {\n position: relative;\n}\n.eg-footer-cell[data-v-f65b9586] {\n box-sizing: border-box;\n border-right: 1px solid #e0e0e0;\n overflow: hidden;\n white-space: nowrap;\n font-size: 13px;\n font-weight: 600;\n padding: 0 4px;\n position: absolute;\n top: 0;\n background: #f8f9fa;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n}\n.eg-row[data-v-f65b9586] {\n position: relative;\n}\n.eg-cell[data-v-f65b9586] {\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-value[data-v-f65b9586] {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n.eg-cell-active[data-v-f65b9586] {\n outline: 2px solid #1a73e8 !important;\n outline-offset: -2px;\n z-index: 3;\n}\n.eg-cell-selected[data-v-f65b9586] {\n background: #e3f2fd !important;\n}\n.eg-cell-fill-preview[data-v-f65b9586] {\n background: #bbdefb !important;\n outline: 1px dashed #1565c0;\n outline-offset: -1px;\n}\n.eg-cell-copy[data-v-f65b9586] {\n outline: 1px dashed #1a73e8;\n outline-offset: -1px;\n}\n.eg-cell-merged-hidden[data-v-f65b9586] {\n display: none !important;\n}\n.eg-cell-invalid[data-v-f65b9586] {\n outline: 2px solid #e53935 !important;\n outline-offset: -2px;\n z-index: 4;\n}\n.eg-cell-input[data-v-f65b9586] {\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-f65b9586] {\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-f65b9586] {\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-f65b9586] {\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-f65b9586] {\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-f65b9586] {\n padding: 6px 16px;\n cursor: pointer;\n position: relative;\n}\n.eg-context-item[data-v-f65b9586]:hover { background: #f0f0f0;\n}\n.eg-context-separator[data-v-f65b9586] {\n height: 1px;\n background: #e0e0e0;\n margin: 4px 0;\n}\n.eg-context-submenu-parent[data-v-f65b9586]::after { content: ' ▶'; float: right;\n}\n.eg-context-submenu[data-v-f65b9586] {\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-f65b9586] { display: block;\n}\n.eg-group-btn[data-v-f65b9586] {\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-f65b9586] { background: #c5cae9;\n}\n.eg-drag-ghost[data-v-f65b9586] {\n position: absolute;\n border: 2px dashed #1a73e8;\n pointer-events: none;\n z-index: 20;\n}\n.eg-resize-line[data-v-f65b9586] {\n pointer-events: none;\n z-index: 9999;\n}\n\n/* Pin left separator shadow */\n.eg-frozen-col-body[data-v-f65b9586]::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-f65b9586] {\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-f65b9586]::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-f65b9586] {\n overflow: hidden;\n flex-shrink: 0;\n border-left: 2px solid #bbb;\n background: #f0f0f0;\n}\n.eg-import-overlay[data-v-f65b9586] {\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-f65b9586] {\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-f65b9586] {\n font-size: 14px;\n font-weight: 600;\n color: #333;\n margin-bottom: 16px;\n}\n.eg-import-bar-wrap[data-v-f65b9586] {\n height: 8px;\n background: #e0e0e0;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 10px;\n}\n.eg-import-bar[data-v-f65b9586] {\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-f65b9586] {\n font-size: 12px;\n color: #666;\n margin-bottom: 4px;\n}\n.eg-import-pct[data-v-f65b9586] {\n font-size: 13px;\n font-weight: 600;\n color: #1a73e8;\n}\n.eg-export-spinner[data-v-f65b9586] {\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-f65b9586 0.8s linear infinite;\n}\n@keyframes eg-spin-data-v-f65b9586 {\nto { transform: rotate(360deg);\n}\n}\n\n/* ===== READONLY CELL ===== */\n.eg-cell-readonly[data-v-f65b9586] {\n background: #f8f8f8;\n cursor: default;\n}\n.eg-cell-readonly.eg-cell-selected[data-v-f65b9586] {\n background: #dce8f5;\n}\n.eg-cell-readonly.eg-cell-active[data-v-f65b9586] {\n outline-color: #aaa;\n}\n\n/* ===== SELECT DROPDOWN ===== */\n.eg-cell-select[data-v-f65b9586] {\n cursor: pointer;\n}\n.eg-cell-select[data-v-f65b9586]::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-f65b9586] {\n justify-content: center;\n padding: 0 2px;\n}\n.eg-validation-tooltip[data-v-f65b9586] {\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/* ===== ROW TRANSITION ===== */\n.eg-row-enter-active[data-v-f65b9586],\n.eg-row-leave-active[data-v-f65b9586] {\n transition: opacity 0.18s ease, transform 0.18s ease;\n}\n.eg-row-enter[data-v-f65b9586],\n.eg-row-leave-to[data-v-f65b9586] {\n opacity: 0;\n transform: translateY(-4px);\n}\n\n/* ===== SIZE VARIANTS ===== */\n\n/* small */\n.eg-size-small .eg-cell[data-v-f65b9586] {\n font-size: 12px;\n padding: 0 3px;\n}\n.eg-size-small .eg-cell-input[data-v-f65b9586] {\n font-size: 12px;\n}\n.eg-size-small .eg-header-cell[data-v-f65b9586] {\n font-size: 11px;\n}\n.eg-size-small .eg-footer-cell[data-v-f65b9586] {\n font-size: 12px;\n}\n.eg-size-small .eg-row-header-cell[data-v-f65b9586] {\n font-size: 10px;\n}\n.eg-size-small .eg-select-option[data-v-f65b9586] {\n font-size: 12px;\n padding: 4px 10px;\n}\n.eg-size-small .eg-context-menu[data-v-f65b9586],\n.eg-size-small .eg-context-item[data-v-f65b9586] {\n font-size: 12px;\n}\n.eg-size-small .eg-context-item[data-v-f65b9586] {\n padding: 4px 14px;\n}\n\n/* large */\n.eg-size-large .eg-cell[data-v-f65b9586] {\n font-size: 14px;\n padding: 0 6px;\n}\n.eg-size-large .eg-cell-input[data-v-f65b9586] {\n font-size: 14px;\n}\n.eg-size-large .eg-header-cell[data-v-f65b9586] {\n font-size: 13px;\n}\n.eg-size-large .eg-footer-cell[data-v-f65b9586] {\n font-size: 14px;\n}\n.eg-size-large .eg-row-header-cell[data-v-f65b9586] {\n font-size: 12px;\n}\n.eg-size-large .eg-select-option[data-v-f65b9586] {\n font-size: 14px;\n padding: 8px 14px;\n}\n.eg-size-large .eg-context-menu[data-v-f65b9586],\n.eg-size-large .eg-context-item[data-v-f65b9586] {\n font-size: 14px;\n}\n.eg-size-large .eg-context-item[data-v-f65b9586] {\n padding: 8px 18px;\n}\n",map:{version:3,sources:["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/ExcelGrid.vue"],names:[],mappings:";AA8zGA;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,4CAAA;AACA;EACA,0BAAA;AACA;AAEA;;;EAGA,kBAAA;AACA;AAEA;EACA,sBAAA;EACA,+BAAA;EACA,gBAAA;EACA,mBAAA;EACA,eAAA;EACA,gBAAA;EACA,cAAA;EACA,kBAAA;EACA,MAAA;EACA,mBAAA;EACA,aAAA;EACA,mBAAA;EACA,yBAAA;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,OAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;EACA,YAAA;AACA;AACA;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;EACA,cAAA;AACA;AACA;EACA,mBAAA;EACA,yBAAA;EACA,cAAA;EACA,eAAA;EACA,gBAAA;EACA,kBAAA;EACA,yCAAA;EACA,mBAAA;EACA,oBAAA;AACA;;AAEA,+BAAA;AACA;;EAEA,oDAAA;AACA;AACA;;EAEA,UAAA;EACA,2BAAA;AACA;;AAEA,8BAAA;;AAEA,UAAA;AACA;EACA,eAAA;EACA,cAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;EACA,iBAAA;AACA;AACA;;EAEA,eAAA;AACA;AACA;EACA,iBAAA;AACA;;AAEA,UAAA;AACA;EACA,eAAA;EACA,cAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;EACA,iBAAA;AACA;AACA;;EAEA,eAAA;AACA;AACA;EACA,iBAAA;AACA",file:"ExcelGrid.vue",sourcesContent:["<template>\n <div\n class=\"eg-wrapper\"\n :class=\"'eg-size-' + size\"\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\"\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\"\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\"\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 <transition :key=\"'tr-fc-' + ri\" :name=\"_rowTransitionName\">\n <div v-if=\"!isRowHidden(ri)\" :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 <slot v-if=\"hasCellSlot(col)\" :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"true\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <slot v-else-if=\"hasLegacyEditSlot(col)\" :name=\"'edit-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <input v-else 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 <eg-checkbox\n :value=\"isCheckboxChecked(ri, ci)\"\n :disabled=\"!isCellEditable(ri, ci)\"\n @change=\"(v) => setCheckboxValue(ri, ci, v)\"\n />\n </template>\n <template v-else>\n <slot :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"false\" :done=\"null\" :cancel=\"null\">\n <slot v-if=\"hasPrefixSlot(col)\" :name=\"'prefix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" /><span v-if=\"hasPrefixSlot(col) || hasSuffixSlot(col)\" class=\"eg-cell-value\">{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</span><template v-else>{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</template><slot v-if=\"hasSuffixSlot(col)\" :name=\"'suffix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" />\n </slot>\n </template>\n </div>\n </template>\n </template>\n </div>\n </transition>\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 <transition :key=\"'tr-m-' + ri\" :name=\"_rowTransitionName\">\n <div\n v-if=\"!isRowHidden(ri)\"\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 <slot v-if=\"hasCellSlot(col)\" :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"true\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <slot v-else-if=\"hasLegacyEditSlot(col)\" :name=\"'edit-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <input\n v-else\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 <eg-checkbox\n :value=\"isCheckboxChecked(ri, ci)\"\n :disabled=\"!isCellEditable(ri, ci)\"\n @change=\"(v) => setCheckboxValue(ri, ci, v)\"\n />\n </template>\n <template v-else>\n <slot :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"false\" :done=\"null\" :cancel=\"null\">\n <slot v-if=\"hasPrefixSlot(col)\" :name=\"'prefix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" /><span v-if=\"hasPrefixSlot(col) || hasSuffixSlot(col)\" class=\"eg-cell-value\">{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</span><template v-else>{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</template><slot v-if=\"hasSuffixSlot(col)\" :name=\"'suffix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" />\n </slot>\n </template>\n </div>\n </template>\n </template>\n </div>\n </transition>\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 <transition :key=\"'tr-pr-' + ri\" :name=\"_rowTransitionName\">\n <div v-if=\"!isRowHidden(ri)\" :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 <slot v-if=\"hasCellSlot(col)\" :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" :isEditing=\"true\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <slot v-else-if=\"hasLegacyEditSlot(col)\" :name=\"'edit-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <input v-else 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 <eg-checkbox\n :value=\"isCheckboxChecked(ri, col._flatIndex)\"\n :disabled=\"!isCellEditable(ri, col._flatIndex)\"\n @change=\"(v) => setCheckboxValue(ri, col._flatIndex, v)\"\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)\" :isEditing=\"false\" :done=\"null\" :cancel=\"null\">\n <slot v-if=\"hasPrefixSlot(col)\" :name=\"'prefix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" /><span v-if=\"hasPrefixSlot(col) || hasSuffixSlot(col)\" class=\"eg-cell-value\">{{ isSelectLikeCol(col) ? getSelectLabel(col._flatIndex, getCellValue(ri, col._flatIndex)) : getCellValue(ri, col._flatIndex) }}</span><template v-else>{{ isSelectLikeCol(col) ? getSelectLabel(col._flatIndex, getCellValue(ri, col._flatIndex)) : getCellValue(ri, col._flatIndex) }}</template><slot v-if=\"hasSuffixSlot(col)\" :name=\"'suffix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" />\n </slot>\n </template>\n </div>\n </template>\n </div>\n </transition>\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!-- ===== FOOTER AREA (column totals) ===== --\x3e\n <div v-if=\"hasFooter\" class=\"eg-footer-area\" :style=\"footerAreaStyle\">\n\n \x3c!-- \"Jami\" label cell (mirrors corner cell) --\x3e\n <div class=\"eg-footer-label\" :style=\"footerLabelStyle\">{{ t.total }}</div>\n\n \x3c!-- Frozen columns footer --\x3e\n <div v-if=\"getFrozenWidth() > 0\" class=\"eg-frozen-col-footer\" :style=\"frozenColFooterStyle\">\n <div :style=\"{ position: 'relative', height: footerRowHeight + 'px' }\">\n <template v-for=\"(col, ci) in leafCols\">\n <template v-if=\"isColumnPinnedLeft(ci) && !isColHidden(ci)\">\n <div :key=\"'ff-' + ci\" class=\"eg-footer-cell\" :style=\"getFooterCellStyle(ci)\">\n {{ getColumnTotalDisplay(ci) }}\n </div>\n </template>\n </template>\n </div>\n </div>\n\n \x3c!-- Scrollable footer (horizontally synced with body via scrollFooter ref) --\x3e\n <div class=\"eg-scroll-col-footer\" ref=\"scrollFooter\" :style=\"scrollColHeaderStyle\">\n <div class=\"eg-scroll-col-footer-inner\" :style=\"{ width: scrollContentWidth + 'px', height: footerRowHeight + 'px', position: 'relative' }\">\n <template v-for=\"(col, ci) in leafCols\">\n <template v-if=\"isColumnScrollable(ci) && !isColHidden(ci)\">\n <div :key=\"'sf-' + ci\" class=\"eg-footer-cell\" :style=\"getFooterCellStyle(ci)\">\n {{ getColumnTotalDisplay(ci) }}\n </div>\n </template>\n </template>\n </div>\n </div>\n\n \x3c!-- Pinned Right columns footer --\x3e\n <div v-if=\"hasPinnedRight\" class=\"eg-pinned-right-footer\" :style=\"pinnedRightFooterStyle\">\n <div :style=\"{ position: 'relative', height: footerRowHeight + 'px' }\">\n <div\n v-for=\"col in pinnedRightCols\"\n :key=\"'prf-' + col._flatIndex\"\n class=\"eg-footer-cell\"\n :style=\"getFooterCellStyle(col._flatIndex)\"\n >{{ getColumnTotalDisplay(col._flatIndex) }}</div>\n </div>\n </div>\n </div>\x3c!-- end footer-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 (flat select) --\x3e\n <eg-select\n v-if=\"selectDropdown && !selectDropdown.tree\"\n ref=\"selectDropdownComp\"\n :value=\"selectDropdown.currentValue\"\n :options=\"selectDropdown.rawOptions\"\n :enable-search=\"selectDropdown.enableSearch\"\n :trigger-rect=\"selectDropdown.rect\"\n :dropdown-only=\"true\"\n @select=\"onComponentSelect\"\n @close=\"closeSelectDropdown\"\n />\n \x3c!-- Tree select dropdown overlay --\x3e\n <eg-tree-select\n v-if=\"selectDropdown && selectDropdown.tree\"\n ref=\"selectDropdownComp\"\n :value=\"selectDropdown.currentValue\"\n :options=\"selectDropdown.rawOptions\"\n :value-key=\"selectDropdown.keys.valueKey\"\n :label-key=\"selectDropdown.keys.labelKey\"\n :children-key=\"selectDropdown.keys.childrenKey\"\n :enable-search=\"selectDropdown.enableSearch\"\n :trigger-rect=\"selectDropdown.rect\"\n :dropdown-only=\"true\"\n @select=\"onComponentSelect\"\n @close=\"closeSelectDropdown\"\n />\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'\nimport EgCheckbox from './EgCheckbox.vue'\nimport EgSelect from './EgSelect.vue'\nimport EgTreeSelect from './EgTreeSelect.vue'\n\nconst SIZE_CONFIG = {\n small: { row: 22, header: 24, footer: 24 },\n middle: { row: 28, header: 28, footer: 28 },\n large: { row: 36, header: 36, footer: 36 }\n}\nconst ROW_HEADER_WIDTH = 50\nconst DEFAULT_COL_WIDTH = 100\nconst OUTLINE_GUTTER = 20\n\nexport default {\n name: 'ExcelGrid',\n\n components: { EgCheckbox, EgSelect, EgTreeSelect },\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 /** Visual size of the grid. Affects row/header height and font sizes. */\n size: { type: String, default: 'middle' },\n\n /** Enable CSS list transitions on row enter/leave (data changes). Disabled during scroll. */\n transitionEnabled: { type: Boolean, default: false },\n\n /** Auto-fit all column widths to content (same as double-clicking each resize handle). Re-applies on data/columns change. */\n autoWidth: { 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 _isScrolling: false,\n\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 // Footer column totals (flat col index → number | null)\n columnTotals: {}\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 hasFooter() { return this.leafCols.some(col => col.showTotal) },\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 _rowTransitionName() {\n return this.transitionEnabled && !this._isScrolling ? 'eg-row' : ''\n },\n\n _sizeConf() { return SIZE_CONFIG[this.size] || SIZE_CONFIG.middle },\n _rowH() { return this._sizeConf.row },\n _headerRowH() { return this._sizeConf.header },\n _footerRowH() { return this._sizeConf.footer },\n\n headerRowHeight() { return this._headerRowH },\n footerRowHeight() { return this._footerRowH },\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, this._rowH, 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\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: (this._headerRowH * 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 footerAreaStyle() {\n return {\n display: 'flex',\n flexDirection: 'row',\n flexShrink: 0,\n position: 'sticky',\n bottom: 0,\n zIndex: 30,\n background: '#f8f9fa',\n boxShadow: '0 -2px 0 0 #d0d0d0'\n }\n },\n\n footerLabelStyle() {\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: this._footerRowH + 'px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n background: '#f0f0f0',\n borderRight: '1px solid #ccc',\n borderTop: '1px solid #ccc',\n boxSizing: 'border-box',\n flexShrink: 0,\n fontSize: '12px',\n fontWeight: 600\n }\n },\n\n frozenColFooterStyle() {\n const w = this.getFrozenWidth()\n return {\n width: w + 'px',\n minWidth: w + 'px',\n height: this._footerRowH + 'px',\n overflow: 'hidden',\n flexShrink: 0\n }\n },\n\n pinnedRightFooterStyle() {\n return {\n width: this.pinnedRightWidth + 'px',\n minWidth: this.pinnedRightWidth + 'px',\n height: this._footerRowH + 'px',\n overflow: 'hidden',\n flexShrink: 0,\n borderLeft: '1px solid #ccc'\n }\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 size() {\n this.rowHeights = {}\n this.$nextTick(() => this.updateVirtualScroll())\n },\n\n autoWidth(v) {\n if (v) this._applyAutoWidth()\n },\n\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 this.computeColumnTotals()\n this.updateVirtualScroll()\n this._applyAutoWidth()\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 this.computeColumnTotals()\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 this._applyAutoWidth()\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 window.addEventListener('scroll', this._onWindowScrollForDropdown, true)\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 window.removeEventListener('scroll', this._onWindowScrollForDropdown, true)\n clearTimeout(this._scrollEndTimer)\n },\n\n methods: {\n // =========================================================\n // VIRTUAL SCROLL\n // =========================================================\n updateVirtualScroll() {\n const footerH = this.hasFooter ? this._footerRowH : 0\n const containerH = this.height - (this._headerRowH * this.headerDepth) - footerH\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.scrollFooter) this.$refs.scrollFooter.scrollLeft = target.scrollLeft\n if (this.$refs.scrollRowHeaders) this.$refs.scrollRowHeaders.scrollTop = target.scrollTop\n\n // Close dropdown on scroll — fixed-position dropdown drifts away from the moving cell\n if (this.selectDropdown) this.closeSelectDropdown()\n\n // Suppress row transitions during scroll to avoid animation on every virtual-scroll paint\n if (this.transitionEnabled) {\n this._isScrolling = true\n clearTimeout(this._scrollEndTimer)\n this._scrollEndTimer = setTimeout(() => { this._isScrolling = false }, 150)\n }\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] || this._rowH\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: (this._headerRowH * rowIndex) + 'px',\n left: left + 'px',\n width: width + 'px',\n height: (this._headerRowH * 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 && this.isSelectLikeCol(col)),\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: (this._headerRowH * rowIndex) + 'px',\n left: left + 'px',\n width: width + 'px',\n height: (this._headerRowH * 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 // =========================================================\n // FOOTER (column totals)\n // =========================================================\n computeColumnTotals() {\n const totals = {}\n const rows = this._dataRaw || []\n this.leafCols.forEach(col => {\n if (!col.showTotal) return\n let sum = 0\n let hasNumber = false\n for (let ri = 0; ri < rows.length; ri++) {\n const row = rows[ri]\n const v = row ? row[col.key] : undefined\n if (typeof v === 'number' && isFinite(v)) {\n sum += v\n hasNumber = true\n }\n }\n totals[col._flatIndex] = hasNumber ? Math.round(sum * 1e6) / 1e6 : null\n })\n this.columnTotals = totals\n },\n\n getColumnTotalDisplay(ci) {\n const col = this.leafCols[ci]\n if (!col || !col.showTotal) return ''\n const v = this.columnTotals[ci]\n return v === null || v === undefined ? '-' : v\n },\n\n getFooterCellStyle(ci) {\n const zone = this.isColumnPinnedLeft(ci) ? 'left' : (this.isColumnPinnedRight(ci) ? 'right' : 'scroll')\n const left = this.getZoneColLeft(ci, zone)\n const w = this.getColW(ci)\n return {\n position: 'absolute',\n left: left + 'px',\n top: '0px',\n width: w + 'px',\n minWidth: w + 'px',\n height: this._footerRowH + 'px'\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, this._rowH, 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 const editCol = this.leafCols[this.editingCell.col]\n if (editCol && this._hasCustomEdit(editCol)) {\n this.cancelEdit()\n } else {\n this.commitEdit()\n }\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 && this.isSelectLikeCol(col) && !this._hasCustomEdit(col) && 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 && this.isSelectLikeCol(col) && !this._hasCustomEdit(col)) {\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 // Delegate keyboard navigation to the active dropdown component\n if (this.selectDropdown) {\n const comp = this.$refs.selectDropdownComp\n if (comp && comp.handleKey) comp.handleKey(e)\n return\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 && this.isSelectLikeCol(activeCol) && !this._hasCustomEdit(activeCol)) {\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 - this._headerRowH * this.headerDepth) / this._rowH)\n this.moveActive(-visibleRows, 0, e)\n return\n }\n if (key === 'PageDown') {\n e.preventDefault()\n const visibleRows = Math.floor((this.height - this._headerRowH * this.headerDepth) / this._rowH)\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 && this.isSelectLikeCol(activeCol) && !this._hasCustomEdit(activeCol)) {\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, this._rowH, 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 (!this._hasCustomEdit(col)) {\n if (this.isSelectLikeCol(col)) { this.openSelectDropdownForActive(); return }\n if (col.type === 'checkbox') return\n }\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 hasCellSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['cell-' + col.key])\n },\n hasLegacyEditSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['edit-' + col.key])\n },\n hasPrefixSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['prefix-' + col.key])\n },\n hasSuffixSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['suffix-' + col.key])\n },\n _hasCustomEdit(col) {\n // For select/treeSelect: cell-{key} slot only customizes display; dropdown still opens normally\n if (col.type === 'select' || col.type === 'treeSelect') {\n return this.hasLegacyEditSlot(col)\n }\n return this.hasCellSlot(col) || this.hasLegacyEditSlot(col)\n },\n\n commitEditSlot(value) {\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 if (!this.isCellValueValid(row, col, value)) {\n this._showInvalidCell(row, col, value)\n this.editingCell = null\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 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 // =========================================================\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) + TREE SELECT (3.22)\n // =========================================================\n\n isSelectLikeCol(col) {\n return !!(col && (col.type === 'select' || col.type === 'treeSelect'))\n },\n\n treeOptionKeys(col) {\n return {\n valueKey: col.optionValueKey || 'value',\n labelKey: col.optionLabelKey || 'label',\n childrenKey: col.optionChildrenKey || 'children'\n }\n },\n\n _treeFindLabel(nodes, keys, value) {\n for (const node of (nodes || [])) {\n if (String(node[keys.valueKey]) === value) {\n return node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey])\n }\n const found = this._treeFindLabel(node[keys.childrenKey], keys, value)\n if (found != null) return found\n }\n return null\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 col = this.leafCols[ci]\n const v = value == null ? '' : String(value)\n if (col && col.type === 'treeSelect') {\n if (!Array.isArray(col.options)) return v\n const found = this._treeFindLabel(col.options, this.treeOptionKeys(col), v)\n return found != null ? found : v\n }\n const opts = this.colOptions(ci)\n if (!opts.length) return v\n const found = opts.find(o => String(o.value) === v)\n return found ? found.label : v\n },\n\n _buildSelectDropdownState(ri, ci, col, currentValue, rect) {\n const rectObj = { top: rect.top, bottom: rect.bottom, left: rect.left, width: rect.width }\n return {\n row: ri, col: ci,\n tree: col.type === 'treeSelect',\n keys: col.type === 'treeSelect' ? this.treeOptionKeys(col) : null,\n rawOptions: col.options || [],\n currentValue,\n rect: rectObj,\n enableSearch: !!col.enableSearch\n }\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 this.selectDropdown = this._buildSelectDropdownState(ri, ci, col, currentValue, rect)\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 this.selectDropdown = this._buildSelectDropdownState(row, col, colDef, currentValue, rect)\n })\n },\n\n onComponentSelect({ value, label }) {\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 if (typeof colDef.onSelect === 'function') {\n colDef.onSelect({ value, label }, newData[row], row, colDef)\n }\n }\n this.closeSelectDropdown()\n this.$refs.wrapper && this.$refs.wrapper.focus()\n },\n\n closeSelectDropdown() {\n this.selectDropdown = null\n },\n\n _onWindowScrollForDropdown(e) {\n if (!this.selectDropdown) return\n const comp = this.$refs.selectDropdownComp\n const dd = comp && comp.$el ? comp.$el : null\n if (dd && dd.contains(e.target)) return\n this.closeSelectDropdown()\n },\n\n _onDocMouseDownForDropdown(e) {\n if (!this.selectDropdown) return\n const comp = this.$refs.selectDropdownComp\n const dd = comp && comp.$el ? comp.$el : null\n if (dd && dd.contains(e.target)) return\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 _applyAutoWidth() {\n if (!this.autoWidth || !this.leafCols.length) return\n this.leafCols.forEach((col, ci) => {\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 this.$set(this.colWidths, ci, Math.max(50, maxLen * 8 + 16))\n })\n this._cellStyleCache = Object.create(null)\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.scrollFooter) this.$refs.scrollFooter.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/* ===== FOOTER AREA (column totals) ===== */\n.eg-footer-area {\n border-top: 1px solid #ccc;\n}\n\n.eg-frozen-col-footer,\n.eg-scroll-col-footer,\n.eg-pinned-right-footer {\n position: relative;\n}\n\n.eg-footer-cell {\n box-sizing: border-box;\n border-right: 1px solid #e0e0e0;\n overflow: hidden;\n white-space: nowrap;\n font-size: 13px;\n font-weight: 600;\n padding: 0 4px;\n position: absolute;\n top: 0;\n background: #f8f9fa;\n display: flex;\n align-items: center;\n justify-content: flex-end;\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-value {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\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 padding: 0 2px;\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/* ===== ROW TRANSITION ===== */\n.eg-row-enter-active,\n.eg-row-leave-active {\n transition: opacity 0.18s ease, transform 0.18s ease;\n}\n.eg-row-enter,\n.eg-row-leave-to {\n opacity: 0;\n transform: translateY(-4px);\n}\n\n/* ===== SIZE VARIANTS ===== */\n\n/* small */\n.eg-size-small .eg-cell {\n font-size: 12px;\n padding: 0 3px;\n}\n.eg-size-small .eg-cell-input {\n font-size: 12px;\n}\n.eg-size-small .eg-header-cell {\n font-size: 11px;\n}\n.eg-size-small .eg-footer-cell {\n font-size: 12px;\n}\n.eg-size-small .eg-row-header-cell {\n font-size: 10px;\n}\n.eg-size-small .eg-select-option {\n font-size: 12px;\n padding: 4px 10px;\n}\n.eg-size-small .eg-context-menu,\n.eg-size-small .eg-context-item {\n font-size: 12px;\n}\n.eg-size-small .eg-context-item {\n padding: 4px 14px;\n}\n\n/* large */\n.eg-size-large .eg-cell {\n font-size: 14px;\n padding: 0 6px;\n}\n.eg-size-large .eg-cell-input {\n font-size: 14px;\n}\n.eg-size-large .eg-header-cell {\n font-size: 13px;\n}\n.eg-size-large .eg-footer-cell {\n font-size: 14px;\n}\n.eg-size-large .eg-row-header-cell {\n font-size: 12px;\n}\n.eg-size-large .eg-select-option {\n font-size: 14px;\n padding: 8px 14px;\n}\n.eg-size-large .eg-context-menu,\n.eg-size-large .eg-context-item {\n font-size: 14px;\n}\n.eg-size-large .eg-context-item {\n padding: 8px 18px;\n}\n</style>\n\n<style>\n/* ── EgSelect / EgTreeSelect dropdown (non-scoped: injected by ExcelGrid on mount) ── */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: 13px;\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n background: #fff;\n cursor: pointer;\n user-select: none;\n color: #333;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover { border-color: #1a73e8; }\n.eg-select-trigger--open {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-trigger--disabled { background: #f5f5f5; cursor: default; color: #aaa; }\n.eg-select-trigger-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.eg-select-placeholder { color: #aaa; }\n.eg-select-caret { flex-shrink: 0; color: #888; font-size: 12px; }\n.eg-select-dropdown {\n background: #fff;\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: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid #ebebeb;\n position: sticky;\n top: 0;\n background: #fff;\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 13px;\n outline: none;\n color: #333;\n background: #fff;\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: #333;\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted { background: #f0f4ff; }\n.eg-select-option--active { font-weight: 600; color: #1a73e8; }\n.eg-select-option--active.eg-select-option--highlighted,\n.eg-select-option--active:hover { background: #e8f0fe; }\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: #888;\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf { visibility: hidden; }\n/* EgCheckbox */\n.eg-checkbox {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n cursor: pointer;\n}\n.eg-checkbox-input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\n.eg-checkbox-box {\n position: relative;\n display: block;\n width: 19px;\n height: 19px;\n box-sizing: border-box;\n background-color: #fff;\n background-repeat: no-repeat;\n background-position: center;\n background-size: 0 0;\n border: 1.5px solid #c2c8d1;\n border-radius: 4px;\n transition: background-color .12s ease, border-color .12s ease, background-size .12s ease;\n}\n.eg-checkbox:hover .eg-checkbox-box { border-color: #1a73e8; }\n.eg-checkbox-input:checked + .eg-checkbox-box {\n background-color: #1a73e8;\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZD0iTTMuNSA4LjNsMyAzIDYtNi42IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==\");\n background-size: 70% 70%;\n border-color: #1a73e8;\n}\n.eg-checkbox-input:focus-visible + .eg-checkbox-box { box-shadow: 0 0 0 3px rgba(26,115,232,0.2); }\n.eg-checkbox--disabled,\n.eg-checkbox--disabled .eg-checkbox-input { cursor: default; }\n.eg-checkbox-input:disabled + .eg-checkbox-box { background-color: #f0f1f3; border-color: #dadde2; }\n.eg-checkbox-input:disabled:checked + .eg-checkbox-box { background-color: #b9c2ce; border-color: #b9c2ce; }\n</style>\n"]},media:void 0}),n("data-v-f65b9586_1",{source:'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* ── EgSelect / EgTreeSelect dropdown (non-scoped: injected by ExcelGrid on mount) ── */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: 13px;\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n background: #fff;\n cursor: pointer;\n user-select: none;\n color: #333;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover { border-color: #1a73e8;\n}\n.eg-select-trigger--open {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-trigger--disabled { background: #f5f5f5; cursor: default; color: #aaa;\n}\n.eg-select-trigger-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n.eg-select-placeholder { color: #aaa;\n}\n.eg-select-caret { flex-shrink: 0; color: #888; font-size: 12px;\n}\n.eg-select-dropdown {\n background: #fff;\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: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid #ebebeb;\n position: sticky;\n top: 0;\n background: #fff;\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 13px;\n outline: none;\n color: #333;\n background: #fff;\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: #333;\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted { background: #f0f4ff;\n}\n.eg-select-option--active { font-weight: 600; color: #1a73e8;\n}\n.eg-select-option--active.eg-select-option--highlighted,\n.eg-select-option--active:hover { background: #e8f0fe;\n}\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: #888;\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf { visibility: hidden;\n}\n/* EgCheckbox */\n.eg-checkbox {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n cursor: pointer;\n}\n.eg-checkbox-input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\n.eg-checkbox-box {\n position: relative;\n display: block;\n width: 19px;\n height: 19px;\n box-sizing: border-box;\n background-color: #fff;\n background-repeat: no-repeat;\n background-position: center;\n background-size: 0 0;\n border: 1.5px solid #c2c8d1;\n border-radius: 4px;\n transition: background-color .12s ease, border-color .12s ease, background-size .12s ease;\n}\n.eg-checkbox:hover .eg-checkbox-box { border-color: #1a73e8;\n}\n.eg-checkbox-input:checked + .eg-checkbox-box {\n background-color: #1a73e8;\n background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZD0iTTMuNSA4LjNsMyAzIDYtNi42IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==");\n background-size: 70% 70%;\n border-color: #1a73e8;\n}\n.eg-checkbox-input:focus-visible + .eg-checkbox-box { box-shadow: 0 0 0 3px rgba(26,115,232,0.2);\n}\n.eg-checkbox--disabled,\n.eg-checkbox--disabled .eg-checkbox-input { cursor: default;\n}\n.eg-checkbox-input:disabled + .eg-checkbox-box { background-color: #f0f1f3; border-color: #dadde2;\n}\n.eg-checkbox-input:disabled:checked + .eg-checkbox-box { background-color: #b9c2ce; border-color: #b9c2ce;\n}\n',map:{version:3,sources:["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/ExcelGrid.vue"],names:[],mappings:";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwyHA,wFAAA;AACA;EACA,kBAAA;EACA,qBAAA;EACA,gBAAA;EACA,eAAA;EACA,oBAAA;AACA;AACA;EACA,aAAA;EACA,mBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;EACA,gBAAA;EACA,sBAAA;EACA,yBAAA;EACA,kBAAA;EACA,gBAAA;EACA,eAAA;EACA,iBAAA;EACA,WAAA;EACA,wDAAA;AACA;AACA,2BAAA,qBAAA;AAAA;AACA;EACA,qBAAA;EACA,2CAAA;AACA;AACA,+BAAA,mBAAA,EAAA,eAAA,EAAA,WAAA;AAAA;AACA,2BAAA,OAAA,EAAA,gBAAA,EAAA,uBAAA,EAAA,mBAAA;AAAA;AACA,yBAAA,WAAA;AAAA;AACA,mBAAA,cAAA,EAAA,WAAA,EAAA,eAAA;AAAA;AACA;EACA,gBAAA;EACA,yBAAA;EACA,kBAAA;EACA,uCAAA;EACA,gBAAA;EACA,iBAAA;EACA,cAAA;AACA;AACA;EACA,kBAAA;EACA,SAAA;EACA,OAAA;EACA,eAAA;EACA,YAAA;EACA,eAAA;AACA;AACA;EACA,gBAAA;EACA,gCAAA;EACA,gBAAA;EACA,MAAA;EACA,gBAAA;EACA,UAAA;AACA;AACA;EACA,WAAA;EACA,sBAAA;EACA,yBAAA;EACA,kBAAA;EACA,gBAAA;EACA,eAAA;EACA,aAAA;EACA,WAAA;EACA,gBAAA;EACA,oBAAA;AACA;AACA;EACA,qBAAA;EACA,2CAAA;AACA;AACA;EACA,aAAA;EACA,uBAAA;EACA,QAAA;EACA,iBAAA;EACA,eAAA;EACA,eAAA;EACA,mBAAA;EACA,sBAAA;EACA,iBAAA;EACA,WAAA;AACA;AACA;iCACA,mBAAA;AAAA;AACA,4BAAA,gBAAA,EAAA,cAAA;AAAA;AACA;kCACA,mBAAA;AAAA;AACA;EACA,UAAA;EACA,WAAA;EACA,kBAAA;EACA,cAAA;EACA,qBAAA;EACA,eAAA;AACA;AACA,gBAAA;AACA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;EACA,cAAA;EACA,eAAA;EACA,WAAA;EACA,eAAA;AACA;AACA,wBAAA,kBAAA;AAAA;AACA,eAAA;AACA;EACA,kBAAA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;EACA,eAAA;AACA;AACA;EACA,kBAAA;EACA,QAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA;EACA,UAAA;EACA,eAAA;AACA;AACA;EACA,kBAAA;EACA,cAAA;EACA,WAAA;EACA,YAAA;EACA,sBAAA;EACA,sBAAA;EACA,4BAAA;EACA,2BAAA;EACA,oBAAA;EACA,2BAAA;EACA,kBAAA;EACA,yFAAA;AACA;AACA,sCAAA,qBAAA;AAAA;AACA;EACA,yBAAA;EACA,+SAAA;EACA,wBAAA;EACA,qBAAA;AACA;AACA,sDAAA,0CAAA;AAAA;AACA;4CACA,eAAA;AAAA;AACA,iDAAA,yBAAA,EAAA,qBAAA;AAAA;AACA,yDAAA,yBAAA,EAAA,qBAAA;AAAA",file:"ExcelGrid.vue",sourcesContent:["<template>\n <div\n class=\"eg-wrapper\"\n :class=\"'eg-size-' + size\"\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\"\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\"\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\"\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 <transition :key=\"'tr-fc-' + ri\" :name=\"_rowTransitionName\">\n <div v-if=\"!isRowHidden(ri)\" :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 <slot v-if=\"hasCellSlot(col)\" :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"true\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <slot v-else-if=\"hasLegacyEditSlot(col)\" :name=\"'edit-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <input v-else 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 <eg-checkbox\n :value=\"isCheckboxChecked(ri, ci)\"\n :disabled=\"!isCellEditable(ri, ci)\"\n @change=\"(v) => setCheckboxValue(ri, ci, v)\"\n />\n </template>\n <template v-else>\n <slot :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"false\" :done=\"null\" :cancel=\"null\">\n <slot v-if=\"hasPrefixSlot(col)\" :name=\"'prefix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" /><span v-if=\"hasPrefixSlot(col) || hasSuffixSlot(col)\" class=\"eg-cell-value\">{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</span><template v-else>{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</template><slot v-if=\"hasSuffixSlot(col)\" :name=\"'suffix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" />\n </slot>\n </template>\n </div>\n </template>\n </template>\n </div>\n </transition>\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 <transition :key=\"'tr-m-' + ri\" :name=\"_rowTransitionName\">\n <div\n v-if=\"!isRowHidden(ri)\"\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 <slot v-if=\"hasCellSlot(col)\" :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"true\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <slot v-else-if=\"hasLegacyEditSlot(col)\" :name=\"'edit-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <input\n v-else\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 <eg-checkbox\n :value=\"isCheckboxChecked(ri, ci)\"\n :disabled=\"!isCellEditable(ri, ci)\"\n @change=\"(v) => setCheckboxValue(ri, ci, v)\"\n />\n </template>\n <template v-else>\n <slot :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"false\" :done=\"null\" :cancel=\"null\">\n <slot v-if=\"hasPrefixSlot(col)\" :name=\"'prefix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" /><span v-if=\"hasPrefixSlot(col) || hasSuffixSlot(col)\" class=\"eg-cell-value\">{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</span><template v-else>{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</template><slot v-if=\"hasSuffixSlot(col)\" :name=\"'suffix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" />\n </slot>\n </template>\n </div>\n </template>\n </template>\n </div>\n </transition>\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 <transition :key=\"'tr-pr-' + ri\" :name=\"_rowTransitionName\">\n <div v-if=\"!isRowHidden(ri)\" :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 <slot v-if=\"hasCellSlot(col)\" :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" :isEditing=\"true\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <slot v-else-if=\"hasLegacyEditSlot(col)\" :name=\"'edit-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <input v-else 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 <eg-checkbox\n :value=\"isCheckboxChecked(ri, col._flatIndex)\"\n :disabled=\"!isCellEditable(ri, col._flatIndex)\"\n @change=\"(v) => setCheckboxValue(ri, col._flatIndex, v)\"\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)\" :isEditing=\"false\" :done=\"null\" :cancel=\"null\">\n <slot v-if=\"hasPrefixSlot(col)\" :name=\"'prefix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" /><span v-if=\"hasPrefixSlot(col) || hasSuffixSlot(col)\" class=\"eg-cell-value\">{{ isSelectLikeCol(col) ? getSelectLabel(col._flatIndex, getCellValue(ri, col._flatIndex)) : getCellValue(ri, col._flatIndex) }}</span><template v-else>{{ isSelectLikeCol(col) ? getSelectLabel(col._flatIndex, getCellValue(ri, col._flatIndex)) : getCellValue(ri, col._flatIndex) }}</template><slot v-if=\"hasSuffixSlot(col)\" :name=\"'suffix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" />\n </slot>\n </template>\n </div>\n </template>\n </div>\n </transition>\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!-- ===== FOOTER AREA (column totals) ===== --\x3e\n <div v-if=\"hasFooter\" class=\"eg-footer-area\" :style=\"footerAreaStyle\">\n\n \x3c!-- \"Jami\" label cell (mirrors corner cell) --\x3e\n <div class=\"eg-footer-label\" :style=\"footerLabelStyle\">{{ t.total }}</div>\n\n \x3c!-- Frozen columns footer --\x3e\n <div v-if=\"getFrozenWidth() > 0\" class=\"eg-frozen-col-footer\" :style=\"frozenColFooterStyle\">\n <div :style=\"{ position: 'relative', height: footerRowHeight + 'px' }\">\n <template v-for=\"(col, ci) in leafCols\">\n <template v-if=\"isColumnPinnedLeft(ci) && !isColHidden(ci)\">\n <div :key=\"'ff-' + ci\" class=\"eg-footer-cell\" :style=\"getFooterCellStyle(ci)\">\n {{ getColumnTotalDisplay(ci) }}\n </div>\n </template>\n </template>\n </div>\n </div>\n\n \x3c!-- Scrollable footer (horizontally synced with body via scrollFooter ref) --\x3e\n <div class=\"eg-scroll-col-footer\" ref=\"scrollFooter\" :style=\"scrollColHeaderStyle\">\n <div class=\"eg-scroll-col-footer-inner\" :style=\"{ width: scrollContentWidth + 'px', height: footerRowHeight + 'px', position: 'relative' }\">\n <template v-for=\"(col, ci) in leafCols\">\n <template v-if=\"isColumnScrollable(ci) && !isColHidden(ci)\">\n <div :key=\"'sf-' + ci\" class=\"eg-footer-cell\" :style=\"getFooterCellStyle(ci)\">\n {{ getColumnTotalDisplay(ci) }}\n </div>\n </template>\n </template>\n </div>\n </div>\n\n \x3c!-- Pinned Right columns footer --\x3e\n <div v-if=\"hasPinnedRight\" class=\"eg-pinned-right-footer\" :style=\"pinnedRightFooterStyle\">\n <div :style=\"{ position: 'relative', height: footerRowHeight + 'px' }\">\n <div\n v-for=\"col in pinnedRightCols\"\n :key=\"'prf-' + col._flatIndex\"\n class=\"eg-footer-cell\"\n :style=\"getFooterCellStyle(col._flatIndex)\"\n >{{ getColumnTotalDisplay(col._flatIndex) }}</div>\n </div>\n </div>\n </div>\x3c!-- end footer-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 (flat select) --\x3e\n <eg-select\n v-if=\"selectDropdown && !selectDropdown.tree\"\n ref=\"selectDropdownComp\"\n :value=\"selectDropdown.currentValue\"\n :options=\"selectDropdown.rawOptions\"\n :enable-search=\"selectDropdown.enableSearch\"\n :trigger-rect=\"selectDropdown.rect\"\n :dropdown-only=\"true\"\n @select=\"onComponentSelect\"\n @close=\"closeSelectDropdown\"\n />\n \x3c!-- Tree select dropdown overlay --\x3e\n <eg-tree-select\n v-if=\"selectDropdown && selectDropdown.tree\"\n ref=\"selectDropdownComp\"\n :value=\"selectDropdown.currentValue\"\n :options=\"selectDropdown.rawOptions\"\n :value-key=\"selectDropdown.keys.valueKey\"\n :label-key=\"selectDropdown.keys.labelKey\"\n :children-key=\"selectDropdown.keys.childrenKey\"\n :enable-search=\"selectDropdown.enableSearch\"\n :trigger-rect=\"selectDropdown.rect\"\n :dropdown-only=\"true\"\n @select=\"onComponentSelect\"\n @close=\"closeSelectDropdown\"\n />\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'\nimport EgCheckbox from './EgCheckbox.vue'\nimport EgSelect from './EgSelect.vue'\nimport EgTreeSelect from './EgTreeSelect.vue'\n\nconst SIZE_CONFIG = {\n small: { row: 22, header: 24, footer: 24 },\n middle: { row: 28, header: 28, footer: 28 },\n large: { row: 36, header: 36, footer: 36 }\n}\nconst ROW_HEADER_WIDTH = 50\nconst DEFAULT_COL_WIDTH = 100\nconst OUTLINE_GUTTER = 20\n\nexport default {\n name: 'ExcelGrid',\n\n components: { EgCheckbox, EgSelect, EgTreeSelect },\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 /** Visual size of the grid. Affects row/header height and font sizes. */\n size: { type: String, default: 'middle' },\n\n /** Enable CSS list transitions on row enter/leave (data changes). Disabled during scroll. */\n transitionEnabled: { type: Boolean, default: false },\n\n /** Auto-fit all column widths to content (same as double-clicking each resize handle). Re-applies on data/columns change. */\n autoWidth: { 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 _isScrolling: false,\n\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 // Footer column totals (flat col index → number | null)\n columnTotals: {}\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 hasFooter() { return this.leafCols.some(col => col.showTotal) },\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 _rowTransitionName() {\n return this.transitionEnabled && !this._isScrolling ? 'eg-row' : ''\n },\n\n _sizeConf() { return SIZE_CONFIG[this.size] || SIZE_CONFIG.middle },\n _rowH() { return this._sizeConf.row },\n _headerRowH() { return this._sizeConf.header },\n _footerRowH() { return this._sizeConf.footer },\n\n headerRowHeight() { return this._headerRowH },\n footerRowHeight() { return this._footerRowH },\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, this._rowH, 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\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: (this._headerRowH * 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 footerAreaStyle() {\n return {\n display: 'flex',\n flexDirection: 'row',\n flexShrink: 0,\n position: 'sticky',\n bottom: 0,\n zIndex: 30,\n background: '#f8f9fa',\n boxShadow: '0 -2px 0 0 #d0d0d0'\n }\n },\n\n footerLabelStyle() {\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: this._footerRowH + 'px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n background: '#f0f0f0',\n borderRight: '1px solid #ccc',\n borderTop: '1px solid #ccc',\n boxSizing: 'border-box',\n flexShrink: 0,\n fontSize: '12px',\n fontWeight: 600\n }\n },\n\n frozenColFooterStyle() {\n const w = this.getFrozenWidth()\n return {\n width: w + 'px',\n minWidth: w + 'px',\n height: this._footerRowH + 'px',\n overflow: 'hidden',\n flexShrink: 0\n }\n },\n\n pinnedRightFooterStyle() {\n return {\n width: this.pinnedRightWidth + 'px',\n minWidth: this.pinnedRightWidth + 'px',\n height: this._footerRowH + 'px',\n overflow: 'hidden',\n flexShrink: 0,\n borderLeft: '1px solid #ccc'\n }\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 size() {\n this.rowHeights = {}\n this.$nextTick(() => this.updateVirtualScroll())\n },\n\n autoWidth(v) {\n if (v) this._applyAutoWidth()\n },\n\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 this.computeColumnTotals()\n this.updateVirtualScroll()\n this._applyAutoWidth()\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 this.computeColumnTotals()\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 this._applyAutoWidth()\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 window.addEventListener('scroll', this._onWindowScrollForDropdown, true)\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 window.removeEventListener('scroll', this._onWindowScrollForDropdown, true)\n clearTimeout(this._scrollEndTimer)\n },\n\n methods: {\n // =========================================================\n // VIRTUAL SCROLL\n // =========================================================\n updateVirtualScroll() {\n const footerH = this.hasFooter ? this._footerRowH : 0\n const containerH = this.height - (this._headerRowH * this.headerDepth) - footerH\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.scrollFooter) this.$refs.scrollFooter.scrollLeft = target.scrollLeft\n if (this.$refs.scrollRowHeaders) this.$refs.scrollRowHeaders.scrollTop = target.scrollTop\n\n // Close dropdown on scroll — fixed-position dropdown drifts away from the moving cell\n if (this.selectDropdown) this.closeSelectDropdown()\n\n // Suppress row transitions during scroll to avoid animation on every virtual-scroll paint\n if (this.transitionEnabled) {\n this._isScrolling = true\n clearTimeout(this._scrollEndTimer)\n this._scrollEndTimer = setTimeout(() => { this._isScrolling = false }, 150)\n }\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] || this._rowH\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: (this._headerRowH * rowIndex) + 'px',\n left: left + 'px',\n width: width + 'px',\n height: (this._headerRowH * 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 && this.isSelectLikeCol(col)),\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: (this._headerRowH * rowIndex) + 'px',\n left: left + 'px',\n width: width + 'px',\n height: (this._headerRowH * 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 // =========================================================\n // FOOTER (column totals)\n // =========================================================\n computeColumnTotals() {\n const totals = {}\n const rows = this._dataRaw || []\n this.leafCols.forEach(col => {\n if (!col.showTotal) return\n let sum = 0\n let hasNumber = false\n for (let ri = 0; ri < rows.length; ri++) {\n const row = rows[ri]\n const v = row ? row[col.key] : undefined\n if (typeof v === 'number' && isFinite(v)) {\n sum += v\n hasNumber = true\n }\n }\n totals[col._flatIndex] = hasNumber ? Math.round(sum * 1e6) / 1e6 : null\n })\n this.columnTotals = totals\n },\n\n getColumnTotalDisplay(ci) {\n const col = this.leafCols[ci]\n if (!col || !col.showTotal) return ''\n const v = this.columnTotals[ci]\n return v === null || v === undefined ? '-' : v\n },\n\n getFooterCellStyle(ci) {\n const zone = this.isColumnPinnedLeft(ci) ? 'left' : (this.isColumnPinnedRight(ci) ? 'right' : 'scroll')\n const left = this.getZoneColLeft(ci, zone)\n const w = this.getColW(ci)\n return {\n position: 'absolute',\n left: left + 'px',\n top: '0px',\n width: w + 'px',\n minWidth: w + 'px',\n height: this._footerRowH + 'px'\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, this._rowH, 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 const editCol = this.leafCols[this.editingCell.col]\n if (editCol && this._hasCustomEdit(editCol)) {\n this.cancelEdit()\n } else {\n this.commitEdit()\n }\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 && this.isSelectLikeCol(col) && !this._hasCustomEdit(col) && 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 && this.isSelectLikeCol(col) && !this._hasCustomEdit(col)) {\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 // Delegate keyboard navigation to the active dropdown component\n if (this.selectDropdown) {\n const comp = this.$refs.selectDropdownComp\n if (comp && comp.handleKey) comp.handleKey(e)\n return\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 && this.isSelectLikeCol(activeCol) && !this._hasCustomEdit(activeCol)) {\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 - this._headerRowH * this.headerDepth) / this._rowH)\n this.moveActive(-visibleRows, 0, e)\n return\n }\n if (key === 'PageDown') {\n e.preventDefault()\n const visibleRows = Math.floor((this.height - this._headerRowH * this.headerDepth) / this._rowH)\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 && this.isSelectLikeCol(activeCol) && !this._hasCustomEdit(activeCol)) {\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, this._rowH, 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 (!this._hasCustomEdit(col)) {\n if (this.isSelectLikeCol(col)) { this.openSelectDropdownForActive(); return }\n if (col.type === 'checkbox') return\n }\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 hasCellSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['cell-' + col.key])\n },\n hasLegacyEditSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['edit-' + col.key])\n },\n hasPrefixSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['prefix-' + col.key])\n },\n hasSuffixSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['suffix-' + col.key])\n },\n _hasCustomEdit(col) {\n // For select/treeSelect: cell-{key} slot only customizes display; dropdown still opens normally\n if (col.type === 'select' || col.type === 'treeSelect') {\n return this.hasLegacyEditSlot(col)\n }\n return this.hasCellSlot(col) || this.hasLegacyEditSlot(col)\n },\n\n commitEditSlot(value) {\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 if (!this.isCellValueValid(row, col, value)) {\n this._showInvalidCell(row, col, value)\n this.editingCell = null\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 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 // =========================================================\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) + TREE SELECT (3.22)\n // =========================================================\n\n isSelectLikeCol(col) {\n return !!(col && (col.type === 'select' || col.type === 'treeSelect'))\n },\n\n treeOptionKeys(col) {\n return {\n valueKey: col.optionValueKey || 'value',\n labelKey: col.optionLabelKey || 'label',\n childrenKey: col.optionChildrenKey || 'children'\n }\n },\n\n _treeFindLabel(nodes, keys, value) {\n for (const node of (nodes || [])) {\n if (String(node[keys.valueKey]) === value) {\n return node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey])\n }\n const found = this._treeFindLabel(node[keys.childrenKey], keys, value)\n if (found != null) return found\n }\n return null\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 col = this.leafCols[ci]\n const v = value == null ? '' : String(value)\n if (col && col.type === 'treeSelect') {\n if (!Array.isArray(col.options)) return v\n const found = this._treeFindLabel(col.options, this.treeOptionKeys(col), v)\n return found != null ? found : v\n }\n const opts = this.colOptions(ci)\n if (!opts.length) return v\n const found = opts.find(o => String(o.value) === v)\n return found ? found.label : v\n },\n\n _buildSelectDropdownState(ri, ci, col, currentValue, rect) {\n const rectObj = { top: rect.top, bottom: rect.bottom, left: rect.left, width: rect.width }\n return {\n row: ri, col: ci,\n tree: col.type === 'treeSelect',\n keys: col.type === 'treeSelect' ? this.treeOptionKeys(col) : null,\n rawOptions: col.options || [],\n currentValue,\n rect: rectObj,\n enableSearch: !!col.enableSearch\n }\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 this.selectDropdown = this._buildSelectDropdownState(ri, ci, col, currentValue, rect)\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 this.selectDropdown = this._buildSelectDropdownState(row, col, colDef, currentValue, rect)\n })\n },\n\n onComponentSelect({ value, label }) {\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 if (typeof colDef.onSelect === 'function') {\n colDef.onSelect({ value, label }, newData[row], row, colDef)\n }\n }\n this.closeSelectDropdown()\n this.$refs.wrapper && this.$refs.wrapper.focus()\n },\n\n closeSelectDropdown() {\n this.selectDropdown = null\n },\n\n _onWindowScrollForDropdown(e) {\n if (!this.selectDropdown) return\n const comp = this.$refs.selectDropdownComp\n const dd = comp && comp.$el ? comp.$el : null\n if (dd && dd.contains(e.target)) return\n this.closeSelectDropdown()\n },\n\n _onDocMouseDownForDropdown(e) {\n if (!this.selectDropdown) return\n const comp = this.$refs.selectDropdownComp\n const dd = comp && comp.$el ? comp.$el : null\n if (dd && dd.contains(e.target)) return\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 _applyAutoWidth() {\n if (!this.autoWidth || !this.leafCols.length) return\n this.leafCols.forEach((col, ci) => {\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 this.$set(this.colWidths, ci, Math.max(50, maxLen * 8 + 16))\n })\n this._cellStyleCache = Object.create(null)\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.scrollFooter) this.$refs.scrollFooter.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/* ===== FOOTER AREA (column totals) ===== */\n.eg-footer-area {\n border-top: 1px solid #ccc;\n}\n\n.eg-frozen-col-footer,\n.eg-scroll-col-footer,\n.eg-pinned-right-footer {\n position: relative;\n}\n\n.eg-footer-cell {\n box-sizing: border-box;\n border-right: 1px solid #e0e0e0;\n overflow: hidden;\n white-space: nowrap;\n font-size: 13px;\n font-weight: 600;\n padding: 0 4px;\n position: absolute;\n top: 0;\n background: #f8f9fa;\n display: flex;\n align-items: center;\n justify-content: flex-end;\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-value {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\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 padding: 0 2px;\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/* ===== ROW TRANSITION ===== */\n.eg-row-enter-active,\n.eg-row-leave-active {\n transition: opacity 0.18s ease, transform 0.18s ease;\n}\n.eg-row-enter,\n.eg-row-leave-to {\n opacity: 0;\n transform: translateY(-4px);\n}\n\n/* ===== SIZE VARIANTS ===== */\n\n/* small */\n.eg-size-small .eg-cell {\n font-size: 12px;\n padding: 0 3px;\n}\n.eg-size-small .eg-cell-input {\n font-size: 12px;\n}\n.eg-size-small .eg-header-cell {\n font-size: 11px;\n}\n.eg-size-small .eg-footer-cell {\n font-size: 12px;\n}\n.eg-size-small .eg-row-header-cell {\n font-size: 10px;\n}\n.eg-size-small .eg-select-option {\n font-size: 12px;\n padding: 4px 10px;\n}\n.eg-size-small .eg-context-menu,\n.eg-size-small .eg-context-item {\n font-size: 12px;\n}\n.eg-size-small .eg-context-item {\n padding: 4px 14px;\n}\n\n/* large */\n.eg-size-large .eg-cell {\n font-size: 14px;\n padding: 0 6px;\n}\n.eg-size-large .eg-cell-input {\n font-size: 14px;\n}\n.eg-size-large .eg-header-cell {\n font-size: 13px;\n}\n.eg-size-large .eg-footer-cell {\n font-size: 14px;\n}\n.eg-size-large .eg-row-header-cell {\n font-size: 12px;\n}\n.eg-size-large .eg-select-option {\n font-size: 14px;\n padding: 8px 14px;\n}\n.eg-size-large .eg-context-menu,\n.eg-size-large .eg-context-item {\n font-size: 14px;\n}\n.eg-size-large .eg-context-item {\n padding: 8px 18px;\n}\n</style>\n\n<style>\n/* ── EgSelect / EgTreeSelect dropdown (non-scoped: injected by ExcelGrid on mount) ── */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: 13px;\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n background: #fff;\n cursor: pointer;\n user-select: none;\n color: #333;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover { border-color: #1a73e8; }\n.eg-select-trigger--open {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-trigger--disabled { background: #f5f5f5; cursor: default; color: #aaa; }\n.eg-select-trigger-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.eg-select-placeholder { color: #aaa; }\n.eg-select-caret { flex-shrink: 0; color: #888; font-size: 12px; }\n.eg-select-dropdown {\n background: #fff;\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: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid #ebebeb;\n position: sticky;\n top: 0;\n background: #fff;\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 13px;\n outline: none;\n color: #333;\n background: #fff;\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: #333;\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted { background: #f0f4ff; }\n.eg-select-option--active { font-weight: 600; color: #1a73e8; }\n.eg-select-option--active.eg-select-option--highlighted,\n.eg-select-option--active:hover { background: #e8f0fe; }\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: #888;\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf { visibility: hidden; }\n/* EgCheckbox */\n.eg-checkbox {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n cursor: pointer;\n}\n.eg-checkbox-input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\n.eg-checkbox-box {\n position: relative;\n display: block;\n width: 19px;\n height: 19px;\n box-sizing: border-box;\n background-color: #fff;\n background-repeat: no-repeat;\n background-position: center;\n background-size: 0 0;\n border: 1.5px solid #c2c8d1;\n border-radius: 4px;\n transition: background-color .12s ease, border-color .12s ease, background-size .12s ease;\n}\n.eg-checkbox:hover .eg-checkbox-box { border-color: #1a73e8; }\n.eg-checkbox-input:checked + .eg-checkbox-box {\n background-color: #1a73e8;\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZD0iTTMuNSA4LjNsMyAzIDYtNi42IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==\");\n background-size: 70% 70%;\n border-color: #1a73e8;\n}\n.eg-checkbox-input:focus-visible + .eg-checkbox-box { box-shadow: 0 0 0 3px rgba(26,115,232,0.2); }\n.eg-checkbox--disabled,\n.eg-checkbox--disabled .eg-checkbox-input { cursor: default; }\n.eg-checkbox-input:disabled + .eg-checkbox-box { background-color: #f0f1f3; border-color: #dadde2; }\n.eg-checkbox-input:disabled:checked + .eg-checkbox-box { background-color: #b9c2ce; border-color: #b9c2ce; }\n</style>\n"]},media:void 0}))},Z,"data-v-f65b9586",false,void 0,!1,L,void 0,void 0);var J={install(n){n.component("ExcelGrid",Q),n.component("EgCheckbox",T),n.component("EgSelect",G),n.component("EgTreeSelect",U)}};n.EgCheckbox=T,n.EgSelect=G,n.EgTreeSelect=U,n.ExcelGrid=Q,n.default=J,Object.defineProperty(n,"__esModule",{value:!0})});
|
|
6
|
+
!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("xlsx")):"function"==typeof define&&define.amd?define(["exports","xlsx"],e):e((n="undefined"!=typeof globalThis?globalThis:n||self).Vue2ExcelGrid={},n.XLSX)}(this,function(n,e){"use strict";function t(n){if(n&&n.__esModule)return n;var e=Object.create(null);return n&&Object.keys(n).forEach(function(t){if("default"!==t){var i=Object.getOwnPropertyDescriptor(n,t);Object.defineProperty(e,t,i.get?i:{enumerable:!0,get:function(){return n[t]}})}}),e.default=n,Object.freeze(e)}var i=t(e);function o(n){return{startRow:Math.min(n.startRow,n.endRow),endRow:Math.max(n.startRow,n.endRow),startCol:Math.min(n.startCol,n.endCol),endCol:Math.max(n.startCol,n.endCol)}}function l(n,e,t){const i=o(t);return n>=i.startRow&&n<=i.endRow&&e>=i.startCol&&e<=i.endCol}function s(n,e){return{startRow:n,endRow:n,startCol:e,endCol:e}}function r(n){const e=[];return function n(t){for(const i of t)i.children&&i.children.length?n(i.children):e.push({...i,_flatIndex:e.length})}(n),e}function a(n){return function n(e){let t=0;for(const i of e)t=i.children&&i.children.length?Math.max(t,1+n(i.children)):Math.max(t,1);return t}(n)}function c(n,e){const t=Array.from({length:e},()=>[]);let i=0;return function n(o,l){for(const s of o)if(s.children&&s.children.length){const e=d(s),o=i;t[l].push({column:s,colspan:e,rowspan:1,isGroup:!0,_firstLeafFlatIndex:o}),n(s.children,l+1)}else{const n=e-l;t[l].push({column:{...s,_flatIndex:i},colspan:1,rowspan:n,isGroup:!1,_firstLeafFlatIndex:i}),i++}}(n,0),t}function d(n){return n.children&&0!==n.children.length?n.children.reduce((n,e)=>n+d(e),0):1}function h(n,e,t=[]){let i=0;for(let o=0;o<n;o++)i+=void 0!==t[o]?t[o]:e;return i}const p=["Dushanba","Seshanba","Chorshanba","Payshanba","Juma","Shanba","Yakshanba"],u=["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(n){return"string"==typeof n&&f.test(n.trim())}function C(n){if("boolean"==typeof n)return!1;const e=String(n).trim();return""!==e&&(!A(e)&&!isNaN(Number(e)))}function w(n,e){const t=function(n){if(0===n.length)return{type:"copy",step:0,base:null};if(1===n.length)return{type:"copy",step:0,base:n[0]};const e=n.map(n=>p.indexOf(n));if(e.every(n=>n>=0))return{type:"weekday_uz",step:n.length>=2&&(e[1]-e[0]+7)%7||1,base:e[0]};const t=n.map(n=>u.indexOf(n));if(t.every(n=>n>=0))return{type:"weekday_en",step:n.length>=2&&(t[1]-t[0]+7)%7||1,base:t[0]};const i=n.map(n=>g.indexOf(n));if(i.every(n=>n>=0))return{type:"month",step:n.length>=2&&(i[1]-i[0]+12)%12||1,base:i[0]};if(n.every(A)){const e=n.map(n=>new Date(n).getTime());if(e.every(n=>!isNaN(n))){const n=e.slice(1).map((n,t)=>n-e[t]);if(n.every(e=>Math.abs(e-n[0])<864e5))return{type:"date",step:n[0],base:e[0]}}}if(n.every(C)){const e=n.map(n=>Number(n)),t=e.slice(1).map((n,t)=>n-e[t]);if(t.every(n=>Math.abs(n-t[0])<1e-10))return{type:"number",step:t[0],base:e[0]}}return{type:"copy",step:0,base:n[0]}}(n),i=n.length,o=[];for(let l=0;l<e;l++){const e=i+l;switch(t.type){case"number":o.push(t.base+t.step*e);break;case"weekday_uz":o.push(p[(t.base+t.step*e)%7]);break;case"weekday_en":o.push(u[(t.base+t.step*e)%7]);break;case"month":o.push(g[(t.base+t.step*e)%12]);break;case"date":{const n=new Date(t.base+t.step*e);o.push(n.toISOString().split("T")[0]);break}default:o.push(n[e%i])}}return o}function m(n,e,t){const i=[];for(let o=t.startRow;o<=t.endRow;o++){const l=[];for(let i=t.startCol;i<=t.endCol;i++){const t=e[i],s=t&&n[o]?n[o][t.key]:"";l.push(void 0!==s?s:"")}i.push(l)}return i}function x(n,e,t,i,o,l="all",s=null,r=null){const a=n.slice(),c=Math.min(t+o.length-1,n.length-1),d=Math.min(i+(o[0]?o[0].length-1:0),e.length-1),h=[];for(let p=t;p<=c;p++){const c=o[p-t];if(!c)continue;let u=null;for(let t=i;t<=d;t++){const o=e[t];if(o&&((s?s(p,t):!1!==o.editable)&&("all"===l||"values"===l))){const e=c[t-i],l=void 0!==e?e:"";if(r&&!r(p,t,l)){h.push({row:p,col:t,value:l});continue}u||(u={...n[p]||{}},a[p]=u),u[o.key]=l}}}return{newData:a,affectedRange:{startRow:t,endRow:c,startCol:i,endCol:d},skipped:h}}function v(n,e){const t=o(n);return e.some(n=>function(n,e){const t=o(n),i=o(e);return!(t.endRow<i.startRow||t.startRow>i.endRow||t.endCol<i.startCol||t.startCol>i.endCol)}(t,o(n)))}function y(n,e,t){return t.find(t=>{const i=o(t);return n>=i.startRow&&n<=i.endRow&&e>=i.startCol&&e<=i.endCol})||null}function b(n,e,t,i){if(!function(n,e){return Number.isInteger(n)&&Number.isInteger(e)&&n<=e&&n>=0}(e,t))return i;return i.find(i=>i.type===n&&i.startIndex===e&&i.endIndex===t)?i:[...i,{type:n,startIndex:e,endIndex:t}]}function R(n,e,t,i){return i.filter(i=>!(i.type===n&&i.startIndex===e&&i.endIndex===t))}function k(n,e){return e.some(e=>e.type===n.type&&e.startIndex===n.startIndex&&e.endIndex===n.endIndex)}function S(n){const e=[];for(const t of n||[])t.children&&t.children.length?e.push(...S(t.children)):t.key&&e.push(t);return e}function E(n,e,t,i,o){for(let l=e;l<=t;l++)for(let e=i;e<=o;e++)if(""!==((n[l]||[])[e]||"").toString().trim())return!0;return!1}function _(n,e,t){return String((n[e]||[])[t]||"").trim().replace(/\n/g," ")}function D(){return new Promise(n=>setTimeout(n,0))}async function M(n,e,t={},o){const l=(n,e)=>o&&o(n,e);l(5,"Fayl o'qilmoqda..."),await D();const s=await n.arrayBuffer();let r;l(20,"Excel tahlil qilinmoqda..."),await D();try{r=i.read(s,{type:"array",cellDates:!0})}catch(n){throw new Error("Excel fayl o'qib bo'lmadi: "+n.message)}const{sheetName:a}=t;let c;if(a){if(c=r.Sheets[a],!c)throw new Error(`Sheet "${a}" topilmadi`)}else{const n=r.SheetNames[0];if(!n)throw new Error("Workbook bo'sh");c=r.Sheets[n]}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"),p=h.s.r,u=h.s.c,g=(c["!merges"]||[]).map(n=>({s:{r:n.s.r-p,c:n.s.c-u},e:{r:n.e.r-p,c:n.e.c-u}})),f=function(n,e){if(!n||0===n.length)return{headerRow:0,headerEndRow:0,dataStartRow:1};const t=n.map(n=>n.filter(n=>""!==n).length);e=e||[];let i,o,l,s=-1;for(let e=0;e<Math.min(n.length,20);e++){const t=n[e].filter(n=>""!==n);if(t.length<2)continue;const i=t.map(n=>{const e=parseInt(String(n).trim(),10);return isNaN(e)?null:e});if(!i.some(n=>null===n)&&i.every((n,e)=>n===e+1)){s=e;break}}if(s>=0){for(l=s+1,o=s-1;o>=0&&0===t[o];)o--;for(o<0&&(o=0),i=o;i>0&&t[i-1]>1;)i--}else{i=0;for(let e=0;e<Math.min(n.length,20);e++)if(t[e]>1){i=e;break}let s=1;for(const n of e)n.s.r===i&&n.e.r>n.s.r&&(s=Math.max(s,n.e.r-n.s.r+1));o=i+s-1,l=o+1}for(;l<n.length&&0===t[l];)l++;return{headerRow:i,headerEndRow:o,dataStartRow:l}}(d,g),A=null!=t.headerRow?t.headerRow:f.headerRow,C=null!=t.headerEndRow?t.headerEndRow:f.headerEndRow,w=null!=t.dataStartRow?t.dataStartRow:f.dataStartRow,m=S(e),x=new Set,{columns:v,colMap:y}=function(n,e,t,i,o,l){let s=-1;for(let i=e;i<=t;i++)for(let e=(n[i]||[]).length-1;e>=0;e--)if(""!==((n[i]||[])[e]||"")){s=Math.max(s,e);break}const r=(i||[]).filter(n=>n.s.r>=e&&n.s.r<=t);for(const n of r)s=Math.max(s,n.e.c);if(s<0)return{columns:[],colMap:[]};const a=[];function c(n,e){const t=o.find(e=>e.title&&e.title.toLowerCase()===n.toLowerCase());if(t)return{key:t.key,title:t.title,width:t.width||100};let i=String(n||"").toLowerCase().replace(/\s+/g,"_").replace(/[^a-z0-9_]/g,"").replace(/^_+|_+$/g,"").replace(/_+/g,"_").replace(/^(\d)/,"col_$1")||`col_${e}`,s=i,r=1;for(;l.has(s)||o.some(n=>n.key===s);)s=`${i}_${r++}`;return l.add(s),{key:s,title:n||s,width:100}}const d=function e(t,i,o,l){if(t>i||o>l)return[];const s=[];let d=o;for(;d<=l;){if(r.some(n=>n.s.r<=t&&n.e.r>=t&&n.s.c<d&&n.e.c>=d)){d++;continue}const o=r.find(n=>n.s.r===t&&n.s.c===d),h=o?_(n,o.s.r,o.s.c):_(n,t,d);if(o&&o.e.c>d){const t=Math.min(o.e.c,l),r=o.e.r+1;if(r<=i&&E(n,r,i,d,t)){const n=e(r,i,d,t);s.push({title:h,children:n})}else for(let n=d;n<=t;n++){const e=c(h,n);s.push(e),a.push({key:e.key,colIndex:n})}d=t+1}else if(o&&o.e.r>=i){const n=c(h,d);s.push(n),a.push({key:n.key,colIndex:d}),d++}else if(o){const t=o.e.r+1;if(t<=i&&E(n,t,i,d,d)){const n=e(t,i,d,d);s.push({title:h,children:n})}else{const n=c(h,d);s.push(n),a.push({key:n.key,colIndex:d})}d++}else if(t===i){const n=c(h,d);s.push(n),a.push({key:n.key,colIndex:d}),d++}else{if(E(n,t+1,i,d,d)){const n=e(t+1,i,d,d);s.push({title:h,children:n})}else{const n=c(h,d);s.push(n),a.push({key:n.key,colIndex:d})}d++}}return s}(e,t,0,s);return{columns:d,colMap:a}}(d,A,C,g,m,x),b=Math.max(d.length-w,0);l(55,`Qatorlar qayta ishlanmoqda (0 / ${b})...`),await D();const R=[];for(let n=w;n<d.length;n+=2e3){const e=Math.min(n+2e3,d.length);for(let t=n;t<e;t++){const n=d[t],e={};for(const{key:t,colIndex:i}of y)e[t]=void 0!==n[i]?n[i]:"";R.push(e)}const t=Math.min(e-w,b);l(55+Math.round(40*t/Math.max(b,1)),`Qatorlar: ${t} / ${b}`),await D()}const k=g.filter(n=>n.s.r>=w).map(n=>({startRow:n.s.r-w,startCol:n.s.c,endRow:n.e.r-w,endCol:n.e.c})).filter(n=>n.startRow>=0&&n.startCol>=0);return l(100,"Tayyor!"),{data:R,columns:v,merges:k}}const z={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",total:"Total"},ru:{copy:"Копировать",cut:"Вырезать",paste:"Вставить",pasteSpecial:"Специальная вставка",pasteAll:"Всё (значение + формат)",pasteValues:"Только значения",pasteFormats:"Только форматы",transpose:"Транспонировать",mergeCells:"Объединить ячейки",unmergeCells:"Разъединить ячейки",hideRows:"Скрыть строку(и)",hideColumns:"Скрыть столбец(цы)",unhideRows:"Показать все строки",unhideColumns:"Показать все столбцы",pinLeft:"Закрепить слева",pinRight:"Закрепить справа",unpin:"Открепить",invalidValue:"Недопустимое значение",total:"Итого"},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",total:"Jami"},"uz-Cyrl":{copy:"Нусхалаш",cut:"Кесиб олиш",paste:"Жойлаштириш",pasteSpecial:"Махсус жойлаштириш",pasteAll:"Ҳаммаси (қиймат + формат)",pasteValues:"Фақат қийматлар",pasteFormats:"Фақат форматлар",transpose:"Транспозиция",mergeCells:"Катакчаларни бирлаштириш",unmergeCells:"Катакчаларни ажратиш",hideRows:"Қаторларни яшириш",hideColumns:"Устунларни яшириш",unhideRows:"Барча қаторларни кўрсатиш",unhideColumns:"Барча устунларни кўрсатиш",pinLeft:"Чапга пин қилиш",pinRight:"Ўнгга пин қилиш",unpin:"Пиндан олиш",invalidValue:"Нотўғри қиймат",total:"Жами"}};var I={name:"EgCheckbox",model:{prop:"value",event:"input"},props:{value:{default:!1},trueValue:{default:!0},falseValue:{default:!1},disabled:{type:Boolean,default:!1}},computed:{isChecked(){return this.value===this.trueValue}},methods:{onChange(n){const e=n.target.checked?this.trueValue:this.falseValue;this.$emit("input",e),this.$emit("change",e)}}};function H(n,e,t,i,o,l,s,r,a,c){"boolean"!=typeof s&&(a=r,r=s,s=!1);const d="function"==typeof t?t.options:t;let h;if(n&&n.render&&(d.render=n.render,d.staticRenderFns=n.staticRenderFns,d._compiled=!0,o&&(d.functional=!0)),i&&(d._scopeId=i),l?(h=function(n){(n=n||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(n=__VUE_SSR_CONTEXT__),e&&e.call(this,a(n)),n&&n._registeredComponents&&n._registeredComponents.add(l)},d._ssrRegister=h):e&&(h=s?function(n){e.call(this,c(n,this.$root.$options.shadowRoot))}:function(n){e.call(this,r(n))}),h)if(d.functional){const n=d.render;d.render=function(e,t){return h.call(t),n(e,t)}}else{const n=d.beforeCreate;d.beforeCreate=n?[].concat(n,h):[h]}return t}const V="undefined"!=typeof navigator&&/msie [6-9]\\b/.test(navigator.userAgent.toLowerCase());function L(n){return(n,e)=>function(n,e){const t=V?e.media||"default":n,i=W[t]||(W[t]={ids:new Set,styles:[]});if(!i.ids.has(n)){i.ids.add(n);let t=e.source;if(e.map&&(t+="\n/*# sourceURL="+e.map.sources[0]+" */",t+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(e.map))))+" */"),i.element||(i.element=document.createElement("style"),i.element.type="text/css",e.media&&i.element.setAttribute("media",e.media),void 0===B&&(B=document.head||document.getElementsByTagName("head")[0]),B.appendChild(i.element)),"styleSheet"in i.element)i.styles.push(t),i.element.styleSheet.cssText=i.styles.filter(Boolean).join("\n");else{const n=i.ids.size-1,e=document.createTextNode(t),o=i.element.childNodes;o[n]&&i.element.removeChild(o[n]),o.length?i.element.insertBefore(e,o[n]):i.element.appendChild(e)}}}(n,e)}let B;const W={};const O=I;var $=function(){var n=this,e=n.$createElement,t=n._self._c||e;return t("label",{staticClass:"eg-checkbox",class:{"eg-checkbox--disabled":n.disabled}},[t("input",{staticClass:"eg-checkbox-input",attrs:{type:"checkbox",disabled:n.disabled},domProps:{checked:n.isChecked},on:{change:n.onChange}}),n._v(" "),t("span",{staticClass:"eg-checkbox-box"})])};$._withStripped=!0;const T=H({render:$,staticRenderFns:[]},function(n){n&&n("data-v-48d91ddc_0",{source:'\n.eg-checkbox {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n cursor: pointer;\n}\n.eg-checkbox-input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\n.eg-checkbox-box {\n position: relative;\n display: block;\n width: 19px;\n height: 19px;\n box-sizing: border-box;\n background-color: #fff;\n background-repeat: no-repeat;\n background-position: center;\n background-size: 0 0;\n border: 1.5px solid #c2c8d1;\n border-radius: 4px;\n transition: background-color .12s ease, border-color .12s ease, background-size .12s ease;\n}\n.eg-checkbox:hover .eg-checkbox-box {\n border-color: #1a73e8;\n}\n.eg-checkbox-input:checked + .eg-checkbox-box {\n background-color: #1a73e8;\n background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZD0iTTMuNSA4LjNsMyAzIDYtNi42IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==");\n background-size: 70% 70%;\n border-color: #1a73e8;\n}\n.eg-checkbox-input:focus-visible + .eg-checkbox-box {\n box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.2);\n}\n.eg-checkbox--disabled,\n.eg-checkbox--disabled .eg-checkbox-input {\n cursor: default;\n}\n.eg-checkbox-input:disabled + .eg-checkbox-box {\n background-color: #f0f1f3;\n border-color: #dadde2;\n}\n.eg-checkbox-input:disabled:checked + .eg-checkbox-box {\n background-color: #b9c2ce;\n border-color: #b9c2ce;\n}\n',map:{version:3,sources:["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/EgCheckbox.vue"],names:[],mappings:";AAuCA;EACA,kBAAA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;EACA,eAAA;AACA;AACA;EACA,kBAAA;EACA,QAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA;EACA,UAAA;EACA,eAAA;AACA;AACA;EACA,kBAAA;EACA,cAAA;EACA,WAAA;EACA,YAAA;EACA,sBAAA;EACA,sBAAA;EACA,4BAAA;EACA,2BAAA;EACA,oBAAA;EACA,2BAAA;EACA,kBAAA;EACA,yFAAA;AACA;AACA;EACA,qBAAA;AACA;AACA;EACA,yBAAA;EACA,+SAAA;EACA,wBAAA;EACA,qBAAA;AACA;AACA;EACA,6CAAA;AACA;AACA;;EAEA,eAAA;AACA;AACA;EACA,yBAAA;EACA,qBAAA;AACA;AACA;EACA,yBAAA;EACA,qBAAA;AACA",file:"EgCheckbox.vue",sourcesContent:['<template>\n <label class="eg-checkbox" :class="{ \'eg-checkbox--disabled\': disabled }">\n <input\n type="checkbox"\n class="eg-checkbox-input"\n :checked="isChecked"\n :disabled="disabled"\n @change="onChange"\n />\n <span class="eg-checkbox-box"></span>\n </label>\n</template>\n\n<script>\nexport default {\n name: \'EgCheckbox\',\n model: { prop: \'value\', event: \'input\' },\n props: {\n value: { default: false },\n trueValue: { default: true },\n falseValue: { default: false },\n disabled: { type: Boolean, default: false }\n },\n computed: {\n isChecked() {\n return this.value === this.trueValue\n }\n },\n methods: {\n onChange(e) {\n const val = e.target.checked ? this.trueValue : this.falseValue\n this.$emit(\'input\', val)\n this.$emit(\'change\', val)\n }\n }\n}\n<\/script>\n\n<style>\n.eg-checkbox {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n cursor: pointer;\n}\n.eg-checkbox-input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\n.eg-checkbox-box {\n position: relative;\n display: block;\n width: 19px;\n height: 19px;\n box-sizing: border-box;\n background-color: #fff;\n background-repeat: no-repeat;\n background-position: center;\n background-size: 0 0;\n border: 1.5px solid #c2c8d1;\n border-radius: 4px;\n transition: background-color .12s ease, border-color .12s ease, background-size .12s ease;\n}\n.eg-checkbox:hover .eg-checkbox-box {\n border-color: #1a73e8;\n}\n.eg-checkbox-input:checked + .eg-checkbox-box {\n background-color: #1a73e8;\n background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZD0iTTMuNSA4LjNsMyAzIDYtNi42IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==");\n background-size: 70% 70%;\n border-color: #1a73e8;\n}\n.eg-checkbox-input:focus-visible + .eg-checkbox-box {\n box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.2);\n}\n.eg-checkbox--disabled,\n.eg-checkbox--disabled .eg-checkbox-input {\n cursor: default;\n}\n.eg-checkbox-input:disabled + .eg-checkbox-box {\n background-color: #f0f1f3;\n border-color: #dadde2;\n}\n.eg-checkbox-input:disabled:checked + .eg-checkbox-box {\n background-color: #b9c2ce;\n border-color: #b9c2ce;\n}\n</style>\n']},media:void 0})},O,void 0,false,void 0,!1,L,void 0,void 0);const F={name:"EgSelect",model:{prop:"value",event:"input"},props:{value:{default:null},options:{type:Array,default:()=>[]},enableSearch:{type:Boolean,default:!1},placeholder:{type:String,default:""},disabled:{type:Boolean,default:!1},dropdownOnly:{type:Boolean,default:!1},triggerRect:{type:Object,default:null}},data:()=>({open:!1,searchQuery:"",highlight:-1}),computed:{normalizedOptions(){return(this.options||[]).map(n=>"string"==typeof n?{value:n,label:n}:{value:n.value,label:null!=n.label?n.label:String(n.value),color:n.color})},filteredOptions(){if(!this.enableSearch||!this.searchQuery.trim())return this.normalizedOptions;const n=this.searchQuery.trim().toLowerCase();return this.normalizedOptions.filter(e=>String(e.label||"").toLowerCase().includes(n))},selectedOpt(){const n=String(null==this.value?"":this.value);return this.normalizedOptions.find(e=>String(e.value)===n)||null},overlayStyle(){if(!this.dropdownOnly||!this.triggerRect)return{position:"fixed",zIndex:9999};const n=this.triggerRect,e=this.enableSearch?42:0,t=Math.min(28*this.filteredOptions.length+8+e,320);return{position:"fixed",top:(window.innerHeight-n.bottom>=t?n.bottom:n.top-t)+"px",left:n.left+"px",minWidth:Math.max(n.width,120)+"px",maxWidth:Math.min(360,window.innerWidth-n.left-16)+"px",zIndex:9999}}},watch:{open(n){n&&this.enableSearch&&this.$nextTick(()=>{this.$refs.searchInput&&this.$refs.searchInput.focus()})}},mounted(){if(this.dropdownOnly){const n=String(null==this.value?"":this.value);this.highlight=this.normalizedOptions.findIndex(e=>String(e.value)===n),this.enableSearch&&this.$nextTick(()=>{this.$refs.searchInput&&this.$refs.searchInput.focus()})}else document.addEventListener("click",this._onDocClick)},beforeDestroy(){this.dropdownOnly||document.removeEventListener("click",this._onDocClick)},methods:{toggle(){if(!this.disabled&&(this.open=!this.open,this.open)){const n=String(null==this.value?"":this.value);this.highlight=this.normalizedOptions.findIndex(e=>String(e.value)===n),this.searchQuery=""}},pickOption(n){this.$emit("input",n.value),this.$emit("select",{value:n.value,label:n.label}),this.dropdownOnly||(this.open=!1,this.searchQuery="",this.highlight=-1)},onSearch(n){this.searchQuery=n.target.value,this.highlight=0},onSearchKey(n){const e=n.key;if("Escape"===e)n.preventDefault(),this.dropdownOnly?this.$emit("close"):(this.open=!1,this.searchQuery="");else if("ArrowDown"===e)n.preventDefault(),this.highlight=Math.min((this.highlight||0)+1,this.filteredOptions.length-1);else if("ArrowUp"===e)n.preventDefault(),this.highlight=Math.max((this.highlight||0)-1,0);else if("Enter"===e){n.preventDefault();const e=this.filteredOptions[this.highlight];e&&this.pickOption(e)}},handleKey(n){const e=n.key;if("Escape"===e)n.preventDefault(),this.$emit("close");else if("Enter"===e||" "===e){n.preventDefault();const e=this.filteredOptions[this.highlight];e?this.pickOption(e):this.$emit("close")}else"ArrowDown"===e?(n.preventDefault(),this.highlight=Math.min((this.highlight||0)+1,this.filteredOptions.length-1)):"ArrowUp"===e&&(n.preventDefault(),this.highlight=Math.max((this.highlight||0)-1,0))},onRootMouseDown(n){this.dropdownOnly&&n.preventDefault()},_onDocClick(n){this.open&&(this.$el&&this.$el.contains(n.target)||(this.open=!1))}}};var P=function(){var n=this,e=n.$createElement,t=n._self._c||e;return t("div",{class:n.dropdownOnly?"eg-select-dropdown":"eg-select-wrapper",style:n.dropdownOnly?n.overlayStyle:null,on:{mousedown:n.onRootMouseDown}},[n.dropdownOnly?n._e():t("div",{staticClass:"eg-select-trigger",class:{"eg-select-trigger--disabled":n.disabled,"eg-select-trigger--open":n.open},on:{click:function(e){return e.stopPropagation(),n.toggle.apply(null,arguments)}}},[n.selectedOpt&&n.selectedOpt.color?t("span",{staticClass:"eg-select-dot",style:{background:n.selectedOpt.color}}):n._e(),n._v(" "),t("span",{staticClass:"eg-select-trigger-label",class:{"eg-select-placeholder":!n.selectedOpt}},[n._v("\n "+n._s(n.selectedOpt?n.selectedOpt.label:n.placeholder)+"\n ")]),n._v(" "),t("span",{staticClass:"eg-select-caret"},[n._v("▾")])]),n._v(" "),n.dropdownOnly?[n.enableSearch?t("div",{staticClass:"eg-select-search",on:{mousedown:function(n){n.stopPropagation()}}},[t("input",{ref:"searchInput",staticClass:"eg-select-search-input",attrs:{placeholder:"Qidirish...",autocomplete:"off",autocorrect:"off",autocapitalize:"off",spellcheck:"false"},domProps:{value:n.searchQuery},on:{input:n.onSearch,keydown:function(e){return e.stopPropagation(),n.onSearchKey.apply(null,arguments)}}})]):n._e(),n._v(" "),n._l(n.filteredOptions,function(e,i){return t("div",{key:String(e.value),staticClass:"eg-select-option",class:{"eg-select-option--active":String(e.value)===String(n.value),"eg-select-option--highlighted":i===n.highlight},on:{mousedown:function(t){return t.preventDefault(),n.pickOption(e)},mouseover:function(e){n.highlight=i}}},[e.color?t("span",{staticClass:"eg-select-dot",style:{background:e.color}}):n._e(),n._v("\n "+n._s(e.label)+"\n ")])})]:n.open?t("div",{staticClass:"eg-select-dropdown eg-select-dropdown--floating",on:{mousedown:function(n){n.stopPropagation(),n.preventDefault()}}},[n.enableSearch?t("div",{staticClass:"eg-select-search",on:{mousedown:function(n){n.stopPropagation()}}},[t("input",{ref:"searchInput",staticClass:"eg-select-search-input",attrs:{placeholder:"Qidirish...",autocomplete:"off",autocorrect:"off",autocapitalize:"off",spellcheck:"false"},domProps:{value:n.searchQuery},on:{input:n.onSearch,keydown:function(e){return e.stopPropagation(),n.onSearchKey.apply(null,arguments)}}})]):n._e(),n._v(" "),n._l(n.filteredOptions,function(e,i){return t("div",{key:String(e.value),staticClass:"eg-select-option",class:{"eg-select-option--active":String(e.value)===String(n.value),"eg-select-option--highlighted":i===n.highlight},on:{mousedown:function(t){return t.preventDefault(),n.pickOption(e)},mouseover:function(e){n.highlight=i}}},[e.color?t("span",{staticClass:"eg-select-dot",style:{background:e.color}}):n._e(),n._v("\n "+n._s(e.label)+"\n ")])})],2):n._e()],2)};P._withStripped=!0;const G=H({render:P,staticRenderFns:[]},function(n){n&&n("data-v-76c9c032_0",{source:"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* Standalone wrapper */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: 13px;\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n background: #fff;\n cursor: pointer;\n user-select: none;\n color: #333;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover {\n border-color: #1a73e8;\n}\n.eg-select-trigger--open {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-trigger--disabled {\n background: #f5f5f5;\n cursor: default;\n color: #aaa;\n}\n.eg-select-trigger-label {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.eg-select-placeholder {\n color: #aaa;\n}\n.eg-select-caret {\n flex-shrink: 0;\n color: #888;\n font-size: 12px;\n}\n\n/* Dropdown panel */\n.eg-select-dropdown {\n background: #fff;\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: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n\n/* Search input inside dropdown */\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid #ebebeb;\n position: sticky;\n top: 0;\n background: #fff;\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 13px;\n outline: none;\n color: #333;\n background: #fff;\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n\n/* Option rows */\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\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/* Color dot */\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n",map:{version:3,sources:["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/EgSelect.vue"],names:[],mappings:";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+OA,uBAAA;AACA;EACA,kBAAA;EACA,qBAAA;EACA,gBAAA;EACA,eAAA;EACA,oBAAA;AACA;AACA;EACA,aAAA;EACA,mBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;EACA,gBAAA;EACA,sBAAA;EACA,yBAAA;EACA,kBAAA;EACA,gBAAA;EACA,eAAA;EACA,iBAAA;EACA,WAAA;EACA,wDAAA;AACA;AACA;EACA,qBAAA;AACA;AACA;EACA,qBAAA;EACA,2CAAA;AACA;AACA;EACA,mBAAA;EACA,eAAA;EACA,WAAA;AACA;AACA;EACA,OAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;AACA;AACA;EACA,WAAA;AACA;AACA;EACA,cAAA;EACA,WAAA;EACA,eAAA;AACA;;AAEA,mBAAA;AACA;EACA,gBAAA;EACA,yBAAA;EACA,kBAAA;EACA,uCAAA;EACA,gBAAA;EACA,iBAAA;EACA,cAAA;AACA;AACA;EACA,kBAAA;EACA,SAAA;EACA,OAAA;EACA,eAAA;EACA,YAAA;EACA,eAAA;AACA;;AAEA,iCAAA;AACA;EACA,gBAAA;EACA,gCAAA;EACA,gBAAA;EACA,MAAA;EACA,gBAAA;EACA,UAAA;AACA;AACA;EACA,WAAA;EACA,sBAAA;EACA,yBAAA;EACA,kBAAA;EACA,gBAAA;EACA,eAAA;EACA,aAAA;EACA,WAAA;EACA,gBAAA;EACA,oBAAA;AACA;AACA;EACA,qBAAA;EACA,2CAAA;AACA;;AAEA,gBAAA;AACA;EACA,aAAA;EACA,uBAAA;EACA,QAAA;EACA,iBAAA;EACA,eAAA;EACA,eAAA;EACA,mBAAA;EACA,sBAAA;EACA,iBAAA;EACA,WAAA;AACA;AACA;;EAEA,mBAAA;AACA;AACA;EACA,gBAAA;EACA,cAAA;AACA;AACA;;EAEA,mBAAA;AACA;;AAEA,cAAA;AACA;EACA,UAAA;EACA,WAAA;EACA,kBAAA;EACA,cAAA;EACA,qBAAA;EACA,eAAA;AACA",file:"EgSelect.vue",sourcesContent:['<template>\n <div\n :class="dropdownOnly ? \'eg-select-dropdown\' : \'eg-select-wrapper\'"\n :style="dropdownOnly ? overlayStyle : null"\n @mousedown="onRootMouseDown"\n >\n \x3c!-- Standalone mode: trigger button --\x3e\n <div\n v-if="!dropdownOnly"\n class="eg-select-trigger"\n :class="{ \'eg-select-trigger--disabled\': disabled, \'eg-select-trigger--open\': open }"\n @click.stop="toggle"\n >\n <span v-if="selectedOpt && selectedOpt.color" class="eg-select-dot" :style="{ background: selectedOpt.color }"></span>\n <span class="eg-select-trigger-label" :class="{ \'eg-select-placeholder\': !selectedOpt }">\n {{ selectedOpt ? selectedOpt.label : placeholder }}\n </span>\n <span class="eg-select-caret">▾</span>\n </div>\n\n \x3c!-- Options panel in grid dropdown-only mode (root IS the dropdown panel) --\x3e\n <template v-if="dropdownOnly">\n <div v-if="enableSearch" class="eg-select-search" @mousedown.stop>\n <input\n ref="searchInput"\n class="eg-select-search-input"\n :value="searchQuery"\n placeholder="Qidirish..."\n autocomplete="off"\n autocorrect="off"\n autocapitalize="off"\n spellcheck="false"\n @input="onSearch"\n @keydown.stop="onSearchKey"\n />\n </div>\n <div\n v-for="(opt, idx) in filteredOptions"\n :key="String(opt.value)"\n class="eg-select-option"\n :class="{\n \'eg-select-option--active\': String(opt.value) === String(value),\n \'eg-select-option--highlighted\': idx === highlight\n }"\n @mousedown.prevent="pickOption(opt)"\n @mouseover="highlight = idx"\n >\n <span v-if="opt.color" class="eg-select-dot" :style="{ background: opt.color }"></span>\n {{ opt.label }}\n </div>\n </template>\n\n \x3c!-- Options panel in standalone mode (floating dropdown below trigger) --\x3e\n <div\n v-else-if="open"\n class="eg-select-dropdown eg-select-dropdown--floating"\n @mousedown.stop.prevent\n >\n <div v-if="enableSearch" class="eg-select-search" @mousedown.stop>\n <input\n ref="searchInput"\n class="eg-select-search-input"\n :value="searchQuery"\n placeholder="Qidirish..."\n autocomplete="off"\n autocorrect="off"\n autocapitalize="off"\n spellcheck="false"\n @input="onSearch"\n @keydown.stop="onSearchKey"\n />\n </div>\n <div\n v-for="(opt, idx) in filteredOptions"\n :key="String(opt.value)"\n class="eg-select-option"\n :class="{\n \'eg-select-option--active\': String(opt.value) === String(value),\n \'eg-select-option--highlighted\': idx === highlight\n }"\n @mousedown.prevent="pickOption(opt)"\n @mouseover="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 </div>\n</template>\n\n<script>\nexport default {\n name: \'EgSelect\',\n model: { prop: \'value\', event: \'input\' },\n props: {\n value: { default: null },\n options: { type: Array, default: () => [] },\n enableSearch: { type: Boolean, default: false },\n placeholder: { type: String, default: \'\' },\n disabled: { type: Boolean, default: false },\n dropdownOnly: { type: Boolean, default: false },\n triggerRect: { type: Object, default: null }\n },\n data() {\n return {\n open: false,\n searchQuery: \'\',\n highlight: -1\n }\n },\n computed: {\n normalizedOptions() {\n return (this.options || []).map(o =>\n typeof o === \'string\'\n ? { value: o, label: o }\n : { value: o.value, label: o.label != null ? o.label : String(o.value), color: o.color }\n )\n },\n filteredOptions() {\n if (!this.enableSearch || !this.searchQuery.trim()) return this.normalizedOptions\n const q = this.searchQuery.trim().toLowerCase()\n return this.normalizedOptions.filter(o => String(o.label || \'\').toLowerCase().includes(q))\n },\n selectedOpt() {\n const v = String(this.value == null ? \'\' : this.value)\n return this.normalizedOptions.find(o => String(o.value) === v) || null\n },\n overlayStyle() {\n if (!this.dropdownOnly || !this.triggerRect) return { position: \'fixed\', zIndex: 9999 }\n const rect = this.triggerRect\n const searchH = this.enableSearch ? 42 : 0\n const approxH = Math.min(this.filteredOptions.length * 28 + 8 + searchH, 320)\n const spaceBelow = window.innerHeight - rect.bottom\n const top = spaceBelow >= approxH ? rect.bottom : rect.top - approxH\n return {\n position: \'fixed\',\n top: top + \'px\',\n left: rect.left + \'px\',\n minWidth: Math.max(rect.width, 120) + \'px\',\n maxWidth: Math.min(360, window.innerWidth - rect.left - 16) + \'px\',\n zIndex: 9999\n }\n }\n },\n watch: {\n open(val) {\n if (val && this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n }\n },\n mounted() {\n if (this.dropdownOnly) {\n const v = String(this.value == null ? \'\' : this.value)\n this.highlight = this.normalizedOptions.findIndex(o => String(o.value) === v)\n if (this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n } else {\n document.addEventListener(\'click\', this._onDocClick)\n }\n },\n beforeDestroy() {\n if (!this.dropdownOnly) {\n document.removeEventListener(\'click\', this._onDocClick)\n }\n },\n methods: {\n toggle() {\n if (this.disabled) return\n this.open = !this.open\n if (this.open) {\n const v = String(this.value == null ? \'\' : this.value)\n this.highlight = this.normalizedOptions.findIndex(o => String(o.value) === v)\n this.searchQuery = \'\'\n }\n },\n pickOption(opt) {\n this.$emit(\'input\', opt.value)\n this.$emit(\'select\', { value: opt.value, label: opt.label })\n if (!this.dropdownOnly) {\n this.open = false\n this.searchQuery = \'\'\n this.highlight = -1\n }\n },\n onSearch(e) {\n this.searchQuery = e.target.value\n this.highlight = 0\n },\n onSearchKey(e) {\n const key = e.key\n if (key === \'Escape\') {\n e.preventDefault()\n if (this.dropdownOnly) this.$emit(\'close\')\n else { this.open = false; this.searchQuery = \'\' }\n } else if (key === \'ArrowDown\') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight || 0) + 1, this.filteredOptions.length - 1)\n } else if (key === \'ArrowUp\') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n } else if (key === \'Enter\') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n }\n },\n handleKey(e) {\n const key = e.key\n if (key === \'Escape\') {\n e.preventDefault()\n this.$emit(\'close\')\n } else if (key === \'Enter\' || key === \' \') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n else this.$emit(\'close\')\n } else if (key === \'ArrowDown\') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight || 0) + 1, this.filteredOptions.length - 1)\n } else if (key === \'ArrowUp\') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n }\n },\n onRootMouseDown(e) {\n if (this.dropdownOnly) e.preventDefault()\n },\n _onDocClick(e) {\n if (!this.open) return\n if (this.$el && this.$el.contains(e.target)) return\n this.open = false\n }\n }\n}\n<\/script>\n\n<style>\n/* Standalone wrapper */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: 13px;\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n background: #fff;\n cursor: pointer;\n user-select: none;\n color: #333;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover {\n border-color: #1a73e8;\n}\n.eg-select-trigger--open {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-trigger--disabled {\n background: #f5f5f5;\n cursor: default;\n color: #aaa;\n}\n.eg-select-trigger-label {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.eg-select-placeholder {\n color: #aaa;\n}\n.eg-select-caret {\n flex-shrink: 0;\n color: #888;\n font-size: 12px;\n}\n\n/* Dropdown panel */\n.eg-select-dropdown {\n background: #fff;\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: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n\n/* Search input inside dropdown */\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid #ebebeb;\n position: sticky;\n top: 0;\n background: #fff;\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 13px;\n outline: none;\n color: #333;\n background: #fff;\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n\n/* Option rows */\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\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/* Color dot */\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n</style>\n']},media:void 0})},F,void 0,false,void 0,!1,L,void 0,void 0);const K={name:"EgTreeSelect",model:{prop:"value",event:"input"},props:{value:{default:null},options:{type:Array,default:()=>[]},valueKey:{type:String,default:"value"},labelKey:{type:String,default:"label"},childrenKey:{type:String,default:"children"},enableSearch:{type:Boolean,default:!1},placeholder:{type:String,default:""},disabled:{type:Boolean,default:!1},dropdownOnly:{type:Boolean,default:!1},triggerRect:{type:Object,default:null}},data:()=>({open:!1,searchQuery:"",highlight:-1,expandedMap:{}}),computed:{keys(){return{valueKey:this.valueKey,labelKey:this.labelKey,childrenKey:this.childrenKey}},flatOptions(){return this._treeFlatten(this.options||[],this.keys,this.expandedMap,0,[])},allFlatOptions(){return this.enableSearch?this._treeFlattenAll(this.options||[],this.keys):null},filteredOptions(){if(!this.enableSearch||!this.searchQuery.trim())return this.flatOptions;const n=this.searchQuery.trim().toLowerCase();return(this.allFlatOptions||this.flatOptions).filter(e=>String(e.label||"").toLowerCase().includes(n))},selectedLabel(){const n=String(null==this.value?"":this.value);if(!n)return null;const e=this._treeFindLabel(this.options||[],this.keys,n);return null!=e?e:null},overlayStyle(){if(!this.dropdownOnly||!this.triggerRect)return{position:"fixed",zIndex:9999};const n=this.triggerRect,e=this.enableSearch?42:0,t=Math.min(28*this.filteredOptions.length+8+e,320);return{position:"fixed",top:(window.innerHeight-n.bottom>=t?n.bottom:n.top-t)+"px",left:n.left+"px",minWidth:Math.max(n.width,120)+"px",maxWidth:Math.min(360,window.innerWidth-n.left-16)+"px",zIndex:9999}}},watch:{open(n){n&&this.enableSearch&&this.$nextTick(()=>{this.$refs.searchInput&&this.$refs.searchInput.focus()})}},mounted(){this.dropdownOnly?(this._initExpanded(),this.enableSearch&&this.$nextTick(()=>{this.$refs.searchInput&&this.$refs.searchInput.focus()})):document.addEventListener("click",this._onDocClick)},beforeDestroy(){this.dropdownOnly||document.removeEventListener("click",this._onDocClick)},methods:{_initExpanded(){const n=String(null==this.value?"":this.value),e=this._treeFindPath(this.options||[],this.keys,n,[])||[],t={};e.forEach(n=>{t[n]=!0}),this.expandedMap=t;const i=this.flatOptions;this.highlight=i.findIndex(e=>String(e.value)===n)},toggle(){this.disabled||(this.open=!this.open,this.open&&(this.searchQuery="",this._initExpanded()))},pickOption(n){this.$emit("input",n.value),this.$emit("select",{value:n.value,label:n.label}),this.dropdownOnly||(this.open=!1,this.searchQuery="",this.highlight=-1)},toggleNode(n){const e=this.flatOptions[n];if(!e||!e.hasChildren)return;const t={...this.expandedMap};t[e.id]?delete t[e.id]:t[e.id]=!0,this.expandedMap=t;const i=this.flatOptions.findIndex(n=>n.id===e.id);this.highlight=i>=0?i:this.highlight},onSearch(n){this.searchQuery=n.target.value,this.highlight=0},onSearchKey(n){const e=n.key;if("Escape"===e)n.preventDefault(),this.dropdownOnly?this.$emit("close"):(this.open=!1,this.searchQuery="");else if("ArrowDown"===e)n.preventDefault(),this.highlight=Math.min((this.highlight||0)+1,this.filteredOptions.length-1);else if("ArrowUp"===e)n.preventDefault(),this.highlight=Math.max((this.highlight||0)-1,0);else if("Enter"===e){n.preventDefault();const e=this.filteredOptions[this.highlight];e&&this.pickOption(e)}},handleKey(n){const e=n.key;if("Escape"===e)n.preventDefault(),this.$emit("close");else if("Enter"===e||" "===e){n.preventDefault();const e=this.filteredOptions[this.highlight];e?this.pickOption(e):this.$emit("close")}else if("ArrowDown"===e)n.preventDefault(),this.highlight=Math.min((this.highlight||0)+1,this.filteredOptions.length-1);else if("ArrowUp"===e)n.preventDefault(),this.highlight=Math.max((this.highlight||0)-1,0);else if(!this.searchQuery&&("ArrowRight"===e||"ArrowLeft"===e)){n.preventDefault();const t=this.flatOptions[this.highlight];t&&t.hasChildren&&("ArrowRight"!==e||t.expanded||this.toggleNode(this.highlight),"ArrowLeft"===e&&t.expanded&&this.toggleNode(this.highlight))}},onRootMouseDown(n){this.dropdownOnly&&n.preventDefault()},_onDocClick(n){this.open&&(this.$el&&this.$el.contains(n.target)||(this.open=!1))},_treeFlatten(n,e,t,i,o){let l=[];return(n||[]).forEach((n,s)=>{const r=o.concat(s),a=r.join("-"),c=Array.isArray(n[e.childrenKey])?n[e.childrenKey]:[],d=c.length>0,h=d&&!!t[a];l.push({value:n[e.valueKey],label:null!=n[e.labelKey]?n[e.labelKey]:String(n[e.valueKey]),color:n.color,id:a,depth:i,hasChildren:d,expanded:h}),h&&(l=l.concat(this._treeFlatten(c,e,t,i+1,r)))}),l},_treeFlattenAll(n,e){const t=[],i=n=>{(n||[]).forEach(n=>{const o=Array.isArray(n[e.childrenKey])?n[e.childrenKey]:[];t.push({value:n[e.valueKey],label:null!=n[e.labelKey]?n[e.labelKey]:String(n[e.valueKey]),color:n.color,id:String(n[e.valueKey]),depth:0,hasChildren:!1,expanded:!1}),o.length&&i(o)})};return i(n),t},_treeFindLabel(n,e,t){for(const i of n||[]){if(String(i[e.valueKey])===t)return null!=i[e.labelKey]?i[e.labelKey]:String(i[e.valueKey]);const n=this._treeFindLabel(i[e.childrenKey],e,t);if(null!=n)return n}return null},_treeFindPath(n,e,t,i){for(let o=0;o<(n||[]).length;o++){const l=n[o],s=i.concat(o);if(String(l[e.valueKey])===t)return[s.join("-")];const r=l[e.childrenKey];if(Array.isArray(r)){const n=this._treeFindPath(r,e,t,s);if(n)return[s.join("-")].concat(n)}}return null}}};var j=function(){var n=this,e=n.$createElement,t=n._self._c||e;return t("div",{class:n.dropdownOnly?"eg-select-dropdown":"eg-select-wrapper",style:n.dropdownOnly?n.overlayStyle:null,on:{mousedown:n.onRootMouseDown}},[n.dropdownOnly?n._e():t("div",{staticClass:"eg-select-trigger",class:{"eg-select-trigger--disabled":n.disabled,"eg-select-trigger--open":n.open},on:{click:function(e){return e.stopPropagation(),n.toggle.apply(null,arguments)}}},[t("span",{staticClass:"eg-select-trigger-label",class:{"eg-select-placeholder":null==n.selectedLabel}},[n._v("\n "+n._s(null!=n.selectedLabel?n.selectedLabel:n.placeholder)+"\n ")]),n._v(" "),t("span",{staticClass:"eg-select-caret"},[n._v("▾")])]),n._v(" "),n.dropdownOnly?[n.enableSearch?t("div",{staticClass:"eg-select-search",on:{mousedown:function(n){n.stopPropagation()}}},[t("input",{ref:"searchInput",staticClass:"eg-select-search-input",attrs:{placeholder:"Qidirish...",autocomplete:"off",autocorrect:"off",autocapitalize:"off",spellcheck:"false"},domProps:{value:n.searchQuery},on:{input:n.onSearch,keydown:function(e){return e.stopPropagation(),n.onSearchKey.apply(null,arguments)}}})]):n._e(),n._v(" "),n._l(n.filteredOptions,function(e,i){return t("div",{key:null!=e.id?e.id:String(e.value),staticClass:"eg-select-option",class:{"eg-select-option--active":String(e.value)===String(n.value),"eg-select-option--highlighted":i===n.highlight},style:n.searchQuery?null:{paddingLeft:12+16*e.depth+"px"},on:{mousedown:function(t){return t.preventDefault(),n.pickOption(e)},mouseover:function(e){n.highlight=i}}},[n.searchQuery?n._e():t("span",{staticClass:"eg-tree-toggle",class:{"eg-tree-toggle--leaf":!e.hasChildren},on:{mousedown:function(e){return e.preventDefault(),e.stopPropagation(),n.toggleNode(i)}}},[n._v(n._s(e.hasChildren?e.expanded?"▾":"▸":""))]),n._v(" "),e.color?t("span",{staticClass:"eg-select-dot",style:{background:e.color}}):n._e(),n._v("\n "+n._s(e.label)+"\n ")])})]:n.open?t("div",{staticClass:"eg-select-dropdown eg-select-dropdown--floating",on:{mousedown:function(n){n.stopPropagation(),n.preventDefault()}}},[n.enableSearch?t("div",{staticClass:"eg-select-search",on:{mousedown:function(n){n.stopPropagation()}}},[t("input",{ref:"searchInput",staticClass:"eg-select-search-input",attrs:{placeholder:"Qidirish...",autocomplete:"off",autocorrect:"off",autocapitalize:"off",spellcheck:"false"},domProps:{value:n.searchQuery},on:{input:n.onSearch,keydown:function(e){return e.stopPropagation(),n.onSearchKey.apply(null,arguments)}}})]):n._e(),n._v(" "),n._l(n.filteredOptions,function(e,i){return t("div",{key:null!=e.id?e.id:String(e.value),staticClass:"eg-select-option",class:{"eg-select-option--active":String(e.value)===String(n.value),"eg-select-option--highlighted":i===n.highlight},style:n.searchQuery?null:{paddingLeft:12+16*e.depth+"px"},on:{mousedown:function(t){return t.preventDefault(),n.pickOption(e)},mouseover:function(e){n.highlight=i}}},[n.searchQuery?n._e():t("span",{staticClass:"eg-tree-toggle",class:{"eg-tree-toggle--leaf":!e.hasChildren},on:{mousedown:function(e){return e.preventDefault(),e.stopPropagation(),n.toggleNode(i)}}},[n._v(n._s(e.hasChildren?e.expanded?"▾":"▸":""))]),n._v(" "),e.color?t("span",{staticClass:"eg-select-dot",style:{background:e.color}}):n._e(),n._v("\n "+n._s(e.label)+"\n ")])})],2):n._e()],2)};j._withStripped=!0;const U=H({render:j,staticRenderFns:[]},function(n){n&&n("data-v-63651711_0",{source:"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: #888;\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf {\n visibility: hidden;\n}\n",map:{version:3,sources:["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/EgTreeSelect.vue"],names:[],mappings:";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+UA,gBAAA;AACA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;EACA,cAAA;EACA,eAAA;EACA,WAAA;EACA,eAAA;AACA;AACA;EACA,kBAAA;AACA",file:"EgTreeSelect.vue",sourcesContent:['<template>\n <div\n :class="dropdownOnly ? \'eg-select-dropdown\' : \'eg-select-wrapper\'"\n :style="dropdownOnly ? overlayStyle : null"\n @mousedown="onRootMouseDown"\n >\n \x3c!-- Standalone mode: trigger button --\x3e\n <div\n v-if="!dropdownOnly"\n class="eg-select-trigger"\n :class="{ \'eg-select-trigger--disabled\': disabled, \'eg-select-trigger--open\': open }"\n @click.stop="toggle"\n >\n <span class="eg-select-trigger-label" :class="{ \'eg-select-placeholder\': selectedLabel == null }">\n {{ selectedLabel != null ? selectedLabel : placeholder }}\n </span>\n <span class="eg-select-caret">▾</span>\n </div>\n\n \x3c!-- Options panel in grid dropdown-only mode --\x3e\n <template v-if="dropdownOnly">\n <div v-if="enableSearch" class="eg-select-search" @mousedown.stop>\n <input\n ref="searchInput"\n class="eg-select-search-input"\n :value="searchQuery"\n placeholder="Qidirish..."\n autocomplete="off"\n autocorrect="off"\n autocapitalize="off"\n spellcheck="false"\n @input="onSearch"\n @keydown.stop="onSearchKey"\n />\n </div>\n <div\n v-for="(opt, idx) in filteredOptions"\n :key="opt.id != null ? opt.id : String(opt.value)"\n class="eg-select-option"\n :class="{\n \'eg-select-option--active\': String(opt.value) === String(value),\n \'eg-select-option--highlighted\': idx === highlight\n }"\n :style="!searchQuery ? { paddingLeft: (12 + opt.depth * 16) + \'px\' } : null"\n @mousedown.prevent="pickOption(opt)"\n @mouseover="highlight = idx"\n >\n <span\n v-if="!searchQuery"\n class="eg-tree-toggle"\n :class="{ \'eg-tree-toggle--leaf\': !opt.hasChildren }"\n @mousedown.prevent.stop="toggleNode(idx)"\n >{{ opt.hasChildren ? (opt.expanded ? \'▾\' : \'▸\') : \'\' }}</span>\n <span v-if="opt.color" class="eg-select-dot" :style="{ background: opt.color }"></span>\n {{ opt.label }}\n </div>\n </template>\n\n \x3c!-- Options panel in standalone mode --\x3e\n <div\n v-else-if="open"\n class="eg-select-dropdown eg-select-dropdown--floating"\n @mousedown.stop.prevent\n >\n <div v-if="enableSearch" class="eg-select-search" @mousedown.stop>\n <input\n ref="searchInput"\n class="eg-select-search-input"\n :value="searchQuery"\n placeholder="Qidirish..."\n autocomplete="off"\n autocorrect="off"\n autocapitalize="off"\n spellcheck="false"\n @input="onSearch"\n @keydown.stop="onSearchKey"\n />\n </div>\n <div\n v-for="(opt, idx) in filteredOptions"\n :key="opt.id != null ? opt.id : String(opt.value)"\n class="eg-select-option"\n :class="{\n \'eg-select-option--active\': String(opt.value) === String(value),\n \'eg-select-option--highlighted\': idx === highlight\n }"\n :style="!searchQuery ? { paddingLeft: (12 + opt.depth * 16) + \'px\' } : null"\n @mousedown.prevent="pickOption(opt)"\n @mouseover="highlight = idx"\n >\n <span\n v-if="!searchQuery"\n class="eg-tree-toggle"\n :class="{ \'eg-tree-toggle--leaf\': !opt.hasChildren }"\n @mousedown.prevent.stop="toggleNode(idx)"\n >{{ opt.hasChildren ? (opt.expanded ? \'▾\' : \'▸\') : \'\' }}</span>\n <span v-if="opt.color" class="eg-select-dot" :style="{ background: opt.color }"></span>\n {{ opt.label }}\n </div>\n </div>\n </div>\n</template>\n\n<script>\nexport default {\n name: \'EgTreeSelect\',\n model: { prop: \'value\', event: \'input\' },\n props: {\n value: { default: null },\n options: { type: Array, default: () => [] },\n valueKey: { type: String, default: \'value\' },\n labelKey: { type: String, default: \'label\' },\n childrenKey: { type: String, default: \'children\' },\n enableSearch: { type: Boolean, default: false },\n placeholder: { type: String, default: \'\' },\n disabled: { type: Boolean, default: false },\n dropdownOnly: { type: Boolean, default: false },\n triggerRect: { type: Object, default: null }\n },\n data() {\n return {\n open: false,\n searchQuery: \'\',\n highlight: -1,\n expandedMap: {}\n }\n },\n computed: {\n keys() {\n return { valueKey: this.valueKey, labelKey: this.labelKey, childrenKey: this.childrenKey }\n },\n flatOptions() {\n return this._treeFlatten(this.options || [], this.keys, this.expandedMap, 0, [])\n },\n allFlatOptions() {\n if (!this.enableSearch) return null\n return this._treeFlattenAll(this.options || [], this.keys)\n },\n filteredOptions() {\n if (!this.enableSearch || !this.searchQuery.trim()) return this.flatOptions\n const q = this.searchQuery.trim().toLowerCase()\n const pool = this.allFlatOptions || this.flatOptions\n return pool.filter(o => String(o.label || \'\').toLowerCase().includes(q))\n },\n selectedLabel() {\n const v = String(this.value == null ? \'\' : this.value)\n if (!v) return null\n const found = this._treeFindLabel(this.options || [], this.keys, v)\n return found != null ? found : null\n },\n overlayStyle() {\n if (!this.dropdownOnly || !this.triggerRect) return { position: \'fixed\', zIndex: 9999 }\n const rect = this.triggerRect\n const searchH = this.enableSearch ? 42 : 0\n const approxH = Math.min(this.filteredOptions.length * 28 + 8 + searchH, 320)\n const spaceBelow = window.innerHeight - rect.bottom\n const top = spaceBelow >= approxH ? rect.bottom : rect.top - approxH\n return {\n position: \'fixed\',\n top: top + \'px\',\n left: rect.left + \'px\',\n minWidth: Math.max(rect.width, 120) + \'px\',\n maxWidth: Math.min(360, window.innerWidth - rect.left - 16) + \'px\',\n zIndex: 9999\n }\n }\n },\n watch: {\n open(val) {\n if (val && this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n }\n },\n mounted() {\n if (this.dropdownOnly) {\n this._initExpanded()\n if (this.enableSearch) {\n this.$nextTick(() => { this.$refs.searchInput && this.$refs.searchInput.focus() })\n }\n } else {\n document.addEventListener(\'click\', this._onDocClick)\n }\n },\n beforeDestroy() {\n if (!this.dropdownOnly) {\n document.removeEventListener(\'click\', this._onDocClick)\n }\n },\n methods: {\n _initExpanded() {\n const v = String(this.value == null ? \'\' : this.value)\n const path = this._treeFindPath(this.options || [], this.keys, v, []) || []\n const expandedMap = {}\n path.forEach(id => { expandedMap[id] = true })\n this.expandedMap = expandedMap\n const flat = this.flatOptions\n this.highlight = flat.findIndex(o => String(o.value) === v)\n },\n toggle() {\n if (this.disabled) return\n this.open = !this.open\n if (this.open) {\n this.searchQuery = \'\'\n this._initExpanded()\n }\n },\n pickOption(opt) {\n this.$emit(\'input\', opt.value)\n this.$emit(\'select\', { value: opt.value, label: opt.label })\n if (!this.dropdownOnly) {\n this.open = false\n this.searchQuery = \'\'\n this.highlight = -1\n }\n },\n toggleNode(idx) {\n const opt = this.flatOptions[idx]\n if (!opt || !opt.hasChildren) return\n const next = { ...this.expandedMap }\n if (next[opt.id]) delete next[opt.id]\n else next[opt.id] = true\n this.expandedMap = next\n const newFlat = this.flatOptions\n const newIdx = newFlat.findIndex(o => o.id === opt.id)\n this.highlight = newIdx >= 0 ? newIdx : this.highlight\n },\n onSearch(e) {\n this.searchQuery = e.target.value\n this.highlight = 0\n },\n onSearchKey(e) {\n const key = e.key\n if (key === \'Escape\') {\n e.preventDefault()\n if (this.dropdownOnly) this.$emit(\'close\')\n else { this.open = false; this.searchQuery = \'\' }\n } else if (key === \'ArrowDown\') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight || 0) + 1, this.filteredOptions.length - 1)\n } else if (key === \'ArrowUp\') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n } else if (key === \'Enter\') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n }\n },\n handleKey(e) {\n const key = e.key\n if (key === \'Escape\') {\n e.preventDefault()\n this.$emit(\'close\')\n } else if (key === \'Enter\' || key === \' \') {\n e.preventDefault()\n const opt = this.filteredOptions[this.highlight]\n if (opt) this.pickOption(opt)\n else this.$emit(\'close\')\n } else if (key === \'ArrowDown\') {\n e.preventDefault()\n this.highlight = Math.min((this.highlight || 0) + 1, this.filteredOptions.length - 1)\n } else if (key === \'ArrowUp\') {\n e.preventDefault()\n this.highlight = Math.max((this.highlight || 0) - 1, 0)\n } else if (!this.searchQuery && (key === \'ArrowRight\' || key === \'ArrowLeft\')) {\n e.preventDefault()\n const opt = this.flatOptions[this.highlight]\n if (opt && opt.hasChildren) {\n if (key === \'ArrowRight\' && !opt.expanded) this.toggleNode(this.highlight)\n if (key === \'ArrowLeft\' && opt.expanded) this.toggleNode(this.highlight)\n }\n }\n },\n onRootMouseDown(e) {\n if (this.dropdownOnly) e.preventDefault()\n },\n _onDocClick(e) {\n if (!this.open) return\n if (this.$el && this.$el.contains(e.target)) return\n this.open = false\n },\n _treeFlatten(nodes, keys, expandedMap, depth, pathArr) {\n let out = []\n ;(nodes || []).forEach((node, idx) => {\n const path = pathArr.concat(idx)\n const id = path.join(\'-\')\n const children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : []\n const hasChildren = children.length > 0\n const expanded = hasChildren && !!expandedMap[id]\n out.push({ value: node[keys.valueKey], label: node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey]), color: node.color, id, depth, hasChildren, expanded })\n if (expanded) out = out.concat(this._treeFlatten(children, keys, expandedMap, depth + 1, path))\n })\n return out\n },\n _treeFlattenAll(nodes, keys) {\n const result = []\n const walk = (list) => {\n ;(list || []).forEach(node => {\n const children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : []\n result.push({ value: node[keys.valueKey], label: node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey]), color: node.color, id: String(node[keys.valueKey]), depth: 0, hasChildren: false, expanded: false })\n if (children.length) walk(children)\n })\n }\n walk(nodes)\n return result\n },\n _treeFindLabel(nodes, keys, value) {\n for (const node of (nodes || [])) {\n if (String(node[keys.valueKey]) === value) {\n return node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey])\n }\n const found = this._treeFindLabel(node[keys.childrenKey], keys, value)\n if (found != null) return found\n }\n return null\n },\n _treeFindPath(nodes, keys, value, pathArr) {\n for (let i = 0; i < (nodes || []).length; i++) {\n const node = nodes[i]\n const path = pathArr.concat(i)\n if (String(node[keys.valueKey]) === value) return [path.join(\'-\')]\n const children = node[keys.childrenKey]\n if (Array.isArray(children)) {\n const sub = this._treeFindPath(children, keys, value, path)\n if (sub) return [path.join(\'-\')].concat(sub)\n }\n }\n return null\n }\n }\n}\n<\/script>\n\n<style>\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: #888;\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf {\n visibility: hidden;\n}\n</style>\n']},media:void 0})},K,void 0,false,void 0,!1,L,void 0,void 0),N={small:{row:22,header:24,footer:24},middle:{row:28,header:28,footer:28},large:{row:36,header:36,footer:36}},Y=50,q=20;const Z={name:"ExcelGrid",components:{EgCheckbox:T,EgSelect:G,EgTreeSelect:U},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},size:{type:String,default:"middle"},transitionEnabled:{type:Boolean,default:!1},autoWidth:{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},_isScrolling:!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:[],columnTotals:{}}),computed:{t(){return n=this.locale,z[n]||z.en;var n},pinnedLeftSet(){const n=new Set;return this.leafCols.forEach(e=>{"left"===this.pinnedColumns[e.key]&&n.add(e._flatIndex)}),n},pinnedRightSet(){const n=new Set;return this.leafCols.forEach(e=>{"right"===this.pinnedColumns[e.key]&&n.add(e._flatIndex)}),n},pinnedLeftCols(){return this.leafCols.filter(n=>this.pinnedLeftSet.has(n._flatIndex))},pinnedRightCols(){return this.leafCols.filter(n=>this.pinnedRightSet.has(n._flatIndex))},hasPinnedLeft(){return this.pinnedLeftSet.size>0},hasPinnedRight(){return this.pinnedRightSet.size>0},hasFooter(){return this.leafCols.some(n=>n.showTotal)},pinnedLeftWidth(){let n=0;for(const e of this.pinnedLeftCols)this.isColHidden(e._flatIndex)||(n+=this.getColW(e._flatIndex));return n},pinnedRightWidth(){let n=0;for(const e of this.pinnedRightCols)this.isColHidden(e._flatIndex)||(n+=this.getColW(e._flatIndex));return n},totalRows(){return this.data.length},_rowTransitionName(){return this.transitionEnabled&&!this._isScrolling?"eg-row":""},_sizeConf(){return N[this.size]||N.middle},_rowH(){return this._sizeConf.row},_headerRowH(){return this._sizeConf.header},_footerRowH(){return this._sizeConf.footer},headerRowHeight(){return this._headerRowH},footerRowHeight(){return this._footerRowH},cumulativeHeights(){return function(n,e,t={},i=new Set){const o=new Array(n+1);o[0]=0;for(let l=0;l<n;l++){const n=i.has(l)?0:void 0!==t[l]?t[l]:e;o[l+1]=o[l]+n}return o}(this.totalRows,this._rowH,this.rowHeights,this.hiddenRowSet)},hiddenColSet(){return new Set(this.hiddenColumns)},hiddenRowSet(){const n=new Set(this.hiddenRows);return function(n,e){const t=new Set;for(const i of n)if("row"===i.type&&k(i,e))for(let n=i.startIndex;n<=i.endIndex;n++)t.add(n);return t}(this.internalGroups,this.internalCollapsedGroups).forEach(e=>n.add(e)),n},hiddenColFromGroupSet(){return function(n,e){const t=new Set;for(const i of n)if("column"===i.type&&k(i,e))for(let n=i.startIndex;n<=i.endIndex;n++)t.add(n);return t}(this.internalGroups,this.internalCollapsedGroups)},effectiveHiddenColSet(){const n=new Set(this.hiddenColSet);return this.hiddenColFromGroupSet.forEach(e=>n.add(e)),n},rowGroups(){return this.internalGroups.filter(n=>"row"===n.type)},colGroups(){return this.internalGroups.filter(n=>"column"===n.type)},visibleRowRange(){const n=[];for(let e=this.virtualStartIndex;e<=this.virtualEndIndex;e++)n.push(e);return n},primaryRange(){return function(n,e){if(!n||0===n.length)return null;return o(n.find(n=>l(e.row,e.col,n))||n[0])}(this.selectionRanges,this.activeCell)},fillHandleVisible(){return this.primaryRange&&!this.editingCell},fillHandleStyle(){if(!this.primaryRange)return{};const n=o(this.primaryRange),e=this.cumulativeHeights[n.endRow+1]||0,t=this.getColLeft(n.endCol+1);return{position:"absolute",width:"6px",height:"6px",background:"#1a73e8",border:"1px solid white",bottom:this.totalVirtualHeight-e+1+"px",right:this.scrollContentWidth-t+1+"px",cursor:"crosshair",zIndex:10}},invalidTooltipStyle(){if(!this.invalidCell||!this.invalidCell.rect)return{};const{rect:n}=this.invalidCell;return{position:"fixed",top:n.bottom+4+"px",left:n.left+"px",zIndex:9999}},dragGhostStyle(){if(!this.dragMove.active||this.dragMove.targetRow<0)return{};const n=o(this.dragMove.sourceRange),e=n.endRow-n.startRow,t=n.endCol-n.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+t)+"px",height:this.getRangeHeight(this.dragMove.targetRow,this.dragMove.targetRow+e)+"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 n=this.rowGroups.length?q:0;return{width:Y+n+"px",minWidth:Y+n+"px",height:this._headerRowH*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 n=this.getFrozenWidth();return{width:n+"px",minWidth:n+"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 n=0;for(let e=0;e<this.leafCols.length;e++)this.isColHidden(e)||this.pinnedRightSet.has(e)||(n+=this.getColW(e));return this.hasPinnedRight&&(n+=this.pinnedRightWidth),n},scrollableContentWidth(){let n=0;for(let e=0;e<this.leafCols.length;e++)!this.isColHidden(e)&&this.isColumnScrollable(e)&&(n+=this.getColW(e));return n},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"}),footerAreaStyle:()=>({display:"flex",flexDirection:"row",flexShrink:0,position:"sticky",bottom:0,zIndex:30,background:"#f8f9fa",boxShadow:"0 -2px 0 0 #d0d0d0"}),footerLabelStyle(){const n=this.rowGroups.length?q:0;return{width:Y+n+"px",minWidth:Y+n+"px",height:this._footerRowH+"px",display:"flex",alignItems:"center",justifyContent:"center",background:"#f0f0f0",borderRight:"1px solid #ccc",borderTop:"1px solid #ccc",boxSizing:"border-box",flexShrink:0,fontSize:"12px",fontWeight:600}},frozenColFooterStyle(){const n=this.getFrozenWidth();return{width:n+"px",minWidth:n+"px",height:this._footerRowH+"px",overflow:"hidden",flexShrink:0}},pinnedRightFooterStyle(){return{width:this.pinnedRightWidth+"px",minWidth:this.pinnedRightWidth+"px",height:this._footerRowH+"px",overflow:"hidden",flexShrink:0,borderLeft:"1px solid #ccc"}},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:{size(){this.rowHeights={},this.$nextTick(()=>this.updateVirtualScroll())},autoWidth(n){n&&this._applyAutoWidth()},columns:{immediate:!0,handler(n){this.leafCols=r(n),this.headerDepth=a(n),this.headerRows=c(n,this.headerDepth),this.leafCols.forEach(n=>{void 0===this.colWidths[n._flatIndex]&&(this.colWidths[n._flatIndex]=n.width||100)}),this.computeColumnTotals(),this.updateVirtualScroll(),this._applyAutoWidth()}},merges:{immediate:!0,handler(n){this.internalMerges=n?[...n]:[]}},collapsedGroups:{immediate:!0,handler(n){this.internalCollapsedGroups=n?[...n]:[]}},data:{immediate:!0,handler(n){this._dataRaw=n||[],this.updateVirtualScroll(),this.computeColumnTotals(),this.$forceUpdate(),this._applyAutoWidth()}},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),window.addEventListener("scroll",this._onWindowScrollForDropdown,!0)},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),window.removeEventListener("scroll",this._onWindowScrollForDropdown,!0),clearTimeout(this._scrollEndTimer)},methods:{updateVirtualScroll(){const n=this.hasFooter?this._footerRowH:0,e=this.height-this._headerRowH*this.headerDepth-n,t=function(n,e,t,i,o=5){if(0===t)return{startIndex:0,endIndex:-1,offsetTop:0,totalHeight:0};const l=i[t],s=n+e;let r=0,a=t-1;for(;r<a;){const e=r+a>>1;i[e+1]<=n?r=e+1:a=e}let c=r,d=c;for(;d<t-1&&i[d+1]<s;)d++;return c=Math.max(0,c-o),d=Math.min(t-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(n){const e=n.target;this.$refs.scrollColHeader&&(this.$refs.scrollColHeader.scrollLeft=e.scrollLeft),this.$refs.scrollFooter&&(this.$refs.scrollFooter.scrollLeft=e.scrollLeft),this.$refs.scrollRowHeaders&&(this.$refs.scrollRowHeaders.scrollTop=e.scrollTop),this.selectDropdown&&this.closeSelectDropdown(),this.transitionEnabled&&(this._isScrolling=!0,clearTimeout(this._scrollEndTimer),this._scrollEndTimer=setTimeout(()=>{this._isScrolling=!1},150)),this._scrollRAF||(this._scrollRAF=requestAnimationFrame(()=>{this._scrollRAF=null,this.scrollTop=e.scrollTop,this.scrollLeft=e.scrollLeft,this.updateVirtualScroll()}))},onScroll(n){},syncHeaderViewportWidth(){const n=this.$refs.scrollBody;n&&this.headerViewportWidth!==n.clientWidth&&(this.headerViewportWidth=n.clientWidth)},isRowHidden(n){return this.hiddenRowSet.has(n)},isColHidden(n){return this.effectiveHiddenColSet.has(n)},getColW(n){return void 0!==this.colWidths[n]?this.colWidths[n]:this.leafCols[n]&&this.leafCols[n].width||100},getRowH(n){return this.isRowHidden(n)?0:this.rowHeights[n]||this._rowH},getColLeft(n){let e=0;for(let t=0;t<n&&t<this.leafCols.length;t++)this.isColHidden(t)||(e+=this.getColW(t));return e},getFrozenWidth(){return this.hasPinnedLeft?this.pinnedLeftWidth:0},isColumnPinnedLeft(n){return this.pinnedLeftSet.has(n)},isColumnPinnedRight(n){return this.pinnedRightSet.has(n)},isColumnScrollable(n){return!this.isColumnPinnedLeft(n)&&!this.isColumnPinnedRight(n)},getZoneColLeft(n,e){let t=0;if("left"===e)for(const e of this.pinnedLeftCols){if(e._flatIndex===n)break;this.isColHidden(e._flatIndex)||(t+=this.getColW(e._flatIndex))}else if("right"===e)for(const e of this.pinnedRightCols){if(e._flatIndex===n)break;this.isColHidden(e._flatIndex)||(t+=this.getColW(e._flatIndex))}else for(let e=0;e<this.leafCols.length;e++){const i=this.leafCols[e];if(i._flatIndex===n)break;!this.isColHidden(i._flatIndex)&&this.isColumnScrollable(i._flatIndex)&&(t+=this.getColW(i._flatIndex))}return t},isCellInZone(n,e,t){if(n.isGroup){const e=n._firstLeafFlatIndex,i=e+n.colspan-1;for(let n=e;n<=i;n++){if(this.isColHidden(n))continue;if((this.isColumnPinnedLeft(n)?"left":this.isColumnPinnedRight(n)?"right":"scroll")===t)return!0}return!1}const i=n.column._flatIndex;if(this.isColHidden(i))return!1;return(this.isColumnPinnedLeft(i)?"left":this.isColumnPinnedRight(i)?"right":"scroll")===t},getHeaderCellStyleForZone(n,e,t){let i=n._firstLeafFlatIndex;if(n.isGroup){const e=i+n.colspan-1;for(let n=i;n<=e;n++){if(this.isColHidden(n))continue;if((this.isColumnPinnedLeft(n)?"left":this.isColumnPinnedRight(n)?"right":"scroll")===t){i=n;break}}}const o=this.getZoneColLeft(i,t);let l=0;if(n.isGroup){const e=n._firstLeafFlatIndex,i=e+n.colspan-1;for(let n=e;n<=i;n++){if(this.isColHidden(n))continue;(this.isColumnPinnedLeft(n)?"left":this.isColumnPinnedRight(n)?"right":"scroll")===t&&(l+=this.getColW(n))}}else l=this.getColW(n.column._flatIndex);return{position:"absolute",top:this._headerRowH*e+"px",left:o+"px",width:l+"px",height:this._headerRowH*n.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(n,e){let t=0;for(let i=n;i<=e&&i<this.leafCols.length;i++)this.isColHidden(i)||(t+=this.getColW(i));return t},getRangeHeight(n,e){let t=0;for(let i=n;i<=e&&i<this.totalRows;i++)this.isRowHidden(i)||(t+=this.getRowH(i));return t},getCellValue(n,e){const t=this.leafCols[e];if(!t)return"";const i=this._dataRaw[n];if(!i)return"";const o=i[t.key];return void 0!==o?o:""},isEditing(n,e){return this.editingCell&&this.editingCell.row===n&&this.editingCell.col===e},isMergedAway(n,e){const t=y(n,e,this.internalMerges);if(!t)return!1;const i=o(t);return!(i.startRow===n&&i.startCol===e)},getMergeSpan(n,e){const t=y(n,e,this.internalMerges);if(!t)return{rowspan:1,colspan:1};const i=o(t);return{rowspan:i.endRow-i.startRow+1,colspan:i.endCol-i.startCol+1}},isSelected(n,e){return t=n,i=e,this.selectionRanges.some(n=>l(t,i,n));var t,i},isActiveCell(n,e){return this.activeCell.row===n&&this.activeCell.col===e},isCopyHighlighted(n,e){return!(!this.clipboardRange||"copy"!==this.clipboardMode)&&l(n,e,this.clipboardRange)},getRowStyle(n){const e=this.getRowH(n),t=e;if(this._rowStyleCache[t])return this._rowStyleCache[t];const i={height:e+"px",minHeight:e+"px"};return this._rowStyleCache[t]=i,i},getRowHeaderStyle(n){const e=this.getRowH(n),t=e;if(this._rowHeaderStyleCache[t])return this._rowHeaderStyleCache[t];const i={height:e+"px"};return this._rowHeaderStyleCache[t]=i,i},getCellStyle(n,e){const t=y(n,e,this.internalMerges),i=t?o(t):null;let l=this.getColW(e),s=this.getRowH(n);i&&i.startRow===n&&i.startCol===e&&(l=this.getRangeWidth(i.startCol,i.endCol),s=this.getRangeHeight(i.startRow,i.endRow));const r=this.isColumnPinnedLeft(e)?"left":this.isColumnPinnedRight(e)?"right":"scroll",a=this.getZoneColLeft(e,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(n,e){const t=this.leafCols[e];return{"eg-cell-active":this.isActiveCell(n,e),"eg-cell-selected":this.isSelected(n,e)&&!this.isActiveCell(n,e),"eg-cell-fill-preview":this.isInFillPreview(n,e),"eg-cell-copy":this.isCopyHighlighted(n,e),"eg-cell-merged-hidden":this.isMergedAway(n,e),"eg-cell-select":!(!t||!this.isSelectLikeCol(t)),"eg-cell-checkbox":!(!t||"checkbox"!==t.type),"eg-cell-readonly":!this.isCellEditable(n,e),"eg-cell-invalid":!(!this.invalidCell||this.invalidCell.row!==n||this.invalidCell.col!==e),"eg-cell--has-suffix":!(!t||!this.hasSuffixSlot(t))}},isInFillPreview(n,e){if(!this.fillDrag.active||!this.fillDrag.sourceRange)return!1;const t=this.fillDrag.sourceRange,i=this.fillDrag.currentRow,o=this.fillDrag.currentCol;return i>t.endRow?n>t.endRow&&n<=i&&e>=t.startCol&&e<=t.endCol:o>t.endCol&&(e>t.endCol&&e<=o&&n>=t.startRow&&n<=t.endRow)},getHeaderCellStyle(n,e,t){const i=t?"left":"scroll";let o=n._firstLeafFlatIndex;if(n.isGroup){const e=o+n.colspan-1;for(let n=o;n<=e;n++)if(!this.isColHidden(n)&&(t?this.isColumnPinnedLeft(n):this.isColumnScrollable(n))){o=n;break}}const l=this.getZoneColLeft(o,i);let s=0;if(n.isGroup){const e=n._firstLeafFlatIndex,i=e+n.colspan-1;for(let n=e;n<=i;n++){if(this.isColHidden(n))continue;(t?this.isColumnPinnedLeft(n):this.isColumnScrollable(n))&&(s+=this.getColW(n))}}else s=this.getColW(n.column._flatIndex);return{position:"absolute",top:this._headerRowH*e+"px",left:l+"px",width:s+"px",height:this._headerRowH*n.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"}},computeColumnTotals(){const n={},e=this._dataRaw||[];this.leafCols.forEach(t=>{if(!t.showTotal)return;let i=0,o=!1;for(let n=0;n<e.length;n++){const l=e[n],s=l?l[t.key]:void 0;"number"==typeof s&&isFinite(s)&&(i+=s,o=!0)}n[t._flatIndex]=o?Math.round(1e6*i)/1e6:null}),this.columnTotals=n},getColumnTotalDisplay(n){const e=this.leafCols[n];if(!e||!e.showTotal)return"";const t=this.columnTotals[n];return null==t?"-":t},getFooterCellStyle(n){const e=this.isColumnPinnedLeft(n)?"left":this.isColumnPinnedRight(n)?"right":"scroll",t=this.getZoneColLeft(n,e),i=this.getColW(n);return{position:"absolute",left:t+"px",top:"0px",width:i+"px",minWidth:i+"px",height:this._footerRowH+"px"}},isCellVisible(n,e,t){if(n.isGroup){const e=n._firstLeafFlatIndex,i=e+n.colspan-1;for(let n=e;n<=i;n++){if(this.isColHidden(n))continue;if(t?this.isColumnPinnedLeft(n):this.isColumnScrollable(n))return!0}return!1}const i=n.column._flatIndex;return!this.isColHidden(i)&&(t?this.isColumnPinnedLeft(i):this.isColumnScrollable(i))},getRowGroupBtnStyle(n){return{position:"absolute",top:h(n.startIndex,this._rowH,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(n){return{position:"absolute",left:this.getColLeft(n.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(n,e,t=!1){n<0||n>=this.totalRows||e<0||e>=this.leafCols.length||(this.activeCell={row:n,col:e},t?this.selectionRanges.push(s(n,e)):this.selectionRanges=[s(n,e)],this.emitSelectionChange())},selectRange(n,e,t,i,o=!0){const l={startRow:n,startCol:e,endRow:t,endCol:i};o?this.selectionRanges=[l]:this.selectionRanges.push(l),this.emitSelectionChange()},selectRow(n){this.activeCell={row:n,col:0},this.selectionRanges=[{startRow:n,endRow:n,startCol:0,endCol:this.leafCols.length-1}],this.emitSelectionChange()},selectColumn(n){this.activeCell={row:0,col:n},this.selectionRanges=[{startRow:0,endRow:this.totalRows-1,startCol:n,endCol:n}],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(n=>o(n))})},onCellMouseDown(n,e,t){if(this.invalidCell=null,this.editingCell){const n=this.leafCols[this.editingCell.col];n&&this._hasCustomEdit(n)?this.cancelEdit():this.commitEdit()}if(this.hideContextMenu(),this.$refs.wrapper&&this.$refs.wrapper.focus(),n.ctrlKey||n.metaKey)this.selectDropdown&&this.closeSelectDropdown(),this.activeCell={row:e,col:t},this.mouseDownRange={startRow:e,startCol:t,endRow:e,endCol:t},this.selectionRanges.push(this.mouseDownRange),this.isCtrlDrag=!0;else if(n.shiftKey&&this.selectionRanges.length>0)this.selectDropdown&&this.closeSelectDropdown(),this.selectionRanges=[(i=this.activeCell,o={row:e,col:t},{startRow:i.row,startCol:i.col,endRow:o.row,endCol:o.col})];else{const i=this.activeCell.row===e&&this.activeCell.col===t;this.activeCell={row:e,col:t},this.mouseDownRange={startRow:e,startCol:t,endRow:e,endCol:t},this.selectionRanges=[this.mouseDownRange],this.isCtrlDrag=!1;const o=this.leafCols[t];if(i&&o&&this.isSelectLikeCol(o)&&!this._hasCustomEdit(o)&&this.isCellEditable(e,t))return this.openSelectDropdown(e,t,n),n.preventDefault(),this.isMouseDown=!0,void this.emitSelectionChange();this.selectDropdown&&this.closeSelectDropdown()}var i,o;this.isMouseDown=!0,this.emitSelectionChange(),n.preventDefault()},onCellMouseOver(n,e,t){if(this.isMouseDown&&!this.fillDrag.active)return this.dragMove.active?(this.dragMove.targetRow=e,void(this.dragMove.targetCol=t)):void(this.mouseDownRange&&(this.mouseDownRange.endRow=e,this.mouseDownRange.endCol=t,this.emitSelectionChange()))},onCellMouseUp(){this.isMouseDown=!1},onCellDblClick(n,e){const t=this.leafCols[e];this.isCellEditable(n,e)&&(t&&this.isSelectLikeCol(t)&&!this._hasCustomEdit(t)?this.openSelectDropdownForActive():t&&"checkbox"===t.type||this.startEditing(n,e,null,!0))},onRowHeaderMouseDown(n,e){const t=this.leafCols.length-1,i={startRow:e,endRow:e,startCol:0,endCol:t};if(n.ctrlKey||n.metaKey)if(n.shiftKey&&this.selectionRanges.length>0){const n=Math.min(this.activeCell.row,e),i=Math.max(this.activeCell.row,e);this.selectionRanges=[...this.selectionRanges,{startRow:n,endRow:i,startCol:0,endCol:t}]}else{const n=this.selectionRanges.findIndex(n=>n.startRow===e&&n.endRow===e&&0===n.startCol&&n.endCol===t);if(n>=0){const e=this.selectionRanges.filter((e,t)=>t!==n);this.selectionRanges=e.length?e:[i]}else this.activeCell={row:e,col:0},this.selectionRanges=[...this.selectionRanges,i]}else if(n.shiftKey&&this.selectionRanges.length>0){const n=Math.min(this.activeCell.row,e),i=Math.max(this.activeCell.row,e);this.selectionRanges=[{startRow:n,endRow:i,startCol:0,endCol:t}]}else this.activeCell={row:e,col:0},this.selectionRanges=[i];this.mouseDownRange=this.selectionRanges[this.selectionRanges.length-1],this.isMouseDown=!0,this.emitSelectionChange(),n.preventDefault()},onColHeaderMouseDown(n,e){if(e.isGroup)return;const t=e.column._flatIndex;if(n.shiftKey&&this.selectionRanges.length>0){const n=Math.min(this.activeCell.col,t),e=Math.max(this.activeCell.col,t);this.selectionRanges=[{startRow:0,endRow:this.totalRows-1,startCol:n,endCol:e}]}else this.activeCell={row:0,col:t},this.selectionRanges=[{startRow:0,endRow:this.totalRows-1,startCol:t,endCol:t}];this.isMouseDown=!0,this.mouseDownRange=this.selectionRanges[0],this.emitSelectionChange(),n.preventDefault()},onColHeaderDblClick(n,e){e.isGroup||this.autoFitColumn(e.column._flatIndex)},onColHeaderContextMenu(n,e){e.isGroup||(this.contextMenu={visible:!0,x:n.clientX,y:n.clientY,row:-1,col:e.column._flatIndex})},onRowContextMenu(n,e){this.contextMenu={visible:!0,x:n.clientX,y:n.clientY,row:e,col:-1}},onCellContextMenu(n,e,t){this.isSelected(e,t)||this.selectCell(e,t),this.contextMenu={visible:!0,x:n.clientX,y:n.clientY,row:e,col:t}},hideContextMenu(){this.contextMenu.visible=!1},onKeyDown(n){if(this.selectDropdown){const e=this.$refs.selectDropdownComp;return void(e&&e.handleKey&&e.handleKey(n))}if(this.editingCell)return;const e=n.key;if((n.ctrlKey||n.metaKey)&&"z"===e&&!n.shiftKey)return n.preventDefault(),void this.undo();if((n.ctrlKey||n.metaKey)&&("y"===e||"z"===e&&n.shiftKey))return n.preventDefault(),void this.redo();if("a"===e&&(n.ctrlKey||n.metaKey))return n.preventDefault(),void this.selectAll();if("c"===e&&(n.ctrlKey||n.metaKey))return n.preventDefault(),void this.copySelection();if("x"===e&&(n.ctrlKey||n.metaKey))return n.preventDefault(),void this.cutSelection();if("v"===e&&(n.ctrlKey||n.metaKey))return n.preventDefault(),void this.pasteAt(this.activeCell.row,this.activeCell.col);if("Delete"===e||"Backspace"===e)return n.preventDefault(),void this.clearSelection();if("F2"===e){n.preventDefault();const e=this.leafCols[this.activeCell.col];return void(e&&this.isSelectLikeCol(e)&&!this._hasCustomEdit(e)?this.openSelectDropdownForActive():e&&"checkbox"===e.type?this.toggleCheckbox(this.activeCell.row,this.activeCell.col):this.startEditing(this.activeCell.row,this.activeCell.col,null,!0))}if(" "===e){const e=this.leafCols[this.activeCell.col];if(e&&"checkbox"===e.type)return n.preventDefault(),void this.toggleCheckbox(this.activeCell.row,this.activeCell.col)}if("Escape"===e)return this.clipboardRange=null,this.clipboardMatrix=null,void(this.clipboardMode=null);if("Tab"===e)return n.preventDefault(),void this.moveActive(0,n.shiftKey?-1:1,n);if("Enter"===e)return n.preventDefault(),void this.moveActive(n.shiftKey?-1:1,0,n);const t={ArrowUp:[-1,0],ArrowDown:[1,0],ArrowLeft:[0,-1],ArrowRight:[0,1]};if(t[e]){n.preventDefault();const[i,l]=t[e];if(n.shiftKey){const n=this.primaryRange||s(this.activeCell.row,this.activeCell.col),e=o(n);-1===i&&(n.startRow=Math.max(0,e.startRow-1)),1===i&&(n.endRow=Math.min(this.totalRows-1,e.endRow+1)),-1===l&&(n.startCol=Math.max(0,e.startCol-1)),1===l&&(n.endCol=Math.min(this.leafCols.length-1,e.endCol+1)),this.selectionRanges=[n],this.emitSelectionChange()}else this.moveActive(i,l,n);return}if("Home"===e)return n.preventDefault(),void(n.ctrlKey?this.selectCell(0,0):this.selectCell(this.activeCell.row,0));if("End"===e)return n.preventDefault(),void(n.ctrlKey?this.selectCell(this.totalRows-1,this.leafCols.length-1):this.selectCell(this.activeCell.row,this.leafCols.length-1));if("PageUp"===e){n.preventDefault();const e=Math.floor((this.height-this._headerRowH*this.headerDepth)/this._rowH);return void this.moveActive(-e,0,n)}if("PageDown"===e){n.preventDefault();const e=Math.floor((this.height-this._headerRowH*this.headerDepth)/this._rowH);return void this.moveActive(e,0,n)}},onKeyPress(n){if(!this.editingCell&&!this.selectDropdown&&!(n.ctrlKey||n.metaKey||n.altKey)&&n.key&&1===n.key.length){const e=this.leafCols[this.activeCell.col];if(e&&this.isSelectLikeCol(e)&&!this._hasCustomEdit(e))return n.preventDefault(),void this.openSelectDropdownForActive();if(e&&"checkbox"===e.type)return void n.preventDefault();this.startEditing(this.activeCell.row,this.activeCell.col,n.key,!1)}},onGlobalKeyDown(n){},moveActive(n,e,t){let i=this.activeCell.row+n,l=this.activeCell.col+e;for(t&&"Tab"===t.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+=e>=0?1:-1;for(;this.isRowHidden(i)&&i>0&&i<this.totalRows-1;)i+=n>=0?1:-1;const r=function(n,e,t){const i=y(n,e,t);if(!i)return{row:n,col:e};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(n,e){const t=this.$refs.scrollBody;if(!t)return;const i=h(n,this._rowH,this.rowHeights),o=i+this.getRowH(n),l=this.getColLeft(e)-this.getFrozenWidth(),s=l+this.getColW(e);i<t.scrollTop&&(t.scrollTop=i),o>t.scrollTop+t.clientHeight&&(t.scrollTop=o-t.clientHeight),l<t.scrollLeft&&(t.scrollLeft=l),s>t.scrollLeft+t.clientWidth&&(t.scrollLeft=s-t.clientWidth)},startEditing(n,e,t=null,i=!1){const o=this.leafCols[e];if(o&&this.isCellEditable(n,e)&&!(n<0||n>=this.totalRows)){if(!this._hasCustomEdit(o)){if(this.isSelectLikeCol(o))return void this.openSelectDropdownForActive();if("checkbox"===o.type)return}this.invalidCell=null,this.editingCell={row:n,col:e},this.editValue=null!==t?t:i?String(this.getCellValue(n,e)||""):"",this.editOriginalValue=String(this.getCellValue(n,e)||""),this.$nextTick(()=>{const n=this.$refs.editInput;if(n){const e=Array.isArray(n)?n[0]:n;e&&(e.focus(),i&&e.setSelectionRange(e.value.length,e.value.length))}})}},commitEdit(){if(!this.editingCell)return;const{row:n,col:e}=this.editingCell,t=this.leafCols[e];if(!t)return void(this.editingCell=null);if(!this.isCellValueValid(n,e,this.editValue))return this._showInvalidCell(n,e,this.editValue),this.editingCell=null,void(this.$refs.wrapper&&this.$refs.wrapper.focus());this.invalidCell=null;const i=this.data,o=i.slice();o[n]={...i[n],[t.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()},hasCellSlot(n){return!!(n&&n.key&&this.$scopedSlots&&this.$scopedSlots["cell-"+n.key])},hasLegacyEditSlot(n){return!!(n&&n.key&&this.$scopedSlots&&this.$scopedSlots["edit-"+n.key])},hasPrefixSlot(n){return!!(n&&n.key&&this.$scopedSlots&&this.$scopedSlots["prefix-"+n.key])},hasSuffixSlot(n){return!!(n&&n.key&&this.$scopedSlots&&this.$scopedSlots["suffix-"+n.key])},_hasCustomEdit(n){return"select"===n.type||"treeSelect"===n.type?this.hasLegacyEditSlot(n):this.hasCellSlot(n)||this.hasLegacyEditSlot(n)},commitEditSlot(n){if(!this.editingCell)return;const{row:e,col:t}=this.editingCell,i=this.leafCols[t];if(!i)return void(this.editingCell=null);if(!this.isCellValueValid(e,t,n))return this._showInvalidCell(e,t,n),this.editingCell=null,void(this.$refs.wrapper&&this.$refs.wrapper.focus());this.invalidCell=null;const o=this.data,l=o.slice();l[e]={...o[e],[i.key]:n},this._emitDataChange(o,l),this.editingCell=null,this.$refs.wrapper&&this.$refs.wrapper.focus()},isCheckboxChecked(n,e){const t=this.getCellValue(n,e);return!0===t||1===t||"string"==typeof t&&["true","1"].includes(t.trim().toLowerCase())},setCheckboxValue(n,e,t){const i=this.leafCols[e];if(!i||!this.isCellEditable(n,e))return;if(!this.isCellValueValid(n,e,t))return void this._showInvalidCell(n,e,t);this.invalidCell=null;const o=this.data,l=o.slice();l[n]={...o[n],[i.key]:t},this._emitDataChange(o,l)},toggleCheckbox(n,e){this.setCheckboxValue(n,e,!this.isCheckboxChecked(n,e))},isCellEditable(n,e){const t=this.leafCols[e];if(!t)return!1;const i=this._dataRaw[n]||{};return"function"==typeof this.cellEditable?!!this.cellEditable(i,n,t,e):"function"==typeof t.editable?!!t.editable(i,n):!1!==t.editable},isCellValueValid(n,e,t){if("function"!=typeof this.cellValidator)return!0;const i=this.leafCols[e];if(!i)return!0;const o=this._dataRaw[n]||{};return!!this.cellValidator(t,o,n,i,e)},_resolveValidationMessage(n,e,t){const i=this.leafCols[e],o=this._dataRaw[n]||{};return"function"==typeof this.cellValidationMessage?this.cellValidationMessage(t,o,n,i,e):"string"==typeof this.cellValidationMessage&&this.cellValidationMessage?this.cellValidationMessage:this.t.invalidValue},_invalidCellRect(){const n=this.$refs.editInput,e=Array.isArray(n)?n[0]:n;if(e)return e.getBoundingClientRect();const t=this.$el?this.$el.querySelector(".eg-cell-active"):null;return t?t.getBoundingClientRect():null},_showInvalidCell(n,e,t){const i=this._resolveValidationMessage(n,e,t);this.invalidCell={row:n,col:e,message:i,rect:this._invalidCellRect()},this.$emit("cell-validation-error",{row:n,col:e,value:t,message:i})},_emitBulkValidationError(n,e){const t=n.map(n=>({row:n.row,col:n.col,value:n.value,message:this._resolveValidationMessage(n.row,n.col,n.value)}));this.$emit("bulk-validation-error",{blocked:e,cells:t})},isSelectLikeCol:n=>!(!n||"select"!==n.type&&"treeSelect"!==n.type),treeOptionKeys:n=>({valueKey:n.optionValueKey||"value",labelKey:n.optionLabelKey||"label",childrenKey:n.optionChildrenKey||"children"}),_treeFindLabel(n,e,t){for(const i of n||[]){if(String(i[e.valueKey])===t)return null!=i[e.labelKey]?i[e.labelKey]:String(i[e.valueKey]);const n=this._treeFindLabel(i[e.childrenKey],e,t);if(null!=n)return n}return null},colOptions(n){const e=this.leafCols[n];return e&&Array.isArray(e.options)?e.options.map(n=>"string"==typeof n?{value:n,label:n}:{value:n.value,label:null!=n.label?n.label:String(n.value),color:n.color}):[]},getSelectLabel(n,e){const t=this.leafCols[n],i=null==e?"":String(e);if(t&&"treeSelect"===t.type){if(!Array.isArray(t.options))return i;const n=this._treeFindLabel(t.options,this.treeOptionKeys(t),i);return null!=n?n:i}const o=this.colOptions(n);if(!o.length)return i;const l=o.find(n=>String(n.value)===i);return l?l.label:i},_buildSelectDropdownState(n,e,t,i,o){const l={top:o.top,bottom:o.bottom,left:o.left,width:o.width};return{row:n,col:e,tree:"treeSelect"===t.type,keys:"treeSelect"===t.type?this.treeOptionKeys(t):null,rawOptions:t.options||[],currentValue:i,rect:l,enableSearch:!!t.enableSearch}},openSelectDropdown(n,e,t){const i=this.leafCols[e];if(!i||!Array.isArray(i.options))return;const o=t.currentTarget.getBoundingClientRect(),l=String(null!=this.getCellValue(n,e)?this.getCellValue(n,e):"");this.selectDropdown=this._buildSelectDropdownState(n,e,i,l,o)},openSelectDropdownForActive(){const{row:n,col:e}=this.activeCell,t=this.leafCols[e];t&&Array.isArray(t.options)&&this.$nextTick(()=>{const i=this.$el?this.$el.querySelector(".eg-cell-active"):null;if(!i)return;const o=i.getBoundingClientRect(),l=String(null!=this.getCellValue(n,e)?this.getCellValue(n,e):"");this.selectDropdown=this._buildSelectDropdownState(n,e,t,l,o)})},onComponentSelect({value:n,label:e}){if(!this.selectDropdown)return;const{row:t,col:i}=this.selectDropdown,o=this.leafCols[i];if(o){if(!this.isCellValueValid(t,i,n))return this._showInvalidCell(t,i,n),this.closeSelectDropdown(),void(this.$refs.wrapper&&this.$refs.wrapper.focus());this.invalidCell=null;const l=this.data,s=l.slice();s[t]={...l[t],[o.key]:n},this._emitDataChange(l,s),"function"==typeof o.onSelect&&o.onSelect({value:n,label:e},s[t],t,o)}this.closeSelectDropdown(),this.$refs.wrapper&&this.$refs.wrapper.focus()},closeSelectDropdown(){this.selectDropdown=null},_onWindowScrollForDropdown(n){if(!this.selectDropdown)return;const e=this.$refs.selectDropdownComp,t=e&&e.$el?e.$el:null;t&&t.contains(n.target)||this.closeSelectDropdown()},_onDocMouseDownForDropdown(n){if(!this.selectDropdown)return;const e=this.$refs.selectDropdownComp,t=e&&e.$el?e.$el:null;t&&t.contains(n.target)||this.closeSelectDropdown()},onEditKeyDown(n){"Escape"===n.key?(n.preventDefault(),this.cancelEdit()):"Enter"!==n.key||n.shiftKey?"Tab"===n.key&&(n.preventDefault(),this.commitEdit(),this.moveActive(0,n.shiftKey?-1:1,n)):(n.preventDefault(),this.commitEdit(),this.moveActive(1,0,n))},clearSelection(){if(!this.selectionRanges.length)return;const n=this.data,e=n.slice();let t=!1;const i=[];for(const n of this.selectionRanges){const o={startRow:Math.min(n.startRow,n.endRow),endRow:Math.max(n.startRow,n.endRow),startCol:Math.min(n.startCol,n.endCol),endCol:Math.max(n.startCol,n.endCol)};for(let n=o.startRow;n<=o.endRow&&n<e.length;n++){let l=null;for(let s=o.startCol;s<=o.endCol;s++){const o=this.leafCols[s];if(o&&this.isCellEditable(n,s)){if(!this.isCellValueValid(n,s,"")){i.push({row:n,col:s,value:""});continue}l||(l={...e[n]}),l[o.key]="",t=!0}}l&&(e[n]=l)}}this.strictBulkValidation&&i.length>0?this._emitBulkValidationError(i,!0):(t&&this._emitDataChange(n,e),i.length>0&&this._emitBulkValidationError(i,!1))},startFillDrag(n){const e=this.primaryRange;e&&(this.fillDrag.active=!0,this.fillDrag.sourceRange=o(e),this.fillDrag.currentRow=e.endRow,this.fillDrag.currentCol=e.endCol,n.preventDefault())},onGlobalMouseMove(n){this.resizing.active?this.handleResizeMove(n):this.fillDrag.active?this.handleFillDragMove(n):this.dragMove.active&&this.handleDragMoveMove(n)},onGlobalMouseUp(n){this.resizing.active?this.commitResize():this.fillDrag.active?this.commitFillDrag():this.dragMove.active?this.commitDragMove(n):this.isMouseDown=!1},handleFillDragMove(n){const e=this.$refs.scrollBody;if(!e)return;const t=e.getBoundingClientRect(),i=n.clientY-t.top+e.scrollTop,o=n.clientX-t.left+e.scrollLeft,l=this.cumulativeHeights,s=this.totalRows;let r=0,a=s-1;for(;r<a;){const n=r+a>>1;l[n+1]<=i?r=n+1:a=n}const c=Math.min(r,s-1);let d=this.getFrozenWidth(),h=0;for(let n=0;n<this.leafCols.length;n++)if(this.isColumnScrollable(n)){if(d+=this.getColW(n),o<d){h=n;break}h=n}this.fillDrag.currentRow=c,this.fillDrag.currentCol=h},commitFillDrag(){const n=this.fillDrag.sourceRange;if(!n)return void(this.fillDrag.active=!1);const e=this.fillDrag.currentRow,t=this.fillDrag.currentCol,i=this.data,o=i.slice();let l;const s=[];if(e>n.endRow){const t=e-n.endRow;for(let e=n.startCol;e<=n.endCol;e++){const l=this.leafCols[e];if(!l)continue;const r=[];for(let e=n.startRow;e<=n.endRow;e++)r.push(i[e]?i[e][l.key]:"");const a=w(r,t);for(let r=0;r<t;r++){const t=n.endRow+1+r;if(t<this.totalRows&&this.isCellEditable(t,e)){if(!this.isCellValueValid(t,e,a[r])){s.push({row:t,col:e,value:a[r]});continue}o[t]===i[t]&&(o[t]={...i[t]}),o[t][l.key]=a[r]}}}l={startRow:n.endRow+1,endRow:e,startCol:n.startCol,endCol:n.endCol}}else if(t>n.endCol){const e=t-n.endCol;for(let t=n.startRow;t<=n.endRow;t++){const l=[];for(let e=n.startCol;e<=n.endCol;e++){const n=this.leafCols[e];l.push(n&&i[t]?i[t][n.key]:"")}const r=w(l,e);for(let l=0;l<e;l++){const e=n.endCol+1+l;if(e<this.leafCols.length){const n=this.leafCols[e];if(n&&this.isCellEditable(t,e)){if(!this.isCellValueValid(t,e,r[l])){s.push({row:t,col:e,value:r[l]});continue}o[t]===i[t]&&(o[t]={...i[t]}),o[t][n.key]=r[l]}}}}l={startRow:n.startRow,endRow:n.endRow,startCol:n.endCol+1,endCol:t}}l&&(this.strictBulkValidation&&s.length>0?this._emitBulkValidationError(s,!0):(this._emitDataChange(i,o),this.$emit("fill",{sourceRange:n,targetRange:l,values:o}),s.length>0&&this._emitBulkValidationError(s,!1))),this.fillDrag.active=!1,this.fillDrag.sourceRange=null},doubleFillDown(){const n=this.primaryRange;if(!n)return;const e=o(n),t=e.startCol>0?e.startCol-1:e.endCol+1;if(t<0||t>=this.leafCols.length)return;const i=this.leafCols[t];if(!i)return;let l=e.endRow;for(let n=e.endRow+1;n<this.totalRows&&(this.data[n]&&""!==this.data[n][i.key]&&void 0!==this.data[n][i.key]);n++)l=n;if(l<=e.endRow)return;const s=l-e.endRow,r=this.data,a=r.slice(),c=[];for(let n=e.startCol;n<=e.endCol;n++){const t=this.leafCols[n];if(!t)continue;const i=[];for(let n=e.startRow;n<=e.endRow;n++)i.push(r[n]?r[n][t.key]:"");const o=w(i,s);for(let i=0;i<s;i++){const l=e.endRow+1+i;if(l<this.totalRows&&this.isCellEditable(l,n)){if(!this.isCellValueValid(l,n,o[i])){c.push({row:l,col:n,value:o[i]});continue}a[l]===r[l]&&(a[l]={...r[l]}),a[l][t.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 n=this.primaryRange;if(!n)return;const e=o(n);this.clipboardMatrix=m(this.data,this.leafCols,e),this.clipboardRange=e,this.clipboardMode="copy";const t=this.clipboardMatrix.map(n=>n.map(n=>(null==n?"":String(n)).replace(/\t/g," ").replace(/\r?\n/g," ")).join("\t")).join("\n");navigator.clipboard&&navigator.clipboard.writeText(t).catch(()=>{})},cutSelection(){this.copySelection(),this.clipboardMode="cut"},async pasteAt(n,e,t={}){const{mode:i="all",transpose:l=!1}=t;let s=null;if(navigator.clipboard)try{const n=await navigator.clipboard.readText();n&&(s=(r=n)?r.split("\n").map(n=>n.split("\t")):[[""]])}catch(n){}var r;if(!s&&this.clipboardMatrix&&(s=this.clipboardMatrix),!s)return;l&&(s=function(n){if(!n||0===n.length)return[];const e=n.length,t=n[0].length;return Array.from({length:t},(t,i)=>Array.from({length:e},(e,t)=>n[t][i]))}(s));if(!this.clipboardMode||"copy"===this.clipboardMode){const t=this.primaryRange;if(t){const i=o(t),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(n,e,t){const i=n.length,o=n[0]?n[0].length:0;if(!i||!o)return n;if(e%i!==0||t%o!==0)return n;const l=[];for(let s=0;s<e;s++){const e=[];for(let l=0;l<t;l++)e.push(n[s%i][l%o]);l.push(e)}return l}(s,l,r),n=i.startRow,e=i.startCol)}}const a=this.data,{newData:c,affectedRange:d,skipped:h}=x(a,this.leafCols,n,e,s,i,(n,e)=>this.isCellEditable(n,e),(n,e,t)=>this.isCellValueValid(n,e,t));if(this.strictBulkValidation&&h.length>0)this._emitBulkValidationError(h,!0);else{if("cut"===this.clipboardMode&&this.clipboardRange){const n=this.clipboardRange;for(let e=n.startRow;e<=n.endRow;e++){c[e]===a[e]&&(c[e]={...a[e]});for(let t=n.startCol;t<=n.endCol;t++){const n=this.leafCols[t];n&&this.isCellEditable(e,t)&&this.isCellValueValid(e,t,"")&&(c[e][n.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(n,e=!1){this.hideContextMenu(),this.pasteAt(this.activeCell.row,this.activeCell.col,{mode:n,transpose:e})},startColResize(n,e){this.$refs.wrapper.getBoundingClientRect(),this.resizing={active:!0,type:"col",index:e,startX:n.clientX,startY:n.clientY,startSize:this.getColW(e),currentPos:n.clientX},n.preventDefault()},startRowResize(n,e){this.resizing={active:!0,type:"row",index:e,startX:n.clientX,startY:n.clientY,startSize:this.getRowH(e),currentPos:n.clientY},n.preventDefault()},handleResizeMove(n){if("col"===this.resizing.type){this.resizing.currentPos=n.clientX;const e=Math.max(20,this.resizing.startSize+(n.clientX-this.resizing.startX));this.$set(this.colWidths,this.resizing.index,e),this._cellStyleCache=Object.create(null)}else this.resizing.currentPos=n.clientY,this._resizePreviewHeight=Math.max(10,this.resizing.startSize+(n.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 n=this.colWidths[this.resizing.index];this.$emit("column-resize",{index:this.resizing.index,newSize:n})}else{this._rowStyleCache=Object.create(null),this._rowHeaderStyleCache=Object.create(null),this._cellStyleCache=Object.create(null);const n=this.rowHeights[this.resizing.index];this.$emit("row-resize",{index:this.resizing.index,newSize:n}),this.updateVirtualScroll()}this.resizing.active=!1},autoFitColumn(n){const e=this.leafCols[n];if(!e)return;let t=(e.title||"").length;for(let n=0;n<Math.min(this.totalRows,500);n++){const i=this.data[n]?String(this.data[n][e.key]||""):"";i.length>t&&(t=i.length)}const i=Math.max(50,8*t+16);this.$set(this.colWidths,n,i),this._cellStyleCache=Object.create(null),this.$emit("column-resize",{index:n,newSize:i})},_applyAutoWidth(){this.autoWidth&&this.leafCols.length&&(this.leafCols.forEach((n,e)=>{let t=(n.title||"").length;for(let e=0;e<Math.min(this.totalRows,500);e++){const i=this.data[e]?String(this.data[e][n.key]||""):"";i.length>t&&(t=i.length)}this.$set(this.colWidths,e,Math.max(50,8*t+16))}),this._cellStyleCache=Object.create(null))},ctxHideRow(){this.hideContextMenu();const n=this.primaryRange;if(!n)return;const e=o(n),t=[...this.hiddenRows];for(let n=e.startRow;n<=e.endRow;n++)t.includes(n)||t.push(n);this.$emit("update:hiddenRows",t)},ctxHideColumn(){this.hideContextMenu();const n=this.primaryRange;if(!n)return;const e=o(n),t=[...this.hiddenColumns];for(let n=e.startCol;n<=e.endCol;n++)t.includes(n)||t.push(n);this.$emit("update:hiddenColumns",t)},ctxUnhideRows(){this.hideContextMenu(),this.$emit("update:hiddenRows",[])},ctxUnhideColumns(){this.hideContextMenu(),this.$emit("update:hiddenColumns",[])},ctxPinLeft(){const n=this.leafCols[this.contextMenu.col];n?(this.$emit("update:pinnedColumns",{...this.pinnedColumns,[n.key]:"left"}),this.hideContextMenu()):this.hideContextMenu()},ctxPinRight(){const n=this.leafCols[this.contextMenu.col];n?(this.$emit("update:pinnedColumns",{...this.pinnedColumns,[n.key]:"right"}),this.hideContextMenu()):this.hideContextMenu()},ctxUnpin(){const n=this.leafCols[this.contextMenu.col];if(!n)return void this.hideContextMenu();const e={...this.pinnedColumns};delete e[n.key],this.$emit("update:pinnedColumns",e),this.hideContextMenu()},isColPinned(n){const e=this.leafCols[n];return!(!e||!this.pinnedColumns[e.key])},mergeCells(n){const e=this.internalMerges.slice(),{merges:t,conflict:i}=function(n,e){const t=o(n);return t.startRow===t.endRow&&t.startCol===t.endCol?{merges:e,conflict:!1}:v(t,e)?{merges:e,conflict:!0}:{merges:[...e,t],conflict:!1}}(n,this.internalMerges);return i?(this.$emit("merge-error",{range:n,reason:"Overlaps existing merge"}),!1):(this.internalMerges=t,this._emitMergeChange(e,[...t]),!0)},unmergeCells(n,e){const t=this.internalMerges.slice(),i=function(n,e,t){return t.filter(t=>{const i=o(t);return!(n>=i.startRow&&n<=i.endRow&&e>=i.startCol&&e<=i.endCol)})}(n,e,this.internalMerges);this.internalMerges=i,this._emitMergeChange(t,[...i])},ctxMerge(){this.hideContextMenu();const n=this.primaryRange;n&&this.mergeCells(o(n))},ctxUnmerge(){this.hideContextMenu(),this.unmergeCells(this.activeCell.row,this.activeCell.col)},groupRows(n,e){this.internalGroups=b("row",n,e,this.internalGroups)},groupColumns(n,e){this.internalGroups=b("column",n,e,this.internalGroups)},ungroupRows(n,e){this.internalGroups=R("row",n,e,this.internalGroups)},ungroupColumns(n,e){this.internalGroups=R("column",n,e,this.internalGroups)},toggleGroup(n){this.internalCollapsedGroups=function(n,e){return e.find(e=>e.type===n.type&&e.startIndex===n.startIndex&&e.endIndex===n.endIndex)?e.filter(e=>!(e.type===n.type&&e.startIndex===n.startIndex&&e.endIndex===n.endIndex)):[...e,{type:n.type,startIndex:n.startIndex,endIndex:n.endIndex}]}(n,this.internalCollapsedGroups),this.$emit("update:collapsedGroups",[...this.internalCollapsedGroups]),this.$emit("group-toggle",{group:n,collapsed:k(n,this.internalCollapsedGroups)}),this.updateVirtualScroll()},isGroupCollapsed(n){return k(n,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(n,e,t){const i=this.primaryRange;i&&(this.dragMove={active:!0,sourceRange:o(i),startX:n.clientX,startY:n.clientY,targetRow:e,targetCol:t,copyMode:n.ctrlKey||n.metaKey},this.$emit("cell-drag-start",{sourceRange:this.dragMove.sourceRange}),n.preventDefault())},handleDragMoveMove(n){this.dragMove.copyMode=n.ctrlKey||n.metaKey,this.$emit("cell-drag-move",{sourceRange:this.dragMove.sourceRange,targetRow:this.dragMove.targetRow,targetCol:this.dragMove.targetCol})},async commitDragMove(n){if(!this.dragMove.active)return;const{sourceRange:e,targetRow:t,targetCol:i,copyMode:o}=this.dragMove,l={startRow:t,startCol:i,endRow:t+(e.endRow-e.startRow),endCol:i+(e.endCol-e.startCol)};let s=!0;if(this.beforeDrop&&(s=await Promise.resolve(this.beforeDrop(e,l))),s){const n=this.data,s=m(n,this.leafCols,e),{newData:r}=x(n,this.leafCols,t,i,s,"all",(n,e)=>this.isCellEditable(n,e));if(!o)for(let t=e.startRow;t<=e.endRow;t++){r[t]===n[t]&&(r[t]={...n[t]});for(let n=e.startCol;n<=e.endCol;n++){const e=this.leafCols[n];e&&this.isCellEditable(t,n)&&(r[t][e.key]="")}}this._emitDataChange(n,r),this.$emit("cell-drop",{sourceRange:e,targetRange:l})}this.dragMove={active:!1,sourceRange:null,startX:0,startY:0,targetRow:-1,targetCol:-1,copyMode:!1},this.isMouseDown=!1},async exportToExcel(n={}){const{filename:e="export.xlsx",sheetName:t="Sheet1",scope:l="all",includeHidden:s=!1}=n;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:e,sheetName:t,scope:l,includeHidden:s};try{null||function(n){const{data:e,columns:t,leafCols:l,merges:s=[],hiddenColSet:d=new Set,hiddenRowSet:h=new Set,range:p=null,filename:u="export.xlsx",sheetName:g="Sheet1",scope:f="all",includeHidden:A=!1}=n,C=l.filter((n,e)=>{if(!A&&d.has(e))return!1;if("selection"===f&&p){const n=o(p);if(e<n.startCol||e>n.endCol)return!1}return!0});let w=0,m=e.length-1;if("selection"===f&&p){const n=o(p);w=n.startRow,m=n.endRow}const x=[];for(let n=w;n<=m;n++)!A&&h.has(n)||x.push({rowIndex:n,data:e[n]});const v=a(t),y=c(t,v),b=i.utils.book_new(),R=[];for(let n=0;n<y.length;n++){const e=[];for(const t of y[n])if(t.isGroup){const n=r(t.column.children||[]).filter(n=>C.some(e=>e.key===n.key));if(0===n.length)continue;e.push(t.column.title||"");for(let t=1;t<n.length;t++)e.push(null)}else{if(!C.some(n=>n.key===t.column.key))continue;t.rowspan,e.push(t.column.title||"")}R.push(e)}for(const{data:n}of x){const e=C.map(e=>{const t=n?n[e.key]:"";return void 0!==t?t:""});R.push(e)}const k=i.utils.aoa_to_sheet(R);k["!cols"]=C.map(n=>({wch:Math.round((n.width||100)/7)}));const S=[];for(const n of s){const e=o(n),t="selection"===f&&p?o(p):{startRow:0,startCol:0};if("selection"===f){if(e.startRow<t.startRow||e.endRow>t.endRow)continue;if(e.startCol<t.startCol||e.endCol>t.endCol)continue}const i=C.findIndex(n=>l.indexOf(n)===e.startCol),s=C.findIndex(n=>l.indexOf(n)===e.endCol);if(i<0||s<0)continue;const r=x.filter(n=>n.rowIndex<e.startRow).length,a=x.filter(n=>n.rowIndex<=e.endRow).length;S.push({s:{r:v+r,c:i},e:{r:v+a-1,c:s}})}S.length&&(k["!merges"]=S),i.utils.book_append_sheet(b,k,g),i.writeFile(b,u)}({...d,hiddenColSet:this.effectiveHiddenColSet,hiddenRowSet:this.hiddenRowSet}),this.$emit("export-success")}catch(n){this.$emit("export-error",n)}finally{this.exportState={active:!1}}},_exportViaWorker:(n,e)=>new Promise((t,i)=>{let o=!1;const l=(e,t)=>{o||(o=!0,n.terminate(),e(t))};n.onmessage=n=>{const{type:e,buffer:o,filename:s,message:r}=n.data;if("result"===e){const n=new Blob([new Uint8Array(o)],{type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}),e=URL.createObjectURL(n),i=document.createElement("a");i.href=e,i.download=s,document.body.appendChild(i),i.click(),document.body.removeChild(i),URL.revokeObjectURL(e),l(t)}else"error"===e&&l(i,new Error(r))},n.onerror=n=>l(i,new Error(n.message||"Export worker xatosi")),n.postMessage(e)}),async importFromFile(n,e={}){this.importState={active:!0,percent:5,message:"Fayl o'qilmoqda..."};try{let t;null||(t=await M(n,this.columns,e,(n,e)=>{this.importState={active:!0,percent:n,message:e}}));const{columns:i,merges:o}=t,l=t.data.map(n=>Object.freeze(n));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.scrollFooter&&(this.$refs.scrollFooter.scrollLeft=0),this.$refs.scrollRowHeaders&&(this.$refs.scrollRowHeaders.scrollTop=0),this.updateVirtualScroll()})}catch(n){this.$emit("import-error",n)}finally{this.importState={active:!1,percent:0,message:""}}},_importViaWorker(n,e,t){return new Promise(async(i,o)=>{let l=!1;const s=(e,t)=>{l||(l=!0,n.terminate(),e(t))};n.onmessage=n=>{const{type:e,percent:t,message:l,data:r,columns:a,merges:c}=n.data;"progress"===e?this.importState={active:!0,percent:t,message:l}:"result"===e?s(i,{data:r,columns:a,merges:c}):"error"===e&&s(o,new Error(l))},n.onerror=n=>s(o,new Error(n.message||"Import worker xatosi"));try{const i=await e.arrayBuffer();n.postMessage({buffer:i,existingColumns:this.columns,options:t},[i])}catch(n){s(o,n)}})},_pushHistory(n){this._historyIndex<this._history.length-1&&this._history.splice(this._historyIndex+1),this._history.push(n),this._history.length>100?this._history.shift():this._historyIndex++},_emitDataChange(n,e){if(!this._historySkip){const t=[],i=[],o=[],l=Math.max(n.length,e.length);for(let s=0;s<l;s++)n[s]!==e[s]&&(t.push(s),i.push(n[s]),o.push(e[s]));t.length>0&&this._pushHistory({type:"data",before:{indices:t,rows:i},after:{indices:t,rows:o}})}this.$emit("input",e)},_emitMergeChange(n,e){this._historySkip||this._pushHistory({type:"merges",before:n,after:e}),this.$emit("update:merges",e)},undo(){if(this._historyIndex<0)return;const n=this._history[this._historyIndex--];this._historySkip=!0;try{if("data"===n.type){const e=this.data.slice();for(let t=0;t<n.before.indices.length;t++)e[n.before.indices[t]]=n.before.rows[t];this.$emit("input",e)}else"merges"===n.type&&(this.internalMerges=n.before.slice(),this.$emit("update:merges",n.before.slice()))}finally{this._historySkip=!1}},redo(){if(this._historyIndex>=this._history.length-1)return;const n=this._history[++this._historyIndex];this._historySkip=!0;try{if("data"===n.type){const e=this.data.slice();for(let t=0;t<n.after.indices.length;t++)e[n.after.indices[t]]=n.after.rows[t];this.$emit("input",e)}else"merges"===n.type&&(this.internalMerges=n.after.slice(),this.$emit("update:merges",n.after.slice()))}finally{this._historySkip=!1}}}};var X=function(){var n=this,e=n.$createElement,t=n._self._c||e;return t("div",{ref:"wrapper",staticClass:"eg-wrapper",class:"eg-size-"+n.size,style:n.wrapperStyle,attrs:{tabindex:"0"},on:{keydown:n.onKeyDown,keypress:n.onKeyPress}},[n.contextMenu.visible?t("div",{staticClass:"eg-context-menu",style:{top:n.contextMenu.y+"px",left:n.contextMenu.x+"px"},on:{click:function(n){n.stopPropagation()}}},[t("div",{staticClass:"eg-context-item",on:{click:n.ctxCopy}},[n._v(n._s(n.t.copy))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxCut}},[n._v(n._s(n.t.cut))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxPaste}},[n._v(n._s(n.t.paste))]),n._v(" "),t("div",{staticClass:"eg-context-separator"}),n._v(" "),t("div",{staticClass:"eg-context-item eg-context-submenu-parent"},[n._v("\n "+n._s(n.t.pasteSpecial)+"\n "),t("div",{staticClass:"eg-context-submenu"},[t("div",{staticClass:"eg-context-item",on:{click:function(e){return n.ctxPasteSpecial("all")}}},[n._v(n._s(n.t.pasteAll))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:function(e){return n.ctxPasteSpecial("values")}}},[n._v(n._s(n.t.pasteValues))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:function(e){return n.ctxPasteSpecial("formats")}}},[n._v(n._s(n.t.pasteFormats))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:function(e){return n.ctxPasteSpecial("all",!0)}}},[n._v(n._s(n.t.transpose))])])]),n._v(" "),t("div",{staticClass:"eg-context-separator"}),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxMerge}},[n._v(n._s(n.t.mergeCells))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxUnmerge}},[n._v(n._s(n.t.unmergeCells))]),n._v(" "),t("div",{staticClass:"eg-context-separator"}),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxHideRow}},[n._v(n._s(n.t.hideRows))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxHideColumn}},[n._v(n._s(n.t.hideColumns))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxUnhideRows}},[n._v(n._s(n.t.unhideRows))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxUnhideColumns}},[n._v(n._s(n.t.unhideColumns))]),n._v(" "),n.contextMenu.row<0&&n.contextMenu.col>=0?[t("div",{staticClass:"eg-context-separator"}),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxPinLeft}},[n._v(n._s(n.t.pinLeft))]),n._v(" "),t("div",{staticClass:"eg-context-item",on:{click:n.ctxPinRight}},[n._v(n._s(n.t.pinRight))]),n._v(" "),n.isColPinned(n.contextMenu.col)?t("div",{staticClass:"eg-context-item",on:{click:n.ctxUnpin}},[n._v(n._s(n.t.unpin))]):n._e()]:n._e()],2):n._e(),n._v(" "),t("div",{ref:"container",staticClass:"eg-container",on:{scroll:n.onScroll,click:n.hideContextMenu}},[t("div",{staticClass:"eg-header-area",style:n.headerAreaStyle},[t("div",{staticClass:"eg-corner",style:n.cornerStyle,on:{click:n.selectAll}}),n._v(" "),n.rowGroups.length?t("div",{staticClass:"eg-row-outline-gutter",style:n.rowOutlineGutterStyle}):n._e(),n._v(" "),n.getFrozenWidth()>0?t("div",{staticClass:"eg-frozen-col-header",style:n.frozenColHeaderStyle},[t("div",{style:{position:"relative",height:n.headerRowHeight*n.headerDepth+"px"}},[n._l(n.headerRows,function(e,i){return[n._l(e,function(e){return[n.isCellVisible(e,i,!0)?[t("div",{key:"fh-"+i+"-"+(e.column.key||e.column.title),staticClass:"eg-header-cell",class:{"eg-header-group":e.isGroup},style:n.getHeaderCellStyle(e,i,!0),on:{mousedown:function(t){return n.onColHeaderMouseDown(t,e)},dblclick:function(t){return n.onColHeaderDblClick(t,e)},contextmenu:function(t){return t.preventDefault(),n.onColHeaderContextMenu(t,e)}}},[e.isGroup?t("span",[n._v(n._s(e.column.title))]):n._t("header-"+e.column.key,function(){return[n._v("\n "+n._s(e.column.title)+"\n ")]},{column:e.column}),n._v(" "),e.isGroup?n._e():t("div",{staticClass:"eg-col-resize-handle",on:{mousedown:function(t){return t.stopPropagation(),n.startColResize(t,e.column._flatIndex)}}})],2)]:n._e()]})]})],2)]):n._e(),n._v(" "),t("div",{ref:"scrollColHeader",staticClass:"eg-scroll-col-header",style:n.scrollColHeaderStyle},[t("div",{staticClass:"eg-scroll-col-header-inner",style:{width:n.scrollContentWidth+"px",height:n.headerRowHeight*n.headerDepth+"px",position:"relative"}},[n._l(n.headerRows,function(e,i){return[n._l(e,function(e){return[n.isCellVisible(e,i,!1)?[t("div",{key:"sh-"+i+"-"+(e.column.key||e.column.title),staticClass:"eg-header-cell",class:{"eg-header-group":e.isGroup},style:n.getHeaderCellStyle(e,i,!1),on:{mousedown:function(t){return n.onColHeaderMouseDown(t,e)},dblclick:function(t){return n.onColHeaderDblClick(t,e)},contextmenu:function(t){return t.preventDefault(),n.onColHeaderContextMenu(t,e)}}},[e.isGroup?t("span",[n._v(n._s(e.column.title))]):n._t("header-"+e.column.key,function(){return[n._v("\n "+n._s(e.column.title)+"\n ")]},{column:e.column}),n._v(" "),e.isGroup?n._e():t("div",{staticClass:"eg-col-resize-handle",on:{mousedown:function(t){return t.stopPropagation(),n.startColResize(t,e.column._flatIndex)}}})],2)]:n._e()]})]})],2)]),n._v(" "),n.hasPinnedRight?t("div",{staticClass:"eg-pinned-right-header",style:n.pinnedRightHeaderStyle},[t("div",{style:{position:"relative",height:n.headerRowHeight*n.headerDepth+"px"}},[n._l(n.headerRows,function(e,i){return[n._l(e,function(e){return[n.isCellInZone(e,i,"right")?[t("div",{key:"prh-"+i+"-"+(e.column.key||e.column.title),staticClass:"eg-header-cell",class:{"eg-header-group":e.isGroup},style:n.getHeaderCellStyleForZone(e,i,"right"),on:{mousedown:function(t){return n.onColHeaderMouseDown(t,e)},dblclick:function(t){return n.onColHeaderDblClick(t,e)},contextmenu:function(t){return t.preventDefault(),n.onColHeaderContextMenu(t,e)}}},[e.isGroup?t("span",[n._v(n._s(e.column.title))]):n._t("header-"+e.column.key,function(){return[n._v("\n "+n._s(e.column.title)+"\n ")]},{column:e.column}),n._v(" "),e.isGroup?n._e():t("div",{staticClass:"eg-col-resize-handle",on:{mousedown:function(t){return t.stopPropagation(),n.startColResize(t,e.column._flatIndex)}}})],2)]:n._e()]})]})],2)]):n._e()]),n._v(" "),t("div",{staticClass:"eg-body-area",style:n.bodyAreaStyle},[t("div",{staticClass:"eg-row-header-col",style:n.rowHeaderColStyle},[t("div",{ref:"scrollRowHeaders",staticClass:"eg-scroll-row-headers",style:n.scrollRowHeadersStyle},[t("div",{style:{height:n.totalVirtualHeight+"px",position:"relative"}},[t("div",{style:{transform:"translateY("+n.virtualOffsetTop+"px)"}},[n._l(n.visibleRowRange,function(e){return[n.isRowHidden(e)?n._e():t("div",{key:"srh-"+e,staticClass:"eg-row-header-cell",style:n.getRowHeaderStyle(e),on:{mousedown:function(t){return n.onRowHeaderMouseDown(t,e)},contextmenu:function(t){return t.preventDefault(),n.onRowContextMenu(t,e)}}},[n._v("\n "+n._s(e+1)+"\n "),t("div",{staticClass:"eg-row-resize-handle",on:{mousedown:function(t){return t.stopPropagation(),n.startRowResize(t,e)}}})])]})],2)])])]),n._v(" "),n.rowGroups.length?t("div",{staticClass:"eg-row-outline-col",style:n.rowOutlineColStyle},[n._l(n.rowGroups,function(e){return[t("div",{key:"rg-"+e.startIndex+"-"+e.endIndex,staticClass:"eg-group-btn",class:{collapsed:n.isGroupCollapsed(e)},style:n.getRowGroupBtnStyle(e),on:{click:function(t){return n.toggleGroup(e)}}},[n._v(n._s(n.isGroupCollapsed(e)?"+":"−"))])]})],2):n._e(),n._v(" "),t("div",{ref:"scrollBody",staticClass:"eg-scroll-body",style:n.scrollBodyStyle,on:{scroll:n.onBodyScroll}},[t("div",{staticClass:"eg-scroll-body-inner",style:{width:n.scrollContentWidth+"px",height:n.totalVirtualHeight+"px",position:"relative"}},[n.getFrozenWidth()>0?t("div",{staticClass:"eg-frozen-col-body",style:n.frozenColBodyStyle},[t("div",{style:{height:n.totalVirtualHeight+"px",position:"relative"}},[t("div",{style:{transform:"translateY("+n.virtualOffsetTop+"px)"}},[n._l(n.visibleRowRange,function(e){return[t("transition",{key:"tr-fc-"+e,attrs:{name:n._rowTransitionName}},[n.isRowHidden(e)?n._e():t("div",{key:"fc-row-"+e,staticClass:"eg-row",style:n.getRowStyle(e)},[n._l(n.leafCols,function(i,o){return[n.isColumnPinnedLeft(o)&&!n.isColHidden(o)?[t("div",{key:"fc-cell-"+e+"-"+o,staticClass:"eg-cell",class:n.getCellClasses(e,o),style:n.getCellStyle(e,o),on:{mousedown:function(t){return n.onCellMouseDown(t,e,o)},mouseover:function(t){return n.onCellMouseOver(t,e,o)},mouseup:n.onCellMouseUp,dblclick:function(t){return n.onCellDblClick(e,o)},contextmenu:function(t){return t.preventDefault(),n.onCellContextMenu(t,e,o)}}},[n.isMergedAway(e,o)?t("div",{staticStyle:{display:"none"}}):n.isEditing(e,o)?[n.hasCellSlot(i)?n._t("cell-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o),isEditing:!0,done:function(e){return n.commitEditSlot(e)},cancel:n.cancelEdit}):n.hasLegacyEditSlot(i)?n._t("edit-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o),done:function(e){return n.commitEditSlot(e)},cancel:n.cancelEdit}):t("input",{directives:[{name:"model",rawName:"v-model",value:n.editValue,expression:"editValue"}],ref:"editInput",refInFor:!0,staticClass:"eg-cell-input",domProps:{value:n.editValue},on:{blur:n.commitEdit,keydown:function(e){return e.stopPropagation(),n.onEditKeyDown.apply(null,arguments)},input:function(e){e.target.composing||(n.editValue=e.target.value)}}})]:"checkbox"===i.type?[t("eg-checkbox",{attrs:{value:n.isCheckboxChecked(e,o),disabled:!n.isCellEditable(e,o)},on:{change:function(t){return n.setCheckboxValue(e,o,t)}}})]:[n._t("cell-"+i.key,function(){return[n.hasPrefixSlot(i)?n._t("prefix-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o)}):n._e(),n.hasPrefixSlot(i)||n.hasSuffixSlot(i)?t("span",{staticClass:"eg-cell-value"},[n._v(n._s(n.isSelectLikeCol(i)?n.getSelectLabel(o,n.getCellValue(e,o)):n.getCellValue(e,o)))]):[n._v(n._s(n.isSelectLikeCol(i)?n.getSelectLabel(o,n.getCellValue(e,o)):n.getCellValue(e,o)))],n.hasSuffixSlot(i)&&n.isSelectLikeCol(i)?t("span",{staticClass:"eg-cell-select-caret-inline"},[n._v("▾")]):n._e(),n.hasSuffixSlot(i)?n._t("suffix-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o)}):n._e()]},{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o),isEditing:!1,done:null,cancel:null})]],2)]:n._e()]})],2)])]})],2)])]):n._e(),n._v(" "),t("div",{staticClass:"eg-main-cells",style:n.mainCellsStyle},[t("div",{style:{height:n.totalVirtualHeight+"px",position:"relative",width:n.scrollableContentWidth+"px"}},[t("div",{style:{transform:"translateY("+n.virtualOffsetTop+"px)"}},[n._l(n.visibleRowRange,function(e){return[t("transition",{key:"tr-m-"+e,attrs:{name:n._rowTransitionName}},[n.isRowHidden(e)?n._e():t("div",{key:"row-"+e,staticClass:"eg-row",style:n.getRowStyle(e),attrs:{"data-row":e}},[n._l(n.leafCols,function(i,o){return[n.isColumnScrollable(o)&&!n.isColHidden(o)?[t("div",{key:"cell-"+e+"-"+o,staticClass:"eg-cell",class:n.getCellClasses(e,o),style:n.getCellStyle(e,o),on:{mousedown:function(t){return n.onCellMouseDown(t,e,o)},mouseover:function(t){return n.onCellMouseOver(t,e,o)},mouseup:n.onCellMouseUp,dblclick:function(t){return n.onCellDblClick(e,o)},contextmenu:function(t){return t.preventDefault(),n.onCellContextMenu(t,e,o)}}},[n.isMergedAway(e,o)?t("div",{staticStyle:{display:"none"}}):n.isEditing(e,o)?[n.hasCellSlot(i)?n._t("cell-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o),isEditing:!0,done:function(e){return n.commitEditSlot(e)},cancel:n.cancelEdit}):n.hasLegacyEditSlot(i)?n._t("edit-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o),done:function(e){return n.commitEditSlot(e)},cancel:n.cancelEdit}):t("input",{directives:[{name:"model",rawName:"v-model",value:n.editValue,expression:"editValue"}],ref:"editInput",refInFor:!0,staticClass:"eg-cell-input",domProps:{value:n.editValue},on:{blur:n.commitEdit,keydown:function(e){return e.stopPropagation(),n.onEditKeyDown.apply(null,arguments)},input:function(e){e.target.composing||(n.editValue=e.target.value)}}})]:"checkbox"===i.type?[t("eg-checkbox",{attrs:{value:n.isCheckboxChecked(e,o),disabled:!n.isCellEditable(e,o)},on:{change:function(t){return n.setCheckboxValue(e,o,t)}}})]:[n._t("cell-"+i.key,function(){return[n.hasPrefixSlot(i)?n._t("prefix-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o)}):n._e(),n.hasPrefixSlot(i)||n.hasSuffixSlot(i)?t("span",{staticClass:"eg-cell-value"},[n._v(n._s(n.isSelectLikeCol(i)?n.getSelectLabel(o,n.getCellValue(e,o)):n.getCellValue(e,o)))]):[n._v(n._s(n.isSelectLikeCol(i)?n.getSelectLabel(o,n.getCellValue(e,o)):n.getCellValue(e,o)))],n.hasSuffixSlot(i)&&n.isSelectLikeCol(i)?t("span",{staticClass:"eg-cell-select-caret-inline"},[n._v("▾")]):n._e(),n.hasSuffixSlot(i)?n._t("suffix-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o)}):n._e()]},{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,o),isEditing:!1,done:null,cancel:null})]],2)]:n._e()]})],2)])]})],2)])]),n._v(" "),n.hasPinnedRight?t("div",{staticClass:"eg-pinned-right-body",style:n.pinnedRightBodyStyle},[t("div",{style:{height:n.totalVirtualHeight+"px",position:"relative"}},[t("div",{style:{transform:"translateY("+n.virtualOffsetTop+"px)"}},[n._l(n.visibleRowRange,function(e){return[t("transition",{key:"tr-pr-"+e,attrs:{name:n._rowTransitionName}},[n.isRowHidden(e)?n._e():t("div",{key:"pr-row-"+e,staticClass:"eg-row",style:n.getRowStyle(e)},[n._l(n.pinnedRightCols,function(i){return[t("div",{key:"pr-cell-"+e+"-"+i._flatIndex,staticClass:"eg-cell",class:n.getCellClasses(e,i._flatIndex),style:n.getCellStyle(e,i._flatIndex),on:{mousedown:function(t){return n.onCellMouseDown(t,e,i._flatIndex)},mouseover:function(t){return n.onCellMouseOver(t,e,i._flatIndex)},mouseup:n.onCellMouseUp,dblclick:function(t){return n.onCellDblClick(e,i._flatIndex)},contextmenu:function(t){return t.preventDefault(),n.onCellContextMenu(t,e,i._flatIndex)}}},[n.isMergedAway(e,i._flatIndex)?t("div",{staticStyle:{display:"none"}}):n.isEditing(e,i._flatIndex)?[n.hasCellSlot(i)?n._t("cell-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,i._flatIndex),isEditing:!0,done:function(e){return n.commitEditSlot(e)},cancel:n.cancelEdit}):n.hasLegacyEditSlot(i)?n._t("edit-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,i._flatIndex),done:function(e){return n.commitEditSlot(e)},cancel:n.cancelEdit}):t("input",{directives:[{name:"model",rawName:"v-model",value:n.editValue,expression:"editValue"}],ref:"editInput",refInFor:!0,staticClass:"eg-cell-input",domProps:{value:n.editValue},on:{blur:n.commitEdit,keydown:function(e){return e.stopPropagation(),n.onEditKeyDown.apply(null,arguments)},input:function(e){e.target.composing||(n.editValue=e.target.value)}}})]:"checkbox"===i.type?[t("eg-checkbox",{attrs:{value:n.isCheckboxChecked(e,i._flatIndex),disabled:!n.isCellEditable(e,i._flatIndex)},on:{change:function(t){return n.setCheckboxValue(e,i._flatIndex,t)}}})]:[n._t("cell-"+i.key,function(){return[n.hasPrefixSlot(i)?n._t("prefix-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,i._flatIndex)}):n._e(),n.hasPrefixSlot(i)||n.hasSuffixSlot(i)?t("span",{staticClass:"eg-cell-value"},[n._v(n._s(n.isSelectLikeCol(i)?n.getSelectLabel(i._flatIndex,n.getCellValue(e,i._flatIndex)):n.getCellValue(e,i._flatIndex)))]):[n._v(n._s(n.isSelectLikeCol(i)?n.getSelectLabel(i._flatIndex,n.getCellValue(e,i._flatIndex)):n.getCellValue(e,i._flatIndex)))],n.hasSuffixSlot(i)&&n.isSelectLikeCol(i)?t("span",{staticClass:"eg-cell-select-caret-inline"},[n._v("▾")]):n._e(),n.hasSuffixSlot(i)?n._t("suffix-"+i.key,null,{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,i._flatIndex)}):n._e()]},{row:n._dataRaw[e],rowIndex:e,column:i,value:n.getCellValue(e,i._flatIndex),isEditing:!1,done:null,cancel:null})]],2)]})],2)])]})],2)])]):n._e(),n._v(" "),n.fillHandleVisible?t("div",{staticClass:"eg-fill-handle",style:n.fillHandleStyle,on:{mousedown:function(e){return e.stopPropagation(),n.startFillDrag.apply(null,arguments)}}}):n._e(),n._v(" "),n.dragMove.active?t("div",{staticClass:"eg-drag-ghost",style:n.dragGhostStyle}):n._e()])]),n._v(" "),n.colGroups.length?t("div",{staticClass:"eg-col-outline-row",style:n.colOutlineRowStyle},[n._l(n.colGroups,function(e){return[t("div",{key:"cg-"+e.startIndex+"-"+e.endIndex,staticClass:"eg-group-btn",class:{collapsed:n.isGroupCollapsed(e)},style:n.getColGroupBtnStyle(e),on:{click:function(t){return n.toggleGroup(e)}}},[n._v(n._s(n.isGroupCollapsed(e)?"+":"−"))])]})],2):n._e()]),n._v(" "),n.hasFooter?t("div",{staticClass:"eg-footer-area",style:n.footerAreaStyle},[t("div",{staticClass:"eg-footer-label",style:n.footerLabelStyle},[n._v(n._s(n.t.total))]),n._v(" "),n.getFrozenWidth()>0?t("div",{staticClass:"eg-frozen-col-footer",style:n.frozenColFooterStyle},[t("div",{style:{position:"relative",height:n.footerRowHeight+"px"}},[n._l(n.leafCols,function(e,i){return[n.isColumnPinnedLeft(i)&&!n.isColHidden(i)?[t("div",{key:"ff-"+i,staticClass:"eg-footer-cell",style:n.getFooterCellStyle(i)},[n._v("\n "+n._s(n.getColumnTotalDisplay(i))+"\n ")])]:n._e()]})],2)]):n._e(),n._v(" "),t("div",{ref:"scrollFooter",staticClass:"eg-scroll-col-footer",style:n.scrollColHeaderStyle},[t("div",{staticClass:"eg-scroll-col-footer-inner",style:{width:n.scrollContentWidth+"px",height:n.footerRowHeight+"px",position:"relative"}},[n._l(n.leafCols,function(e,i){return[n.isColumnScrollable(i)&&!n.isColHidden(i)?[t("div",{key:"sf-"+i,staticClass:"eg-footer-cell",style:n.getFooterCellStyle(i)},[n._v("\n "+n._s(n.getColumnTotalDisplay(i))+"\n ")])]:n._e()]})],2)]),n._v(" "),n.hasPinnedRight?t("div",{staticClass:"eg-pinned-right-footer",style:n.pinnedRightFooterStyle},[t("div",{style:{position:"relative",height:n.footerRowHeight+"px"}},n._l(n.pinnedRightCols,function(e){return t("div",{key:"prf-"+e._flatIndex,staticClass:"eg-footer-cell",style:n.getFooterCellStyle(e._flatIndex)},[n._v(n._s(n.getColumnTotalDisplay(e._flatIndex)))])}),0)]):n._e()]):n._e(),n._v(" "),n.resizing.active?t("div",{staticClass:"eg-resize-line",style:n.resizeLineStyle}):n._e()]),n._v(" "),n.importState.active?t("div",{staticClass:"eg-import-overlay"},[t("div",{staticClass:"eg-import-dialog"},[t("div",{staticClass:"eg-import-title"},[n._v("Fayl import qilinmoqda")]),n._v(" "),t("div",{staticClass:"eg-import-bar-wrap"},[t("div",{staticClass:"eg-import-bar",style:{width:n.importState.percent+"%"}})]),n._v(" "),t("div",{staticClass:"eg-import-msg"},[n._v(n._s(n.importState.message))]),n._v(" "),t("div",{staticClass:"eg-import-pct"},[n._v(n._s(n.importState.percent)+"%")])])]):n._e(),n._v(" "),n.exportState.active?t("div",{staticClass:"eg-import-overlay"},[n._m(0)]):n._e(),n._v(" "),n.selectDropdown&&!n.selectDropdown.tree?t("eg-select",{ref:"selectDropdownComp",attrs:{value:n.selectDropdown.currentValue,options:n.selectDropdown.rawOptions,"enable-search":n.selectDropdown.enableSearch,"trigger-rect":n.selectDropdown.rect,"dropdown-only":!0},on:{select:n.onComponentSelect,close:n.closeSelectDropdown}}):n._e(),n._v(" "),n.selectDropdown&&n.selectDropdown.tree?t("eg-tree-select",{ref:"selectDropdownComp",attrs:{value:n.selectDropdown.currentValue,options:n.selectDropdown.rawOptions,"value-key":n.selectDropdown.keys.valueKey,"label-key":n.selectDropdown.keys.labelKey,"children-key":n.selectDropdown.keys.childrenKey,"enable-search":n.selectDropdown.enableSearch,"trigger-rect":n.selectDropdown.rect,"dropdown-only":!0},on:{select:n.onComponentSelect,close:n.closeSelectDropdown}}):n._e(),n._v(" "),n.invalidCell&&n.invalidCell.rect?t("div",{staticClass:"eg-validation-tooltip",style:n.invalidTooltipStyle},[n._v(n._s(n.invalidCell.message))]):n._e()],1)};X._withStripped=!0;const Q=H({render:X,staticRenderFns:[function(){var n=this,e=n.$createElement,t=n._self._c||e;return t("div",{staticClass:"eg-import-dialog"},[t("div",{staticClass:"eg-import-title"},[n._v("Fayl export qilinmoqda...")]),n._v(" "),t("div",{staticClass:"eg-export-spinner"})])}]},function(n){n&&(n("data-v-29e88078_0",{source:"\n.eg-wrapper[data-v-29e88078] {\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-29e88078] {\n display: flex;\n flex-direction: column;\n flex: 1;\n overflow: auto;\n position: relative;\n}\n.eg-header-area[data-v-29e88078] {\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-29e88078] {\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-29e88078]:hover { background: #e0e0e0;\n}\n.eg-header-group[data-v-29e88078] { background: #e8eaf6;\n}\n.eg-col-resize-handle[data-v-29e88078] {\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-29e88078] {\n display: flex;\n flex: 1;\n overflow: hidden;\n}\n\n/* ===== FOOTER AREA (column totals) ===== */\n.eg-footer-area[data-v-29e88078] {\n border-top: 1px solid #ccc;\n}\n.eg-frozen-col-footer[data-v-29e88078],\n.eg-scroll-col-footer[data-v-29e88078],\n.eg-pinned-right-footer[data-v-29e88078] {\n position: relative;\n}\n.eg-footer-cell[data-v-29e88078] {\n box-sizing: border-box;\n border-right: 1px solid #e0e0e0;\n overflow: hidden;\n white-space: nowrap;\n font-size: 13px;\n font-weight: 600;\n padding: 0 4px;\n position: absolute;\n top: 0;\n background: #f8f9fa;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n}\n.eg-row[data-v-29e88078] {\n position: relative;\n}\n.eg-cell[data-v-29e88078] {\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-value[data-v-29e88078] {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n.eg-cell--has-suffix.eg-cell-select[data-v-29e88078]::after { display: none;\n}\n.eg-cell-select-caret-inline[data-v-29e88078] {\n flex-shrink: 0;\n color: #aaa;\n font-size: 11px;\n margin-right: 2px;\n}\n.eg-cell-active[data-v-29e88078] {\n outline: 2px solid #1a73e8 !important;\n outline-offset: -2px;\n z-index: 3;\n}\n.eg-cell-selected[data-v-29e88078] {\n background: #e3f2fd !important;\n}\n.eg-cell-fill-preview[data-v-29e88078] {\n background: #bbdefb !important;\n outline: 1px dashed #1565c0;\n outline-offset: -1px;\n}\n.eg-cell-copy[data-v-29e88078] {\n outline: 1px dashed #1a73e8;\n outline-offset: -1px;\n}\n.eg-cell-merged-hidden[data-v-29e88078] {\n display: none !important;\n}\n.eg-cell-invalid[data-v-29e88078] {\n outline: 2px solid #e53935 !important;\n outline-offset: -2px;\n z-index: 4;\n}\n.eg-cell-input[data-v-29e88078] {\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-29e88078] {\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-29e88078] {\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-29e88078] {\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-29e88078] {\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-29e88078] {\n padding: 6px 16px;\n cursor: pointer;\n position: relative;\n}\n.eg-context-item[data-v-29e88078]:hover { background: #f0f0f0;\n}\n.eg-context-separator[data-v-29e88078] {\n height: 1px;\n background: #e0e0e0;\n margin: 4px 0;\n}\n.eg-context-submenu-parent[data-v-29e88078]::after { content: ' ▶'; float: right;\n}\n.eg-context-submenu[data-v-29e88078] {\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-29e88078] { display: block;\n}\n.eg-group-btn[data-v-29e88078] {\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-29e88078] { background: #c5cae9;\n}\n.eg-drag-ghost[data-v-29e88078] {\n position: absolute;\n border: 2px dashed #1a73e8;\n pointer-events: none;\n z-index: 20;\n}\n.eg-resize-line[data-v-29e88078] {\n pointer-events: none;\n z-index: 9999;\n}\n\n/* Pin left separator shadow */\n.eg-frozen-col-body[data-v-29e88078]::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-29e88078] {\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-29e88078]::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-29e88078] {\n overflow: hidden;\n flex-shrink: 0;\n border-left: 2px solid #bbb;\n background: #f0f0f0;\n}\n.eg-import-overlay[data-v-29e88078] {\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-29e88078] {\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-29e88078] {\n font-size: 14px;\n font-weight: 600;\n color: #333;\n margin-bottom: 16px;\n}\n.eg-import-bar-wrap[data-v-29e88078] {\n height: 8px;\n background: #e0e0e0;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 10px;\n}\n.eg-import-bar[data-v-29e88078] {\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-29e88078] {\n font-size: 12px;\n color: #666;\n margin-bottom: 4px;\n}\n.eg-import-pct[data-v-29e88078] {\n font-size: 13px;\n font-weight: 600;\n color: #1a73e8;\n}\n.eg-export-spinner[data-v-29e88078] {\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-29e88078 0.8s linear infinite;\n}\n@keyframes eg-spin-data-v-29e88078 {\nto { transform: rotate(360deg);\n}\n}\n\n/* ===== READONLY CELL ===== */\n.eg-cell-readonly[data-v-29e88078] {\n background: #f8f8f8;\n cursor: default;\n}\n.eg-cell-readonly.eg-cell-selected[data-v-29e88078] {\n background: #dce8f5;\n}\n.eg-cell-readonly.eg-cell-active[data-v-29e88078] {\n outline-color: #aaa;\n}\n\n/* ===== SELECT DROPDOWN ===== */\n.eg-cell-select[data-v-29e88078] {\n cursor: pointer;\n}\n.eg-cell-select[data-v-29e88078]::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-29e88078] {\n justify-content: center;\n padding: 0 2px;\n}\n.eg-validation-tooltip[data-v-29e88078] {\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/* ===== ROW TRANSITION ===== */\n.eg-row-enter-active[data-v-29e88078],\n.eg-row-leave-active[data-v-29e88078] {\n transition: opacity 0.18s ease, transform 0.18s ease;\n}\n.eg-row-enter[data-v-29e88078],\n.eg-row-leave-to[data-v-29e88078] {\n opacity: 0;\n transform: translateY(-4px);\n}\n\n/* ===== SIZE VARIANTS ===== */\n\n/* small */\n.eg-size-small .eg-cell[data-v-29e88078] {\n font-size: 12px;\n padding: 0 3px;\n}\n.eg-size-small .eg-cell-input[data-v-29e88078] {\n font-size: 12px;\n}\n.eg-size-small .eg-header-cell[data-v-29e88078] {\n font-size: 11px;\n}\n.eg-size-small .eg-footer-cell[data-v-29e88078] {\n font-size: 12px;\n}\n.eg-size-small .eg-row-header-cell[data-v-29e88078] {\n font-size: 10px;\n}\n.eg-size-small .eg-select-option[data-v-29e88078] {\n font-size: 12px;\n padding: 4px 10px;\n}\n.eg-size-small .eg-context-menu[data-v-29e88078],\n.eg-size-small .eg-context-item[data-v-29e88078] {\n font-size: 12px;\n}\n.eg-size-small .eg-context-item[data-v-29e88078] {\n padding: 4px 14px;\n}\n\n/* large */\n.eg-size-large .eg-cell[data-v-29e88078] {\n font-size: 14px;\n padding: 0 6px;\n}\n.eg-size-large .eg-cell-input[data-v-29e88078] {\n font-size: 14px;\n}\n.eg-size-large .eg-header-cell[data-v-29e88078] {\n font-size: 13px;\n}\n.eg-size-large .eg-footer-cell[data-v-29e88078] {\n font-size: 14px;\n}\n.eg-size-large .eg-row-header-cell[data-v-29e88078] {\n font-size: 12px;\n}\n.eg-size-large .eg-select-option[data-v-29e88078] {\n font-size: 14px;\n padding: 8px 14px;\n}\n.eg-size-large .eg-context-menu[data-v-29e88078],\n.eg-size-large .eg-context-item[data-v-29e88078] {\n font-size: 14px;\n}\n.eg-size-large .eg-context-item[data-v-29e88078] {\n padding: 8px 18px;\n}\n",map:{version:3,sources:["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/ExcelGrid.vue"],names:[],mappings:";AA+zGA;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,4CAAA;AACA;EACA,0BAAA;AACA;AAEA;;;EAGA,kBAAA;AACA;AAEA;EACA,sBAAA;EACA,+BAAA;EACA,gBAAA;EACA,mBAAA;EACA,eAAA;EACA,gBAAA;EACA,cAAA;EACA,kBAAA;EACA,MAAA;EACA,mBAAA;EACA,aAAA;EACA,mBAAA;EACA,yBAAA;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,OAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;EACA,YAAA;AACA;AACA,8DAAA,aAAA;AAAA;AACA;EACA,cAAA;EACA,WAAA;EACA,eAAA;EACA,iBAAA;AACA;AACA;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;EACA,cAAA;AACA;AACA;EACA,mBAAA;EACA,yBAAA;EACA,cAAA;EACA,eAAA;EACA,gBAAA;EACA,kBAAA;EACA,yCAAA;EACA,mBAAA;EACA,oBAAA;AACA;;AAEA,+BAAA;AACA;;EAEA,oDAAA;AACA;AACA;;EAEA,UAAA;EACA,2BAAA;AACA;;AAEA,8BAAA;;AAEA,UAAA;AACA;EACA,eAAA;EACA,cAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;EACA,iBAAA;AACA;AACA;;EAEA,eAAA;AACA;AACA;EACA,iBAAA;AACA;;AAEA,UAAA;AACA;EACA,eAAA;EACA,cAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;AACA;AACA;EACA,eAAA;EACA,iBAAA;AACA;AACA;;EAEA,eAAA;AACA;AACA;EACA,iBAAA;AACA",file:"ExcelGrid.vue",sourcesContent:["<template>\n <div\n class=\"eg-wrapper\"\n :class=\"'eg-size-' + size\"\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\"\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\"\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\"\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 <transition :key=\"'tr-fc-' + ri\" :name=\"_rowTransitionName\">\n <div v-if=\"!isRowHidden(ri)\" :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 <slot v-if=\"hasCellSlot(col)\" :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"true\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <slot v-else-if=\"hasLegacyEditSlot(col)\" :name=\"'edit-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <input v-else 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 <eg-checkbox\n :value=\"isCheckboxChecked(ri, ci)\"\n :disabled=\"!isCellEditable(ri, ci)\"\n @change=\"(v) => setCheckboxValue(ri, ci, v)\"\n />\n </template>\n <template v-else>\n <slot :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"false\" :done=\"null\" :cancel=\"null\">\n <slot v-if=\"hasPrefixSlot(col)\" :name=\"'prefix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" /><span v-if=\"hasPrefixSlot(col) || hasSuffixSlot(col)\" class=\"eg-cell-value\">{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</span><template v-else>{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</template><span v-if=\"hasSuffixSlot(col) && isSelectLikeCol(col)\" class=\"eg-cell-select-caret-inline\">▾</span><slot v-if=\"hasSuffixSlot(col)\" :name=\"'suffix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" />\n </slot>\n </template>\n </div>\n </template>\n </template>\n </div>\n </transition>\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 <transition :key=\"'tr-m-' + ri\" :name=\"_rowTransitionName\">\n <div\n v-if=\"!isRowHidden(ri)\"\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 <slot v-if=\"hasCellSlot(col)\" :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"true\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <slot v-else-if=\"hasLegacyEditSlot(col)\" :name=\"'edit-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <input\n v-else\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 <eg-checkbox\n :value=\"isCheckboxChecked(ri, ci)\"\n :disabled=\"!isCellEditable(ri, ci)\"\n @change=\"(v) => setCheckboxValue(ri, ci, v)\"\n />\n </template>\n <template v-else>\n <slot :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"false\" :done=\"null\" :cancel=\"null\">\n <slot v-if=\"hasPrefixSlot(col)\" :name=\"'prefix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" /><span v-if=\"hasPrefixSlot(col) || hasSuffixSlot(col)\" class=\"eg-cell-value\">{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</span><template v-else>{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</template><span v-if=\"hasSuffixSlot(col) && isSelectLikeCol(col)\" class=\"eg-cell-select-caret-inline\">▾</span><slot v-if=\"hasSuffixSlot(col)\" :name=\"'suffix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" />\n </slot>\n </template>\n </div>\n </template>\n </template>\n </div>\n </transition>\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 <transition :key=\"'tr-pr-' + ri\" :name=\"_rowTransitionName\">\n <div v-if=\"!isRowHidden(ri)\" :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 <slot v-if=\"hasCellSlot(col)\" :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" :isEditing=\"true\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <slot v-else-if=\"hasLegacyEditSlot(col)\" :name=\"'edit-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <input v-else 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 <eg-checkbox\n :value=\"isCheckboxChecked(ri, col._flatIndex)\"\n :disabled=\"!isCellEditable(ri, col._flatIndex)\"\n @change=\"(v) => setCheckboxValue(ri, col._flatIndex, v)\"\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)\" :isEditing=\"false\" :done=\"null\" :cancel=\"null\">\n <slot v-if=\"hasPrefixSlot(col)\" :name=\"'prefix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" /><span v-if=\"hasPrefixSlot(col) || hasSuffixSlot(col)\" class=\"eg-cell-value\">{{ isSelectLikeCol(col) ? getSelectLabel(col._flatIndex, getCellValue(ri, col._flatIndex)) : getCellValue(ri, col._flatIndex) }}</span><template v-else>{{ isSelectLikeCol(col) ? getSelectLabel(col._flatIndex, getCellValue(ri, col._flatIndex)) : getCellValue(ri, col._flatIndex) }}</template><span v-if=\"hasSuffixSlot(col) && isSelectLikeCol(col)\" class=\"eg-cell-select-caret-inline\">▾</span><slot v-if=\"hasSuffixSlot(col)\" :name=\"'suffix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" />\n </slot>\n </template>\n </div>\n </template>\n </div>\n </transition>\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!-- ===== FOOTER AREA (column totals) ===== --\x3e\n <div v-if=\"hasFooter\" class=\"eg-footer-area\" :style=\"footerAreaStyle\">\n\n \x3c!-- \"Jami\" label cell (mirrors corner cell) --\x3e\n <div class=\"eg-footer-label\" :style=\"footerLabelStyle\">{{ t.total }}</div>\n\n \x3c!-- Frozen columns footer --\x3e\n <div v-if=\"getFrozenWidth() > 0\" class=\"eg-frozen-col-footer\" :style=\"frozenColFooterStyle\">\n <div :style=\"{ position: 'relative', height: footerRowHeight + 'px' }\">\n <template v-for=\"(col, ci) in leafCols\">\n <template v-if=\"isColumnPinnedLeft(ci) && !isColHidden(ci)\">\n <div :key=\"'ff-' + ci\" class=\"eg-footer-cell\" :style=\"getFooterCellStyle(ci)\">\n {{ getColumnTotalDisplay(ci) }}\n </div>\n </template>\n </template>\n </div>\n </div>\n\n \x3c!-- Scrollable footer (horizontally synced with body via scrollFooter ref) --\x3e\n <div class=\"eg-scroll-col-footer\" ref=\"scrollFooter\" :style=\"scrollColHeaderStyle\">\n <div class=\"eg-scroll-col-footer-inner\" :style=\"{ width: scrollContentWidth + 'px', height: footerRowHeight + 'px', position: 'relative' }\">\n <template v-for=\"(col, ci) in leafCols\">\n <template v-if=\"isColumnScrollable(ci) && !isColHidden(ci)\">\n <div :key=\"'sf-' + ci\" class=\"eg-footer-cell\" :style=\"getFooterCellStyle(ci)\">\n {{ getColumnTotalDisplay(ci) }}\n </div>\n </template>\n </template>\n </div>\n </div>\n\n \x3c!-- Pinned Right columns footer --\x3e\n <div v-if=\"hasPinnedRight\" class=\"eg-pinned-right-footer\" :style=\"pinnedRightFooterStyle\">\n <div :style=\"{ position: 'relative', height: footerRowHeight + 'px' }\">\n <div\n v-for=\"col in pinnedRightCols\"\n :key=\"'prf-' + col._flatIndex\"\n class=\"eg-footer-cell\"\n :style=\"getFooterCellStyle(col._flatIndex)\"\n >{{ getColumnTotalDisplay(col._flatIndex) }}</div>\n </div>\n </div>\n </div>\x3c!-- end footer-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 (flat select) --\x3e\n <eg-select\n v-if=\"selectDropdown && !selectDropdown.tree\"\n ref=\"selectDropdownComp\"\n :value=\"selectDropdown.currentValue\"\n :options=\"selectDropdown.rawOptions\"\n :enable-search=\"selectDropdown.enableSearch\"\n :trigger-rect=\"selectDropdown.rect\"\n :dropdown-only=\"true\"\n @select=\"onComponentSelect\"\n @close=\"closeSelectDropdown\"\n />\n \x3c!-- Tree select dropdown overlay --\x3e\n <eg-tree-select\n v-if=\"selectDropdown && selectDropdown.tree\"\n ref=\"selectDropdownComp\"\n :value=\"selectDropdown.currentValue\"\n :options=\"selectDropdown.rawOptions\"\n :value-key=\"selectDropdown.keys.valueKey\"\n :label-key=\"selectDropdown.keys.labelKey\"\n :children-key=\"selectDropdown.keys.childrenKey\"\n :enable-search=\"selectDropdown.enableSearch\"\n :trigger-rect=\"selectDropdown.rect\"\n :dropdown-only=\"true\"\n @select=\"onComponentSelect\"\n @close=\"closeSelectDropdown\"\n />\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'\nimport EgCheckbox from './EgCheckbox.vue'\nimport EgSelect from './EgSelect.vue'\nimport EgTreeSelect from './EgTreeSelect.vue'\n\nconst SIZE_CONFIG = {\n small: { row: 22, header: 24, footer: 24 },\n middle: { row: 28, header: 28, footer: 28 },\n large: { row: 36, header: 36, footer: 36 }\n}\nconst ROW_HEADER_WIDTH = 50\nconst DEFAULT_COL_WIDTH = 100\nconst OUTLINE_GUTTER = 20\n\nexport default {\n name: 'ExcelGrid',\n\n components: { EgCheckbox, EgSelect, EgTreeSelect },\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 /** Visual size of the grid. Affects row/header height and font sizes. */\n size: { type: String, default: 'middle' },\n\n /** Enable CSS list transitions on row enter/leave (data changes). Disabled during scroll. */\n transitionEnabled: { type: Boolean, default: false },\n\n /** Auto-fit all column widths to content (same as double-clicking each resize handle). Re-applies on data/columns change. */\n autoWidth: { 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 _isScrolling: false,\n\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 // Footer column totals (flat col index → number | null)\n columnTotals: {}\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 hasFooter() { return this.leafCols.some(col => col.showTotal) },\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 _rowTransitionName() {\n return this.transitionEnabled && !this._isScrolling ? 'eg-row' : ''\n },\n\n _sizeConf() { return SIZE_CONFIG[this.size] || SIZE_CONFIG.middle },\n _rowH() { return this._sizeConf.row },\n _headerRowH() { return this._sizeConf.header },\n _footerRowH() { return this._sizeConf.footer },\n\n headerRowHeight() { return this._headerRowH },\n footerRowHeight() { return this._footerRowH },\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, this._rowH, 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\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: (this._headerRowH * 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 footerAreaStyle() {\n return {\n display: 'flex',\n flexDirection: 'row',\n flexShrink: 0,\n position: 'sticky',\n bottom: 0,\n zIndex: 30,\n background: '#f8f9fa',\n boxShadow: '0 -2px 0 0 #d0d0d0'\n }\n },\n\n footerLabelStyle() {\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: this._footerRowH + 'px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n background: '#f0f0f0',\n borderRight: '1px solid #ccc',\n borderTop: '1px solid #ccc',\n boxSizing: 'border-box',\n flexShrink: 0,\n fontSize: '12px',\n fontWeight: 600\n }\n },\n\n frozenColFooterStyle() {\n const w = this.getFrozenWidth()\n return {\n width: w + 'px',\n minWidth: w + 'px',\n height: this._footerRowH + 'px',\n overflow: 'hidden',\n flexShrink: 0\n }\n },\n\n pinnedRightFooterStyle() {\n return {\n width: this.pinnedRightWidth + 'px',\n minWidth: this.pinnedRightWidth + 'px',\n height: this._footerRowH + 'px',\n overflow: 'hidden',\n flexShrink: 0,\n borderLeft: '1px solid #ccc'\n }\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 size() {\n this.rowHeights = {}\n this.$nextTick(() => this.updateVirtualScroll())\n },\n\n autoWidth(v) {\n if (v) this._applyAutoWidth()\n },\n\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 this.computeColumnTotals()\n this.updateVirtualScroll()\n this._applyAutoWidth()\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 this.computeColumnTotals()\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 this._applyAutoWidth()\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 window.addEventListener('scroll', this._onWindowScrollForDropdown, true)\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 window.removeEventListener('scroll', this._onWindowScrollForDropdown, true)\n clearTimeout(this._scrollEndTimer)\n },\n\n methods: {\n // =========================================================\n // VIRTUAL SCROLL\n // =========================================================\n updateVirtualScroll() {\n const footerH = this.hasFooter ? this._footerRowH : 0\n const containerH = this.height - (this._headerRowH * this.headerDepth) - footerH\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.scrollFooter) this.$refs.scrollFooter.scrollLeft = target.scrollLeft\n if (this.$refs.scrollRowHeaders) this.$refs.scrollRowHeaders.scrollTop = target.scrollTop\n\n // Close dropdown on scroll — fixed-position dropdown drifts away from the moving cell\n if (this.selectDropdown) this.closeSelectDropdown()\n\n // Suppress row transitions during scroll to avoid animation on every virtual-scroll paint\n if (this.transitionEnabled) {\n this._isScrolling = true\n clearTimeout(this._scrollEndTimer)\n this._scrollEndTimer = setTimeout(() => { this._isScrolling = false }, 150)\n }\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] || this._rowH\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: (this._headerRowH * rowIndex) + 'px',\n left: left + 'px',\n width: width + 'px',\n height: (this._headerRowH * 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 && this.isSelectLikeCol(col)),\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 'eg-cell--has-suffix': !!(col && this.hasSuffixSlot(col))\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: (this._headerRowH * rowIndex) + 'px',\n left: left + 'px',\n width: width + 'px',\n height: (this._headerRowH * 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 // =========================================================\n // FOOTER (column totals)\n // =========================================================\n computeColumnTotals() {\n const totals = {}\n const rows = this._dataRaw || []\n this.leafCols.forEach(col => {\n if (!col.showTotal) return\n let sum = 0\n let hasNumber = false\n for (let ri = 0; ri < rows.length; ri++) {\n const row = rows[ri]\n const v = row ? row[col.key] : undefined\n if (typeof v === 'number' && isFinite(v)) {\n sum += v\n hasNumber = true\n }\n }\n totals[col._flatIndex] = hasNumber ? Math.round(sum * 1e6) / 1e6 : null\n })\n this.columnTotals = totals\n },\n\n getColumnTotalDisplay(ci) {\n const col = this.leafCols[ci]\n if (!col || !col.showTotal) return ''\n const v = this.columnTotals[ci]\n return v === null || v === undefined ? '-' : v\n },\n\n getFooterCellStyle(ci) {\n const zone = this.isColumnPinnedLeft(ci) ? 'left' : (this.isColumnPinnedRight(ci) ? 'right' : 'scroll')\n const left = this.getZoneColLeft(ci, zone)\n const w = this.getColW(ci)\n return {\n position: 'absolute',\n left: left + 'px',\n top: '0px',\n width: w + 'px',\n minWidth: w + 'px',\n height: this._footerRowH + 'px'\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, this._rowH, 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 const editCol = this.leafCols[this.editingCell.col]\n if (editCol && this._hasCustomEdit(editCol)) {\n this.cancelEdit()\n } else {\n this.commitEdit()\n }\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 && this.isSelectLikeCol(col) && !this._hasCustomEdit(col) && 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 && this.isSelectLikeCol(col) && !this._hasCustomEdit(col)) {\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 // Delegate keyboard navigation to the active dropdown component\n if (this.selectDropdown) {\n const comp = this.$refs.selectDropdownComp\n if (comp && comp.handleKey) comp.handleKey(e)\n return\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 && this.isSelectLikeCol(activeCol) && !this._hasCustomEdit(activeCol)) {\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 - this._headerRowH * this.headerDepth) / this._rowH)\n this.moveActive(-visibleRows, 0, e)\n return\n }\n if (key === 'PageDown') {\n e.preventDefault()\n const visibleRows = Math.floor((this.height - this._headerRowH * this.headerDepth) / this._rowH)\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 && this.isSelectLikeCol(activeCol) && !this._hasCustomEdit(activeCol)) {\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, this._rowH, 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 (!this._hasCustomEdit(col)) {\n if (this.isSelectLikeCol(col)) { this.openSelectDropdownForActive(); return }\n if (col.type === 'checkbox') return\n }\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 hasCellSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['cell-' + col.key])\n },\n hasLegacyEditSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['edit-' + col.key])\n },\n hasPrefixSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['prefix-' + col.key])\n },\n hasSuffixSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['suffix-' + col.key])\n },\n _hasCustomEdit(col) {\n // For select/treeSelect: cell-{key} slot only customizes display; dropdown still opens normally\n if (col.type === 'select' || col.type === 'treeSelect') {\n return this.hasLegacyEditSlot(col)\n }\n return this.hasCellSlot(col) || this.hasLegacyEditSlot(col)\n },\n\n commitEditSlot(value) {\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 if (!this.isCellValueValid(row, col, value)) {\n this._showInvalidCell(row, col, value)\n this.editingCell = null\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 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 // =========================================================\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) + TREE SELECT (3.22)\n // =========================================================\n\n isSelectLikeCol(col) {\n return !!(col && (col.type === 'select' || col.type === 'treeSelect'))\n },\n\n treeOptionKeys(col) {\n return {\n valueKey: col.optionValueKey || 'value',\n labelKey: col.optionLabelKey || 'label',\n childrenKey: col.optionChildrenKey || 'children'\n }\n },\n\n _treeFindLabel(nodes, keys, value) {\n for (const node of (nodes || [])) {\n if (String(node[keys.valueKey]) === value) {\n return node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey])\n }\n const found = this._treeFindLabel(node[keys.childrenKey], keys, value)\n if (found != null) return found\n }\n return null\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 col = this.leafCols[ci]\n const v = value == null ? '' : String(value)\n if (col && col.type === 'treeSelect') {\n if (!Array.isArray(col.options)) return v\n const found = this._treeFindLabel(col.options, this.treeOptionKeys(col), v)\n return found != null ? found : v\n }\n const opts = this.colOptions(ci)\n if (!opts.length) return v\n const found = opts.find(o => String(o.value) === v)\n return found ? found.label : v\n },\n\n _buildSelectDropdownState(ri, ci, col, currentValue, rect) {\n const rectObj = { top: rect.top, bottom: rect.bottom, left: rect.left, width: rect.width }\n return {\n row: ri, col: ci,\n tree: col.type === 'treeSelect',\n keys: col.type === 'treeSelect' ? this.treeOptionKeys(col) : null,\n rawOptions: col.options || [],\n currentValue,\n rect: rectObj,\n enableSearch: !!col.enableSearch\n }\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 this.selectDropdown = this._buildSelectDropdownState(ri, ci, col, currentValue, rect)\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 this.selectDropdown = this._buildSelectDropdownState(row, col, colDef, currentValue, rect)\n })\n },\n\n onComponentSelect({ value, label }) {\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 if (typeof colDef.onSelect === 'function') {\n colDef.onSelect({ value, label }, newData[row], row, colDef)\n }\n }\n this.closeSelectDropdown()\n this.$refs.wrapper && this.$refs.wrapper.focus()\n },\n\n closeSelectDropdown() {\n this.selectDropdown = null\n },\n\n _onWindowScrollForDropdown(e) {\n if (!this.selectDropdown) return\n const comp = this.$refs.selectDropdownComp\n const dd = comp && comp.$el ? comp.$el : null\n if (dd && dd.contains(e.target)) return\n this.closeSelectDropdown()\n },\n\n _onDocMouseDownForDropdown(e) {\n if (!this.selectDropdown) return\n const comp = this.$refs.selectDropdownComp\n const dd = comp && comp.$el ? comp.$el : null\n if (dd && dd.contains(e.target)) return\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 _applyAutoWidth() {\n if (!this.autoWidth || !this.leafCols.length) return\n this.leafCols.forEach((col, ci) => {\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 this.$set(this.colWidths, ci, Math.max(50, maxLen * 8 + 16))\n })\n this._cellStyleCache = Object.create(null)\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.scrollFooter) this.$refs.scrollFooter.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/* ===== FOOTER AREA (column totals) ===== */\n.eg-footer-area {\n border-top: 1px solid #ccc;\n}\n\n.eg-frozen-col-footer,\n.eg-scroll-col-footer,\n.eg-pinned-right-footer {\n position: relative;\n}\n\n.eg-footer-cell {\n box-sizing: border-box;\n border-right: 1px solid #e0e0e0;\n overflow: hidden;\n white-space: nowrap;\n font-size: 13px;\n font-weight: 600;\n padding: 0 4px;\n position: absolute;\n top: 0;\n background: #f8f9fa;\n display: flex;\n align-items: center;\n justify-content: flex-end;\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-value {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n.eg-cell--has-suffix.eg-cell-select::after { display: none; }\n.eg-cell-select-caret-inline {\n flex-shrink: 0;\n color: #aaa;\n font-size: 11px;\n margin-right: 2px;\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 padding: 0 2px;\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/* ===== ROW TRANSITION ===== */\n.eg-row-enter-active,\n.eg-row-leave-active {\n transition: opacity 0.18s ease, transform 0.18s ease;\n}\n.eg-row-enter,\n.eg-row-leave-to {\n opacity: 0;\n transform: translateY(-4px);\n}\n\n/* ===== SIZE VARIANTS ===== */\n\n/* small */\n.eg-size-small .eg-cell {\n font-size: 12px;\n padding: 0 3px;\n}\n.eg-size-small .eg-cell-input {\n font-size: 12px;\n}\n.eg-size-small .eg-header-cell {\n font-size: 11px;\n}\n.eg-size-small .eg-footer-cell {\n font-size: 12px;\n}\n.eg-size-small .eg-row-header-cell {\n font-size: 10px;\n}\n.eg-size-small .eg-select-option {\n font-size: 12px;\n padding: 4px 10px;\n}\n.eg-size-small .eg-context-menu,\n.eg-size-small .eg-context-item {\n font-size: 12px;\n}\n.eg-size-small .eg-context-item {\n padding: 4px 14px;\n}\n\n/* large */\n.eg-size-large .eg-cell {\n font-size: 14px;\n padding: 0 6px;\n}\n.eg-size-large .eg-cell-input {\n font-size: 14px;\n}\n.eg-size-large .eg-header-cell {\n font-size: 13px;\n}\n.eg-size-large .eg-footer-cell {\n font-size: 14px;\n}\n.eg-size-large .eg-row-header-cell {\n font-size: 12px;\n}\n.eg-size-large .eg-select-option {\n font-size: 14px;\n padding: 8px 14px;\n}\n.eg-size-large .eg-context-menu,\n.eg-size-large .eg-context-item {\n font-size: 14px;\n}\n.eg-size-large .eg-context-item {\n padding: 8px 18px;\n}\n</style>\n\n<style>\n/* ── EgSelect / EgTreeSelect dropdown (non-scoped: injected by ExcelGrid on mount) ── */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: 13px;\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n background: #fff;\n cursor: pointer;\n user-select: none;\n color: #333;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover { border-color: #1a73e8; }\n.eg-select-trigger--open {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-trigger--disabled { background: #f5f5f5; cursor: default; color: #aaa; }\n.eg-select-trigger-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.eg-select-placeholder { color: #aaa; }\n.eg-select-caret { flex-shrink: 0; color: #888; font-size: 12px; }\n.eg-select-dropdown {\n background: #fff;\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: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid #ebebeb;\n position: sticky;\n top: 0;\n background: #fff;\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 13px;\n outline: none;\n color: #333;\n background: #fff;\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: #333;\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted { background: #f0f4ff; }\n.eg-select-option--active { font-weight: 600; color: #1a73e8; }\n.eg-select-option--active.eg-select-option--highlighted,\n.eg-select-option--active:hover { background: #e8f0fe; }\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: #888;\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf { visibility: hidden; }\n/* EgCheckbox */\n.eg-checkbox {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n cursor: pointer;\n}\n.eg-checkbox-input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\n.eg-checkbox-box {\n position: relative;\n display: block;\n width: 19px;\n height: 19px;\n box-sizing: border-box;\n background-color: #fff;\n background-repeat: no-repeat;\n background-position: center;\n background-size: 0 0;\n border: 1.5px solid #c2c8d1;\n border-radius: 4px;\n transition: background-color .12s ease, border-color .12s ease, background-size .12s ease;\n}\n.eg-checkbox:hover .eg-checkbox-box { border-color: #1a73e8; }\n.eg-checkbox-input:checked + .eg-checkbox-box {\n background-color: #1a73e8;\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZD0iTTMuNSA4LjNsMyAzIDYtNi42IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==\");\n background-size: 70% 70%;\n border-color: #1a73e8;\n}\n.eg-checkbox-input:focus-visible + .eg-checkbox-box { box-shadow: 0 0 0 3px rgba(26,115,232,0.2); }\n.eg-checkbox--disabled,\n.eg-checkbox--disabled .eg-checkbox-input { cursor: default; }\n.eg-checkbox-input:disabled + .eg-checkbox-box { background-color: #f0f1f3; border-color: #dadde2; }\n.eg-checkbox-input:disabled:checked + .eg-checkbox-box { background-color: #b9c2ce; border-color: #b9c2ce; }\n</style>\n"]},media:void 0}),n("data-v-29e88078_1",{source:'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* ── EgSelect / EgTreeSelect dropdown (non-scoped: injected by ExcelGrid on mount) ── */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: 13px;\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n background: #fff;\n cursor: pointer;\n user-select: none;\n color: #333;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover { border-color: #1a73e8;\n}\n.eg-select-trigger--open {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-trigger--disabled { background: #f5f5f5; cursor: default; color: #aaa;\n}\n.eg-select-trigger-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n.eg-select-placeholder { color: #aaa;\n}\n.eg-select-caret { flex-shrink: 0; color: #888; font-size: 12px;\n}\n.eg-select-dropdown {\n background: #fff;\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: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid #ebebeb;\n position: sticky;\n top: 0;\n background: #fff;\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 13px;\n outline: none;\n color: #333;\n background: #fff;\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: #333;\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted { background: #f0f4ff;\n}\n.eg-select-option--active { font-weight: 600; color: #1a73e8;\n}\n.eg-select-option--active.eg-select-option--highlighted,\n.eg-select-option--active:hover { background: #e8f0fe;\n}\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: #888;\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf { visibility: hidden;\n}\n/* EgCheckbox */\n.eg-checkbox {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n cursor: pointer;\n}\n.eg-checkbox-input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\n.eg-checkbox-box {\n position: relative;\n display: block;\n width: 19px;\n height: 19px;\n box-sizing: border-box;\n background-color: #fff;\n background-repeat: no-repeat;\n background-position: center;\n background-size: 0 0;\n border: 1.5px solid #c2c8d1;\n border-radius: 4px;\n transition: background-color .12s ease, border-color .12s ease, background-size .12s ease;\n}\n.eg-checkbox:hover .eg-checkbox-box { border-color: #1a73e8;\n}\n.eg-checkbox-input:checked + .eg-checkbox-box {\n background-color: #1a73e8;\n background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZD0iTTMuNSA4LjNsMyAzIDYtNi42IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==");\n background-size: 70% 70%;\n border-color: #1a73e8;\n}\n.eg-checkbox-input:focus-visible + .eg-checkbox-box { box-shadow: 0 0 0 3px rgba(26,115,232,0.2);\n}\n.eg-checkbox--disabled,\n.eg-checkbox--disabled .eg-checkbox-input { cursor: default;\n}\n.eg-checkbox-input:disabled + .eg-checkbox-box { background-color: #f0f1f3; border-color: #dadde2;\n}\n.eg-checkbox-input:disabled:checked + .eg-checkbox-box { background-color: #b9c2ce; border-color: #b9c2ce;\n}\n',map:{version:3,sources:["/Users/macmini/Projects/Realsoft/vue2-excel-grid/src/components/ExcelGrid.vue"],names:[],mappings:";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgzHA,wFAAA;AACA;EACA,kBAAA;EACA,qBAAA;EACA,gBAAA;EACA,eAAA;EACA,oBAAA;AACA;AACA;EACA,aAAA;EACA,mBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;EACA,gBAAA;EACA,sBAAA;EACA,yBAAA;EACA,kBAAA;EACA,gBAAA;EACA,eAAA;EACA,iBAAA;EACA,WAAA;EACA,wDAAA;AACA;AACA,2BAAA,qBAAA;AAAA;AACA;EACA,qBAAA;EACA,2CAAA;AACA;AACA,+BAAA,mBAAA,EAAA,eAAA,EAAA,WAAA;AAAA;AACA,2BAAA,OAAA,EAAA,gBAAA,EAAA,uBAAA,EAAA,mBAAA;AAAA;AACA,yBAAA,WAAA;AAAA;AACA,mBAAA,cAAA,EAAA,WAAA,EAAA,eAAA;AAAA;AACA;EACA,gBAAA;EACA,yBAAA;EACA,kBAAA;EACA,uCAAA;EACA,gBAAA;EACA,iBAAA;EACA,cAAA;AACA;AACA;EACA,kBAAA;EACA,SAAA;EACA,OAAA;EACA,eAAA;EACA,YAAA;EACA,eAAA;AACA;AACA;EACA,gBAAA;EACA,gCAAA;EACA,gBAAA;EACA,MAAA;EACA,gBAAA;EACA,UAAA;AACA;AACA;EACA,WAAA;EACA,sBAAA;EACA,yBAAA;EACA,kBAAA;EACA,gBAAA;EACA,eAAA;EACA,aAAA;EACA,WAAA;EACA,gBAAA;EACA,oBAAA;AACA;AACA;EACA,qBAAA;EACA,2CAAA;AACA;AACA;EACA,aAAA;EACA,uBAAA;EACA,QAAA;EACA,iBAAA;EACA,eAAA;EACA,eAAA;EACA,mBAAA;EACA,sBAAA;EACA,iBAAA;EACA,WAAA;AACA;AACA;iCACA,mBAAA;AAAA;AACA,4BAAA,gBAAA,EAAA,cAAA;AAAA;AACA;kCACA,mBAAA;AAAA;AACA;EACA,UAAA;EACA,WAAA;EACA,kBAAA;EACA,cAAA;EACA,qBAAA;EACA,eAAA;AACA;AACA,gBAAA;AACA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;EACA,cAAA;EACA,eAAA;EACA,WAAA;EACA,eAAA;AACA;AACA,wBAAA,kBAAA;AAAA;AACA,eAAA;AACA;EACA,kBAAA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;EACA,eAAA;AACA;AACA;EACA,kBAAA;EACA,QAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA;EACA,UAAA;EACA,eAAA;AACA;AACA;EACA,kBAAA;EACA,cAAA;EACA,WAAA;EACA,YAAA;EACA,sBAAA;EACA,sBAAA;EACA,4BAAA;EACA,2BAAA;EACA,oBAAA;EACA,2BAAA;EACA,kBAAA;EACA,yFAAA;AACA;AACA,sCAAA,qBAAA;AAAA;AACA;EACA,yBAAA;EACA,+SAAA;EACA,wBAAA;EACA,qBAAA;AACA;AACA,sDAAA,0CAAA;AAAA;AACA;4CACA,eAAA;AAAA;AACA,iDAAA,yBAAA,EAAA,qBAAA;AAAA;AACA,yDAAA,yBAAA,EAAA,qBAAA;AAAA",file:"ExcelGrid.vue",sourcesContent:["<template>\n <div\n class=\"eg-wrapper\"\n :class=\"'eg-size-' + size\"\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\"\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\"\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\"\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 <transition :key=\"'tr-fc-' + ri\" :name=\"_rowTransitionName\">\n <div v-if=\"!isRowHidden(ri)\" :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 <slot v-if=\"hasCellSlot(col)\" :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"true\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <slot v-else-if=\"hasLegacyEditSlot(col)\" :name=\"'edit-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <input v-else 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 <eg-checkbox\n :value=\"isCheckboxChecked(ri, ci)\"\n :disabled=\"!isCellEditable(ri, ci)\"\n @change=\"(v) => setCheckboxValue(ri, ci, v)\"\n />\n </template>\n <template v-else>\n <slot :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"false\" :done=\"null\" :cancel=\"null\">\n <slot v-if=\"hasPrefixSlot(col)\" :name=\"'prefix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" /><span v-if=\"hasPrefixSlot(col) || hasSuffixSlot(col)\" class=\"eg-cell-value\">{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</span><template v-else>{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</template><span v-if=\"hasSuffixSlot(col) && isSelectLikeCol(col)\" class=\"eg-cell-select-caret-inline\">▾</span><slot v-if=\"hasSuffixSlot(col)\" :name=\"'suffix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" />\n </slot>\n </template>\n </div>\n </template>\n </template>\n </div>\n </transition>\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 <transition :key=\"'tr-m-' + ri\" :name=\"_rowTransitionName\">\n <div\n v-if=\"!isRowHidden(ri)\"\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 <slot v-if=\"hasCellSlot(col)\" :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"true\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <slot v-else-if=\"hasLegacyEditSlot(col)\" :name=\"'edit-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <input\n v-else\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 <eg-checkbox\n :value=\"isCheckboxChecked(ri, ci)\"\n :disabled=\"!isCellEditable(ri, ci)\"\n @change=\"(v) => setCheckboxValue(ri, ci, v)\"\n />\n </template>\n <template v-else>\n <slot :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" :isEditing=\"false\" :done=\"null\" :cancel=\"null\">\n <slot v-if=\"hasPrefixSlot(col)\" :name=\"'prefix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" /><span v-if=\"hasPrefixSlot(col) || hasSuffixSlot(col)\" class=\"eg-cell-value\">{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</span><template v-else>{{ isSelectLikeCol(col) ? getSelectLabel(ci, getCellValue(ri, ci)) : getCellValue(ri, ci) }}</template><span v-if=\"hasSuffixSlot(col) && isSelectLikeCol(col)\" class=\"eg-cell-select-caret-inline\">▾</span><slot v-if=\"hasSuffixSlot(col)\" :name=\"'suffix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, ci)\" />\n </slot>\n </template>\n </div>\n </template>\n </template>\n </div>\n </transition>\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 <transition :key=\"'tr-pr-' + ri\" :name=\"_rowTransitionName\">\n <div v-if=\"!isRowHidden(ri)\" :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 <slot v-if=\"hasCellSlot(col)\" :name=\"'cell-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" :isEditing=\"true\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <slot v-else-if=\"hasLegacyEditSlot(col)\" :name=\"'edit-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" :done=\"(v) => commitEditSlot(v)\" :cancel=\"cancelEdit\" />\n <input v-else 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 <eg-checkbox\n :value=\"isCheckboxChecked(ri, col._flatIndex)\"\n :disabled=\"!isCellEditable(ri, col._flatIndex)\"\n @change=\"(v) => setCheckboxValue(ri, col._flatIndex, v)\"\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)\" :isEditing=\"false\" :done=\"null\" :cancel=\"null\">\n <slot v-if=\"hasPrefixSlot(col)\" :name=\"'prefix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" /><span v-if=\"hasPrefixSlot(col) || hasSuffixSlot(col)\" class=\"eg-cell-value\">{{ isSelectLikeCol(col) ? getSelectLabel(col._flatIndex, getCellValue(ri, col._flatIndex)) : getCellValue(ri, col._flatIndex) }}</span><template v-else>{{ isSelectLikeCol(col) ? getSelectLabel(col._flatIndex, getCellValue(ri, col._flatIndex)) : getCellValue(ri, col._flatIndex) }}</template><span v-if=\"hasSuffixSlot(col) && isSelectLikeCol(col)\" class=\"eg-cell-select-caret-inline\">▾</span><slot v-if=\"hasSuffixSlot(col)\" :name=\"'suffix-' + col.key\" :row=\"_dataRaw[ri]\" :rowIndex=\"ri\" :column=\"col\" :value=\"getCellValue(ri, col._flatIndex)\" />\n </slot>\n </template>\n </div>\n </template>\n </div>\n </transition>\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!-- ===== FOOTER AREA (column totals) ===== --\x3e\n <div v-if=\"hasFooter\" class=\"eg-footer-area\" :style=\"footerAreaStyle\">\n\n \x3c!-- \"Jami\" label cell (mirrors corner cell) --\x3e\n <div class=\"eg-footer-label\" :style=\"footerLabelStyle\">{{ t.total }}</div>\n\n \x3c!-- Frozen columns footer --\x3e\n <div v-if=\"getFrozenWidth() > 0\" class=\"eg-frozen-col-footer\" :style=\"frozenColFooterStyle\">\n <div :style=\"{ position: 'relative', height: footerRowHeight + 'px' }\">\n <template v-for=\"(col, ci) in leafCols\">\n <template v-if=\"isColumnPinnedLeft(ci) && !isColHidden(ci)\">\n <div :key=\"'ff-' + ci\" class=\"eg-footer-cell\" :style=\"getFooterCellStyle(ci)\">\n {{ getColumnTotalDisplay(ci) }}\n </div>\n </template>\n </template>\n </div>\n </div>\n\n \x3c!-- Scrollable footer (horizontally synced with body via scrollFooter ref) --\x3e\n <div class=\"eg-scroll-col-footer\" ref=\"scrollFooter\" :style=\"scrollColHeaderStyle\">\n <div class=\"eg-scroll-col-footer-inner\" :style=\"{ width: scrollContentWidth + 'px', height: footerRowHeight + 'px', position: 'relative' }\">\n <template v-for=\"(col, ci) in leafCols\">\n <template v-if=\"isColumnScrollable(ci) && !isColHidden(ci)\">\n <div :key=\"'sf-' + ci\" class=\"eg-footer-cell\" :style=\"getFooterCellStyle(ci)\">\n {{ getColumnTotalDisplay(ci) }}\n </div>\n </template>\n </template>\n </div>\n </div>\n\n \x3c!-- Pinned Right columns footer --\x3e\n <div v-if=\"hasPinnedRight\" class=\"eg-pinned-right-footer\" :style=\"pinnedRightFooterStyle\">\n <div :style=\"{ position: 'relative', height: footerRowHeight + 'px' }\">\n <div\n v-for=\"col in pinnedRightCols\"\n :key=\"'prf-' + col._flatIndex\"\n class=\"eg-footer-cell\"\n :style=\"getFooterCellStyle(col._flatIndex)\"\n >{{ getColumnTotalDisplay(col._flatIndex) }}</div>\n </div>\n </div>\n </div>\x3c!-- end footer-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 (flat select) --\x3e\n <eg-select\n v-if=\"selectDropdown && !selectDropdown.tree\"\n ref=\"selectDropdownComp\"\n :value=\"selectDropdown.currentValue\"\n :options=\"selectDropdown.rawOptions\"\n :enable-search=\"selectDropdown.enableSearch\"\n :trigger-rect=\"selectDropdown.rect\"\n :dropdown-only=\"true\"\n @select=\"onComponentSelect\"\n @close=\"closeSelectDropdown\"\n />\n \x3c!-- Tree select dropdown overlay --\x3e\n <eg-tree-select\n v-if=\"selectDropdown && selectDropdown.tree\"\n ref=\"selectDropdownComp\"\n :value=\"selectDropdown.currentValue\"\n :options=\"selectDropdown.rawOptions\"\n :value-key=\"selectDropdown.keys.valueKey\"\n :label-key=\"selectDropdown.keys.labelKey\"\n :children-key=\"selectDropdown.keys.childrenKey\"\n :enable-search=\"selectDropdown.enableSearch\"\n :trigger-rect=\"selectDropdown.rect\"\n :dropdown-only=\"true\"\n @select=\"onComponentSelect\"\n @close=\"closeSelectDropdown\"\n />\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'\nimport EgCheckbox from './EgCheckbox.vue'\nimport EgSelect from './EgSelect.vue'\nimport EgTreeSelect from './EgTreeSelect.vue'\n\nconst SIZE_CONFIG = {\n small: { row: 22, header: 24, footer: 24 },\n middle: { row: 28, header: 28, footer: 28 },\n large: { row: 36, header: 36, footer: 36 }\n}\nconst ROW_HEADER_WIDTH = 50\nconst DEFAULT_COL_WIDTH = 100\nconst OUTLINE_GUTTER = 20\n\nexport default {\n name: 'ExcelGrid',\n\n components: { EgCheckbox, EgSelect, EgTreeSelect },\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 /** Visual size of the grid. Affects row/header height and font sizes. */\n size: { type: String, default: 'middle' },\n\n /** Enable CSS list transitions on row enter/leave (data changes). Disabled during scroll. */\n transitionEnabled: { type: Boolean, default: false },\n\n /** Auto-fit all column widths to content (same as double-clicking each resize handle). Re-applies on data/columns change. */\n autoWidth: { 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 _isScrolling: false,\n\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 // Footer column totals (flat col index → number | null)\n columnTotals: {}\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 hasFooter() { return this.leafCols.some(col => col.showTotal) },\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 _rowTransitionName() {\n return this.transitionEnabled && !this._isScrolling ? 'eg-row' : ''\n },\n\n _sizeConf() { return SIZE_CONFIG[this.size] || SIZE_CONFIG.middle },\n _rowH() { return this._sizeConf.row },\n _headerRowH() { return this._sizeConf.header },\n _footerRowH() { return this._sizeConf.footer },\n\n headerRowHeight() { return this._headerRowH },\n footerRowHeight() { return this._footerRowH },\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, this._rowH, 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\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: (this._headerRowH * 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 footerAreaStyle() {\n return {\n display: 'flex',\n flexDirection: 'row',\n flexShrink: 0,\n position: 'sticky',\n bottom: 0,\n zIndex: 30,\n background: '#f8f9fa',\n boxShadow: '0 -2px 0 0 #d0d0d0'\n }\n },\n\n footerLabelStyle() {\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: this._footerRowH + 'px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n background: '#f0f0f0',\n borderRight: '1px solid #ccc',\n borderTop: '1px solid #ccc',\n boxSizing: 'border-box',\n flexShrink: 0,\n fontSize: '12px',\n fontWeight: 600\n }\n },\n\n frozenColFooterStyle() {\n const w = this.getFrozenWidth()\n return {\n width: w + 'px',\n minWidth: w + 'px',\n height: this._footerRowH + 'px',\n overflow: 'hidden',\n flexShrink: 0\n }\n },\n\n pinnedRightFooterStyle() {\n return {\n width: this.pinnedRightWidth + 'px',\n minWidth: this.pinnedRightWidth + 'px',\n height: this._footerRowH + 'px',\n overflow: 'hidden',\n flexShrink: 0,\n borderLeft: '1px solid #ccc'\n }\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 size() {\n this.rowHeights = {}\n this.$nextTick(() => this.updateVirtualScroll())\n },\n\n autoWidth(v) {\n if (v) this._applyAutoWidth()\n },\n\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 this.computeColumnTotals()\n this.updateVirtualScroll()\n this._applyAutoWidth()\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 this.computeColumnTotals()\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 this._applyAutoWidth()\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 window.addEventListener('scroll', this._onWindowScrollForDropdown, true)\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 window.removeEventListener('scroll', this._onWindowScrollForDropdown, true)\n clearTimeout(this._scrollEndTimer)\n },\n\n methods: {\n // =========================================================\n // VIRTUAL SCROLL\n // =========================================================\n updateVirtualScroll() {\n const footerH = this.hasFooter ? this._footerRowH : 0\n const containerH = this.height - (this._headerRowH * this.headerDepth) - footerH\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.scrollFooter) this.$refs.scrollFooter.scrollLeft = target.scrollLeft\n if (this.$refs.scrollRowHeaders) this.$refs.scrollRowHeaders.scrollTop = target.scrollTop\n\n // Close dropdown on scroll — fixed-position dropdown drifts away from the moving cell\n if (this.selectDropdown) this.closeSelectDropdown()\n\n // Suppress row transitions during scroll to avoid animation on every virtual-scroll paint\n if (this.transitionEnabled) {\n this._isScrolling = true\n clearTimeout(this._scrollEndTimer)\n this._scrollEndTimer = setTimeout(() => { this._isScrolling = false }, 150)\n }\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] || this._rowH\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: (this._headerRowH * rowIndex) + 'px',\n left: left + 'px',\n width: width + 'px',\n height: (this._headerRowH * 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 && this.isSelectLikeCol(col)),\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 'eg-cell--has-suffix': !!(col && this.hasSuffixSlot(col))\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: (this._headerRowH * rowIndex) + 'px',\n left: left + 'px',\n width: width + 'px',\n height: (this._headerRowH * 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 // =========================================================\n // FOOTER (column totals)\n // =========================================================\n computeColumnTotals() {\n const totals = {}\n const rows = this._dataRaw || []\n this.leafCols.forEach(col => {\n if (!col.showTotal) return\n let sum = 0\n let hasNumber = false\n for (let ri = 0; ri < rows.length; ri++) {\n const row = rows[ri]\n const v = row ? row[col.key] : undefined\n if (typeof v === 'number' && isFinite(v)) {\n sum += v\n hasNumber = true\n }\n }\n totals[col._flatIndex] = hasNumber ? Math.round(sum * 1e6) / 1e6 : null\n })\n this.columnTotals = totals\n },\n\n getColumnTotalDisplay(ci) {\n const col = this.leafCols[ci]\n if (!col || !col.showTotal) return ''\n const v = this.columnTotals[ci]\n return v === null || v === undefined ? '-' : v\n },\n\n getFooterCellStyle(ci) {\n const zone = this.isColumnPinnedLeft(ci) ? 'left' : (this.isColumnPinnedRight(ci) ? 'right' : 'scroll')\n const left = this.getZoneColLeft(ci, zone)\n const w = this.getColW(ci)\n return {\n position: 'absolute',\n left: left + 'px',\n top: '0px',\n width: w + 'px',\n minWidth: w + 'px',\n height: this._footerRowH + 'px'\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, this._rowH, 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 const editCol = this.leafCols[this.editingCell.col]\n if (editCol && this._hasCustomEdit(editCol)) {\n this.cancelEdit()\n } else {\n this.commitEdit()\n }\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 && this.isSelectLikeCol(col) && !this._hasCustomEdit(col) && 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 && this.isSelectLikeCol(col) && !this._hasCustomEdit(col)) {\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 // Delegate keyboard navigation to the active dropdown component\n if (this.selectDropdown) {\n const comp = this.$refs.selectDropdownComp\n if (comp && comp.handleKey) comp.handleKey(e)\n return\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 && this.isSelectLikeCol(activeCol) && !this._hasCustomEdit(activeCol)) {\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 - this._headerRowH * this.headerDepth) / this._rowH)\n this.moveActive(-visibleRows, 0, e)\n return\n }\n if (key === 'PageDown') {\n e.preventDefault()\n const visibleRows = Math.floor((this.height - this._headerRowH * this.headerDepth) / this._rowH)\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 && this.isSelectLikeCol(activeCol) && !this._hasCustomEdit(activeCol)) {\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, this._rowH, 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 (!this._hasCustomEdit(col)) {\n if (this.isSelectLikeCol(col)) { this.openSelectDropdownForActive(); return }\n if (col.type === 'checkbox') return\n }\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 hasCellSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['cell-' + col.key])\n },\n hasLegacyEditSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['edit-' + col.key])\n },\n hasPrefixSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['prefix-' + col.key])\n },\n hasSuffixSlot(col) {\n return !!(col && col.key && this.$scopedSlots && this.$scopedSlots['suffix-' + col.key])\n },\n _hasCustomEdit(col) {\n // For select/treeSelect: cell-{key} slot only customizes display; dropdown still opens normally\n if (col.type === 'select' || col.type === 'treeSelect') {\n return this.hasLegacyEditSlot(col)\n }\n return this.hasCellSlot(col) || this.hasLegacyEditSlot(col)\n },\n\n commitEditSlot(value) {\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 if (!this.isCellValueValid(row, col, value)) {\n this._showInvalidCell(row, col, value)\n this.editingCell = null\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 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 // =========================================================\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) + TREE SELECT (3.22)\n // =========================================================\n\n isSelectLikeCol(col) {\n return !!(col && (col.type === 'select' || col.type === 'treeSelect'))\n },\n\n treeOptionKeys(col) {\n return {\n valueKey: col.optionValueKey || 'value',\n labelKey: col.optionLabelKey || 'label',\n childrenKey: col.optionChildrenKey || 'children'\n }\n },\n\n _treeFindLabel(nodes, keys, value) {\n for (const node of (nodes || [])) {\n if (String(node[keys.valueKey]) === value) {\n return node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey])\n }\n const found = this._treeFindLabel(node[keys.childrenKey], keys, value)\n if (found != null) return found\n }\n return null\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 col = this.leafCols[ci]\n const v = value == null ? '' : String(value)\n if (col && col.type === 'treeSelect') {\n if (!Array.isArray(col.options)) return v\n const found = this._treeFindLabel(col.options, this.treeOptionKeys(col), v)\n return found != null ? found : v\n }\n const opts = this.colOptions(ci)\n if (!opts.length) return v\n const found = opts.find(o => String(o.value) === v)\n return found ? found.label : v\n },\n\n _buildSelectDropdownState(ri, ci, col, currentValue, rect) {\n const rectObj = { top: rect.top, bottom: rect.bottom, left: rect.left, width: rect.width }\n return {\n row: ri, col: ci,\n tree: col.type === 'treeSelect',\n keys: col.type === 'treeSelect' ? this.treeOptionKeys(col) : null,\n rawOptions: col.options || [],\n currentValue,\n rect: rectObj,\n enableSearch: !!col.enableSearch\n }\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 this.selectDropdown = this._buildSelectDropdownState(ri, ci, col, currentValue, rect)\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 this.selectDropdown = this._buildSelectDropdownState(row, col, colDef, currentValue, rect)\n })\n },\n\n onComponentSelect({ value, label }) {\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 if (typeof colDef.onSelect === 'function') {\n colDef.onSelect({ value, label }, newData[row], row, colDef)\n }\n }\n this.closeSelectDropdown()\n this.$refs.wrapper && this.$refs.wrapper.focus()\n },\n\n closeSelectDropdown() {\n this.selectDropdown = null\n },\n\n _onWindowScrollForDropdown(e) {\n if (!this.selectDropdown) return\n const comp = this.$refs.selectDropdownComp\n const dd = comp && comp.$el ? comp.$el : null\n if (dd && dd.contains(e.target)) return\n this.closeSelectDropdown()\n },\n\n _onDocMouseDownForDropdown(e) {\n if (!this.selectDropdown) return\n const comp = this.$refs.selectDropdownComp\n const dd = comp && comp.$el ? comp.$el : null\n if (dd && dd.contains(e.target)) return\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 _applyAutoWidth() {\n if (!this.autoWidth || !this.leafCols.length) return\n this.leafCols.forEach((col, ci) => {\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 this.$set(this.colWidths, ci, Math.max(50, maxLen * 8 + 16))\n })\n this._cellStyleCache = Object.create(null)\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.scrollFooter) this.$refs.scrollFooter.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/* ===== FOOTER AREA (column totals) ===== */\n.eg-footer-area {\n border-top: 1px solid #ccc;\n}\n\n.eg-frozen-col-footer,\n.eg-scroll-col-footer,\n.eg-pinned-right-footer {\n position: relative;\n}\n\n.eg-footer-cell {\n box-sizing: border-box;\n border-right: 1px solid #e0e0e0;\n overflow: hidden;\n white-space: nowrap;\n font-size: 13px;\n font-weight: 600;\n padding: 0 4px;\n position: absolute;\n top: 0;\n background: #f8f9fa;\n display: flex;\n align-items: center;\n justify-content: flex-end;\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-value {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n.eg-cell--has-suffix.eg-cell-select::after { display: none; }\n.eg-cell-select-caret-inline {\n flex-shrink: 0;\n color: #aaa;\n font-size: 11px;\n margin-right: 2px;\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 padding: 0 2px;\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/* ===== ROW TRANSITION ===== */\n.eg-row-enter-active,\n.eg-row-leave-active {\n transition: opacity 0.18s ease, transform 0.18s ease;\n}\n.eg-row-enter,\n.eg-row-leave-to {\n opacity: 0;\n transform: translateY(-4px);\n}\n\n/* ===== SIZE VARIANTS ===== */\n\n/* small */\n.eg-size-small .eg-cell {\n font-size: 12px;\n padding: 0 3px;\n}\n.eg-size-small .eg-cell-input {\n font-size: 12px;\n}\n.eg-size-small .eg-header-cell {\n font-size: 11px;\n}\n.eg-size-small .eg-footer-cell {\n font-size: 12px;\n}\n.eg-size-small .eg-row-header-cell {\n font-size: 10px;\n}\n.eg-size-small .eg-select-option {\n font-size: 12px;\n padding: 4px 10px;\n}\n.eg-size-small .eg-context-menu,\n.eg-size-small .eg-context-item {\n font-size: 12px;\n}\n.eg-size-small .eg-context-item {\n padding: 4px 14px;\n}\n\n/* large */\n.eg-size-large .eg-cell {\n font-size: 14px;\n padding: 0 6px;\n}\n.eg-size-large .eg-cell-input {\n font-size: 14px;\n}\n.eg-size-large .eg-header-cell {\n font-size: 13px;\n}\n.eg-size-large .eg-footer-cell {\n font-size: 14px;\n}\n.eg-size-large .eg-row-header-cell {\n font-size: 12px;\n}\n.eg-size-large .eg-select-option {\n font-size: 14px;\n padding: 8px 14px;\n}\n.eg-size-large .eg-context-menu,\n.eg-size-large .eg-context-item {\n font-size: 14px;\n}\n.eg-size-large .eg-context-item {\n padding: 8px 18px;\n}\n</style>\n\n<style>\n/* ── EgSelect / EgTreeSelect dropdown (non-scoped: injected by ExcelGrid on mount) ── */\n.eg-select-wrapper {\n position: relative;\n display: inline-block;\n min-width: 120px;\n font-size: 13px;\n font-family: inherit;\n}\n.eg-select-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 30px;\n padding: 4px 8px;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n background: #fff;\n cursor: pointer;\n user-select: none;\n color: #333;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n.eg-select-trigger:hover { border-color: #1a73e8; }\n.eg-select-trigger--open {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-trigger--disabled { background: #f5f5f5; cursor: default; color: #aaa; }\n.eg-select-trigger-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.eg-select-placeholder { color: #aaa; }\n.eg-select-caret { flex-shrink: 0; color: #888; font-size: 12px; }\n.eg-select-dropdown {\n background: #fff;\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: 320px;\n padding: 4px 0;\n}\n.eg-select-dropdown--floating {\n position: absolute;\n top: 100%;\n left: 0;\n min-width: 100%;\n z-index: 999;\n margin-top: 2px;\n}\n.eg-select-search {\n padding: 6px 8px;\n border-bottom: 1px solid #ebebeb;\n position: sticky;\n top: 0;\n background: #fff;\n z-index: 1;\n}\n.eg-select-search-input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 13px;\n outline: none;\n color: #333;\n background: #fff;\n font-family: inherit;\n}\n.eg-select-search-input:focus {\n border-color: #1a73e8;\n box-shadow: 0 0 0 2px rgba(26,115,232,0.15);\n}\n.eg-select-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n white-space: normal;\n word-break: break-word;\n user-select: none;\n color: #333;\n}\n.eg-select-option:hover,\n.eg-select-option--highlighted { background: #f0f4ff; }\n.eg-select-option--active { font-weight: 600; color: #1a73e8; }\n.eg-select-option--active.eg-select-option--highlighted,\n.eg-select-option--active:hover { background: #e8f0fe; }\n.eg-select-dot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n display: inline-block;\n margin-top: 3px;\n}\n/* Tree toggle */\n.eg-tree-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n font-size: 10px;\n color: #888;\n margin-top: 1px;\n}\n.eg-tree-toggle--leaf { visibility: hidden; }\n/* EgCheckbox */\n.eg-checkbox {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n cursor: pointer;\n}\n.eg-checkbox-input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\n.eg-checkbox-box {\n position: relative;\n display: block;\n width: 19px;\n height: 19px;\n box-sizing: border-box;\n background-color: #fff;\n background-repeat: no-repeat;\n background-position: center;\n background-size: 0 0;\n border: 1.5px solid #c2c8d1;\n border-radius: 4px;\n transition: background-color .12s ease, border-color .12s ease, background-size .12s ease;\n}\n.eg-checkbox:hover .eg-checkbox-box { border-color: #1a73e8; }\n.eg-checkbox-input:checked + .eg-checkbox-box {\n background-color: #1a73e8;\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHBhdGggZD0iTTMuNSA4LjNsMyAzIDYtNi42IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==\");\n background-size: 70% 70%;\n border-color: #1a73e8;\n}\n.eg-checkbox-input:focus-visible + .eg-checkbox-box { box-shadow: 0 0 0 3px rgba(26,115,232,0.2); }\n.eg-checkbox--disabled,\n.eg-checkbox--disabled .eg-checkbox-input { cursor: default; }\n.eg-checkbox-input:disabled + .eg-checkbox-box { background-color: #f0f1f3; border-color: #dadde2; }\n.eg-checkbox-input:disabled:checked + .eg-checkbox-box { background-color: #b9c2ce; border-color: #b9c2ce; }\n</style>\n"]},media:void 0}))},Z,"data-v-29e88078",false,void 0,!1,L,void 0,void 0);var J={install(n){n.component("ExcelGrid",Q),n.component("EgCheckbox",T),n.component("EgSelect",G),n.component("EgTreeSelect",U)}};n.EgCheckbox=T,n.EgSelect=G,n.EgTreeSelect=U,n.ExcelGrid=Q,n.default=J,Object.defineProperty(n,"__esModule",{value:!0})});
|