@toolbox-web/grid 1.29.0 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all.js +2 -2
- package/all.js.map +1 -1
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/lib/core/grid.d.ts +7 -0
- package/lib/core/types.d.ts +2 -0
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts +18 -4
- package/lib/plugins/pinned-columns/index.js +1 -1
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-columns/pinned-columns.d.ts +38 -2
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/print/index.js.map +1 -1
- package/lib/plugins/reorder-columns/index.js.map +1 -1
- package/lib/plugins/reorder-rows/index.js.map +1 -1
- package/lib/plugins/responsive/index.js.map +1 -1
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tooltip/index.js.map +1 -1
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +1 -1
- package/umd/grid.all.umd.js +1 -1
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +1 -1
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/pinned-columns.umd.js +1 -1
- package/umd/plugins/pinned-columns.umd.js.map +1 -1
- package/umd/plugins/reorder-columns.umd.js.map +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/constants"),require("../../core/internal/utils"),require("../../core/plugin/base-plugin")):"function"==typeof define&&define.amd?define(["exports","../../core/constants","../../core/internal/utils","../../core/plugin/base-plugin"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_pinnedColumns={},e.TbwGrid,e.TbwGrid,e.TbwGrid)}(this,function(e,t,s,i){"use strict";function n(e){return e.pinned??e.sticky??e.meta?.pinned??e.meta?.sticky}function r(e,t){return s.resolveInlinePosition(e,t)}function l(e,t){const s=n(e);return!!s&&"left"===r(s,t)}function o(e,t){const s=n(e);return!!s&&"right"===r(s,t)}function d(e){return e.some(e=>null!=n(e))}function c(e,i){const n={addGroupEnd:new Set,removeGroupEnd:new Set},r=Array.from(e.querySelectorAll(".header-row .cell"));if(!r.length)return n;const d=s.getDirection(e);let c=0;for(const s of i)if(l(s,d)){const i=r.find(e=>e.getAttribute("data-field")===s.field);i&&(i.classList.add(t.GridClasses.STICKY_LEFT),i.style.position="sticky",i.style.left=c+"px",e.querySelectorAll(`.data-grid-row .cell[data-field="${s.field}"]`).forEach(e=>{e.classList.add(t.GridClasses.STICKY_LEFT),e.style.position="sticky",e.style.left=c+"px"}),c+=i.offsetWidth)}let f=0;for(const s of[...i].reverse())if(o(s,d)){const i=r.find(e=>e.getAttribute("data-field")===s.field);i&&(i.classList.add(t.GridClasses.STICKY_RIGHT),i.style.position="sticky",i.style.right=f+"px",e.querySelectorAll(`.data-grid-row .cell[data-field="${s.field}"]`).forEach(e=>{e.classList.add(t.GridClasses.STICKY_RIGHT),e.style.position="sticky",e.style.right=f+"px"}),f+=i.offsetWidth)}const u=function(e,s,i,n){const r={addGroupEnd:new Set,removeGroupEnd:new Set},d=Array.from(e.querySelectorAll(".header-group-row .header-group-cell"));if(!d.length)return r;for(const c of d){const e=c.style.gridColumn;if(!e)continue;const d=e.match(/^(\d+)\s*\/\s*span\s+(\d+)$/);if(!d)continue;const f=parseInt(d[1],10)-1,u=f+parseInt(d[2],10)-1,p=s.slice(f,u+1);if(!p.length)continue;const h=p.every(e=>l(e,n)),g=p.every(e=>o(e,n));if(h){const e=p[0].field,s=i.find(t=>t.getAttribute("data-field")===e);s&&(c.classList.add(t.GridClasses.STICKY_LEFT),c.style.position="sticky",c.style.left=s.style.left)}else if(g){const e=p[p.length-1].field,s=i.find(t=>t.getAttribute("data-field")===e);s&&(c.classList.add(t.GridClasses.STICKY_RIGHT),c.style.position="sticky",c.style.right=s.style.right)}else c.classList.contains("implicit-group")&&a(c,p,f,i,n,r)}return r}(e,i,r,d);if(u.addGroupEnd.size>0||u.removeGroupEnd.size>0){for(const t of u.addGroupEnd){const s=r.find(e=>e.getAttribute("data-field")===t);s&&s.classList.add("group-end"),e.querySelectorAll(`.data-grid-row .cell[data-field="${t}"]`).forEach(e=>{e.classList.add("group-end")})}for(const t of u.removeGroupEnd){const s=r.find(e=>e.getAttribute("data-field")===t);s&&s.classList.remove("group-end"),e.querySelectorAll(`.data-grid-row .cell[data-field="${t}"]`).forEach(e=>{e.classList.remove("group-end")})}}return u}function f(e,t){return l(e,t)?"left":o(e,t)?"right":"none"}function a(e,s,i,n,r,l){const o=[];for(let t=0;t<s.length;t++){const e=f(s[t],r),n=o[o.length-1];n&&n.state===e?n.cols.push(s[t]):o.push({state:e,cols:[s[t]],colStart:i+t})}if(o.length<=1)return;const d=e.parentElement;if(!d)return;const c=e.nextSibling;d.removeChild(e);for(const f of o){const s=document.createElement("div");if(s.className=e.className,s.setAttribute("data-group",e.getAttribute("data-group")||""),s.style.gridColumn=`${f.colStart+1} / span ${f.cols.length}`,"left"===f.state){const e=f.cols[0].field,i=n.find(t=>t.getAttribute("data-field")===e);i&&(s.classList.add(t.GridClasses.STICKY_LEFT),s.style.position="sticky",s.style.left=i.style.left)}else if("right"===f.state){const e=f.cols[f.cols.length-1].field,i=n.find(t=>t.getAttribute("data-field")===e);i&&(s.classList.add(t.GridClasses.STICKY_RIGHT),s.style.position="sticky",s.style.right=i.style.right)}else if("none"===f.state){f.cols.every(e=>String(e.field||"").startsWith("__tbw_"))&&(s.style.borderRightStyle="none")}c?d.insertBefore(s,c):d.appendChild(s)}for(let t=0;t<o.length;t++){const e=o[t],s=o[t+1];if("none"!==e.state&&s&&"none"===s.state){const t=e.cols[e.cols.length-1].field;t&&l.addGroupEnd.add(t)}if("none"===e.state){if(e.cols.every(e=>String(e.field||"").startsWith("__tbw_"))){const t=e.cols[e.cols.length-1].field;t&&l.removeGroupEnd.add(t)}}}}function u(e){e.querySelectorAll(`.${t.GridClasses.STICKY_LEFT}, .${t.GridClasses.STICKY_RIGHT}`).forEach(e=>{e.classList.remove(t.GridClasses.STICKY_LEFT,t.GridClasses.STICKY_RIGHT),e.style.position="",e.style.left="",e.style.right=""})}const p="canMoveColumn";class h extends i.BaseGridPlugin{static manifest={ownedProperties:[{property:"pinned",level:"column",description:'the "pinned" column property',isUsed:e=>"left"===e||"right"===e||"start"===e||"end"===e},{property:"sticky",level:"column",description:'the "sticky" column property (deprecated, use "pinned")',isUsed:e=>"left"===e||"right"===e||"start"===e||"end"===e}],queries:[{type:p,description:"Prevents pinned (sticky) columns from being moved/reordered"},{type:"getStickyOffsets",description:"Returns the sticky offsets for left/right pinned columns"},{type:"getContextMenuItems",description:"Contributes pin/unpin items to the header context menu"}]};name="pinnedColumns";get defaultConfig(){return{}}isApplied=!1;leftOffsets=new Map;rightOffsets=new Map;#e={addGroupEnd:new Set,removeGroupEnd:new Set};#t=[];detach(){this.leftOffsets.clear(),this.rightOffsets.clear(),this.isApplied=!1,this.#e={addGroupEnd:new Set,removeGroupEnd:new Set},this.#t=[]}static detect(e,t){const s=t?.columns;return!!Array.isArray(s)&&d(s)}processColumns(e){const t=[...e];if(this.isApplied=d(t),!this.isApplied)return t;const i=this.gridElement;return function(e,t="ltr"){const s=[],i=[],l=[];for(const o of e){const e=n(o);e?"left"===r(e,t)?s.push(o):l.push(o):i.push(o)}return[...s,...i,...l]}(t,i?s.getDirection(i):"ltr")}afterRender(){if(!this.isApplied)return;const e=this.gridElement,t=[...this.columns];if(!d(t))return u(e),void(this.isApplied=!1);queueMicrotask(()=>{this.#e=c(e,t)})}afterCellRender(e){if(!this.isApplied)return;const t=e.column.field;this.#e.addGroupEnd.has(t)?e.cellElement.classList.add("group-end"):this.#e.removeGroupEnd.has(t)&&e.cellElement.classList.remove("group-end")}handleQuery(e){switch(e.type){case p:return null==n(e.context)&&void 0;case"getStickyOffsets":return{left:Object.fromEntries(this.leftOffsets),right:Object.fromEntries(this.rightOffsets)};case"getContextMenuItems":{const t=e.context;if(!t.isHeader)return;const s=t.column;if(!s?.field)return;if(s.meta?.lockPinning)return;const i=[];return null!=n(s)?i.push({id:"pinned/unpin",label:"Unpin Column",icon:"📌",order:40,action:()=>this.setPinPosition(s.field,void 0)}):(i.push({id:"pinned/pin-left",label:"Pin Left",icon:"⬅",order:40,action:()=>this.setPinPosition(s.field,"left")}),i.push({id:"pinned/pin-right",label:"Pin Right",icon:"➡",order:41,action:()=>this.setPinPosition(s.field,"right")})),i}default:return}}setPinPosition(e,t){const s=this.columns;if(!s?.length)return;const i=s.findIndex(t=>t.field===e);if(-1===i)return;const r=this.gridElement;if(t){0===this.#t.length&&(this.#t=s.map(e=>e.field));const i=s.map(s=>{if(s.field!==e)return s;const i={...s};return i.pinned=t,delete i.sticky,i});r.columns=i}else{const t={...s[i]};delete t.pinned,delete t.sticky;const l=[...s];l.splice(i,1);const o=this.#t.indexOf(e);if(o>=0){let e=l.length;for(let t=0;t<l.length;t++){if(n(l[t]))continue;if(this.#t.indexOf(l[t].field)>o){e=t;break}}l.splice(e,0,t)}else l.splice(Math.min(i,l.length),0,t);l.some(e=>null!=n(e))||(this.#t=[]),r.columns=l}}refreshStickyOffsets(){const e=[...this.columns];c(this.gridElement,e)}getLeftPinnedColumns(){return function(e,t="ltr"){return e.filter(e=>l(e,t))}([...this.columns],s.getDirection(this.gridElement))}getRightPinnedColumns(){return function(e,t="ltr"){return e.filter(e=>o(e,t))}([...this.columns],s.getDirection(this.gridElement))}clearStickyPositions(){u(this.gridElement)}getHorizontalScrollOffsets(e,s){if(!this.isApplied)return;let i=0,n=0;if(e){const s=e.querySelectorAll(`.${t.GridClasses.STICKY_LEFT}`),r=e.querySelectorAll(`.${t.GridClasses.STICKY_RIGHT}`);s.forEach(e=>{i+=e.offsetWidth}),r.forEach(e=>{n+=e.offsetWidth})}else{this.gridElement.querySelectorAll(".header-row .cell").forEach(e=>{e.classList.contains(t.GridClasses.STICKY_LEFT)?i+=e.offsetWidth:e.classList.contains(t.GridClasses.STICKY_RIGHT)&&(n+=e.offsetWidth)})}const r=s?.classList.contains(t.GridClasses.STICKY_LEFT)||s?.classList.contains(t.GridClasses.STICKY_RIGHT);return{left:i,right:n,skipScroll:r}}}e.PinnedColumnsPlugin=h,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/constants"),require("../../core/internal/utils"),require("../../core/plugin/base-plugin")):"function"==typeof define&&define.amd?define(["exports","../../core/constants","../../core/internal/utils","../../core/plugin/base-plugin"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_pinnedColumns={},e.TbwGrid,e.TbwGrid,e.TbwGrid)}(this,function(e,t,s,i){"use strict";function n(e){return e.pinned??e.sticky??e.meta?.pinned??e.meta?.sticky}function l(e,t){return s.resolveInlinePosition(e,t)}function r(e,t){const s=n(e);return!!s&&"left"===l(s,t)}function o(e,t){const s=n(e);return!!s&&"right"===l(s,t)}function d(e){return e.some(e=>null!=n(e))}function a(e,i){const n={groupEndAdjustments:{addGroupEnd:new Set,removeGroupEnd:new Set},leftOffsets:new Map,rightOffsets:new Map,splitGroups:[]},l=Array.from(e.querySelectorAll(".header-row .cell"));if(!l.length)return n;const d=s.getDirection(e),a=new Map,f=new Map;let p=0;for(const s of i)if(r(s,d)){const i=l.find(e=>e.getAttribute("data-field")===s.field);i&&(a.set(s.field,p),i.classList.add(t.GridClasses.STICKY_LEFT),i.style.position="sticky",i.style.left=p+"px",e.querySelectorAll(`.data-grid-row .cell[data-field="${s.field}"]`).forEach(e=>{e.classList.add(t.GridClasses.STICKY_LEFT),e.style.position="sticky",e.style.left=p+"px"}),p+=i.offsetWidth)}let g=0;for(const s of[...i].reverse())if(o(s,d)){const i=l.find(e=>e.getAttribute("data-field")===s.field);i&&(f.set(s.field,g),i.classList.add(t.GridClasses.STICKY_RIGHT),i.style.position="sticky",i.style.right=g+"px",e.querySelectorAll(`.data-grid-row .cell[data-field="${s.field}"]`).forEach(e=>{e.classList.add(t.GridClasses.STICKY_RIGHT),e.style.position="sticky",e.style.right=g+"px"}),g+=i.offsetWidth)}const h=[],m=function(e,s,i,n,l){const d={addGroupEnd:new Set,removeGroupEnd:new Set},a=Array.from(e.querySelectorAll(".header-group-row .header-group-cell"));if(!a.length)return d;for(const f of a){const e=f.style.gridColumn;if(!e)continue;const a=e.match(/^(\d+)\s*\/\s*span\s+(\d+)$/);if(!a)continue;const p=parseInt(a[1],10)-1,g=p+parseInt(a[2],10)-1,h=s.slice(p,g+1);if(!h.length)continue;const m=h.every(e=>r(e,n)),y=h.every(e=>o(e,n));if(m){const e=h[0].field,s=i.find(t=>t.getAttribute("data-field")===e);s&&(f.classList.add(t.GridClasses.STICKY_LEFT),f.style.position="sticky",f.style.left=s.style.left)}else if(y){const e=h[h.length-1].field,s=i.find(t=>t.getAttribute("data-field")===e);s&&(f.classList.add(t.GridClasses.STICKY_RIGHT),f.style.position="sticky",f.style.right=s.style.right)}else if(f.classList.contains("implicit-group"))c(f,h,p,i,n,d);else{const e=h.some(e=>r(e,n)),t=h.some(e=>o(e,n));(e||t)&&u(f,h,p,i,n,d,l)}}return d}(e,i,l,d,h);if(m.addGroupEnd.size>0||m.removeGroupEnd.size>0){for(const t of m.addGroupEnd){const s=l.find(e=>e.getAttribute("data-field")===t);s&&s.classList.add("group-end"),e.querySelectorAll(`.data-grid-row .cell[data-field="${t}"]`).forEach(e=>{e.classList.add("group-end")})}for(const t of m.removeGroupEnd){const s=l.find(e=>e.getAttribute("data-field")===t);s&&s.classList.remove("group-end"),e.querySelectorAll(`.data-grid-row .cell[data-field="${t}"]`).forEach(e=>{e.classList.remove("group-end")})}}return{groupEndAdjustments:m,leftOffsets:a,rightOffsets:f,splitGroups:h}}function f(e,t){return r(e,t)?"left":o(e,t)?"right":"none"}function c(e,s,i,n,l,r){const o=[];for(let t=0;t<s.length;t++){const e=f(s[t],l),n=o[o.length-1];n&&n.state===e?n.cols.push(s[t]):o.push({state:e,cols:[s[t]],colStart:i+t})}if(o.length<=1)return;const d=e.parentElement;if(!d)return;const a=e.nextSibling;d.removeChild(e);for(const f of o){const s=document.createElement("div");if(s.className=e.className,s.setAttribute("data-group",e.getAttribute("data-group")||""),s.style.gridColumn=`${f.colStart+1} / span ${f.cols.length}`,"left"===f.state){const e=f.cols[0].field,i=n.find(t=>t.getAttribute("data-field")===e);i&&(s.classList.add(t.GridClasses.STICKY_LEFT),s.style.position="sticky",s.style.left=i.style.left)}else if("right"===f.state){const e=f.cols[f.cols.length-1].field,i=n.find(t=>t.getAttribute("data-field")===e);i&&(s.classList.add(t.GridClasses.STICKY_RIGHT),s.style.position="sticky",s.style.right=i.style.right)}else if("none"===f.state){f.cols.every(e=>String(e.field||"").startsWith("__tbw_"))&&(s.style.borderRightStyle="none")}a?d.insertBefore(s,a):d.appendChild(s)}for(let t=0;t<o.length;t++){const e=o[t],s=o[t+1];if("none"!==e.state&&s&&"none"===s.state){const t=e.cols[e.cols.length-1].field;t&&r.addGroupEnd.add(t)}if("none"===e.state){if(e.cols.every(e=>String(e.field||"").startsWith("__tbw_"))){const t=e.cols[e.cols.length-1].field;t&&r.removeGroupEnd.add(t)}}}}function u(e,s,i,n,l,r,o){const d=[];for(let t=0;t<s.length;t++){const e=f(s[t],l),n=d[d.length-1];n&&n.state===e?n.cols.push(s[t]):d.push({state:e,cols:[s[t]],colStart:i+t})}if(d.length<=1)return;const a=e.parentElement;if(!a)return;const c=e.textContent||"",u=e.getAttribute("data-group")||"",p=e.nextSibling;a.removeChild(e);const g=d.findIndex(e=>"left"===e.state);let h=-1;for(let t=d.length-1;t>=0;t--)if("right"===d[t].state){h=t;break}const m=g>=0?g:h,y=g>=0&&g+1<d.length?g+1:h>=0&&h-1>=0?h-1:-1;let C,S,G,E="";for(let f=0;f<d.length;f++){const s=d[f],i=document.createElement("div");if(i.className=e.className,i.setAttribute("data-group",u),i.style.gridColumn=`${s.colStart+1} / span ${s.cols.length}`,"left"===s.state){const e=s.cols[0].field,l=n.find(t=>t.getAttribute("data-field")===e);l&&(i.classList.add(t.GridClasses.STICKY_LEFT),i.style.position="sticky",i.style.left=l.style.left,l.style.left),f===m&&(i.style.borderRightStyle="none",C=i,E=s.cols[s.cols.length-1].field)}else if("right"===s.state){const e=s.cols[s.cols.length-1].field,l=n.find(t=>t.getAttribute("data-field")===e);l&&(i.classList.add(t.GridClasses.STICKY_RIGHT),i.style.position="sticky",i.style.right=l.style.right),f===m&&(i.style.borderLeftStyle="none",C=i,E=s.cols[0].field)}if(f===y){i.style.overflow="visible";const e=document.createElement("span");e.textContent=c,e.style.position="relative",e.style.zIndex="36",e.style.display="block",e.style.overflow="hidden",e.style.textOverflow="ellipsis",e.style.whiteSpace="nowrap",i.appendChild(e),S=i,G=e}p?a.insertBefore(i,p):a.appendChild(i)}C&&S&&G&&E&&o.push({groupId:u,label:c,pinnedFragment:C,scrollableFragment:S,floatLabel:G,pinnedField:E,isTransferred:!1,floatOffset:0})}function p(e){e.querySelectorAll(`.${t.GridClasses.STICKY_LEFT}, .${t.GridClasses.STICKY_RIGHT}`).forEach(e=>{e.classList.remove(t.GridClasses.STICKY_LEFT,t.GridClasses.STICKY_RIGHT),e.style.position="",e.style.left="",e.style.right=""})}const g="canMoveColumn";class h extends i.BaseGridPlugin{static manifest={ownedProperties:[{property:"pinned",level:"column",description:'the "pinned" column property',isUsed:e=>"left"===e||"right"===e||"start"===e||"end"===e},{property:"sticky",level:"column",description:'the "sticky" column property (deprecated, use "pinned")',isUsed:e=>"left"===e||"right"===e||"start"===e||"end"===e}],queries:[{type:g,description:"Prevents pinned (sticky) columns from being moved/reordered"},{type:"getStickyOffsets",description:"Returns the sticky offsets for left/right pinned columns"},{type:"getContextMenuItems",description:"Contributes pin/unpin items to the header context menu"}]};name="pinnedColumns";get defaultConfig(){return{}}isApplied=!1;leftOffsets=new Map;rightOffsets=new Map;#e={addGroupEnd:new Set,removeGroupEnd:new Set};#t=[];#s=[];detach(){this.leftOffsets.clear(),this.rightOffsets.clear(),this.isApplied=!1,this.#e={addGroupEnd:new Set,removeGroupEnd:new Set},this.#t=[],this.#s=[]}static detect(e,t){const s=t?.columns;return!!Array.isArray(s)&&d(s)}processColumns(e){const t=[...e];if(this.isApplied=d(t),!this.isApplied)return t;const i=this.gridElement;return function(e,t="ltr"){const s=[],i=[],r=[];for(const o of e){const e=n(o);e?"left"===l(e,t)?s.push(o):r.push(o):i.push(o)}return[...s,...i,...r]}(t,i?s.getDirection(i):"ltr")}afterRender(){if(!this.isApplied)return;const e=this.gridElement,t=[...this.columns];if(!d(t))return p(e),void(this.isApplied=!1);queueMicrotask(()=>{const s=a(e,t);this.#e=s.groupEndAdjustments,this.leftOffsets=s.leftOffsets,this.rightOffsets=s.rightOffsets,this.#t=s.splitGroups,this.#i()})}afterCellRender(e){if(!this.isApplied)return;const s=e.column.field,i=e.cellElement,n=this.leftOffsets.get(s);if(void 0!==n)i.classList.contains(t.GridClasses.STICKY_LEFT)||i.classList.add(t.GridClasses.STICKY_LEFT),i.style.position="sticky",i.style.left=n+"px";else{const e=this.rightOffsets.get(s);void 0!==e&&(i.classList.contains(t.GridClasses.STICKY_RIGHT)||i.classList.add(t.GridClasses.STICKY_RIGHT),i.style.position="sticky",i.style.right=e+"px")}this.#e.addGroupEnd.has(s)?e.cellElement.classList.add("group-end"):this.#e.removeGroupEnd.has(s)&&e.cellElement.classList.remove("group-end")}onScroll(e){this.#i()}#i(){if(!this.isApplied||0===this.#t.length)return;const e=this.gridElement;for(const t of this.#t){const s=t.pinnedFragment.getBoundingClientRect(),i=t.scrollableFragment.getBoundingClientRect().right<=s.right;if(i&&!t.isTransferred?(t.pinnedFragment.textContent=t.label,t.pinnedFragment.style.overflow="hidden",t.pinnedFragment.style.textOverflow="ellipsis",t.pinnedFragment.style.whiteSpace="nowrap",t.pinnedFragment.style.borderRightStyle="",t.floatLabel.style.visibility="hidden",t.floatLabel.style.transform="",t.floatOffset=0,this.#e.addGroupEnd.add(t.pinnedField),e.querySelectorAll(`.header-row .cell[data-field="${t.pinnedField}"], .data-grid-row .cell[data-field="${t.pinnedField}"]`).forEach(e=>e.classList.add("group-end")),t.isTransferred=!0):!i&&t.isTransferred&&(t.pinnedFragment.textContent="",t.pinnedFragment.style.overflow="",t.pinnedFragment.style.textOverflow="",t.pinnedFragment.style.whiteSpace="",t.pinnedFragment.style.borderRightStyle="none",t.floatLabel.style.visibility="",t.floatLabel.style.transform="",t.floatOffset=0,this.#e.addGroupEnd.delete(t.pinnedField),e.querySelectorAll(`.header-row .cell[data-field="${t.pinnedField}"], .data-grid-row .cell[data-field="${t.pinnedField}"]`).forEach(e=>e.classList.remove("group-end")),t.isTransferred=!1),!t.isTransferred){const e=t.floatLabel.getBoundingClientRect().left-t.floatOffset,i=s.left;e<i?(t.floatOffset=i-e,t.floatLabel.style.transform=`translateX(${t.floatOffset}px)`):(t.floatOffset=0,t.floatLabel.style.transform="")}}}handleQuery(e){switch(e.type){case g:return null==n(e.context)&&void 0;case"getStickyOffsets":return{left:Object.fromEntries(this.leftOffsets),right:Object.fromEntries(this.rightOffsets)};case"getContextMenuItems":{const t=e.context;if(!t.isHeader)return;const s=t.column;if(!s?.field)return;if(s.meta?.lockPinning)return;const i=[];return null!=n(s)?i.push({id:"pinned/unpin",label:"Unpin Column",icon:"📌",order:40,action:()=>this.setPinPosition(s.field,void 0)}):(i.push({id:"pinned/pin-left",label:"Pin Left",icon:"⬅",order:40,action:()=>this.setPinPosition(s.field,"left")}),i.push({id:"pinned/pin-right",label:"Pin Right",icon:"➡",order:41,action:()=>this.setPinPosition(s.field,"right")})),i}default:return}}setPinPosition(e,t){const s=this.columns;if(!s?.length)return;const i=s.findIndex(t=>t.field===e);if(-1===i)return;const l=this.gridElement;if(t){0===this.#s.length&&(this.#s=s.map(e=>e.field));const i=s.map(s=>{if(s.field!==e)return s;const i={...s};return i.pinned=t,delete i.sticky,i});l.columns=i}else{const t={...s[i]};delete t.pinned,delete t.sticky;const r=[...s];r.splice(i,1);const o=this.#s.indexOf(e);if(o>=0){let e=r.length;for(let t=0;t<r.length;t++){if(n(r[t]))continue;if(this.#s.indexOf(r[t].field)>o){e=t;break}}r.splice(e,0,t)}else r.splice(Math.min(i,r.length),0,t);r.some(e=>null!=n(e))||(this.#s=[]),l.columns=r}}refreshStickyOffsets(){const e=[...this.columns],t=a(this.gridElement,e);this.#e=t.groupEndAdjustments,this.leftOffsets=t.leftOffsets,this.rightOffsets=t.rightOffsets,this.#t=t.splitGroups}getLeftPinnedColumns(){return function(e,t="ltr"){return e.filter(e=>r(e,t))}([...this.columns],s.getDirection(this.gridElement))}getRightPinnedColumns(){return function(e,t="ltr"){return e.filter(e=>o(e,t))}([...this.columns],s.getDirection(this.gridElement))}clearStickyPositions(){p(this.gridElement)}getHorizontalScrollOffsets(e,s){if(!this.isApplied)return;let i=0,n=0;if(e){const s=e.querySelectorAll(`.${t.GridClasses.STICKY_LEFT}`),l=e.querySelectorAll(`.${t.GridClasses.STICKY_RIGHT}`);s.forEach(e=>{i+=e.offsetWidth}),l.forEach(e=>{n+=e.offsetWidth})}else{this.gridElement.querySelectorAll(".header-row .cell").forEach(e=>{e.classList.contains(t.GridClasses.STICKY_LEFT)?i+=e.offsetWidth:e.classList.contains(t.GridClasses.STICKY_RIGHT)&&(n+=e.offsetWidth)})}const l=s?.classList.contains(t.GridClasses.STICKY_LEFT)||s?.classList.contains(t.GridClasses.STICKY_RIGHT);return{left:i,right:n,skipScroll:l}}}e.PinnedColumnsPlugin=h,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
|
|
2
2
|
//# sourceMappingURL=pinned-columns.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinned-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/pinned-columns/pinned-columns.ts","../../../../../libs/grid/src/lib/plugins/pinned-columns/PinnedColumnsPlugin.ts"],"sourcesContent":["/**\n * Pinned Columns Core Logic\n *\n * Pure functions for applying pinned (sticky) column positioning.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { GridClasses } from '../../core/constants';\nimport { getDirection, resolveInlinePosition, type TextDirection } from '../../core/internal/utils';\nimport type { PinnedPosition, ResolvedPinnedPosition } from './types';\n\n// Keep deprecated imports working (StickyPosition = PinnedPosition)\ntype StickyPosition = PinnedPosition;\ntype ResolvedStickyPosition = ResolvedPinnedPosition;\n\n/**\n * Get the effective pinned position from a column, checking `pinned` first then `sticky` (deprecated).\n *\n * @param col - Column configuration object\n * @returns The pinned position, or undefined if not pinned\n */\nexport function getColumnPinned(col: any): PinnedPosition | undefined {\n return col.pinned ?? col.sticky ?? col.meta?.pinned ?? col.meta?.sticky;\n}\n\n/**\n * Resolve a pinned position to a physical position based on text direction.\n *\n * - `'left'` / `'right'` → unchanged (physical values)\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n *\n * @param position - The pinned position (logical or physical)\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical pinned position ('left' or 'right')\n */\nexport function resolveStickyPosition(position: StickyPosition, direction: TextDirection): ResolvedStickyPosition {\n return resolveInlinePosition(position, direction);\n}\n\n/**\n * Check if a column is pinned on the left (after resolving logical positions).\n */\nfunction isResolvedLeft(col: any, direction: TextDirection): boolean {\n const pinned = getColumnPinned(col);\n if (!pinned) return false;\n return resolveStickyPosition(pinned, direction) === 'left';\n}\n\n/**\n * Check if a column is pinned on the right (after resolving logical positions).\n */\nfunction isResolvedRight(col: any, direction: TextDirection): boolean {\n const pinned = getColumnPinned(col);\n if (!pinned) return false;\n return resolveStickyPosition(pinned, direction) === 'right';\n}\n\n/**\n * Get columns that should be sticky on the left.\n *\n * @param columns - Array of column configurations\n * @param direction - Text direction (default: 'ltr')\n * @returns Array of columns with sticky='left' or sticky='start' (in LTR)\n */\nexport function getLeftStickyColumns(columns: any[], direction: TextDirection = 'ltr'): any[] {\n return columns.filter((col) => isResolvedLeft(col, direction));\n}\n\n/**\n * Get columns that should be sticky on the right.\n *\n * @param columns - Array of column configurations\n * @param direction - Text direction (default: 'ltr')\n * @returns Array of columns with sticky='right' or sticky='end' (in LTR)\n */\nexport function getRightStickyColumns(columns: any[], direction: TextDirection = 'ltr'): any[] {\n return columns.filter((col) => isResolvedRight(col, direction));\n}\n\n/**\n * Check if any columns have sticky positioning.\n *\n * @param columns - Array of column configurations\n * @returns True if any column has sticky position\n */\nexport function hasStickyColumns(columns: any[]): boolean {\n return columns.some((col) => getColumnPinned(col) != null);\n}\n\n/**\n * Get the sticky position of a column.\n *\n * @param column - Column configuration\n * @returns The sticky position or null if not sticky\n */\nexport function getColumnStickyPosition(column: any): StickyPosition | null {\n return getColumnPinned(column) ?? null;\n}\n\n/**\n * Calculate left offsets for sticky-left columns.\n * Returns a map of field -> offset in pixels.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @param direction - Text direction (default: 'ltr')\n * @returns Map of field to left offset\n */\nexport function calculateLeftStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n direction: TextDirection = 'ltr',\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n for (const col of columns) {\n if (isResolvedLeft(col, direction)) {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Calculate right offsets for sticky-right columns.\n * Processes columns in reverse order.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @param direction - Text direction (default: 'ltr')\n * @returns Map of field to right offset\n */\nexport function calculateRightStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n direction: TextDirection = 'ltr',\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n // Process in reverse for right-sticky columns\n const reversed = [...columns].reverse();\n for (const col of reversed) {\n if (isResolvedRight(col, direction)) {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Adjustments to `group-end` borders at pin boundaries within implicit groups.\n * - `addGroupEnd`: fields that should gain `group-end` (last pinned column at boundary)\n * - `removeGroupEnd`: fields that should lose `group-end` (lone non-pinned remnant)\n */\nexport interface GroupEndAdjustments {\n addGroupEnd: Set<string>;\n removeGroupEnd: Set<string>;\n}\n\n/**\n * Apply sticky offsets to header and body cells.\n * This modifies the DOM elements in place.\n *\n * @param host - The grid host element (render root for DOM queries)\n * @param columns - Array of column configurations\n * @returns Group-end adjustments for `afterCellRender` hooks to maintain during scroll\n */\nexport function applyStickyOffsets(host: HTMLElement, columns: any[]): GroupEndAdjustments {\n const empty: GroupEndAdjustments = { addGroupEnd: new Set(), removeGroupEnd: new Set() };\n\n // With light DOM, query the host element directly\n const headerCells = Array.from(host.querySelectorAll('.header-row .cell')) as HTMLElement[];\n if (!headerCells.length) return empty;\n\n // Detect text direction from the host element\n const direction = getDirection(host);\n\n // Apply left sticky (includes 'start' in LTR, 'end' in RTL)\n let left = 0;\n for (const col of columns) {\n if (isResolvedLeft(col, direction)) {\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add(GridClasses.STICKY_LEFT);\n cell.style.position = 'sticky';\n cell.style.left = left + 'px';\n // Body cells: use data-field for reliable matching (data-col indices may differ\n // between _columns and _visibleColumns due to hidden/utility columns)\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${col.field}\"]`).forEach((el) => {\n el.classList.add(GridClasses.STICKY_LEFT);\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.left = left + 'px';\n });\n left += cell.offsetWidth;\n }\n }\n }\n\n // Apply right sticky (includes 'end' in LTR, 'start' in RTL) - process in reverse\n let right = 0;\n for (const col of [...columns].reverse()) {\n if (isResolvedRight(col, direction)) {\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add(GridClasses.STICKY_RIGHT);\n cell.style.position = 'sticky';\n cell.style.right = right + 'px';\n // Body cells: use data-field for reliable matching\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${col.field}\"]`).forEach((el) => {\n el.classList.add(GridClasses.STICKY_RIGHT);\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.right = right + 'px';\n });\n right += cell.offsetWidth;\n }\n }\n }\n\n // Apply sticky offsets to column group header cells and collect group-end adjustments\n const adjustments = applyGroupHeaderStickyOffsets(host, columns, headerCells, direction);\n\n // Apply group-end adjustments to header cells and visible body cells\n if (adjustments.addGroupEnd.size > 0 || adjustments.removeGroupEnd.size > 0) {\n for (const field of adjustments.addGroupEnd) {\n const hCell = headerCells.find((c) => c.getAttribute('data-field') === field);\n if (hCell) hCell.classList.add('group-end');\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${field}\"]`).forEach((el) => {\n el.classList.add('group-end');\n });\n }\n for (const field of adjustments.removeGroupEnd) {\n const hCell = headerCells.find((c) => c.getAttribute('data-field') === field);\n if (hCell) hCell.classList.remove('group-end');\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${field}\"]`).forEach((el) => {\n el.classList.remove('group-end');\n });\n }\n }\n\n return adjustments;\n}\n\n/**\n * Apply sticky offsets to column group header cells.\n * - If ALL columns in a group are pinned the same direction, the whole cell is pinned.\n * - If an implicit (unlabelled) group mixes pinned and non-pinned columns,\n * the cell is split at pin boundaries so pinned portions can be sticky.\n *\n * @param host - The grid host element\n * @param columns - Array of column configurations\n * @param headerCells - Already-queried header cells (with sticky offsets applied)\n * @param direction - Text direction\n * @returns Group-end adjustments for pin boundaries within implicit groups\n */\nfunction applyGroupHeaderStickyOffsets(\n host: HTMLElement,\n columns: any[],\n headerCells: HTMLElement[],\n direction: TextDirection,\n): GroupEndAdjustments {\n const adjustments: GroupEndAdjustments = { addGroupEnd: new Set(), removeGroupEnd: new Set() };\n const groupCells = Array.from(host.querySelectorAll('.header-group-row .header-group-cell')) as HTMLElement[];\n if (!groupCells.length) return adjustments;\n\n for (const groupCell of groupCells) {\n // Parse gridColumn to find which column range this group spans\n // Format: \"startCol / span N\" (1-based)\n const gridCol = groupCell.style.gridColumn;\n if (!gridCol) continue;\n\n const match = gridCol.match(/^(\\d+)\\s*\\/\\s*span\\s+(\\d+)$/);\n if (!match) continue;\n\n const startIdx = parseInt(match[1], 10) - 1; // Convert to 0-based\n const span = parseInt(match[2], 10);\n const endIdx = startIdx + span - 1;\n\n // Get the columns this group spans\n const spannedColumns = columns.slice(startIdx, endIdx + 1);\n if (!spannedColumns.length) continue;\n\n const allLeft = spannedColumns.every((col: any) => isResolvedLeft(col, direction));\n const allRight = spannedColumns.every((col: any) => isResolvedRight(col, direction));\n\n if (allLeft) {\n const firstField = spannedColumns[0].field;\n const firstCell = headerCells.find((c) => c.getAttribute('data-field') === firstField);\n if (firstCell) {\n groupCell.classList.add(GridClasses.STICKY_LEFT);\n groupCell.style.position = 'sticky';\n groupCell.style.left = firstCell.style.left;\n }\n } else if (allRight) {\n const lastField = spannedColumns[spannedColumns.length - 1].field;\n const lastCell = headerCells.find((c) => c.getAttribute('data-field') === lastField);\n if (lastCell) {\n groupCell.classList.add(GridClasses.STICKY_RIGHT);\n groupCell.style.position = 'sticky';\n groupCell.style.right = lastCell.style.right;\n }\n } else if (groupCell.classList.contains('implicit-group')) {\n // Implicit group with mixed pinning: split into separate cells so pinned\n // portions become sticky while non-pinned portions scroll normally.\n splitMixedPinImplicitGroup(groupCell, spannedColumns, startIdx, headerCells, direction, adjustments);\n }\n }\n\n return adjustments;\n}\n\n/** Classify a column's pin state after resolving logical positions. */\ntype PinState = 'left' | 'right' | 'none';\n\nfunction getPinState(col: any, direction: TextDirection): PinState {\n if (isResolvedLeft(col, direction)) return 'left';\n if (isResolvedRight(col, direction)) return 'right';\n return 'none';\n}\n\n/**\n * Split an implicit (unlabelled) group header cell into fragments at pin-state\n * boundaries. Each fragment becomes its own header-group-cell; pinned fragments\n * get sticky positioning.\n *\n * Also populates `adjustments` with group-end border changes:\n * - Last column of a left-pinned run gets `group-end` (visual separator at pin edge)\n * - Last column of a subsequent non-pinned run that contains only utility columns\n * loses `group-end` (it visually merges with the adjacent explicit group)\n */\nfunction splitMixedPinImplicitGroup(\n groupCell: HTMLElement,\n spannedColumns: any[],\n startIdx: number,\n headerCells: HTMLElement[],\n direction: TextDirection,\n adjustments: GroupEndAdjustments,\n): void {\n // Partition columns into contiguous runs of the same pin state\n const runs: { state: PinState; cols: any[]; colStart: number }[] = [];\n for (let i = 0; i < spannedColumns.length; i++) {\n const state = getPinState(spannedColumns[i], direction);\n const prev = runs[runs.length - 1];\n if (prev && prev.state === state) {\n prev.cols.push(spannedColumns[i]);\n } else {\n runs.push({ state, cols: [spannedColumns[i]], colStart: startIdx + i });\n }\n }\n\n if (runs.length <= 1) return; // Nothing to split\n\n const parent = groupCell.parentElement;\n if (!parent) return;\n\n const nextSibling = groupCell.nextSibling;\n parent.removeChild(groupCell);\n\n for (const run of runs) {\n const cell = document.createElement('div');\n cell.className = groupCell.className; // Preserves implicit-group, cell, header-group-cell\n cell.setAttribute('data-group', groupCell.getAttribute('data-group') || '');\n cell.style.gridColumn = `${run.colStart + 1} / span ${run.cols.length}`;\n\n if (run.state === 'left') {\n const firstField = run.cols[0].field;\n const firstCell = headerCells.find((c) => c.getAttribute('data-field') === firstField);\n if (firstCell) {\n cell.classList.add(GridClasses.STICKY_LEFT);\n cell.style.position = 'sticky';\n cell.style.left = firstCell.style.left;\n }\n } else if (run.state === 'right') {\n const lastField = run.cols[run.cols.length - 1].field;\n const lastCell = headerCells.find((c) => c.getAttribute('data-field') === lastField);\n if (lastCell) {\n cell.classList.add(GridClasses.STICKY_RIGHT);\n cell.style.position = 'sticky';\n cell.style.right = lastCell.style.right;\n }\n } else if (run.state === 'none') {\n // Suppress border on utility-only non-pinned remnants — they visually merge\n // with the adjacent explicit group.\n const allUtility = run.cols.every((c: any) => String(c.field || '').startsWith('__tbw_'));\n if (allUtility) {\n cell.style.borderRightStyle = 'none';\n }\n }\n\n if (nextSibling) {\n parent.insertBefore(cell, nextSibling);\n } else {\n parent.appendChild(cell);\n }\n }\n\n // Compute group-end adjustments at pin boundaries.\n // When a pinned run is followed by a non-pinned run, the last column of the\n // pinned run should be the visual group boundary (group-end).\n // The non-pinned remnant's last column should lose group-end if all its\n // columns are utility columns (e.g. __tbw_expander) — they visually merge\n // with the adjacent explicit group.\n for (let ri = 0; ri < runs.length; ri++) {\n const run = runs[ri];\n const nextRun = runs[ri + 1];\n\n if (run.state !== 'none' && nextRun && nextRun.state === 'none') {\n // Last column of pinned run gets group-end\n const lastPinnedField = run.cols[run.cols.length - 1].field;\n if (lastPinnedField) adjustments.addGroupEnd.add(lastPinnedField);\n }\n\n if (run.state === 'none') {\n // Check if all columns in this non-pinned run are utility columns\n const allUtility = run.cols.every((c: any) => String(c.field || '').startsWith('__tbw_'));\n if (allUtility) {\n // Remove group-end from the last column — it visually merges with the next group\n const lastField = run.cols[run.cols.length - 1].field;\n if (lastField) adjustments.removeGroupEnd.add(lastField);\n }\n }\n }\n}\n\n/**\n * Reorder columns so that pinned-left columns come first and pinned-right columns come last.\n * Maintains the relative order within each group (left-pinned, unpinned, right-pinned).\n *\n * @param columns - Array of column configurations (in their current order)\n * @param direction - Text direction ('ltr' or 'rtl'), used to resolve logical positions\n * @returns New array with pinned columns moved to the edges\n */\nexport function reorderColumnsForPinning(columns: readonly any[], direction: TextDirection = 'ltr'): any[] {\n const left: any[] = [];\n const middle: any[] = [];\n const right: any[] = [];\n\n for (const col of columns) {\n const pinned = getColumnPinned(col);\n if (pinned) {\n const resolved = resolveStickyPosition(pinned, direction);\n if (resolved === 'left') left.push(col);\n else right.push(col);\n } else {\n middle.push(col);\n }\n }\n\n return [...left, ...middle, ...right];\n}\n\n/**\n * Clear sticky positioning from all cells.\n *\n * @param host - The grid host element (render root for DOM queries)\n */\nexport function clearStickyOffsets(host: HTMLElement): void {\n // With light DOM, query the host element directly\n const cells = host.querySelectorAll(`.${GridClasses.STICKY_LEFT}, .${GridClasses.STICKY_RIGHT}`);\n cells.forEach((cell) => {\n cell.classList.remove(GridClasses.STICKY_LEFT, GridClasses.STICKY_RIGHT);\n (cell as HTMLElement).style.position = '';\n (cell as HTMLElement).style.left = '';\n (cell as HTMLElement).style.right = '';\n });\n}\n","/**\n * Pinned Columns Plugin (Class-based)\n *\n * Enables column pinning (sticky left/right positioning).\n */\n\nimport { GridClasses } from '../../core/constants';\nimport { getDirection } from '../../core/internal/utils';\nimport type { AfterCellRenderContext, PluginManifest, PluginQuery } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport type { ContextMenuParams, HeaderContextMenuItem } from '../context-menu/types';\nimport {\n applyStickyOffsets,\n clearStickyOffsets,\n getColumnPinned,\n getLeftStickyColumns,\n getRightStickyColumns,\n hasStickyColumns,\n reorderColumnsForPinning,\n type GroupEndAdjustments,\n} from './pinned-columns';\nimport type { PinnedColumnsConfig, PinnedPosition } from './types';\n\n/** Query type constant for checking if a column can be moved */\nconst QUERY_CAN_MOVE_COLUMN = 'canMoveColumn';\n\n/**\n * Pinned Columns Plugin for tbw-grid\n *\n * Freezes columns to the left or right edge of the grid—essential for keeping key\n * identifiers or action buttons visible while scrolling through wide datasets. Just set\n * `pinned: 'left'` or `pinned: 'right'` on your column definitions.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n * ```\n *\n * ## Column Configuration\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `pinned` | `'left' \\| 'right' \\| 'start' \\| 'end'` | Pin column to edge (logical or physical) |\n * | `meta.lockPinning` | `boolean` | `false` | Prevent user from pin/unpin via context menu |\n *\n * ### RTL Support\n *\n * Use logical values (`start`/`end`) for grids that work in both LTR and RTL layouts:\n * - `'start'` - Pins to left in LTR, right in RTL\n * - `'end'` - Pins to right in LTR, left in RTL\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-shadow` | `4px 0 8px rgba(0,0,0,0.1)` | Shadow on pinned column edge |\n * | `--tbw-pinned-border` | `var(--tbw-color-border)` | Border between pinned and scrollable |\n *\n * @example Pin ID Left and Actions Right\n * ```ts\n * import { queryGrid } from '@toolbox-web/grid';\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n *\n * const grid = queryGrid('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'left', width: 80 },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * { field: 'department', header: 'Department' },\n * { field: 'actions', header: 'Actions', pinned: 'right', width: 120 },\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @example RTL-Compatible Pinning\n * ```ts\n * // Same config works in LTR and RTL\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'start' }, // Left in LTR, Right in RTL\n * { field: 'name', header: 'Name' },\n * { field: 'actions', header: 'Actions', pinned: 'end' }, // Right in LTR, Left in RTL\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link PinnedColumnsConfig} for configuration options\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedColumnsPlugin extends BaseGridPlugin<PinnedColumnsConfig> {\n /**\n * Plugin manifest - declares owned properties and handled queries.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'pinned',\n level: 'column',\n description: 'the \"pinned\" column property',\n isUsed: (v) => v === 'left' || v === 'right' || v === 'start' || v === 'end',\n },\n {\n property: 'sticky',\n level: 'column',\n description: 'the \"sticky\" column property (deprecated, use \"pinned\")',\n isUsed: (v) => v === 'left' || v === 'right' || v === 'start' || v === 'end',\n },\n ],\n queries: [\n {\n type: QUERY_CAN_MOVE_COLUMN,\n description: 'Prevents pinned (sticky) columns from being moved/reordered',\n },\n {\n type: 'getStickyOffsets',\n description: 'Returns the sticky offsets for left/right pinned columns',\n },\n {\n type: 'getContextMenuItems',\n description: 'Contributes pin/unpin items to the header context menu',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'pinnedColumns';\n\n /** @internal */\n protected override get defaultConfig(): Partial<PinnedColumnsConfig> {\n return {};\n }\n\n // #region Internal State\n private isApplied = false;\n private leftOffsets = new Map<string, number>();\n private rightOffsets = new Map<string, number>();\n /** Group-end adjustments for pin boundaries within implicit groups. */\n #groupEndAdjustments: GroupEndAdjustments = { addGroupEnd: new Set(), removeGroupEnd: new Set() };\n /**\n * Snapshot of the column field order before the first context-menu pin.\n * Used to restore original positions when unpinning.\n */\n #originalColumnOrder: string[] = [];\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.leftOffsets.clear();\n this.rightOffsets.clear();\n this.isApplied = false;\n this.#groupEndAdjustments = { addGroupEnd: new Set(), removeGroupEnd: new Set() };\n this.#originalColumnOrder = [];\n }\n // #endregion\n\n // #region Detection\n\n /**\n * Auto-detect sticky columns from column configuration.\n */\n static detect(rows: readonly unknown[], config: { columns?: ColumnConfig[] }): boolean {\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasStickyColumns(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n const cols = [...columns];\n this.isApplied = hasStickyColumns(cols);\n if (!this.isApplied) return cols;\n\n const host = this.gridElement;\n const direction = host ? getDirection(host) : 'ltr';\n return reorderColumnsForPinning(cols, direction) as ColumnConfig[];\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isApplied) {\n return;\n }\n\n const host = this.gridElement;\n const columns = [...this.columns];\n\n if (!hasStickyColumns(columns)) {\n clearStickyOffsets(host);\n this.isApplied = false;\n return;\n }\n\n // Apply sticky offsets after a microtask to ensure DOM is ready\n queueMicrotask(() => {\n this.#groupEndAdjustments = applyStickyOffsets(host, columns);\n });\n }\n\n /**\n * Maintain group-end adjustments on cells rendered during scroll.\n * Runs after GroupingColumnsPlugin.afterCellRender (which sets group-end\n * based on group boundaries), overriding it at pin boundaries.\n * @internal\n */\n override afterCellRender(context: AfterCellRenderContext): void {\n if (!this.isApplied) return;\n const field = context.column.field;\n if (this.#groupEndAdjustments.addGroupEnd.has(field)) {\n context.cellElement.classList.add('group-end');\n } else if (this.#groupEndAdjustments.removeGroupEnd.has(field)) {\n context.cellElement.classList.remove('group-end');\n }\n }\n\n /**\n * Handle inter-plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n switch (query.type) {\n case QUERY_CAN_MOVE_COLUMN: {\n // Prevent pinned columns from being moved/reordered.\n // Pinned columns have fixed positions and should not be draggable.\n const column = query.context as ColumnConfig;\n if (getColumnPinned(column) != null) {\n return false;\n }\n return undefined; // Let other plugins or default behavior decide\n }\n case 'getStickyOffsets': {\n // Return the calculated sticky offsets for column virtualization\n return {\n left: Object.fromEntries(this.leftOffsets),\n right: Object.fromEntries(this.rightOffsets),\n };\n }\n case 'getContextMenuItems': {\n const params = query.context as ContextMenuParams;\n if (!params.isHeader) return undefined;\n\n const column = params.column as ColumnConfig;\n if (!column?.field) return undefined;\n\n // Don't offer pin/unpin for locked-pinning columns\n if (column.meta?.lockPinning) return undefined;\n\n const pinned = getColumnPinned(column);\n const isPinned = pinned != null;\n const items: HeaderContextMenuItem[] = [];\n\n if (isPinned) {\n items.push({\n id: 'pinned/unpin',\n label: 'Unpin Column',\n icon: '📌',\n order: 40,\n action: () => this.setPinPosition(column.field, undefined),\n });\n } else {\n items.push({\n id: 'pinned/pin-left',\n label: 'Pin Left',\n icon: '⬅',\n order: 40,\n action: () => this.setPinPosition(column.field, 'left'),\n });\n items.push({\n id: 'pinned/pin-right',\n label: 'Pin Right',\n icon: '➡',\n order: 41,\n action: () => this.setPinPosition(column.field, 'right'),\n });\n }\n\n return items;\n }\n default:\n return undefined;\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Set the pin position for a column.\n * Updates the column's `pinned` property and triggers a full re-render.\n *\n * @param field - The field name of the column to pin/unpin\n * @param position - The pin position (`'left'`, `'right'`, `'start'`, `'end'`), or `undefined` to unpin\n */\n setPinPosition(field: string, position: PinnedPosition | undefined): void {\n // Read the currently-visible columns from the plugin accessor.\n // These are the post-processColumns result, which is the authoritative column set.\n const currentColumns = this.columns;\n if (!currentColumns?.length) return;\n\n const currentIndex = currentColumns.findIndex((col) => col.field === field);\n if (currentIndex === -1) return;\n\n const gridEl = this.gridElement as HTMLElement & { columns?: ColumnConfig[] };\n\n if (position) {\n // PINNING: snapshot original column order if this is the first context-menu pin.\n // The snapshot lets us restore columns to their original positions on unpin.\n if (this.#originalColumnOrder.length === 0) {\n this.#originalColumnOrder = currentColumns.map((c) => c.field);\n }\n\n // Set the pinned property; processColumns will reorder on next render\n const updated = currentColumns.map((col) => {\n if (col.field !== field) return col;\n const copy = { ...col };\n (copy as ColumnConfig & { pinned?: PinnedPosition }).pinned = position;\n delete (copy as ColumnConfig & { sticky?: PinnedPosition }).sticky;\n return copy;\n });\n\n gridEl.columns = updated;\n } else {\n // UNPINNING: restore column to its original position\n const col = currentColumns[currentIndex];\n const copy = { ...col };\n delete (copy as ColumnConfig & { pinned?: PinnedPosition }).pinned;\n delete (copy as ColumnConfig & { sticky?: PinnedPosition }).sticky;\n\n // Remove from current position\n const remaining = [...currentColumns];\n remaining.splice(currentIndex, 1);\n\n // Find the best insertion point using the original order snapshot\n const originalIndex = this.#originalColumnOrder.indexOf(field);\n if (originalIndex >= 0) {\n // Scan remaining non-pinned columns and find the first whose original\n // position is greater than this column's original position.\n let insertIndex = remaining.length;\n for (let i = 0; i < remaining.length; i++) {\n if (getColumnPinned(remaining[i])) continue; // skip pinned columns\n const otherOriginal = this.#originalColumnOrder.indexOf(remaining[i].field);\n if (otherOriginal > originalIndex) {\n insertIndex = i;\n break;\n }\n }\n remaining.splice(insertIndex, 0, copy);\n } else {\n // Original position unknown — keep at current index\n remaining.splice(Math.min(currentIndex, remaining.length), 0, copy);\n }\n\n // If no more pinned columns remain, clear the snapshot\n if (!remaining.some((c) => getColumnPinned(c) != null)) {\n this.#originalColumnOrder = [];\n }\n\n gridEl.columns = remaining;\n }\n }\n\n /**\n * Re-apply sticky offsets (e.g., after column resize).\n */\n refreshStickyOffsets(): void {\n const columns = [...this.columns];\n applyStickyOffsets(this.gridElement, columns);\n }\n\n /**\n * Get columns pinned to the left (after resolving logical positions for current direction).\n */\n getLeftPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n const direction = getDirection(this.gridElement);\n return getLeftStickyColumns(columns, direction);\n }\n\n /**\n * Get columns pinned to the right (after resolving logical positions for current direction).\n */\n getRightPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n const direction = getDirection(this.gridElement);\n return getRightStickyColumns(columns, direction);\n }\n\n /**\n * Clear all sticky positioning.\n */\n clearStickyPositions(): void {\n clearStickyOffsets(this.gridElement);\n }\n\n /**\n * Report horizontal scroll boundary offsets for pinned columns.\n * Used by keyboard navigation to ensure focused cells aren't hidden behind sticky columns.\n * @internal\n */\n override getHorizontalScrollOffsets(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } | undefined {\n if (!this.isApplied) {\n return undefined;\n }\n\n let left = 0;\n let right = 0;\n\n if (rowEl) {\n // Calculate from rendered cells in the row\n const stickyLeftCells = rowEl.querySelectorAll(`.${GridClasses.STICKY_LEFT}`);\n const stickyRightCells = rowEl.querySelectorAll(`.${GridClasses.STICKY_RIGHT}`);\n stickyLeftCells.forEach((el) => {\n left += (el as HTMLElement).offsetWidth;\n });\n stickyRightCells.forEach((el) => {\n right += (el as HTMLElement).offsetWidth;\n });\n } else {\n // Fall back to header row if no row element provided\n const host = this.gridElement;\n const headerCells = host.querySelectorAll('.header-row .cell');\n headerCells.forEach((cell) => {\n if (cell.classList.contains(GridClasses.STICKY_LEFT)) {\n left += (cell as HTMLElement).offsetWidth;\n } else if (cell.classList.contains(GridClasses.STICKY_RIGHT)) {\n right += (cell as HTMLElement).offsetWidth;\n }\n });\n }\n\n // Skip horizontal scrolling if focused cell is pinned (it's always visible)\n const skipScroll =\n focusedCell?.classList.contains(GridClasses.STICKY_LEFT) ||\n focusedCell?.classList.contains(GridClasses.STICKY_RIGHT);\n\n return { left, right, skipScroll };\n }\n // #endregion\n}\n"],"names":["getColumnPinned","col","pinned","sticky","meta","resolveStickyPosition","position","direction","resolveInlinePosition","isResolvedLeft","isResolvedRight","hasStickyColumns","columns","some","applyStickyOffsets","host","empty","addGroupEnd","Set","removeGroupEnd","headerCells","Array","from","querySelectorAll","length","getDirection","left","cell","find","c","getAttribute","field","classList","add","GridClasses","STICKY_LEFT","style","forEach","el","offsetWidth","right","reverse","STICKY_RIGHT","adjustments","groupCells","groupCell","gridCol","gridColumn","match","startIdx","parseInt","endIdx","spannedColumns","slice","allLeft","every","allRight","firstField","firstCell","lastField","lastCell","contains","splitMixedPinImplicitGroup","applyGroupHeaderStickyOffsets","size","hCell","remove","getPinState","runs","i","state","prev","cols","push","colStart","parent","parentElement","nextSibling","removeChild","run","document","createElement","className","setAttribute","String","startsWith","borderRightStyle","insertBefore","appendChild","ri","nextRun","lastPinnedField","clearStickyOffsets","QUERY_CAN_MOVE_COLUMN","PinnedColumnsPlugin","BaseGridPlugin","static","ownedProperties","property","level","description","isUsed","v","queries","type","name","defaultConfig","isApplied","leftOffsets","Map","rightOffsets","groupEndAdjustments","originalColumnOrder","detach","this","clear","detect","rows","config","isArray","processColumns","gridElement","middle","reorderColumnsForPinning","afterRender","queueMicrotask","afterCellRender","context","column","has","cellElement","handleQuery","query","Object","fromEntries","params","isHeader","lockPinning","items","id","label","icon","order","action","setPinPosition","currentColumns","currentIndex","findIndex","gridEl","map","updated","copy","remaining","splice","originalIndex","indexOf","insertIndex","Math","min","refreshStickyOffsets","getLeftPinnedColumns","filter","getLeftStickyColumns","getRightPinnedColumns","getRightStickyColumns","clearStickyPositions","getHorizontalScrollOffsets","rowEl","focusedCell","stickyLeftCells","stickyRightCells","skipScroll"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pinned-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/pinned-columns/pinned-columns.ts","../../../../../libs/grid/src/lib/plugins/pinned-columns/PinnedColumnsPlugin.ts"],"sourcesContent":["/**\n * Pinned Columns Core Logic\n *\n * Pure functions for applying pinned (sticky) column positioning.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { GridClasses } from '../../core/constants';\nimport { getDirection, resolveInlinePosition, type TextDirection } from '../../core/internal/utils';\nimport type { PinnedPosition, ResolvedPinnedPosition } from './types';\n\n// Keep deprecated imports working (StickyPosition = PinnedPosition)\ntype StickyPosition = PinnedPosition;\ntype ResolvedStickyPosition = ResolvedPinnedPosition;\n\n/**\n * Get the effective pinned position from a column, checking `pinned` first then `sticky` (deprecated).\n *\n * @param col - Column configuration object\n * @returns The pinned position, or undefined if not pinned\n */\nexport function getColumnPinned(col: any): PinnedPosition | undefined {\n return col.pinned ?? col.sticky ?? col.meta?.pinned ?? col.meta?.sticky;\n}\n\n/**\n * Resolve a pinned position to a physical position based on text direction.\n *\n * - `'left'` / `'right'` → unchanged (physical values)\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n *\n * @param position - The pinned position (logical or physical)\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical pinned position ('left' or 'right')\n */\nexport function resolveStickyPosition(position: StickyPosition, direction: TextDirection): ResolvedStickyPosition {\n return resolveInlinePosition(position, direction);\n}\n\n/**\n * Check if a column is pinned on the left (after resolving logical positions).\n */\nfunction isResolvedLeft(col: any, direction: TextDirection): boolean {\n const pinned = getColumnPinned(col);\n if (!pinned) return false;\n return resolveStickyPosition(pinned, direction) === 'left';\n}\n\n/**\n * Check if a column is pinned on the right (after resolving logical positions).\n */\nfunction isResolvedRight(col: any, direction: TextDirection): boolean {\n const pinned = getColumnPinned(col);\n if (!pinned) return false;\n return resolveStickyPosition(pinned, direction) === 'right';\n}\n\n/**\n * Get columns that should be sticky on the left.\n *\n * @param columns - Array of column configurations\n * @param direction - Text direction (default: 'ltr')\n * @returns Array of columns with sticky='left' or sticky='start' (in LTR)\n */\nexport function getLeftStickyColumns(columns: any[], direction: TextDirection = 'ltr'): any[] {\n return columns.filter((col) => isResolvedLeft(col, direction));\n}\n\n/**\n * Get columns that should be sticky on the right.\n *\n * @param columns - Array of column configurations\n * @param direction - Text direction (default: 'ltr')\n * @returns Array of columns with sticky='right' or sticky='end' (in LTR)\n */\nexport function getRightStickyColumns(columns: any[], direction: TextDirection = 'ltr'): any[] {\n return columns.filter((col) => isResolvedRight(col, direction));\n}\n\n/**\n * Check if any columns have sticky positioning.\n *\n * @param columns - Array of column configurations\n * @returns True if any column has sticky position\n */\nexport function hasStickyColumns(columns: any[]): boolean {\n return columns.some((col) => getColumnPinned(col) != null);\n}\n\n/**\n * Get the sticky position of a column.\n *\n * @param column - Column configuration\n * @returns The sticky position or null if not sticky\n */\nexport function getColumnStickyPosition(column: any): StickyPosition | null {\n return getColumnPinned(column) ?? null;\n}\n\n/**\n * Calculate left offsets for sticky-left columns.\n * Returns a map of field -> offset in pixels.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @param direction - Text direction (default: 'ltr')\n * @returns Map of field to left offset\n */\nexport function calculateLeftStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n direction: TextDirection = 'ltr',\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n for (const col of columns) {\n if (isResolvedLeft(col, direction)) {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Calculate right offsets for sticky-right columns.\n * Processes columns in reverse order.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @param direction - Text direction (default: 'ltr')\n * @returns Map of field to right offset\n */\nexport function calculateRightStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n direction: TextDirection = 'ltr',\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n // Process in reverse for right-sticky columns\n const reversed = [...columns].reverse();\n for (const col of reversed) {\n if (isResolvedRight(col, direction)) {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Adjustments to `group-end` borders at pin boundaries within implicit groups.\n * - `addGroupEnd`: fields that should gain `group-end` (last pinned column at boundary)\n * - `removeGroupEnd`: fields that should lose `group-end` (lone non-pinned remnant)\n */\nexport interface GroupEndAdjustments {\n addGroupEnd: Set<string>;\n removeGroupEnd: Set<string>;\n}\n\n/**\n * Result of applying sticky offsets — includes both the group-end adjustments\n * and the measured left/right offset maps for use in per-cell rendering hooks.\n */\nexport interface StickyOffsetsResult {\n groupEndAdjustments: GroupEndAdjustments;\n leftOffsets: Map<string, number>;\n rightOffsets: Map<string, number>;\n splitGroups: SplitGroupState[];\n}\n\n/**\n * State for an explicit group header that was split at a pin boundary.\n *\n * The pinned fragment is sticky and initially empty. The non-pinned fragment\n * holds the label in a CSS-sticky span that floats toward the pinned area on\n * horizontal scroll. When the non-pinned fragment scrolls far enough, the\n * plugin's `onScroll` handler transfers the label into the pinned fragment.\n */\nexport interface SplitGroupState {\n /** Group identifier (data-group attribute value). */\n groupId: string;\n /** Original group label text. */\n label: string;\n /** The sticky (pinned) fragment element. */\n pinnedFragment: HTMLElement;\n /** The scrollable (non-pinned) fragment element. */\n scrollableFragment: HTMLElement;\n /** The floating <span> inside the scrollable fragment. */\n floatLabel: HTMLElement;\n /** Field of the last pinned column — used to toggle `.group-end`. */\n pinnedField: string;\n /** Whether the label is currently shown inside the pinned fragment. */\n isTransferred: boolean;\n /** Current translateX offset applied to the floating label (px). */\n floatOffset: number;\n}\n\n/**\n * Apply sticky offsets to header and body cells.\n * This modifies the DOM elements in place.\n *\n * @param host - The grid host element (render root for DOM queries)\n * @param columns - Array of column configurations\n * @returns Sticky offsets result with group-end adjustments and measured offset maps\n */\nexport function applyStickyOffsets(host: HTMLElement, columns: any[]): StickyOffsetsResult {\n const emptyResult: StickyOffsetsResult = {\n groupEndAdjustments: { addGroupEnd: new Set(), removeGroupEnd: new Set() },\n leftOffsets: new Map(),\n rightOffsets: new Map(),\n splitGroups: [],\n };\n\n // With light DOM, query the host element directly\n const headerCells = Array.from(host.querySelectorAll('.header-row .cell')) as HTMLElement[];\n if (!headerCells.length) return emptyResult;\n\n // Detect text direction from the host element\n const direction = getDirection(host);\n\n // Collect measured offsets for caching in the plugin\n const leftOffsets = new Map<string, number>();\n const rightOffsets = new Map<string, number>();\n\n // Apply left sticky (includes 'start' in LTR, 'end' in RTL)\n let left = 0;\n for (const col of columns) {\n if (isResolvedLeft(col, direction)) {\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n leftOffsets.set(col.field, left);\n cell.classList.add(GridClasses.STICKY_LEFT);\n cell.style.position = 'sticky';\n cell.style.left = left + 'px';\n // Body cells: use data-field for reliable matching (data-col indices may differ\n // between _columns and _visibleColumns due to hidden/utility columns)\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${col.field}\"]`).forEach((el) => {\n el.classList.add(GridClasses.STICKY_LEFT);\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.left = left + 'px';\n });\n left += cell.offsetWidth;\n }\n }\n }\n\n // Apply right sticky (includes 'end' in LTR, 'start' in RTL) - process in reverse\n let right = 0;\n for (const col of [...columns].reverse()) {\n if (isResolvedRight(col, direction)) {\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n rightOffsets.set(col.field, right);\n cell.classList.add(GridClasses.STICKY_RIGHT);\n cell.style.position = 'sticky';\n cell.style.right = right + 'px';\n // Body cells: use data-field for reliable matching\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${col.field}\"]`).forEach((el) => {\n el.classList.add(GridClasses.STICKY_RIGHT);\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.right = right + 'px';\n });\n right += cell.offsetWidth;\n }\n }\n }\n\n // Apply sticky offsets to column group header cells and collect group-end adjustments\n const splitGroups: SplitGroupState[] = [];\n const adjustments = applyGroupHeaderStickyOffsets(host, columns, headerCells, direction, splitGroups);\n\n // Apply group-end adjustments to header cells and visible body cells\n if (adjustments.addGroupEnd.size > 0 || adjustments.removeGroupEnd.size > 0) {\n for (const field of adjustments.addGroupEnd) {\n const hCell = headerCells.find((c) => c.getAttribute('data-field') === field);\n if (hCell) hCell.classList.add('group-end');\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${field}\"]`).forEach((el) => {\n el.classList.add('group-end');\n });\n }\n for (const field of adjustments.removeGroupEnd) {\n const hCell = headerCells.find((c) => c.getAttribute('data-field') === field);\n if (hCell) hCell.classList.remove('group-end');\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${field}\"]`).forEach((el) => {\n el.classList.remove('group-end');\n });\n }\n }\n\n return { groupEndAdjustments: adjustments, leftOffsets, rightOffsets, splitGroups };\n}\n\n/**\n * Apply sticky offsets to column group header cells.\n * - If ALL columns in a group are pinned the same direction, the whole cell is pinned.\n * - If an implicit (unlabelled) group mixes pinned and non-pinned columns,\n * the cell is split at pin boundaries so pinned portions can be sticky.\n *\n * @param host - The grid host element\n * @param columns - Array of column configurations\n * @param headerCells - Already-queried header cells (with sticky offsets applied)\n * @param direction - Text direction\n * @returns Group-end adjustments for pin boundaries within implicit groups\n */\nfunction applyGroupHeaderStickyOffsets(\n host: HTMLElement,\n columns: any[],\n headerCells: HTMLElement[],\n direction: TextDirection,\n splitGroups: SplitGroupState[],\n): GroupEndAdjustments {\n const adjustments: GroupEndAdjustments = { addGroupEnd: new Set(), removeGroupEnd: new Set() };\n const groupCells = Array.from(host.querySelectorAll('.header-group-row .header-group-cell')) as HTMLElement[];\n if (!groupCells.length) return adjustments;\n\n for (const groupCell of groupCells) {\n // Parse gridColumn to find which column range this group spans\n // Format: \"startCol / span N\" (1-based)\n const gridCol = groupCell.style.gridColumn;\n if (!gridCol) continue;\n\n const match = gridCol.match(/^(\\d+)\\s*\\/\\s*span\\s+(\\d+)$/);\n if (!match) continue;\n\n const startIdx = parseInt(match[1], 10) - 1; // Convert to 0-based\n const span = parseInt(match[2], 10);\n const endIdx = startIdx + span - 1;\n\n // Get the columns this group spans\n const spannedColumns = columns.slice(startIdx, endIdx + 1);\n if (!spannedColumns.length) continue;\n\n const allLeft = spannedColumns.every((col: any) => isResolvedLeft(col, direction));\n const allRight = spannedColumns.every((col: any) => isResolvedRight(col, direction));\n\n if (allLeft) {\n const firstField = spannedColumns[0].field;\n const firstCell = headerCells.find((c) => c.getAttribute('data-field') === firstField);\n if (firstCell) {\n groupCell.classList.add(GridClasses.STICKY_LEFT);\n groupCell.style.position = 'sticky';\n groupCell.style.left = firstCell.style.left;\n }\n } else if (allRight) {\n const lastField = spannedColumns[spannedColumns.length - 1].field;\n const lastCell = headerCells.find((c) => c.getAttribute('data-field') === lastField);\n if (lastCell) {\n groupCell.classList.add(GridClasses.STICKY_RIGHT);\n groupCell.style.position = 'sticky';\n groupCell.style.right = lastCell.style.right;\n }\n } else if (groupCell.classList.contains('implicit-group')) {\n // Implicit group with mixed pinning: split into separate cells so pinned\n // portions become sticky while non-pinned portions scroll normally.\n splitMixedPinImplicitGroup(groupCell, spannedColumns, startIdx, headerCells, direction, adjustments);\n } else {\n // Explicit (labelled) group with mixed pinning: split the group header so\n // the pinned fragment keeps the label and sticks, while the non-pinned\n // remainder scrolls away. This makes the group header visually shrink to\n // the pinned column width when scrolled horizontally.\n const someLeft = spannedColumns.some((col: any) => isResolvedLeft(col, direction));\n const someRight = spannedColumns.some((col: any) => isResolvedRight(col, direction));\n if (someLeft || someRight) {\n splitMixedPinExplicitGroup(\n groupCell,\n spannedColumns,\n startIdx,\n headerCells,\n direction,\n adjustments,\n splitGroups,\n );\n }\n }\n }\n\n return adjustments;\n}\n\n/** Classify a column's pin state after resolving logical positions. */\ntype PinState = 'left' | 'right' | 'none';\n\nfunction getPinState(col: any, direction: TextDirection): PinState {\n if (isResolvedLeft(col, direction)) return 'left';\n if (isResolvedRight(col, direction)) return 'right';\n return 'none';\n}\n\n/**\n * Split an implicit (unlabelled) group header cell into fragments at pin-state\n * boundaries. Each fragment becomes its own header-group-cell; pinned fragments\n * get sticky positioning.\n *\n * Also populates `adjustments` with group-end border changes:\n * - Last column of a left-pinned run gets `group-end` (visual separator at pin edge)\n * - Last column of a subsequent non-pinned run that contains only utility columns\n * loses `group-end` (it visually merges with the adjacent explicit group)\n */\nfunction splitMixedPinImplicitGroup(\n groupCell: HTMLElement,\n spannedColumns: any[],\n startIdx: number,\n headerCells: HTMLElement[],\n direction: TextDirection,\n adjustments: GroupEndAdjustments,\n): void {\n // Partition columns into contiguous runs of the same pin state\n const runs: { state: PinState; cols: any[]; colStart: number }[] = [];\n for (let i = 0; i < spannedColumns.length; i++) {\n const state = getPinState(spannedColumns[i], direction);\n const prev = runs[runs.length - 1];\n if (prev && prev.state === state) {\n prev.cols.push(spannedColumns[i]);\n } else {\n runs.push({ state, cols: [spannedColumns[i]], colStart: startIdx + i });\n }\n }\n\n if (runs.length <= 1) return; // Nothing to split\n\n const parent = groupCell.parentElement;\n if (!parent) return;\n\n const nextSibling = groupCell.nextSibling;\n parent.removeChild(groupCell);\n\n for (const run of runs) {\n const cell = document.createElement('div');\n cell.className = groupCell.className; // Preserves implicit-group, cell, header-group-cell\n cell.setAttribute('data-group', groupCell.getAttribute('data-group') || '');\n cell.style.gridColumn = `${run.colStart + 1} / span ${run.cols.length}`;\n\n if (run.state === 'left') {\n const firstField = run.cols[0].field;\n const firstCell = headerCells.find((c) => c.getAttribute('data-field') === firstField);\n if (firstCell) {\n cell.classList.add(GridClasses.STICKY_LEFT);\n cell.style.position = 'sticky';\n cell.style.left = firstCell.style.left;\n }\n } else if (run.state === 'right') {\n const lastField = run.cols[run.cols.length - 1].field;\n const lastCell = headerCells.find((c) => c.getAttribute('data-field') === lastField);\n if (lastCell) {\n cell.classList.add(GridClasses.STICKY_RIGHT);\n cell.style.position = 'sticky';\n cell.style.right = lastCell.style.right;\n }\n } else if (run.state === 'none') {\n // Suppress border on utility-only non-pinned remnants — they visually merge\n // with the adjacent explicit group.\n const allUtility = run.cols.every((c: any) => String(c.field || '').startsWith('__tbw_'));\n if (allUtility) {\n cell.style.borderRightStyle = 'none';\n }\n }\n\n if (nextSibling) {\n parent.insertBefore(cell, nextSibling);\n } else {\n parent.appendChild(cell);\n }\n }\n\n // Compute group-end adjustments at pin boundaries.\n // When a pinned run is followed by a non-pinned run, the last column of the\n // pinned run should be the visual group boundary (group-end).\n // The non-pinned remnant's last column should lose group-end if all its\n // columns are utility columns (e.g. __tbw_expander) — they visually merge\n // with the adjacent explicit group.\n for (let ri = 0; ri < runs.length; ri++) {\n const run = runs[ri];\n const nextRun = runs[ri + 1];\n\n if (run.state !== 'none' && nextRun && nextRun.state === 'none') {\n // Last column of pinned run gets group-end\n const lastPinnedField = run.cols[run.cols.length - 1].field;\n if (lastPinnedField) adjustments.addGroupEnd.add(lastPinnedField);\n }\n\n if (run.state === 'none') {\n // Check if all columns in this non-pinned run are utility columns\n const allUtility = run.cols.every((c: any) => String(c.field || '').startsWith('__tbw_'));\n if (allUtility) {\n // Remove group-end from the last column — it visually merges with the next group\n const lastField = run.cols[run.cols.length - 1].field;\n if (lastField) adjustments.removeGroupEnd.add(lastField);\n }\n }\n }\n}\n\n/**\n * Split an explicit (labelled) group header cell at pin boundaries.\n *\n * **Pinned fragment:** sticky, initially empty (no label, no `.group-end`).\n * **Non-pinned fragment:** carries the label inside a CSS-sticky elevated `<span>`.\n * The span floats left over the pinned fragment as the user scrolls horizontally.\n *\n * When the non-pinned fragment's left edge reaches the pinned fragment's right\n * edge (i.e. only the pinned column width remains), the plugin's `onScroll` hook\n * transfers the label into the pinned fragment and adds `.group-end`.\n */\nfunction splitMixedPinExplicitGroup(\n groupCell: HTMLElement,\n spannedColumns: any[],\n startIdx: number,\n headerCells: HTMLElement[],\n direction: TextDirection,\n _adjustments: GroupEndAdjustments,\n splitGroups: SplitGroupState[],\n): void {\n // Partition columns into contiguous runs of the same pin state\n const runs: { state: PinState; cols: any[]; colStart: number }[] = [];\n for (let i = 0; i < spannedColumns.length; i++) {\n const state = getPinState(spannedColumns[i], direction);\n const prev = runs[runs.length - 1];\n if (prev && prev.state === state) {\n prev.cols.push(spannedColumns[i]);\n } else {\n runs.push({ state, cols: [spannedColumns[i]], colStart: startIdx + i });\n }\n }\n\n if (runs.length <= 1) return; // Nothing to split\n\n const parent = groupCell.parentElement;\n if (!parent) return;\n\n const label = groupCell.textContent || '';\n const groupId = groupCell.getAttribute('data-group') || '';\n const nextSibling = groupCell.nextSibling;\n parent.removeChild(groupCell);\n\n // Identify the pinned run and the adjacent non-pinned run.\n const firstLeftRunIdx = runs.findIndex((r) => r.state === 'left');\n let lastRightRunIdx = -1;\n for (let i = runs.length - 1; i >= 0; i--) {\n if (runs[i].state === 'right') {\n lastRightRunIdx = i;\n break;\n }\n }\n const pinnedRunIdx = firstLeftRunIdx >= 0 ? firstLeftRunIdx : lastRightRunIdx;\n const floatRunIdx =\n firstLeftRunIdx >= 0 && firstLeftRunIdx + 1 < runs.length\n ? firstLeftRunIdx + 1\n : lastRightRunIdx >= 0 && lastRightRunIdx - 1 >= 0\n ? lastRightRunIdx - 1\n : -1;\n\n let pinnedStickyOffset = '0px';\n let pinnedFragment: HTMLElement | undefined;\n let scrollableFragment: HTMLElement | undefined;\n let floatLabel: HTMLElement | undefined;\n let pinnedField = '';\n\n for (let ri = 0; ri < runs.length; ri++) {\n const run = runs[ri];\n const cell = document.createElement('div');\n cell.className = groupCell.className;\n cell.setAttribute('data-group', groupId);\n cell.style.gridColumn = `${run.colStart + 1} / span ${run.cols.length}`;\n\n if (run.state === 'left') {\n const firstField = run.cols[0].field;\n const firstCell = headerCells.find((c) => c.getAttribute('data-field') === firstField);\n if (firstCell) {\n cell.classList.add(GridClasses.STICKY_LEFT);\n cell.style.position = 'sticky';\n cell.style.left = firstCell.style.left;\n pinnedStickyOffset = firstCell.style.left;\n }\n\n if (ri === pinnedRunIdx) {\n // Pinned fragment: empty, no group-end, hide border so it merges visually\n cell.style.borderRightStyle = 'none';\n pinnedFragment = cell;\n pinnedField = run.cols[run.cols.length - 1].field;\n }\n } else if (run.state === 'right') {\n const lastField = run.cols[run.cols.length - 1].field;\n const lastCell = headerCells.find((c) => c.getAttribute('data-field') === lastField);\n if (lastCell) {\n cell.classList.add(GridClasses.STICKY_RIGHT);\n cell.style.position = 'sticky';\n cell.style.right = lastCell.style.right;\n }\n\n if (ri === pinnedRunIdx) {\n cell.style.borderLeftStyle = 'none';\n pinnedFragment = cell;\n pinnedField = run.cols[0].field;\n }\n }\n\n // The non-pinned run that carries the floating label.\n // CSS sticky won't work here because .header-group-cell has overflow:hidden\n // (set by grouping-columns.css for text clipping). So the plugin's onScroll\n // handler manually translates the span to simulate sticky behavior.\n if (ri === floatRunIdx) {\n // Allow the span to overflow outside the cell when translated left\n cell.style.overflow = 'visible';\n\n const span = document.createElement('span');\n span.textContent = label;\n span.style.position = 'relative';\n span.style.zIndex = '36';\n span.style.display = 'block';\n span.style.overflow = 'hidden';\n span.style.textOverflow = 'ellipsis';\n span.style.whiteSpace = 'nowrap';\n\n cell.appendChild(span);\n scrollableFragment = cell;\n floatLabel = span;\n }\n\n if (nextSibling) {\n parent.insertBefore(cell, nextSibling);\n } else {\n parent.appendChild(cell);\n }\n }\n\n // Register the split group state so the plugin can manage label transfer on scroll.\n if (pinnedFragment && scrollableFragment && floatLabel && pinnedField) {\n splitGroups.push({\n groupId,\n label,\n pinnedFragment,\n scrollableFragment,\n floatLabel,\n pinnedField,\n isTransferred: false,\n floatOffset: 0,\n });\n }\n}\n\n/**\n * Reorder columns so that pinned-left columns come first and pinned-right columns come last.\n * Maintains the relative order within each group (left-pinned, unpinned, right-pinned).\n *\n * @param columns - Array of column configurations (in their current order)\n * @param direction - Text direction ('ltr' or 'rtl'), used to resolve logical positions\n * @returns New array with pinned columns moved to the edges\n */\nexport function reorderColumnsForPinning(columns: readonly any[], direction: TextDirection = 'ltr'): any[] {\n const left: any[] = [];\n const middle: any[] = [];\n const right: any[] = [];\n\n for (const col of columns) {\n const pinned = getColumnPinned(col);\n if (pinned) {\n const resolved = resolveStickyPosition(pinned, direction);\n if (resolved === 'left') left.push(col);\n else right.push(col);\n } else {\n middle.push(col);\n }\n }\n\n return [...left, ...middle, ...right];\n}\n\n/**\n * Clear sticky positioning from all cells.\n *\n * @param host - The grid host element (render root for DOM queries)\n */\nexport function clearStickyOffsets(host: HTMLElement): void {\n // With light DOM, query the host element directly\n const cells = host.querySelectorAll(`.${GridClasses.STICKY_LEFT}, .${GridClasses.STICKY_RIGHT}`);\n cells.forEach((cell) => {\n cell.classList.remove(GridClasses.STICKY_LEFT, GridClasses.STICKY_RIGHT);\n (cell as HTMLElement).style.position = '';\n (cell as HTMLElement).style.left = '';\n (cell as HTMLElement).style.right = '';\n });\n}\n","/**\n * Pinned Columns Plugin (Class-based)\n *\n * Enables column pinning (sticky left/right positioning).\n */\n\nimport { GridClasses } from '../../core/constants';\nimport { getDirection } from '../../core/internal/utils';\nimport type { AfterCellRenderContext, PluginManifest, PluginQuery, ScrollEvent } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport type { ContextMenuParams, HeaderContextMenuItem } from '../context-menu/types';\nimport {\n applyStickyOffsets,\n clearStickyOffsets,\n getColumnPinned,\n getLeftStickyColumns,\n getRightStickyColumns,\n hasStickyColumns,\n reorderColumnsForPinning,\n type GroupEndAdjustments,\n type SplitGroupState,\n} from './pinned-columns';\nimport type { PinnedColumnsConfig, PinnedPosition } from './types';\n\n/** Query type constant for checking if a column can be moved */\nconst QUERY_CAN_MOVE_COLUMN = 'canMoveColumn';\n\n/**\n * Pinned Columns Plugin for tbw-grid\n *\n * Freezes columns to the left or right edge of the grid—essential for keeping key\n * identifiers or action buttons visible while scrolling through wide datasets. Just set\n * `pinned: 'left'` or `pinned: 'right'` on your column definitions.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n * ```\n *\n * ## Column Configuration\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `pinned` | `'left' \\| 'right' \\| 'start' \\| 'end'` | Pin column to edge (logical or physical) |\n * | `meta.lockPinning` | `boolean` | `false` | Prevent user from pin/unpin via context menu |\n *\n * ### RTL Support\n *\n * Use logical values (`start`/`end`) for grids that work in both LTR and RTL layouts:\n * - `'start'` - Pins to left in LTR, right in RTL\n * - `'end'` - Pins to right in LTR, left in RTL\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-shadow` | `4px 0 8px rgba(0,0,0,0.1)` | Shadow on pinned column edge |\n * | `--tbw-pinned-border` | `var(--tbw-color-border)` | Border between pinned and scrollable |\n *\n * @example Pin ID Left and Actions Right\n * ```ts\n * import { queryGrid } from '@toolbox-web/grid';\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n *\n * const grid = queryGrid('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'left', width: 80 },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * { field: 'department', header: 'Department' },\n * { field: 'actions', header: 'Actions', pinned: 'right', width: 120 },\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @example RTL-Compatible Pinning\n * ```ts\n * // Same config works in LTR and RTL\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'start' }, // Left in LTR, Right in RTL\n * { field: 'name', header: 'Name' },\n * { field: 'actions', header: 'Actions', pinned: 'end' }, // Right in LTR, Left in RTL\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link PinnedColumnsConfig} for configuration options\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedColumnsPlugin extends BaseGridPlugin<PinnedColumnsConfig> {\n /**\n * Plugin manifest - declares owned properties and handled queries.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'pinned',\n level: 'column',\n description: 'the \"pinned\" column property',\n isUsed: (v) => v === 'left' || v === 'right' || v === 'start' || v === 'end',\n },\n {\n property: 'sticky',\n level: 'column',\n description: 'the \"sticky\" column property (deprecated, use \"pinned\")',\n isUsed: (v) => v === 'left' || v === 'right' || v === 'start' || v === 'end',\n },\n ],\n queries: [\n {\n type: QUERY_CAN_MOVE_COLUMN,\n description: 'Prevents pinned (sticky) columns from being moved/reordered',\n },\n {\n type: 'getStickyOffsets',\n description: 'Returns the sticky offsets for left/right pinned columns',\n },\n {\n type: 'getContextMenuItems',\n description: 'Contributes pin/unpin items to the header context menu',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'pinnedColumns';\n\n /** @internal */\n protected override get defaultConfig(): Partial<PinnedColumnsConfig> {\n return {};\n }\n\n // #region Internal State\n private isApplied = false;\n private leftOffsets = new Map<string, number>();\n private rightOffsets = new Map<string, number>();\n /** Group-end adjustments for pin boundaries within implicit groups. */\n #groupEndAdjustments: GroupEndAdjustments = { addGroupEnd: new Set(), removeGroupEnd: new Set() };\n /** Split explicit-group state for scroll-driven label transfer. */\n #splitGroups: SplitGroupState[] = [];\n /**\n * Snapshot of the column field order before the first context-menu pin.\n * Used to restore original positions when unpinning.\n */\n #originalColumnOrder: string[] = [];\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.leftOffsets.clear();\n this.rightOffsets.clear();\n this.isApplied = false;\n this.#groupEndAdjustments = { addGroupEnd: new Set(), removeGroupEnd: new Set() };\n this.#splitGroups = [];\n this.#originalColumnOrder = [];\n }\n // #endregion\n\n // #region Detection\n\n /**\n * Auto-detect sticky columns from column configuration.\n */\n static detect(rows: readonly unknown[], config: { columns?: ColumnConfig[] }): boolean {\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasStickyColumns(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n const cols = [...columns];\n this.isApplied = hasStickyColumns(cols);\n if (!this.isApplied) return cols;\n\n const host = this.gridElement;\n const direction = host ? getDirection(host) : 'ltr';\n return reorderColumnsForPinning(cols, direction) as ColumnConfig[];\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isApplied) {\n return;\n }\n\n const host = this.gridElement;\n const columns = [...this.columns];\n\n if (!hasStickyColumns(columns)) {\n clearStickyOffsets(host);\n this.isApplied = false;\n return;\n }\n\n // Apply sticky offsets after a microtask to ensure DOM is ready\n queueMicrotask(() => {\n const result = applyStickyOffsets(host, columns);\n this.#groupEndAdjustments = result.groupEndAdjustments;\n this.leftOffsets = result.leftOffsets;\n this.rightOffsets = result.rightOffsets;\n this.#splitGroups = result.splitGroups;\n // Re-apply scroll-driven state (transfer + translateX) to the fresh DOM elements\n this.#updateSplitGroupScroll();\n });\n }\n\n /**\n * Apply sticky positioning and group-end adjustments on cells rendered during scroll.\n * When virtualization recycles row pool elements, newly rendered cells lack the\n * inline sticky styles applied by `applyStickyOffsets` in `afterRender`. This hook\n * ensures every cell gets correct sticky positioning using the cached offset maps.\n * @internal\n */\n override afterCellRender(context: AfterCellRenderContext): void {\n if (!this.isApplied) return;\n const field = context.column.field;\n const cellEl = context.cellElement;\n\n // Apply sticky positioning from cached offset maps\n const leftOffset = this.leftOffsets.get(field);\n if (leftOffset !== undefined) {\n if (!cellEl.classList.contains(GridClasses.STICKY_LEFT)) {\n cellEl.classList.add(GridClasses.STICKY_LEFT);\n }\n cellEl.style.position = 'sticky';\n cellEl.style.left = leftOffset + 'px';\n } else {\n const rightOffset = this.rightOffsets.get(field);\n if (rightOffset !== undefined) {\n if (!cellEl.classList.contains(GridClasses.STICKY_RIGHT)) {\n cellEl.classList.add(GridClasses.STICKY_RIGHT);\n }\n cellEl.style.position = 'sticky';\n cellEl.style.right = rightOffset + 'px';\n }\n }\n\n // Maintain group-end adjustments at pin boundaries\n if (this.#groupEndAdjustments.addGroupEnd.has(field)) {\n context.cellElement.classList.add('group-end');\n } else if (this.#groupEndAdjustments.removeGroupEnd.has(field)) {\n context.cellElement.classList.remove('group-end');\n }\n }\n\n /**\n * Handle horizontal scroll to manage floating group labels.\n *\n * When an explicit column group has a mix of pinned and non-pinned columns,\n * the label starts in the scrollable fragment and floats toward the pinned\n * column via CSS `position: sticky`. Once the scrollable fragment scrolls\n * far enough that the label would be clipped, this hook transfers the label\n * into the sticky pinned fragment and applies `.group-end` to the pinned\n * column cells, creating a visual separator. Scrolling back reverses the\n * transfer.\n * @internal\n */\n override onScroll(_event: ScrollEvent): void {\n this.#updateSplitGroupScroll();\n }\n\n /**\n * Apply scroll-driven state to split group headers.\n *\n * Handles both the manual translateX positioning (simulating sticky) and the\n * transfer of the label into/out of the pinned fragment with `.group-end`.\n * Called from `onScroll` on every scroll event and from `afterRender` after\n * the split group DOM is rebuilt (e.g. after selection click triggers re-render).\n */\n #updateSplitGroupScroll(): void {\n if (!this.isApplied || this.#splitGroups.length === 0) return;\n\n const host = this.gridElement;\n\n for (const sg of this.#splitGroups) {\n const pinnedRect = sg.pinnedFragment.getBoundingClientRect();\n const scrollableRect = sg.scrollableFragment.getBoundingClientRect();\n\n // Transfer when the non-pinned fragment's right edge reaches the pinned\n // fragment's right edge — all non-pinned columns scrolled behind the pin.\n const shouldTransfer = scrollableRect.right <= pinnedRect.right;\n\n if (shouldTransfer && !sg.isTransferred) {\n // Move label into the pinned fragment\n sg.pinnedFragment.textContent = sg.label;\n sg.pinnedFragment.style.overflow = 'hidden';\n sg.pinnedFragment.style.textOverflow = 'ellipsis';\n sg.pinnedFragment.style.whiteSpace = 'nowrap';\n sg.pinnedFragment.style.borderRightStyle = '';\n sg.floatLabel.style.visibility = 'hidden';\n sg.floatLabel.style.transform = '';\n sg.floatOffset = 0;\n\n // Add group-end to pinned column header + body cells\n this.#groupEndAdjustments.addGroupEnd.add(sg.pinnedField);\n host\n .querySelectorAll(\n `.header-row .cell[data-field=\"${sg.pinnedField}\"], .data-grid-row .cell[data-field=\"${sg.pinnedField}\"]`,\n )\n .forEach((el) => el.classList.add('group-end'));\n\n sg.isTransferred = true;\n } else if (!shouldTransfer && sg.isTransferred) {\n // Reverse transfer — label goes back to the floating span\n sg.pinnedFragment.textContent = '';\n sg.pinnedFragment.style.overflow = '';\n sg.pinnedFragment.style.textOverflow = '';\n sg.pinnedFragment.style.whiteSpace = '';\n sg.pinnedFragment.style.borderRightStyle = 'none';\n sg.floatLabel.style.visibility = '';\n sg.floatLabel.style.transform = '';\n sg.floatOffset = 0;\n\n // Remove group-end from pinned column cells\n this.#groupEndAdjustments.addGroupEnd.delete(sg.pinnedField);\n host\n .querySelectorAll(\n `.header-row .cell[data-field=\"${sg.pinnedField}\"], .data-grid-row .cell[data-field=\"${sg.pinnedField}\"]`,\n )\n .forEach((el) => el.classList.remove('group-end'));\n\n sg.isTransferred = false;\n }\n\n // Manually position the floating label to simulate sticky behavior.\n // CSS sticky can't be used because .header-group-cell has overflow:hidden.\n // We track the current translateX offset to correctly compute the span's\n // natural (un-translated) position from its measured rect.\n if (!sg.isTransferred) {\n const spanRect = sg.floatLabel.getBoundingClientRect();\n const spanNaturalLeft = spanRect.left - sg.floatOffset;\n const targetLeft = pinnedRect.left;\n if (spanNaturalLeft < targetLeft) {\n sg.floatOffset = targetLeft - spanNaturalLeft;\n sg.floatLabel.style.transform = `translateX(${sg.floatOffset}px)`;\n } else {\n sg.floatOffset = 0;\n sg.floatLabel.style.transform = '';\n }\n }\n }\n }\n\n /**\n * Handle inter-plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n switch (query.type) {\n case QUERY_CAN_MOVE_COLUMN: {\n // Prevent pinned columns from being moved/reordered.\n // Pinned columns have fixed positions and should not be draggable.\n const column = query.context as ColumnConfig;\n if (getColumnPinned(column) != null) {\n return false;\n }\n return undefined; // Let other plugins or default behavior decide\n }\n case 'getStickyOffsets': {\n // Return the calculated sticky offsets for column virtualization\n return {\n left: Object.fromEntries(this.leftOffsets),\n right: Object.fromEntries(this.rightOffsets),\n };\n }\n case 'getContextMenuItems': {\n const params = query.context as ContextMenuParams;\n if (!params.isHeader) return undefined;\n\n const column = params.column as ColumnConfig;\n if (!column?.field) return undefined;\n\n // Don't offer pin/unpin for locked-pinning columns\n if (column.meta?.lockPinning) return undefined;\n\n const pinned = getColumnPinned(column);\n const isPinned = pinned != null;\n const items: HeaderContextMenuItem[] = [];\n\n if (isPinned) {\n items.push({\n id: 'pinned/unpin',\n label: 'Unpin Column',\n icon: '📌',\n order: 40,\n action: () => this.setPinPosition(column.field, undefined),\n });\n } else {\n items.push({\n id: 'pinned/pin-left',\n label: 'Pin Left',\n icon: '⬅',\n order: 40,\n action: () => this.setPinPosition(column.field, 'left'),\n });\n items.push({\n id: 'pinned/pin-right',\n label: 'Pin Right',\n icon: '➡',\n order: 41,\n action: () => this.setPinPosition(column.field, 'right'),\n });\n }\n\n return items;\n }\n default:\n return undefined;\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Set the pin position for a column.\n * Updates the column's `pinned` property and triggers a full re-render.\n *\n * @param field - The field name of the column to pin/unpin\n * @param position - The pin position (`'left'`, `'right'`, `'start'`, `'end'`), or `undefined` to unpin\n */\n setPinPosition(field: string, position: PinnedPosition | undefined): void {\n // Read the currently-visible columns from the plugin accessor.\n // These are the post-processColumns result, which is the authoritative column set.\n const currentColumns = this.columns;\n if (!currentColumns?.length) return;\n\n const currentIndex = currentColumns.findIndex((col) => col.field === field);\n if (currentIndex === -1) return;\n\n const gridEl = this.gridElement as HTMLElement & { columns?: ColumnConfig[] };\n\n if (position) {\n // PINNING: snapshot original column order if this is the first context-menu pin.\n // The snapshot lets us restore columns to their original positions on unpin.\n if (this.#originalColumnOrder.length === 0) {\n this.#originalColumnOrder = currentColumns.map((c) => c.field);\n }\n\n // Set the pinned property; processColumns will reorder on next render\n const updated = currentColumns.map((col) => {\n if (col.field !== field) return col;\n const copy = { ...col };\n (copy as ColumnConfig & { pinned?: PinnedPosition }).pinned = position;\n delete (copy as ColumnConfig & { sticky?: PinnedPosition }).sticky;\n return copy;\n });\n\n gridEl.columns = updated;\n } else {\n // UNPINNING: restore column to its original position\n const col = currentColumns[currentIndex];\n const copy = { ...col };\n delete (copy as ColumnConfig & { pinned?: PinnedPosition }).pinned;\n delete (copy as ColumnConfig & { sticky?: PinnedPosition }).sticky;\n\n // Remove from current position\n const remaining = [...currentColumns];\n remaining.splice(currentIndex, 1);\n\n // Find the best insertion point using the original order snapshot\n const originalIndex = this.#originalColumnOrder.indexOf(field);\n if (originalIndex >= 0) {\n // Scan remaining non-pinned columns and find the first whose original\n // position is greater than this column's original position.\n let insertIndex = remaining.length;\n for (let i = 0; i < remaining.length; i++) {\n if (getColumnPinned(remaining[i])) continue; // skip pinned columns\n const otherOriginal = this.#originalColumnOrder.indexOf(remaining[i].field);\n if (otherOriginal > originalIndex) {\n insertIndex = i;\n break;\n }\n }\n remaining.splice(insertIndex, 0, copy);\n } else {\n // Original position unknown — keep at current index\n remaining.splice(Math.min(currentIndex, remaining.length), 0, copy);\n }\n\n // If no more pinned columns remain, clear the snapshot\n if (!remaining.some((c) => getColumnPinned(c) != null)) {\n this.#originalColumnOrder = [];\n }\n\n gridEl.columns = remaining;\n }\n }\n\n /**\n * Re-apply sticky offsets (e.g., after column resize).\n */\n refreshStickyOffsets(): void {\n const columns = [...this.columns];\n const result = applyStickyOffsets(this.gridElement, columns);\n this.#groupEndAdjustments = result.groupEndAdjustments;\n this.leftOffsets = result.leftOffsets;\n this.rightOffsets = result.rightOffsets;\n this.#splitGroups = result.splitGroups;\n }\n\n /**\n * Get columns pinned to the left (after resolving logical positions for current direction).\n */\n getLeftPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n const direction = getDirection(this.gridElement);\n return getLeftStickyColumns(columns, direction);\n }\n\n /**\n * Get columns pinned to the right (after resolving logical positions for current direction).\n */\n getRightPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n const direction = getDirection(this.gridElement);\n return getRightStickyColumns(columns, direction);\n }\n\n /**\n * Clear all sticky positioning.\n */\n clearStickyPositions(): void {\n clearStickyOffsets(this.gridElement);\n }\n\n /**\n * Report horizontal scroll boundary offsets for pinned columns.\n * Used by keyboard navigation to ensure focused cells aren't hidden behind sticky columns.\n * @internal\n */\n override getHorizontalScrollOffsets(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } | undefined {\n if (!this.isApplied) {\n return undefined;\n }\n\n let left = 0;\n let right = 0;\n\n if (rowEl) {\n // Calculate from rendered cells in the row\n const stickyLeftCells = rowEl.querySelectorAll(`.${GridClasses.STICKY_LEFT}`);\n const stickyRightCells = rowEl.querySelectorAll(`.${GridClasses.STICKY_RIGHT}`);\n stickyLeftCells.forEach((el) => {\n left += (el as HTMLElement).offsetWidth;\n });\n stickyRightCells.forEach((el) => {\n right += (el as HTMLElement).offsetWidth;\n });\n } else {\n // Fall back to header row if no row element provided\n const host = this.gridElement;\n const headerCells = host.querySelectorAll('.header-row .cell');\n headerCells.forEach((cell) => {\n if (cell.classList.contains(GridClasses.STICKY_LEFT)) {\n left += (cell as HTMLElement).offsetWidth;\n } else if (cell.classList.contains(GridClasses.STICKY_RIGHT)) {\n right += (cell as HTMLElement).offsetWidth;\n }\n });\n }\n\n // Skip horizontal scrolling if focused cell is pinned (it's always visible)\n const skipScroll =\n focusedCell?.classList.contains(GridClasses.STICKY_LEFT) ||\n focusedCell?.classList.contains(GridClasses.STICKY_RIGHT);\n\n return { left, right, skipScroll };\n }\n // #endregion\n}\n"],"names":["getColumnPinned","col","pinned","sticky","meta","resolveStickyPosition","position","direction","resolveInlinePosition","isResolvedLeft","isResolvedRight","hasStickyColumns","columns","some","applyStickyOffsets","host","emptyResult","groupEndAdjustments","addGroupEnd","Set","removeGroupEnd","leftOffsets","Map","rightOffsets","splitGroups","headerCells","Array","from","querySelectorAll","length","getDirection","left","cell","find","c","getAttribute","field","set","classList","add","GridClasses","STICKY_LEFT","style","forEach","el","offsetWidth","right","reverse","STICKY_RIGHT","adjustments","groupCells","groupCell","gridCol","gridColumn","match","startIdx","parseInt","endIdx","spannedColumns","slice","allLeft","every","allRight","firstField","firstCell","lastField","lastCell","contains","splitMixedPinImplicitGroup","someLeft","someRight","splitMixedPinExplicitGroup","applyGroupHeaderStickyOffsets","size","hCell","remove","getPinState","runs","i","state","prev","cols","push","colStart","parent","parentElement","nextSibling","removeChild","run","document","createElement","className","setAttribute","String","startsWith","borderRightStyle","insertBefore","appendChild","ri","nextRun","lastPinnedField","_adjustments","label","textContent","groupId","firstLeftRunIdx","findIndex","r","lastRightRunIdx","pinnedRunIdx","floatRunIdx","pinnedFragment","scrollableFragment","floatLabel","pinnedField","borderLeftStyle","overflow","span","zIndex","display","textOverflow","whiteSpace","isTransferred","floatOffset","clearStickyOffsets","QUERY_CAN_MOVE_COLUMN","PinnedColumnsPlugin","BaseGridPlugin","static","ownedProperties","property","level","description","isUsed","v","queries","type","name","defaultConfig","isApplied","originalColumnOrder","detach","this","clear","detect","rows","config","isArray","processColumns","gridElement","middle","reorderColumnsForPinning","afterRender","queueMicrotask","result","updateSplitGroupScroll","afterCellRender","context","column","cellEl","cellElement","leftOffset","get","rightOffset","has","onScroll","_event","sg","pinnedRect","getBoundingClientRect","shouldTransfer","visibility","transform","delete","spanNaturalLeft","targetLeft","handleQuery","query","Object","fromEntries","params","isHeader","lockPinning","items","id","icon","order","action","setPinPosition","currentColumns","currentIndex","gridEl","map","updated","copy","remaining","splice","originalIndex","indexOf","insertIndex","Math","min","refreshStickyOffsets","getLeftPinnedColumns","filter","getLeftStickyColumns","getRightPinnedColumns","getRightStickyColumns","clearStickyPositions","getHorizontalScrollOffsets","rowEl","focusedCell","stickyLeftCells","stickyRightCells","skipScroll"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reorder-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/reorder-columns/column-drag.ts","../../../../../libs/grid/src/lib/plugins/reorder-columns/ReorderPlugin.ts"],"sourcesContent":["/**\n * Column Reordering Core Logic\n *\n * Pure functions for column drag and reordering operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Check if a column can be moved based on its own metadata.\n * This checks column-level properties like lockPosition and suppressMovable.\n *\n * Note: For full movability checks including plugin constraints (e.g., pinned columns),\n * use `grid.query<boolean>('canMoveColumn', column)` which queries all plugins that\n * declare the 'canMoveColumn' query in their manifest.\n *\n * @param column - The column configuration to check\n * @returns True if the column can be moved based on its metadata\n */\nexport function canMoveColumn(column: { meta?: Record<string, unknown> }): boolean {\n // Check for lockPosition or suppressMovable properties in the column config\n const meta = column.meta ?? {};\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Move a column from one position to another in the order array.\n *\n * @param columns - Array of field names in current order\n * @param fromIndex - The current index of the column to move\n * @param toIndex - The target index to move the column to\n * @returns New array with updated order\n */\nexport function moveColumn(columns: string[], fromIndex: number, toIndex: number): string[] {\n if (fromIndex === toIndex) return columns;\n if (fromIndex < 0 || fromIndex >= columns.length) return columns;\n if (toIndex < 0 || toIndex > columns.length) return columns;\n\n const result = [...columns];\n const [removed] = result.splice(fromIndex, 1);\n result.splice(toIndex, 0, removed);\n return result;\n}\n\n/**\n * Calculate the drop index based on the current drag position.\n *\n * @param dragX - The current X position of the drag\n * @param headerRect - The bounding rect of the header container\n * @param columnWidths - Array of column widths in order\n * @returns The index where the column should be dropped\n */\nexport function getDropIndex(dragX: number, headerRect: DOMRect, columnWidths: number[]): number {\n let x = headerRect.left;\n\n for (let i = 0; i < columnWidths.length; i++) {\n const mid = x + columnWidths[i] / 2;\n if (dragX < mid) return i;\n x += columnWidths[i];\n }\n\n return columnWidths.length;\n}\n\n/**\n * Reorder columns according to a specified order.\n * Columns not in the order array are appended at the end.\n *\n * @param columns - Array of column configurations\n * @param order - Array of field names specifying the desired order\n * @returns New array of columns in the specified order\n */\nexport function reorderColumns<TRow = unknown>(columns: ColumnConfig<TRow>[], order: string[]): ColumnConfig<TRow>[] {\n const columnMap = new Map<string, ColumnConfig<TRow>>(columns.map((c) => [c.field as string, c]));\n const reordered: ColumnConfig<TRow>[] = [];\n\n // Add columns in specified order\n for (const field of order) {\n const col = columnMap.get(field);\n if (col) {\n reordered.push(col);\n columnMap.delete(field);\n }\n }\n\n // Add any remaining columns not in order\n for (const col of columnMap.values()) {\n reordered.push(col);\n }\n\n return reordered;\n}\n","/**\n * Column Reordering Plugin (Class-based)\n *\n * Provides drag-and-drop column reordering functionality for tbw-grid.\n * Supports keyboard and mouse interactions with visual feedback.\n * Uses FLIP animation technique for smooth column transitions.\n *\n * Animation respects grid-level animation.mode setting but style is plugin-configured.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, GridHost } from '../../core/types';\nimport { canMoveColumn, moveColumn } from './column-drag';\nimport styles from './reorder.css?inline';\nimport type { ColumnMoveDetail, ReorderConfig } from './types';\n\n/**\n * Column Reorder Plugin for tbw-grid\n *\n * Lets users rearrange columns by dragging and dropping column headers. Supports smooth\n * FLIP animations, fade transitions, or instant reordering. Animation respects the\n * grid-level `animation.mode` setting.\n *\n * ## Installation\n *\n * ```ts\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder-columns';\n * ```\n *\n * ## Keyboard Shortcuts\n *\n * | Key | Action |\n * |-----|--------|\n * | `Alt + ←` | Move focused column left |\n * | `Alt + →` | Move focused column right |\n *\n * ## Events\n *\n * | Event | Detail | Cancelable | Description |\n * |-------|--------|------------|-------------|\n * | `column-move` | `{ field, fromIndex, toIndex, columnOrder }` | Yes | Fired when a column move is attempted |\n *\n * @example Basic Drag-and-Drop Reordering\n * ```ts\n * import { queryGrid } from '@toolbox-web/grid';\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder-columns';\n *\n * const grid = queryGrid('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID' },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [new ReorderPlugin({ animation: 'flip', animationDuration: 200 })],\n * };\n *\n * // Persist column order\n * grid.on('column-move', ({ columnOrder }) => {\n * localStorage.setItem('columnOrder', JSON.stringify(columnOrder));\n * });\n * ```\n *\n * @example Prevent Moves That Break Group Boundaries\n * ```ts\n * grid.on('column-move', (detail, e) => {\n * if (!isValidMoveWithinGroup(detail.field, detail.fromIndex, detail.toIndex)) {\n * e.preventDefault(); // Column snaps back to original position\n * }\n * });\n * ```\n *\n * @see {@link ReorderConfig} for all configuration options\n * @see {@link ColumnMoveDetail} for the event detail structure\n * @see GroupingColumnsPlugin for column group integration\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ReorderPlugin extends BaseGridPlugin<ReorderConfig> {\n /** @internal */\n readonly name = 'reorderColumns';\n /** @internal */\n override readonly aliases = ['reorder'] as const;\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<ReorderConfig> {\n return {\n animation: 'flip', // Plugin's own default\n };\n }\n\n /**\n * Resolve animation type from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationType(): false | 'flip' | 'fade' {\n // Check if animations are globally disabled\n if (!this.isAnimationEnabled) return false;\n\n // Plugin config (with default from defaultConfig)\n if (this.config.animation !== undefined) return this.config.animation;\n\n return 'flip'; // Plugin default\n }\n\n /**\n * Get animation duration, allowing plugin config override.\n * Uses base class animationDuration for default.\n */\n protected override get animationDuration(): number {\n // Plugin config override\n if (this.config.animationDuration !== undefined) {\n return this.config.animationDuration;\n }\n return super.animationDuration;\n }\n\n // #region Internal State\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n /** When dragging a group header, holds the field names in that fragment. */\n private draggedGroupFields: string[] = [];\n\n /** Typed internal grid accessor. */\n get #internalGrid(): GridHost {\n return this.grid as unknown as GridHost;\n }\n\n /**\n * Check if a column can be moved, considering both column config and plugin queries.\n */\n private canMoveColumnWithPlugins(column: ColumnConfig | undefined): boolean {\n if (!column || !canMoveColumn(column)) return false;\n // Query plugins that respond to 'canMoveColumn' (e.g., PinnedColumnsPlugin)\n const responses = this.grid.query<boolean>('canMoveColumn', column);\n return !responses.includes(false);\n }\n\n /**\n * Clear all drag-related classes from header cells and group header cells.\n */\n private clearDragClasses(): void {\n this.gridElement?.querySelectorAll('.header-row > .cell, .header-group-row > .cell').forEach((h) => {\n h.classList.remove(GridClasses.DRAGGING, 'drop-target', 'drop-before', 'drop-after');\n });\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for reorder requests from other plugins (e.g., VisibilityPlugin)\n // Uses disconnectSignal for automatic cleanup - no need for manual removeEventListener\n this.gridElement.addEventListener(\n 'column-reorder-request',\n (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail?.field && typeof detail.toIndex === 'number') {\n this.moveColumn(detail.field, detail.toIndex);\n }\n },\n { signal: this.disconnectSignal },\n );\n }\n\n /** @internal */\n override detach(): void {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.draggedGroupFields = [];\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const headers = gridEl.querySelectorAll('.header-row > .cell');\n\n headers.forEach((header) => {\n const headerEl = header as HTMLElement;\n const field = headerEl.getAttribute('data-field');\n if (!field) return;\n\n const column = this.columns.find((c) => c.field === field);\n if (!this.canMoveColumnWithPlugins(column)) {\n headerEl.draggable = false;\n return;\n }\n\n headerEl.draggable = true;\n\n // Remove existing listeners to prevent duplicates\n if (headerEl.getAttribute('data-dragstart-bound')) return;\n headerEl.setAttribute('data-dragstart-bound', 'true');\n\n headerEl.addEventListener('dragstart', (e: DragEvent) => {\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = orderIndex;\n this.draggedGroupFields = []; // Clear any stale group state\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n headerEl.classList.add(GridClasses.DRAGGING);\n });\n\n headerEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.draggedGroupFields = [];\n this.clearDragClasses();\n });\n\n headerEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n // Skip if dragging this same individual column\n if (this.draggedField === field && this.draggedGroupFields.length === 0) return;\n // Skip if this column is part of the dragged group fragment\n if (this.draggedGroupFields.includes(field)) return;\n\n const rect = headerEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.dropIndex = e.clientX < midX ? orderIndex : orderIndex + 1;\n\n this.clearDragClasses();\n // Re-mark dragged elements\n if (this.draggedGroupFields.length > 0) {\n for (const f of this.draggedGroupFields) {\n this.gridElement\n ?.querySelector(`.header-row > .cell[data-field=\"${f}\"]`)\n ?.classList.add(GridClasses.DRAGGING);\n }\n } else if (this.draggedField) {\n this.gridElement\n ?.querySelector(`.header-row > .cell[data-field=\"${this.draggedField}\"]`)\n ?.classList.add(GridClasses.DRAGGING);\n }\n headerEl.classList.add('drop-target');\n headerEl.classList.toggle('drop-before', e.clientX < midX);\n headerEl.classList.toggle('drop-after', e.clientX >= midX);\n });\n\n headerEl.addEventListener('dragleave', () => {\n headerEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n headerEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n\n // Group fragment drop onto individual column header\n if (this.draggedGroupFields.length > 0) {\n if (this.draggedGroupFields.includes(field)) return;\n const rect = headerEl.getBoundingClientRect();\n const before = e.clientX < rect.left + rect.width / 2;\n this.executeGroupBlockMove(this.draggedGroupFields, [field], before);\n return;\n }\n\n // Individual column drop\n const draggedField = this.draggedField;\n const draggedIndex = this.draggedIndex;\n const dropIndex = this.dropIndex;\n\n if (!this.isDragging || draggedField === null || draggedIndex === null || dropIndex === null) {\n return;\n }\n\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n const currentOrder = this.getColumnOrder();\n const newOrder = moveColumn(currentOrder, draggedIndex, effectiveToIndex);\n\n const detail: ColumnMoveDetail = {\n field: draggedField,\n fromIndex: draggedIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n };\n\n // Emit cancelable event first - only update if not cancelled\n const cancelled = this.emitCancelable('column-move', detail);\n if (!cancelled) {\n // Update the grid's column order (with optional view transition)\n this.updateColumnOrder(newOrder);\n }\n });\n });\n\n // Set up drag listeners for group header cells (if column grouping is active).\n // Deferred to a microtask because GroupingColumnsPlugin.afterRender() creates the\n // .header-group-row DOM, and it may run after this plugin in hook order.\n queueMicrotask(() => this.setupGroupHeaderDrag(gridEl));\n }\n\n /**\n * Set up drag-and-drop listeners on group header cells (.header-group-row > .cell).\n * Dragging a group header moves all columns in that fragment as a block.\n * Implicit groups (ungrouped column spans) are not draggable.\n */\n private setupGroupHeaderDrag(gridEl: HTMLElement): void {\n const groupHeaders = gridEl.querySelectorAll('.header-group-row > .cell[data-group]');\n\n groupHeaders.forEach((gh) => {\n const groupHeaderEl = gh as HTMLElement;\n const groupId = groupHeaderEl.getAttribute('data-group');\n if (!groupId || groupId.startsWith('__implicit__')) return;\n\n // Already bound?\n if (groupHeaderEl.getAttribute('data-group-drag-bound')) return;\n groupHeaderEl.setAttribute('data-group-drag-bound', 'true');\n\n // Determine which columns are in this fragment by reading the grid-column style\n const fragmentFields = this.getGroupFragmentFields(groupHeaderEl, groupId);\n if (fragmentFields.length === 0) return;\n\n // Check if all columns in the fragment can be moved\n const allMovable = fragmentFields.every((f) => {\n const col = this.columns.find((c) => c.field === f);\n return this.canMoveColumnWithPlugins(col);\n });\n if (!allMovable) return;\n\n groupHeaderEl.draggable = true;\n groupHeaderEl.style.cursor = 'grab';\n\n groupHeaderEl.addEventListener('dragstart', (e: DragEvent) => {\n this.isDragging = true;\n this.draggedField = null;\n this.draggedIndex = null;\n this.draggedGroupFields = [...fragmentFields];\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', `group:${groupId}`);\n }\n\n groupHeaderEl.classList.add(GridClasses.DRAGGING);\n // Also mark the individual column headers as dragging\n for (const f of fragmentFields) {\n gridEl.querySelector(`.header-row > .cell[data-field=\"${f}\"]`)?.classList.add(GridClasses.DRAGGING);\n }\n });\n\n groupHeaderEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.draggedGroupFields = [];\n this.clearDragClasses();\n });\n\n // Group header is also a drop target for other groups / individual columns\n groupHeaderEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n // If dragging the same fragment, ignore\n if (\n this.draggedGroupFields.length > 0 &&\n this.draggedGroupFields.length === fragmentFields.length &&\n this.draggedGroupFields.every((f) => fragmentFields.includes(f))\n )\n return;\n\n const rect = groupHeaderEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n const before = e.clientX < midX;\n\n this.clearDragClasses();\n groupHeaderEl.classList.add('drop-target');\n groupHeaderEl.classList.toggle('drop-before', before);\n groupHeaderEl.classList.toggle('drop-after', !before);\n });\n\n groupHeaderEl.addEventListener('dragleave', () => {\n groupHeaderEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n groupHeaderEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n\n const rect = groupHeaderEl.getBoundingClientRect();\n const before = e.clientX < rect.left + rect.width / 2;\n\n if (this.draggedGroupFields.length > 0) {\n // Group-to-group drop: move dragged fragment as block relative to this fragment\n if (\n this.draggedGroupFields.length === fragmentFields.length &&\n this.draggedGroupFields.every((f) => fragmentFields.includes(f))\n )\n return;\n this.executeGroupBlockMove(this.draggedGroupFields, fragmentFields, before);\n } else if (this.draggedField) {\n // Individual column dropped onto group header\n const currentOrder = this.getColumnOrder();\n const anchorField = before ? fragmentFields[0] : fragmentFields[fragmentFields.length - 1];\n const fromIndex = currentOrder.indexOf(this.draggedField);\n const toIndex = before ? currentOrder.indexOf(anchorField) : currentOrder.indexOf(anchorField) + 1;\n if (fromIndex === -1 || toIndex === -1) return;\n const effectiveToIndex = toIndex > fromIndex ? toIndex - 1 : toIndex;\n const newOrder = moveColumn(currentOrder, fromIndex, effectiveToIndex);\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field: this.draggedField,\n fromIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n });\n if (!cancelled) this.updateColumnOrder(newOrder);\n }\n });\n });\n }\n\n /**\n * Get the column field names that belong to a group header fragment.\n * Reads the grid-column CSS style of the header cell to determine the column range.\n */\n private getGroupFragmentFields(groupHeaderEl: HTMLElement, _groupId: string): string[] {\n // Parse grid-column (e.g., \"2 / span 3\") to get start index and span\n const gridColumn = groupHeaderEl.style.gridColumn;\n const match = /(\\d+)\\s*\\/\\s*span\\s+(\\d+)/.exec(gridColumn);\n if (!match) return [];\n\n const startCol = parseInt(match[1], 10); // 1-based CSS grid column\n const span = parseInt(match[2], 10);\n\n // Map CSS grid columns to visible column fields\n const visibleColumns = this.visibleColumns;\n const fields: string[] = [];\n for (let i = startCol - 1; i < startCol - 1 + span && i < visibleColumns.length; i++) {\n const col = visibleColumns[i];\n if (col) fields.push(col.field);\n }\n return fields;\n }\n\n /**\n * Move a group of columns as a block to a new position relative to target fields.\n */\n private executeGroupBlockMove(draggedFields: string[], targetFields: string[], before: boolean): void {\n const currentOrder = this.getColumnOrder();\n const remaining = currentOrder.filter((f) => !draggedFields.includes(f));\n\n const anchorField = before ? targetFields[0] : targetFields[targetFields.length - 1];\n const insertAt = remaining.indexOf(anchorField);\n if (insertAt === -1) return;\n\n const insertIndex = before ? insertAt : insertAt + 1;\n const draggedInOrder = currentOrder.filter((f) => draggedFields.includes(f));\n remaining.splice(insertIndex, 0, ...draggedInOrder);\n\n // Emit cancelable column-move for the first field so lockGroupOrder guard can check\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field: draggedFields[0],\n fromIndex: currentOrder.indexOf(draggedFields[0]),\n toIndex: insertIndex,\n columnOrder: remaining,\n });\n if (!cancelled) {\n this.updateColumnOrder(remaining);\n }\n }\n\n /**\n * Handle Alt+Arrow keyboard shortcuts for column reordering.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n if (!event.altKey || (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')) {\n return;\n }\n\n const grid = this.#internalGrid;\n const focusCol = grid._focusCol;\n const columns = grid._visibleColumns;\n\n if (focusCol < 0 || focusCol >= columns.length) return;\n\n const column = columns[focusCol];\n if (!this.canMoveColumnWithPlugins(column)) return;\n\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(column.field);\n if (fromIndex === -1) return;\n\n const toIndex = event.key === 'ArrowLeft' ? fromIndex - 1 : fromIndex + 1;\n\n // Check bounds\n if (toIndex < 0 || toIndex >= currentOrder.length) return;\n\n // Check if target position is allowed (e.g., not into pinned area)\n const targetColumn = columns.find((c) => c.field === currentOrder[toIndex]);\n if (!this.canMoveColumnWithPlugins(targetColumn)) return;\n\n this.moveColumn(column.field, toIndex);\n\n // Update focus to follow the moved column and refresh visual focus state\n grid._focusCol = toIndex;\n ensureCellVisible(this.#internalGrid);\n\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current column order from the grid.\n * @returns Array of field names in display order\n */\n getColumnOrder(): string[] {\n return this.grid.getColumnOrder();\n }\n\n /**\n * Move a column to a new position.\n * @param field - The field name of the column to move\n * @param toIndex - The target index\n */\n moveColumn(field: string, toIndex: number): void {\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(field);\n if (fromIndex === -1) return;\n\n const newOrder = moveColumn(currentOrder, fromIndex, toIndex);\n\n // Emit cancelable event first - only update if not cancelled\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field,\n fromIndex,\n toIndex,\n columnOrder: newOrder,\n });\n if (!cancelled) {\n // Update with view transition\n this.updateColumnOrder(newOrder);\n }\n }\n\n /**\n * Set a specific column order.\n * @param order - Array of field names in desired order\n */\n setColumnOrder(order: string[]): void {\n this.updateColumnOrder(order);\n }\n\n /**\n * Reset column order to the original configuration order.\n */\n resetColumnOrder(): void {\n const originalOrder = this.columns.map((c) => c.field);\n this.updateColumnOrder(originalOrder);\n }\n // #endregion\n\n // #region View Transition\n\n /**\n * Capture header cell positions before reorder.\n */\n private captureHeaderPositions(): Map<string, number> {\n const positions = new Map<string, number>();\n this.gridElement?.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (field) positions.set(field, cell.getBoundingClientRect().left);\n });\n return positions;\n }\n\n /**\n * Apply FLIP animation for column reorder.\n * Uses CSS transitions - JS sets initial transform and toggles class.\n * @param oldPositions - Header positions captured before DOM change\n */\n private animateFLIP(oldPositions: Map<string, number>): void {\n const gridEl = this.gridElement;\n if (!gridEl || oldPositions.size === 0) return;\n\n // Compute deltas from header cells (one per column, stable reference).\n // All cells in the same column share the same horizontal offset.\n const deltas = new Map<string, number>();\n gridEl.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n const oldLeft = oldPositions.get(field);\n if (oldLeft === undefined) return;\n const deltaX = oldLeft - cell.getBoundingClientRect().left;\n if (Math.abs(deltaX) > 1) deltas.set(field, deltaX);\n });\n\n if (deltas.size === 0) return;\n\n // Apply transforms to ALL cells (headers + body).\n // After forceLayout(), body cells are fully rebuilt in new DOM order\n // with correct data-field attributes, so header-derived deltas apply correctly.\n const cells: HTMLElement[] = [];\n gridEl.querySelectorAll('.cell[data-field]').forEach((cell) => {\n const deltaX = deltas.get(cell.getAttribute('data-field') ?? '');\n if (deltaX !== undefined) {\n const el = cell as HTMLElement;\n el.style.transform = `translateX(${deltaX}px)`;\n cells.push(el);\n }\n });\n\n if (cells.length === 0) return;\n\n // Force reflow then animate to final position via CSS transition\n void gridEl.offsetHeight;\n\n const duration = this.animationDuration;\n\n requestAnimationFrame(() => {\n cells.forEach((el) => {\n el.classList.add('flip-animating');\n el.style.transform = '';\n });\n\n // Cleanup after animation\n setTimeout(() => {\n cells.forEach((el) => {\n el.style.transform = '';\n el.classList.remove('flip-animating');\n });\n }, duration + 50);\n });\n }\n\n /**\n * Apply crossfade animation for moved columns.\n * Uses CSS keyframes - JS just toggles classes.\n */\n private animateFade(applyChange: () => void): void {\n const gridEl = this.gridElement;\n if (!gridEl) {\n applyChange();\n return;\n }\n\n // Capture old positions to detect which columns moved\n const oldPositions = this.captureHeaderPositions();\n\n // Apply the change first\n applyChange();\n\n // Find which columns changed position\n const movedFields = new Set<string>();\n gridEl.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n const oldLeft = oldPositions.get(field);\n if (oldLeft === undefined) return;\n const newLeft = cell.getBoundingClientRect().left;\n if (Math.abs(oldLeft - newLeft) > 1) {\n movedFields.add(field);\n }\n });\n\n if (movedFields.size === 0) return;\n\n // Add animation class to moved columns (headers + body cells)\n const cells: HTMLElement[] = [];\n gridEl.querySelectorAll('.cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (field && movedFields.has(field)) {\n const el = cell as HTMLElement;\n el.classList.add('fade-animating');\n cells.push(el);\n }\n });\n\n if (cells.length === 0) return;\n\n // Remove class after animation completes\n const duration = this.animationDuration;\n setTimeout(() => {\n cells.forEach((el) => el.classList.remove('fade-animating'));\n }, duration + 50);\n }\n\n /**\n * Update column order with configured animation.\n */\n private updateColumnOrder(newOrder: string[]): void {\n const animation = this.animationType;\n\n if (animation === 'flip' && this.gridElement) {\n const oldPositions = this.captureHeaderPositions();\n this.grid.setColumnOrder(newOrder);\n // Force a full render cycle so body cells are rebuilt in new column order.\n // setColumnOrder rebuilds headers synchronously but body cells are async\n // (VIRTUALIZATION phase only patches content in place via fastPatchRow).\n // forceLayout triggers processColumns which bumps the row render epoch,\n // ensuring body cells are fully rebuilt with correct DOM order.\n if (typeof this.grid.forceLayout === 'function') {\n this.grid.forceLayout().then(() => {\n this.animateFLIP(oldPositions);\n });\n } else {\n // Fallback: animate headers only (body cells may not be rebuilt yet)\n requestAnimationFrame(() => {\n this.animateFLIP(oldPositions);\n });\n }\n } else if (animation === 'fade') {\n this.animateFade(() => this.grid.setColumnOrder(newOrder));\n } else {\n this.grid.setColumnOrder(newOrder);\n }\n\n this.grid.requestStateChange?.();\n }\n // #endregion\n}\n"],"names":["moveColumn","columns","fromIndex","toIndex","length","result","removed","splice","ReorderPlugin","BaseGridPlugin","name","aliases","styles","defaultConfig","animation","animationType","this","isAnimationEnabled","config","animationDuration","super","isDragging","draggedField","draggedIndex","dropIndex","draggedGroupFields","internalGrid","grid","canMoveColumnWithPlugins","column","meta","lockPosition","suppressMovable","canMoveColumn","query","includes","clearDragClasses","gridElement","querySelectorAll","forEach","h","classList","remove","GridClasses","DRAGGING","attach","addEventListener","e","detail","field","signal","disconnectSignal","detach","afterRender","gridEl","header","headerEl","getAttribute","find","c","draggable","setAttribute","orderIndex","getColumnOrder","indexOf","dataTransfer","effectAllowed","setData","add","preventDefault","rect","getBoundingClientRect","midX","left","width","clientX","f","querySelector","toggle","before","executeGroupBlockMove","effectiveToIndex","newOrder","columnOrder","emitCancelable","updateColumnOrder","queueMicrotask","setupGroupHeaderDrag","gh","groupHeaderEl","groupId","startsWith","fragmentFields","getGroupFragmentFields","every","col","style","cursor","currentOrder","anchorField","_groupId","gridColumn","match","exec","startCol","parseInt","span","visibleColumns","fields","i","push","draggedFields","targetFields","remaining","filter","insertAt","insertIndex","draggedInOrder","onKeyDown","event","altKey","key","focusCol","_focusCol","_visibleColumns","targetColumn","ensureCellVisible","stopPropagation","setColumnOrder","order","resetColumnOrder","originalOrder","map","captureHeaderPositions","positions","Map","cell","set","animateFLIP","oldPositions","size","deltas","oldLeft","get","deltaX","Math","abs","cells","el","transform","offsetHeight","duration","requestAnimationFrame","setTimeout","animateFade","applyChange","movedFields","Set","newLeft","has","forceLayout","then","requestStateChange"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reorder-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/reorder-columns/column-drag.ts","../../../../../libs/grid/src/lib/plugins/reorder-columns/ReorderPlugin.ts"],"sourcesContent":["/**\n * Column Reordering Core Logic\n *\n * Pure functions for column drag and reordering operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Check if a column can be moved based on its own metadata.\n * This checks column-level properties like lockPosition and suppressMovable.\n *\n * Note: For full movability checks including plugin constraints (e.g., pinned columns),\n * use `grid.query<boolean>('canMoveColumn', column)` which queries all plugins that\n * declare the 'canMoveColumn' query in their manifest.\n *\n * @param column - The column configuration to check\n * @returns True if the column can be moved based on its metadata\n */\nexport function canMoveColumn(column: { meta?: Record<string, unknown> }): boolean {\n // Check for lockPosition or suppressMovable properties in the column config\n const meta = column.meta ?? {};\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Move a column from one position to another in the order array.\n *\n * @param columns - Array of field names in current order\n * @param fromIndex - The current index of the column to move\n * @param toIndex - The target index to move the column to\n * @returns New array with updated order\n */\nexport function moveColumn(columns: string[], fromIndex: number, toIndex: number): string[] {\n if (fromIndex === toIndex) return columns;\n if (fromIndex < 0 || fromIndex >= columns.length) return columns;\n if (toIndex < 0 || toIndex > columns.length) return columns;\n\n const result = [...columns];\n const [removed] = result.splice(fromIndex, 1);\n result.splice(toIndex, 0, removed);\n return result;\n}\n\n/**\n * Calculate the drop index based on the current drag position.\n *\n * @param dragX - The current X position of the drag\n * @param headerRect - The bounding rect of the header container\n * @param columnWidths - Array of column widths in order\n * @returns The index where the column should be dropped\n */\nexport function getDropIndex(dragX: number, headerRect: DOMRect, columnWidths: number[]): number {\n let x = headerRect.left;\n\n for (let i = 0; i < columnWidths.length; i++) {\n const mid = x + columnWidths[i] / 2;\n if (dragX < mid) return i;\n x += columnWidths[i];\n }\n\n return columnWidths.length;\n}\n\n/**\n * Reorder columns according to a specified order.\n * Columns not in the order array are appended at the end.\n *\n * @param columns - Array of column configurations\n * @param order - Array of field names specifying the desired order\n * @returns New array of columns in the specified order\n */\nexport function reorderColumns<TRow = unknown>(columns: ColumnConfig<TRow>[], order: string[]): ColumnConfig<TRow>[] {\n const columnMap = new Map<string, ColumnConfig<TRow>>(columns.map((c) => [c.field as string, c]));\n const reordered: ColumnConfig<TRow>[] = [];\n\n // Add columns in specified order\n for (const field of order) {\n const col = columnMap.get(field);\n if (col) {\n reordered.push(col);\n columnMap.delete(field);\n }\n }\n\n // Add any remaining columns not in order\n for (const col of columnMap.values()) {\n reordered.push(col);\n }\n\n return reordered;\n}\n","/**\n * Column Reordering Plugin (Class-based)\n *\n * Provides drag-and-drop column reordering functionality for tbw-grid.\n * Supports keyboard and mouse interactions with visual feedback.\n * Uses FLIP animation technique for smooth column transitions.\n *\n * Animation respects grid-level animation.mode setting but style is plugin-configured.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, GridHost } from '../../core/types';\nimport { canMoveColumn, moveColumn } from './column-drag';\nimport styles from './reorder.css?inline';\nimport type { ColumnMoveDetail, ReorderConfig } from './types';\n\n/**\n * Column Reorder Plugin for tbw-grid\n *\n * Lets users rearrange columns by dragging and dropping column headers. Supports smooth\n * FLIP animations, fade transitions, or instant reordering. Animation respects the\n * grid-level `animation.mode` setting.\n *\n * ## Installation\n *\n * ```ts\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder-columns';\n * ```\n *\n * ## Keyboard Shortcuts\n *\n * | Key | Action |\n * |-----|--------|\n * | `Alt + ←` | Move focused column left |\n * | `Alt + →` | Move focused column right |\n *\n * ## Events\n *\n * | Event | Detail | Cancelable | Description |\n * |-------|--------|------------|-------------|\n * | `column-move` | `{ field, fromIndex, toIndex, columnOrder }` | Yes | Fired when a column move is attempted |\n *\n * @example Basic Drag-and-Drop Reordering\n * ```ts\n * import { queryGrid } from '@toolbox-web/grid';\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder-columns';\n *\n * const grid = queryGrid('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID' },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [new ReorderPlugin({ animation: 'flip', animationDuration: 200 })],\n * };\n *\n * // Persist column order\n * grid.on('column-move', ({ columnOrder }) => {\n * localStorage.setItem('columnOrder', JSON.stringify(columnOrder));\n * });\n * ```\n *\n * @example Prevent Moves That Break Group Boundaries\n * ```ts\n * grid.on('column-move', (detail, e) => {\n * if (!isValidMoveWithinGroup(detail.field, detail.fromIndex, detail.toIndex)) {\n * e.preventDefault(); // Column snaps back to original position\n * }\n * });\n * ```\n *\n * @see {@link ReorderConfig} for all configuration options\n * @see {@link ColumnMoveDetail} for the event detail structure\n * @see GroupingColumnsPlugin for column group integration\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ReorderPlugin extends BaseGridPlugin<ReorderConfig> {\n /** @internal */\n readonly name = 'reorderColumns';\n /** @internal */\n override readonly aliases = ['reorder'] as const;\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<ReorderConfig> {\n return {\n animation: 'flip', // Plugin's own default\n };\n }\n\n /**\n * Resolve animation type from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationType(): false | 'flip' | 'fade' {\n // Check if animations are globally disabled\n if (!this.isAnimationEnabled) return false;\n\n // Plugin config (with default from defaultConfig)\n if (this.config.animation !== undefined) return this.config.animation;\n\n return 'flip'; // Plugin default\n }\n\n /**\n * Get animation duration, allowing plugin config override.\n * Uses base class animationDuration for default.\n */\n protected override get animationDuration(): number {\n // Plugin config override\n if (this.config.animationDuration !== undefined) {\n return this.config.animationDuration;\n }\n return super.animationDuration;\n }\n\n // #region Internal State\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n /** When dragging a group header, holds the field names in that fragment. */\n private draggedGroupFields: string[] = [];\n\n /** Typed internal grid accessor. */\n get #internalGrid(): GridHost {\n return this.grid as unknown as GridHost;\n }\n\n /**\n * Check if a column can be moved, considering both column config and plugin queries.\n */\n private canMoveColumnWithPlugins(column: ColumnConfig | undefined): boolean {\n if (!column || !canMoveColumn(column)) return false;\n // Query plugins that respond to 'canMoveColumn' (e.g., PinnedColumnsPlugin)\n const responses = this.grid.query<boolean>('canMoveColumn', column);\n return !responses.includes(false);\n }\n\n /**\n * Clear all drag-related classes from header cells and group header cells.\n */\n private clearDragClasses(): void {\n this.gridElement?.querySelectorAll('.header-row > .cell, .header-group-row > .cell').forEach((h) => {\n h.classList.remove(GridClasses.DRAGGING, 'drop-target', 'drop-before', 'drop-after');\n });\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for reorder requests from other plugins (e.g., VisibilityPlugin)\n // Uses disconnectSignal for automatic cleanup - no need for manual removeEventListener\n this.gridElement.addEventListener(\n 'column-reorder-request',\n (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail?.field && typeof detail.toIndex === 'number') {\n this.moveColumn(detail.field, detail.toIndex);\n }\n },\n { signal: this.disconnectSignal },\n );\n }\n\n /** @internal */\n override detach(): void {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.draggedGroupFields = [];\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const headers = gridEl.querySelectorAll('.header-row > .cell');\n\n headers.forEach((header) => {\n const headerEl = header as HTMLElement;\n const field = headerEl.getAttribute('data-field');\n if (!field) return;\n\n const column = this.columns.find((c) => c.field === field);\n if (!this.canMoveColumnWithPlugins(column)) {\n headerEl.draggable = false;\n return;\n }\n\n headerEl.draggable = true;\n\n // Remove existing listeners to prevent duplicates\n if (headerEl.getAttribute('data-dragstart-bound')) return;\n headerEl.setAttribute('data-dragstart-bound', 'true');\n\n headerEl.addEventListener('dragstart', (e: DragEvent) => {\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = orderIndex;\n this.draggedGroupFields = []; // Clear any stale group state\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n headerEl.classList.add(GridClasses.DRAGGING);\n });\n\n headerEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.draggedGroupFields = [];\n this.clearDragClasses();\n });\n\n headerEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n // Skip if dragging this same individual column\n if (this.draggedField === field && this.draggedGroupFields.length === 0) return;\n // Skip if this column is part of the dragged group fragment\n if (this.draggedGroupFields.includes(field)) return;\n\n const rect = headerEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.dropIndex = e.clientX < midX ? orderIndex : orderIndex + 1;\n\n this.clearDragClasses();\n // Re-mark dragged elements\n if (this.draggedGroupFields.length > 0) {\n for (const f of this.draggedGroupFields) {\n this.gridElement\n ?.querySelector(`.header-row > .cell[data-field=\"${f}\"]`)\n ?.classList.add(GridClasses.DRAGGING);\n }\n } else if (this.draggedField) {\n this.gridElement\n ?.querySelector(`.header-row > .cell[data-field=\"${this.draggedField}\"]`)\n ?.classList.add(GridClasses.DRAGGING);\n }\n headerEl.classList.add('drop-target');\n headerEl.classList.toggle('drop-before', e.clientX < midX);\n headerEl.classList.toggle('drop-after', e.clientX >= midX);\n });\n\n headerEl.addEventListener('dragleave', () => {\n headerEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n headerEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n\n // Group fragment drop onto individual column header\n if (this.draggedGroupFields.length > 0) {\n if (this.draggedGroupFields.includes(field)) return;\n const rect = headerEl.getBoundingClientRect();\n const before = e.clientX < rect.left + rect.width / 2;\n this.executeGroupBlockMove(this.draggedGroupFields, [field], before);\n return;\n }\n\n // Individual column drop\n const draggedField = this.draggedField;\n const draggedIndex = this.draggedIndex;\n const dropIndex = this.dropIndex;\n\n if (!this.isDragging || draggedField === null || draggedIndex === null || dropIndex === null) {\n return;\n }\n\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n const currentOrder = this.getColumnOrder();\n const newOrder = moveColumn(currentOrder, draggedIndex, effectiveToIndex);\n\n const detail: ColumnMoveDetail = {\n field: draggedField,\n fromIndex: draggedIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n };\n\n // Emit cancelable event first - only update if not cancelled\n const cancelled = this.emitCancelable('column-move', detail);\n if (!cancelled) {\n // Update the grid's column order (with optional view transition)\n this.updateColumnOrder(newOrder);\n }\n });\n });\n\n // Set up drag listeners for group header cells (if column grouping is active).\n // Deferred to a microtask because GroupingColumnsPlugin.afterRender() creates the\n // .header-group-row DOM, and it may run after this plugin in hook order.\n queueMicrotask(() => this.setupGroupHeaderDrag(gridEl));\n }\n\n /**\n * Set up drag-and-drop listeners on group header cells (.header-group-row > .cell).\n * Dragging a group header moves all columns in that fragment as a block.\n * Implicit groups (ungrouped column spans) are not draggable.\n */\n private setupGroupHeaderDrag(gridEl: HTMLElement): void {\n const groupHeaders = gridEl.querySelectorAll('.header-group-row > .cell[data-group]');\n\n groupHeaders.forEach((gh) => {\n const groupHeaderEl = gh as HTMLElement;\n const groupId = groupHeaderEl.getAttribute('data-group');\n if (!groupId || groupId.startsWith('__implicit__')) return;\n\n // Already bound?\n if (groupHeaderEl.getAttribute('data-group-drag-bound')) return;\n groupHeaderEl.setAttribute('data-group-drag-bound', 'true');\n\n // Determine which columns are in this fragment by reading the grid-column style\n const fragmentFields = this.getGroupFragmentFields(groupHeaderEl, groupId);\n if (fragmentFields.length === 0) return;\n\n // Check if all columns in the fragment can be moved\n const allMovable = fragmentFields.every((f) => {\n const col = this.columns.find((c) => c.field === f);\n return this.canMoveColumnWithPlugins(col);\n });\n if (!allMovable) return;\n\n groupHeaderEl.draggable = true;\n groupHeaderEl.style.cursor = 'grab';\n\n groupHeaderEl.addEventListener('dragstart', (e: DragEvent) => {\n this.isDragging = true;\n this.draggedField = null;\n this.draggedIndex = null;\n this.draggedGroupFields = [...fragmentFields];\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', `group:${groupId}`);\n }\n\n groupHeaderEl.classList.add(GridClasses.DRAGGING);\n // Also mark the individual column headers as dragging\n for (const f of fragmentFields) {\n gridEl.querySelector(`.header-row > .cell[data-field=\"${f}\"]`)?.classList.add(GridClasses.DRAGGING);\n }\n });\n\n groupHeaderEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.draggedGroupFields = [];\n this.clearDragClasses();\n });\n\n // Group header is also a drop target for other groups / individual columns\n groupHeaderEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n // If dragging the same fragment, ignore\n if (\n this.draggedGroupFields.length > 0 &&\n this.draggedGroupFields.length === fragmentFields.length &&\n this.draggedGroupFields.every((f) => fragmentFields.includes(f))\n )\n return;\n\n const rect = groupHeaderEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n const before = e.clientX < midX;\n\n this.clearDragClasses();\n groupHeaderEl.classList.add('drop-target');\n groupHeaderEl.classList.toggle('drop-before', before);\n groupHeaderEl.classList.toggle('drop-after', !before);\n });\n\n groupHeaderEl.addEventListener('dragleave', () => {\n groupHeaderEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n groupHeaderEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n\n const rect = groupHeaderEl.getBoundingClientRect();\n const before = e.clientX < rect.left + rect.width / 2;\n\n if (this.draggedGroupFields.length > 0) {\n // Group-to-group drop: move dragged fragment as block relative to this fragment\n if (\n this.draggedGroupFields.length === fragmentFields.length &&\n this.draggedGroupFields.every((f) => fragmentFields.includes(f))\n )\n return;\n this.executeGroupBlockMove(this.draggedGroupFields, fragmentFields, before);\n } else if (this.draggedField) {\n // Individual column dropped onto group header\n const currentOrder = this.getColumnOrder();\n const anchorField = before ? fragmentFields[0] : fragmentFields[fragmentFields.length - 1];\n const fromIndex = currentOrder.indexOf(this.draggedField);\n const toIndex = before ? currentOrder.indexOf(anchorField) : currentOrder.indexOf(anchorField) + 1;\n if (fromIndex === -1 || toIndex === -1) return;\n const effectiveToIndex = toIndex > fromIndex ? toIndex - 1 : toIndex;\n const newOrder = moveColumn(currentOrder, fromIndex, effectiveToIndex);\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field: this.draggedField,\n fromIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n });\n if (!cancelled) this.updateColumnOrder(newOrder);\n }\n });\n });\n }\n\n /**\n * Get the column field names that belong to a group header fragment.\n * Reads the grid-column CSS style of the header cell to determine the column range.\n */\n private getGroupFragmentFields(groupHeaderEl: HTMLElement, _groupId: string): string[] {\n // Parse grid-column (e.g., \"2 / span 3\") to get start index and span\n const gridColumn = groupHeaderEl.style.gridColumn;\n const match = /(\\d+)\\s*\\/\\s*span\\s+(\\d+)/.exec(gridColumn);\n if (!match) return [];\n\n const startCol = parseInt(match[1], 10); // 1-based CSS grid column\n const span = parseInt(match[2], 10);\n\n // Map CSS grid columns to visible column fields\n const visibleColumns = this.visibleColumns;\n const fields: string[] = [];\n for (let i = startCol - 1; i < startCol - 1 + span && i < visibleColumns.length; i++) {\n const col = visibleColumns[i];\n if (col) fields.push(col.field);\n }\n return fields;\n }\n\n /**\n * Move a group of columns as a block to a new position relative to target fields.\n */\n private executeGroupBlockMove(draggedFields: string[], targetFields: string[], before: boolean): void {\n const currentOrder = this.getColumnOrder();\n const remaining = currentOrder.filter((f) => !draggedFields.includes(f));\n\n const anchorField = before ? targetFields[0] : targetFields[targetFields.length - 1];\n const insertAt = remaining.indexOf(anchorField);\n if (insertAt === -1) return;\n\n const insertIndex = before ? insertAt : insertAt + 1;\n const draggedInOrder = currentOrder.filter((f) => draggedFields.includes(f));\n remaining.splice(insertIndex, 0, ...draggedInOrder);\n\n // Emit cancelable column-move for the first field so lockGroupOrder guard can check\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field: draggedFields[0],\n fromIndex: currentOrder.indexOf(draggedFields[0]),\n toIndex: insertIndex,\n columnOrder: remaining,\n });\n if (!cancelled) {\n this.updateColumnOrder(remaining);\n }\n }\n\n /**\n * Handle Alt+Arrow keyboard shortcuts for column reordering.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n if (!event.altKey || (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')) {\n return;\n }\n\n const grid = this.#internalGrid;\n const focusCol = grid._focusCol;\n const columns = grid._visibleColumns;\n\n if (focusCol < 0 || focusCol >= columns.length) return;\n\n const column = columns[focusCol];\n if (!this.canMoveColumnWithPlugins(column)) return;\n\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(column.field);\n if (fromIndex === -1) return;\n\n const toIndex = event.key === 'ArrowLeft' ? fromIndex - 1 : fromIndex + 1;\n\n // Check bounds\n if (toIndex < 0 || toIndex >= currentOrder.length) return;\n\n // Check if target position is allowed (e.g., not into pinned area)\n const targetColumn = columns.find((c) => c.field === currentOrder[toIndex]);\n if (!this.canMoveColumnWithPlugins(targetColumn)) return;\n\n this.moveColumn(column.field, toIndex);\n\n // Update focus to follow the moved column and refresh visual focus state\n grid._focusCol = toIndex;\n ensureCellVisible(this.#internalGrid);\n\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current column order from the grid.\n * @returns Array of field names in display order\n */\n getColumnOrder(): string[] {\n return this.grid.getColumnOrder();\n }\n\n /**\n * Move a column to a new position.\n * @param field - The field name of the column to move\n * @param toIndex - The target index\n */\n moveColumn(field: string, toIndex: number): void {\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(field);\n if (fromIndex === -1) return;\n\n const newOrder = moveColumn(currentOrder, fromIndex, toIndex);\n\n // Emit cancelable event first - only update if not cancelled\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field,\n fromIndex,\n toIndex,\n columnOrder: newOrder,\n });\n if (!cancelled) {\n // Update with view transition\n this.updateColumnOrder(newOrder);\n }\n }\n\n /**\n * Set a specific column order.\n * @param order - Array of field names in desired order\n */\n setColumnOrder(order: string[]): void {\n this.updateColumnOrder(order);\n }\n\n /**\n * Reset column order to the original configuration order.\n */\n resetColumnOrder(): void {\n const originalOrder = this.columns.map((c) => c.field);\n this.updateColumnOrder(originalOrder);\n }\n // #endregion\n\n // #region View Transition\n\n /**\n * Capture header cell positions before reorder.\n */\n private captureHeaderPositions(): Map<string, number> {\n const positions = new Map<string, number>();\n this.gridElement?.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (field) positions.set(field, cell.getBoundingClientRect().left);\n });\n return positions;\n }\n\n /**\n * Apply FLIP animation for column reorder.\n * Uses CSS transitions - JS sets initial transform and toggles class.\n * @param oldPositions - Header positions captured before DOM change\n */\n private animateFLIP(oldPositions: Map<string, number>): void {\n const gridEl = this.gridElement;\n if (!gridEl || oldPositions.size === 0) return;\n\n // Compute deltas from header cells (one per column, stable reference).\n // All cells in the same column share the same horizontal offset.\n const deltas = new Map<string, number>();\n gridEl.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n const oldLeft = oldPositions.get(field);\n if (oldLeft === undefined) return;\n const deltaX = oldLeft - cell.getBoundingClientRect().left;\n if (Math.abs(deltaX) > 1) deltas.set(field, deltaX);\n });\n\n if (deltas.size === 0) return;\n\n // Apply transforms to ALL cells (headers + body).\n // After forceLayout(), body cells are fully rebuilt in new DOM order\n // with correct data-field attributes, so header-derived deltas apply correctly.\n const cells: HTMLElement[] = [];\n gridEl.querySelectorAll('.cell[data-field]').forEach((cell) => {\n const deltaX = deltas.get(cell.getAttribute('data-field') ?? '');\n if (deltaX !== undefined) {\n const el = cell as HTMLElement;\n el.style.transform = `translateX(${deltaX}px)`;\n cells.push(el);\n }\n });\n\n if (cells.length === 0) return;\n\n // Force reflow then animate to final position via CSS transition\n void gridEl.offsetHeight;\n\n const duration = this.animationDuration;\n\n requestAnimationFrame(() => {\n cells.forEach((el) => {\n el.classList.add('flip-animating');\n el.style.transform = '';\n });\n\n // Cleanup after animation\n setTimeout(() => {\n cells.forEach((el) => {\n el.style.transform = '';\n el.classList.remove('flip-animating');\n });\n }, duration + 50);\n });\n }\n\n /**\n * Apply crossfade animation for moved columns.\n * Uses CSS keyframes - JS just toggles classes.\n */\n private animateFade(applyChange: () => void): void {\n const gridEl = this.gridElement;\n if (!gridEl) {\n applyChange();\n return;\n }\n\n // Capture old positions to detect which columns moved\n const oldPositions = this.captureHeaderPositions();\n\n // Apply the change first\n applyChange();\n\n // Find which columns changed position\n const movedFields = new Set<string>();\n gridEl.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n const oldLeft = oldPositions.get(field);\n if (oldLeft === undefined) return;\n const newLeft = cell.getBoundingClientRect().left;\n if (Math.abs(oldLeft - newLeft) > 1) {\n movedFields.add(field);\n }\n });\n\n if (movedFields.size === 0) return;\n\n // Add animation class to moved columns (headers + body cells)\n const cells: HTMLElement[] = [];\n gridEl.querySelectorAll('.cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (field && movedFields.has(field)) {\n const el = cell as HTMLElement;\n el.classList.add('fade-animating');\n cells.push(el);\n }\n });\n\n if (cells.length === 0) return;\n\n // Remove class after animation completes\n const duration = this.animationDuration;\n setTimeout(() => {\n cells.forEach((el) => el.classList.remove('fade-animating'));\n }, duration + 50);\n }\n\n /**\n * Update column order with configured animation.\n */\n private updateColumnOrder(newOrder: string[]): void {\n const animation = this.animationType;\n\n if (animation === 'flip' && this.gridElement) {\n const oldPositions = this.captureHeaderPositions();\n this.grid.setColumnOrder(newOrder);\n // setColumnOrder now requests COLUMNS phase (not just VIRTUALIZATION), so\n // processColumns() runs and bumps __rowRenderEpoch. forceLayout() waits\n // for the pending phase to flush, ensuring body cells are fully rebuilt\n // with correct data-field attributes before the FLIP animation runs.\n if (typeof this.grid.forceLayout === 'function') {\n this.grid.forceLayout().then(() => {\n this.animateFLIP(oldPositions);\n });\n } else {\n // Fallback: animate headers only (body cells may not be rebuilt yet)\n requestAnimationFrame(() => {\n this.animateFLIP(oldPositions);\n });\n }\n } else if (animation === 'fade') {\n this.animateFade(() => this.grid.setColumnOrder(newOrder));\n } else {\n this.grid.setColumnOrder(newOrder);\n }\n\n this.grid.requestStateChange?.();\n }\n // #endregion\n}\n"],"names":["moveColumn","columns","fromIndex","toIndex","length","result","removed","splice","ReorderPlugin","BaseGridPlugin","name","aliases","styles","defaultConfig","animation","animationType","this","isAnimationEnabled","config","animationDuration","super","isDragging","draggedField","draggedIndex","dropIndex","draggedGroupFields","internalGrid","grid","canMoveColumnWithPlugins","column","meta","lockPosition","suppressMovable","canMoveColumn","query","includes","clearDragClasses","gridElement","querySelectorAll","forEach","h","classList","remove","GridClasses","DRAGGING","attach","addEventListener","e","detail","field","signal","disconnectSignal","detach","afterRender","gridEl","header","headerEl","getAttribute","find","c","draggable","setAttribute","orderIndex","getColumnOrder","indexOf","dataTransfer","effectAllowed","setData","add","preventDefault","rect","getBoundingClientRect","midX","left","width","clientX","f","querySelector","toggle","before","executeGroupBlockMove","effectiveToIndex","newOrder","columnOrder","emitCancelable","updateColumnOrder","queueMicrotask","setupGroupHeaderDrag","gh","groupHeaderEl","groupId","startsWith","fragmentFields","getGroupFragmentFields","every","col","style","cursor","currentOrder","anchorField","_groupId","gridColumn","match","exec","startCol","parseInt","span","visibleColumns","fields","i","push","draggedFields","targetFields","remaining","filter","insertAt","insertIndex","draggedInOrder","onKeyDown","event","altKey","key","focusCol","_focusCol","_visibleColumns","targetColumn","ensureCellVisible","stopPropagation","setColumnOrder","order","resetColumnOrder","originalOrder","map","captureHeaderPositions","positions","Map","cell","set","animateFLIP","oldPositions","size","deltas","oldLeft","get","deltaX","Math","abs","cells","el","transform","offsetHeight","duration","requestAnimationFrame","setTimeout","animateFade","applyChange","movedFields","Set","newLeft","has","forceLayout","then","requestStateChange"],"mappings":"8eAiCO,SAASA,EAAWC,EAAmBC,EAAmBC,GAC/D,GAAID,IAAcC,EAAS,OAAOF,EAClC,GAAIC,EAAY,GAAKA,GAAaD,EAAQG,OAAQ,OAAOH,EACzD,GAAIE,EAAU,GAAKA,EAAUF,EAAQG,OAAQ,OAAOH,EAEpD,MAAMI,EAAS,IAAIJ,IACZK,GAAWD,EAAOE,OAAOL,EAAW,GAE3C,OADAG,EAAOE,OAAOJ,EAAS,EAAGG,GACnBD,CACT,CCsCO,MAAMG,UAAsBC,EAAAA,eAExBC,KAAO,iBAEEC,QAAU,CAAC,WAEXC,8vBAGlB,iBAAuBC,GACrB,MAAO,CACLC,UAAW,OAEf,CAMA,iBAAYC,GAEV,QAAKC,KAAKC,0BAGoB,IAA1BD,KAAKE,OAAOJ,UAAgCE,KAAKE,OAAOJ,UAErD,OACT,CAMA,qBAAuBK,GAErB,YAAsC,IAAlCH,KAAKE,OAAOC,kBACPH,KAAKE,OAAOC,kBAEdC,MAAMD,iBACf,CAGQE,YAAa,EACbC,aAA8B,KAC9BC,aAA8B,KAC9BC,UAA2B,KAE3BC,mBAA+B,GAGvC,KAAIC,GACF,OAAOV,KAAKW,IACd,CAKQ,wBAAAC,CAAyBC,GAC/B,IAAKA,IDvHF,SAAuBA,GAE5B,MAAMC,EAAOD,EAAOC,MAAQ,CAAA,EAC5B,OAA6B,IAAtBA,EAAKC,eAAkD,IAAzBD,EAAKE,eAC5C,CCmHoBC,CAAcJ,GAAS,OAAO,EAG9C,OADkBb,KAAKW,KAAKO,MAAe,gBAAiBL,GAC1CM,UAAS,EAC7B,CAKQ,gBAAAC,GACNpB,KAAKqB,aAAaC,iBAAiB,kDAAkDC,QAASC,IAC5FA,EAAEC,UAAUC,OAAOC,EAAAA,YAAYC,SAAU,cAAe,cAAe,eAE3E,CAMS,MAAAC,CAAOlB,GACdP,MAAMyB,OAAOlB,GAIbX,KAAKqB,YAAYS,iBACf,yBACCC,IACC,MAAMC,EAAUD,EAAkBC,OAC9BA,GAAQC,OAAmC,iBAAnBD,EAAO7C,SACjCa,KAAKhB,WAAWgD,EAAOC,MAAOD,EAAO7C,UAGzC,CAAE+C,OAAQlC,KAAKmC,kBAEnB,CAGS,MAAAC,GACPpC,KAAKK,YAAa,EAClBL,KAAKM,aAAe,KACpBN,KAAKO,aAAe,KACpBP,KAAKQ,UAAY,KACjBR,KAAKS,mBAAqB,EAC5B,CAMS,WAAA4B,GACP,MAAMC,EAAStC,KAAKqB,YACpB,IAAKiB,EAAQ,OAEGA,EAAOhB,iBAAiB,uBAEhCC,QAASgB,IACf,MAAMC,EAAWD,EACXN,EAAQO,EAASC,aAAa,cACpC,IAAKR,EAAO,OAEZ,MAAMpB,EAASb,KAAKf,QAAQyD,KAAMC,GAAMA,EAAEV,QAAUA,GAC/CjC,KAAKY,yBAAyBC,IAKnC2B,EAASI,WAAY,EAGjBJ,EAASC,aAAa,0BAC1BD,EAASK,aAAa,uBAAwB,QAE9CL,EAASV,iBAAiB,YAAcC,IACtC,MACMe,EADe9C,KAAK+C,iBACMC,QAAQf,GACxCjC,KAAKK,YAAa,EAClBL,KAAKM,aAAe2B,EACpBjC,KAAKO,aAAeuC,EACpB9C,KAAKS,mBAAqB,GAEtBsB,EAAEkB,eACJlB,EAAEkB,aAAaC,cAAgB,OAC/BnB,EAAEkB,aAAaE,QAAQ,aAAclB,IAGvCO,EAASf,UAAU2B,IAAIzB,EAAAA,YAAYC,YAGrCY,EAASV,iBAAiB,UAAW,KACnC9B,KAAKK,YAAa,EAClBL,KAAKM,aAAe,KACpBN,KAAKO,aAAe,KACpBP,KAAKQ,UAAY,KACjBR,KAAKS,mBAAqB,GAC1BT,KAAKoB,qBAGPoB,EAASV,iBAAiB,WAAaC,IAErC,GADAA,EAAEsB,kBACGrD,KAAKK,WAAY,OAEtB,GAAIL,KAAKM,eAAiB2B,GAA4C,IAAnCjC,KAAKS,mBAAmBrB,OAAc,OAEzE,GAAIY,KAAKS,mBAAmBU,SAASc,GAAQ,OAE7C,MAAMqB,EAAOd,EAASe,wBAChBC,EAAOF,EAAKG,KAAOH,EAAKI,MAAQ,EAGhCZ,EADe9C,KAAK+C,iBACMC,QAAQf,GAKxC,GAJAjC,KAAKQ,UAAYuB,EAAE4B,QAAUH,EAAOV,EAAaA,EAAa,EAE9D9C,KAAKoB,mBAEDpB,KAAKS,mBAAmBrB,OAAS,EACnC,IAAA,MAAWwE,KAAK5D,KAAKS,mBACnBT,KAAKqB,aACDwC,cAAc,mCAAmCD,QACjDnC,UAAU2B,IAAIzB,EAAAA,YAAYC,eAEvB5B,KAAKM,cACdN,KAAKqB,aACDwC,cAAc,mCAAmC7D,KAAKM,mBACtDmB,UAAU2B,IAAIzB,EAAAA,YAAYC,UAEhCY,EAASf,UAAU2B,IAAI,eACvBZ,EAASf,UAAUqC,OAAO,cAAe/B,EAAE4B,QAAUH,GACrDhB,EAASf,UAAUqC,OAAO,aAAc/B,EAAE4B,SAAWH,KAGvDhB,EAASV,iBAAiB,YAAa,KACrCU,EAASf,UAAUC,OAAO,cAAe,cAAe,gBAG1Dc,EAASV,iBAAiB,OAASC,IAEjC,GADAA,EAAEsB,kBACGrD,KAAKK,WAAY,OAGtB,GAAIL,KAAKS,mBAAmBrB,OAAS,EAAG,CACtC,GAAIY,KAAKS,mBAAmBU,SAASc,GAAQ,OAC7C,MAAMqB,EAAOd,EAASe,wBAChBQ,EAAShC,EAAE4B,QAAUL,EAAKG,KAAOH,EAAKI,MAAQ,EAEpD,YADA1D,KAAKgE,sBAAsBhE,KAAKS,mBAAoB,CAACwB,GAAQ8B,EAE/D,CAGA,MAAMzD,EAAeN,KAAKM,aACpBC,EAAeP,KAAKO,aACpBC,EAAYR,KAAKQ,UAEvB,IAAKR,KAAKK,YAA+B,OAAjBC,GAA0C,OAAjBC,GAAuC,OAAdC,EACxE,OAGF,MAAMyD,EAAmBzD,EAAYD,EAAeC,EAAY,EAAIA,EAE9D0D,EAAWlF,EADIgB,KAAK+C,iBACgBxC,EAAc0D,GAElDjC,EAA2B,CAC/BC,MAAO3B,EACPpB,UAAWqB,EACXpB,QAAS8E,EACTE,YAAaD,GAIGlE,KAAKoE,eAAe,cAAepC,IAGnDhC,KAAKqE,kBAAkBH,OA7GzB1B,EAASI,WAAY,IAqHzB0B,eAAe,IAAMtE,KAAKuE,qBAAqBjC,GACjD,CAOQ,oBAAAiC,CAAqBjC,GACNA,EAAOhB,iBAAiB,yCAEhCC,QAASiD,IACpB,MAAMC,EAAgBD,EAChBE,EAAUD,EAAchC,aAAa,cAC3C,IAAKiC,GAAWA,EAAQC,WAAW,gBAAiB,OAGpD,GAAIF,EAAchC,aAAa,yBAA0B,OACzDgC,EAAc5B,aAAa,wBAAyB,QAGpD,MAAM+B,EAAiB5E,KAAK6E,uBAAuBJ,EAAeC,GAClE,GAA8B,IAA1BE,EAAexF,OAAc,OAGdwF,EAAeE,MAAOlB,IACvC,MAAMmB,EAAM/E,KAAKf,QAAQyD,KAAMC,GAAMA,EAAEV,QAAU2B,GACjD,OAAO5D,KAAKY,yBAAyBmE,OAIvCN,EAAc7B,WAAY,EAC1B6B,EAAcO,MAAMC,OAAS,OAE7BR,EAAc3C,iBAAiB,YAAcC,IAC3C/B,KAAKK,YAAa,EAClBL,KAAKM,aAAe,KACpBN,KAAKO,aAAe,KACpBP,KAAKS,mBAAqB,IAAImE,GAE1B7C,EAAEkB,eACJlB,EAAEkB,aAAaC,cAAgB,OAC/BnB,EAAEkB,aAAaE,QAAQ,aAAc,SAASuB,MAGhDD,EAAchD,UAAU2B,IAAIzB,EAAAA,YAAYC,UAExC,IAAA,MAAWgC,KAAKgB,EACdtC,EAAOuB,cAAc,mCAAmCD,QAAQnC,UAAU2B,IAAIzB,EAAAA,YAAYC,YAI9F6C,EAAc3C,iBAAiB,UAAW,KACxC9B,KAAKK,YAAa,EAClBL,KAAKM,aAAe,KACpBN,KAAKO,aAAe,KACpBP,KAAKQ,UAAY,KACjBR,KAAKS,mBAAqB,GAC1BT,KAAKoB,qBAIPqD,EAAc3C,iBAAiB,WAAaC,IAE1C,GADAA,EAAEsB,kBACGrD,KAAKK,WAAY,OAEtB,GACEL,KAAKS,mBAAmBrB,OAAS,GACjCY,KAAKS,mBAAmBrB,SAAWwF,EAAexF,QAClDY,KAAKS,mBAAmBqE,MAAOlB,GAAMgB,EAAezD,SAASyC,IAE7D,OAEF,MAAMN,EAAOmB,EAAclB,wBACrBC,EAAOF,EAAKG,KAAOH,EAAKI,MAAQ,EAChCK,EAAShC,EAAE4B,QAAUH,EAE3BxD,KAAKoB,mBACLqD,EAAchD,UAAU2B,IAAI,eAC5BqB,EAAchD,UAAUqC,OAAO,cAAeC,GAC9CU,EAAchD,UAAUqC,OAAO,cAAeC,KAGhDU,EAAc3C,iBAAiB,YAAa,KAC1C2C,EAAchD,UAAUC,OAAO,cAAe,cAAe,gBAG/D+C,EAAc3C,iBAAiB,OAASC,IAEtC,GADAA,EAAEsB,kBACGrD,KAAKK,WAAY,OAEtB,MAAMiD,EAAOmB,EAAclB,wBACrBQ,EAAShC,EAAE4B,QAAUL,EAAKG,KAAOH,EAAKI,MAAQ,EAEpD,GAAI1D,KAAKS,mBAAmBrB,OAAS,EAAG,CAEtC,GACEY,KAAKS,mBAAmBrB,SAAWwF,EAAexF,QAClDY,KAAKS,mBAAmBqE,MAAOlB,GAAMgB,EAAezD,SAASyC,IAE7D,OACF5D,KAAKgE,sBAAsBhE,KAAKS,mBAAoBmE,EAAgBb,EACtE,MAAA,GAAW/D,KAAKM,aAAc,CAE5B,MAAM4E,EAAelF,KAAK+C,iBACpBoC,EAAcpB,EAASa,EAAe,GAAKA,EAAeA,EAAexF,OAAS,GAClFF,EAAYgG,EAAalC,QAAQhD,KAAKM,cACtCnB,EAAU4E,EAASmB,EAAalC,QAAQmC,GAAeD,EAAalC,QAAQmC,GAAe,EACjG,IAAkB,IAAdjG,IAAgC,IAAZC,EAAgB,OACxC,MAAM8E,EAAmB9E,EAAUD,EAAYC,EAAU,EAAIA,EACvD+E,EAAWlF,EAAWkG,EAAchG,EAAW+E,GACnCjE,KAAKoE,eAAiC,cAAe,CACrEnC,MAAOjC,KAAKM,aACZpB,YACAC,QAAS8E,EACTE,YAAaD,KAEClE,KAAKqE,kBAAkBH,EACzC,MAGN,CAMQ,sBAAAW,CAAuBJ,EAA4BW,GAEzD,MAAMC,EAAaZ,EAAcO,MAAMK,WACjCC,EAAQ,4BAA4BC,KAAKF,GAC/C,IAAKC,EAAO,MAAO,GAEnB,MAAME,EAAWC,SAASH,EAAM,GAAI,IAC9BI,EAAOD,SAASH,EAAM,GAAI,IAG1BK,EAAiB3F,KAAK2F,eACtBC,EAAmB,GACzB,IAAA,IAASC,EAAIL,EAAW,EAAGK,EAAIL,EAAW,EAAIE,GAAQG,EAAIF,EAAevG,OAAQyG,IAAK,CACpF,MAAMd,EAAMY,EAAeE,GACvBd,GAAKa,EAAOE,KAAKf,EAAI9C,MAC3B,CACA,OAAO2D,CACT,CAKQ,qBAAA5B,CAAsB+B,EAAyBC,EAAwBjC,GAC7E,MAAMmB,EAAelF,KAAK+C,iBACpBkD,EAAYf,EAAagB,OAAQtC,IAAOmC,EAAc5E,SAASyC,IAE/DuB,EAAcpB,EAASiC,EAAa,GAAKA,EAAaA,EAAa5G,OAAS,GAC5E+G,EAAWF,EAAUjD,QAAQmC,GACnC,IAAiB,IAAbgB,EAAiB,OAErB,MAAMC,EAAcrC,EAASoC,EAAWA,EAAW,EAC7CE,EAAiBnB,EAAagB,OAAQtC,GAAMmC,EAAc5E,SAASyC,IACzEqC,EAAU1G,OAAO6G,EAAa,KAAMC,GAGlBrG,KAAKoE,eAAiC,cAAe,CACrEnC,MAAO8D,EAAc,GACrB7G,UAAWgG,EAAalC,QAAQ+C,EAAc,IAC9C5G,QAASiH,EACTjC,YAAa8B,KAGbjG,KAAKqE,kBAAkB4B,EAE3B,CAMS,SAAAK,CAAUC,GACjB,IAAKA,EAAMC,QAAyB,cAAdD,EAAME,KAAqC,eAAdF,EAAME,IACvD,OAGF,MAAM9F,EAAOX,MAAKU,EACZgG,EAAW/F,EAAKgG,UAChB1H,EAAU0B,EAAKiG,gBAErB,GAAIF,EAAW,GAAKA,GAAYzH,EAAQG,OAAQ,OAEhD,MAAMyB,EAAS5B,EAAQyH,GACvB,IAAK1G,KAAKY,yBAAyBC,GAAS,OAE5C,MAAMqE,EAAelF,KAAK+C,iBACpB7D,EAAYgG,EAAalC,QAAQnC,EAAOoB,OAC9C,IAAkB,IAAd/C,EAAkB,OAEtB,MAAMC,EAAwB,cAAdoH,EAAME,IAAsBvH,EAAY,EAAIA,EAAY,EAGxE,GAAIC,EAAU,GAAKA,GAAW+F,EAAa9F,OAAQ,OAGnD,MAAMyH,EAAe5H,EAAQyD,KAAMC,GAAMA,EAAEV,QAAUiD,EAAa/F,IAClE,OAAKa,KAAKY,yBAAyBiG,IAEnC7G,KAAKhB,WAAW6B,EAAOoB,MAAO9C,GAG9BwB,EAAKgG,UAAYxH,EACjB2H,EAAAA,kBAAkB9G,MAAKU,GAEvB6F,EAAMlD,iBACNkD,EAAMQ,mBACC,QAVP,CAWF,CASA,cAAAhE,GACE,OAAO/C,KAAKW,KAAKoC,gBACnB,CAOA,UAAA/D,CAAWiD,EAAe9C,GACxB,MAAM+F,EAAelF,KAAK+C,iBACpB7D,EAAYgG,EAAalC,QAAQf,GACvC,IAAkB,IAAd/C,EAAkB,OAEtB,MAAMgF,EAAWlF,EAAWkG,EAAchG,EAAWC,GAGnCa,KAAKoE,eAAiC,cAAe,CACrEnC,QACA/C,YACAC,UACAgF,YAAaD,KAIblE,KAAKqE,kBAAkBH,EAE3B,CAMA,cAAA8C,CAAeC,GACbjH,KAAKqE,kBAAkB4C,EACzB,CAKA,gBAAAC,GACE,MAAMC,EAAgBnH,KAAKf,QAAQmI,IAAKzE,GAAMA,EAAEV,OAChDjC,KAAKqE,kBAAkB8C,EACzB,CAQQ,sBAAAE,GACN,MAAMC,MAAgBC,IAKtB,OAJAvH,KAAKqB,aAAaC,iBAAiB,mCAAmCC,QAASiG,IAC7E,MAAMvF,EAAQuF,EAAK/E,aAAa,cAC5BR,GAAOqF,EAAUG,IAAIxF,EAAOuF,EAAKjE,wBAAwBE,QAExD6D,CACT,CAOQ,WAAAI,CAAYC,GAClB,MAAMrF,EAAStC,KAAKqB,YACpB,IAAKiB,GAAgC,IAAtBqF,EAAaC,KAAY,OAIxC,MAAMC,MAAaN,IAUnB,GATAjF,EAAOhB,iBAAiB,mCAAmCC,QAASiG,IAClE,MAAMvF,EAAQuF,EAAK/E,aAAa,cAChC,IAAKR,EAAO,OACZ,MAAM6F,EAAUH,EAAaI,IAAI9F,GACjC,QAAgB,IAAZ6F,EAAuB,OAC3B,MAAME,EAASF,EAAUN,EAAKjE,wBAAwBE,KAClDwE,KAAKC,IAAIF,GAAU,GAAGH,EAAOJ,IAAIxF,EAAO+F,KAG1B,IAAhBH,EAAOD,KAAY,OAKvB,MAAMO,EAAuB,GAU7B,GATA7F,EAAOhB,iBAAiB,qBAAqBC,QAASiG,IACpD,MAAMQ,EAASH,EAAOE,IAAIP,EAAK/E,aAAa,eAAiB,IAC7D,QAAe,IAAXuF,EAAsB,CACxB,MAAMI,EAAKZ,EACXY,EAAGpD,MAAMqD,UAAY,cAAcL,OACnCG,EAAMrC,KAAKsC,EACb,IAGmB,IAAjBD,EAAM/I,OAAc,OAGnBkD,EAAOgG,aAEZ,MAAMC,EAAWvI,KAAKG,kBAEtBqI,sBAAsB,KACpBL,EAAM5G,QAAS6G,IACbA,EAAG3G,UAAU2B,IAAI,kBACjBgF,EAAGpD,MAAMqD,UAAY,KAIvBI,WAAW,KACTN,EAAM5G,QAAS6G,IACbA,EAAGpD,MAAMqD,UAAY,GACrBD,EAAG3G,UAAUC,OAAO,qBAErB6G,EAAW,KAElB,CAMQ,WAAAG,CAAYC,GAClB,MAAMrG,EAAStC,KAAKqB,YACpB,IAAKiB,EAEH,YADAqG,IAKF,MAAMhB,EAAe3H,KAAKqH,yBAG1BsB,IAGA,MAAMC,MAAkBC,IAYxB,GAXAvG,EAAOhB,iBAAiB,mCAAmCC,QAASiG,IAClE,MAAMvF,EAAQuF,EAAK/E,aAAa,cAChC,IAAKR,EAAO,OACZ,MAAM6F,EAAUH,EAAaI,IAAI9F,GACjC,QAAgB,IAAZ6F,EAAuB,OAC3B,MAAMgB,EAAUtB,EAAKjE,wBAAwBE,KACzCwE,KAAKC,IAAIJ,EAAUgB,GAAW,GAChCF,EAAYxF,IAAInB,KAIK,IAArB2G,EAAYhB,KAAY,OAG5B,MAAMO,EAAuB,GAU7B,GATA7F,EAAOhB,iBAAiB,qBAAqBC,QAASiG,IACpD,MAAMvF,EAAQuF,EAAK/E,aAAa,cAChC,GAAIR,GAAS2G,EAAYG,IAAI9G,GAAQ,CACnC,MAAMmG,EAAKZ,EACXY,EAAG3G,UAAU2B,IAAI,kBACjB+E,EAAMrC,KAAKsC,EACb,IAGmB,IAAjBD,EAAM/I,OAAc,OAGxB,MAAMmJ,EAAWvI,KAAKG,kBACtBsI,WAAW,KACTN,EAAM5G,QAAS6G,GAAOA,EAAG3G,UAAUC,OAAO,oBACzC6G,EAAW,GAChB,CAKQ,iBAAAlE,CAAkBH,GACxB,MAAMpE,EAAYE,KAAKD,cAEvB,GAAkB,SAAdD,GAAwBE,KAAKqB,YAAa,CAC5C,MAAMsG,EAAe3H,KAAKqH,yBAC1BrH,KAAKW,KAAKqG,eAAe9C,GAKY,mBAA1BlE,KAAKW,KAAKqI,YACnBhJ,KAAKW,KAAKqI,cAAcC,KAAK,KAC3BjJ,KAAK0H,YAAYC,KAInBa,sBAAsB,KACpBxI,KAAK0H,YAAYC,IAGvB,KAAyB,SAAd7H,EACTE,KAAK0I,YAAY,IAAM1I,KAAKW,KAAKqG,eAAe9C,IAEhDlE,KAAKW,KAAKqG,eAAe9C,GAG3BlE,KAAKW,KAAKuI,sBACZ"}
|