@toolbox-web/grid 0.3.3 → 0.4.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.
Files changed (137) hide show
  1. package/all.d.ts +19 -19
  2. package/all.d.ts.map +1 -1
  3. package/all.js +1775 -1202
  4. package/all.js.map +1 -1
  5. package/index.js +2152 -2028
  6. package/index.js.map +1 -1
  7. package/lib/core/grid.d.ts +22 -12
  8. package/lib/core/grid.d.ts.map +1 -1
  9. package/lib/core/internal/columns.d.ts +0 -9
  10. package/lib/core/internal/columns.d.ts.map +1 -1
  11. package/lib/core/internal/config-manager.d.ts +236 -0
  12. package/lib/core/internal/config-manager.d.ts.map +1 -0
  13. package/lib/core/internal/event-delegation.d.ts.map +1 -1
  14. package/lib/core/internal/header.d.ts.map +1 -1
  15. package/lib/core/internal/keyboard.d.ts.map +1 -1
  16. package/lib/core/internal/render-scheduler.d.ts +123 -0
  17. package/lib/core/internal/render-scheduler.d.ts.map +1 -0
  18. package/lib/core/internal/rows.d.ts +8 -3
  19. package/lib/core/internal/rows.d.ts.map +1 -1
  20. package/lib/core/internal/sanitize.d.ts +2 -2
  21. package/lib/core/internal/sanitize.d.ts.map +1 -1
  22. package/lib/core/internal/shell.d.ts +40 -2
  23. package/lib/core/internal/shell.d.ts.map +1 -1
  24. package/lib/core/internal/validate-config.d.ts +11 -0
  25. package/lib/core/internal/validate-config.d.ts.map +1 -0
  26. package/lib/core/plugin/base-plugin.d.ts +70 -0
  27. package/lib/core/plugin/base-plugin.d.ts.map +1 -1
  28. package/lib/core/plugin/plugin-manager.d.ts +13 -2
  29. package/lib/core/plugin/plugin-manager.d.ts.map +1 -1
  30. package/lib/core/plugin/types.d.ts +17 -3
  31. package/lib/core/plugin/types.d.ts.map +1 -1
  32. package/lib/core/types.d.ts +112 -12
  33. package/lib/core/types.d.ts.map +1 -1
  34. package/lib/plugins/clipboard/ClipboardPlugin.d.ts.map +1 -1
  35. package/lib/plugins/clipboard/index.js +50 -18
  36. package/lib/plugins/clipboard/index.js.map +1 -1
  37. package/lib/plugins/column-virtualization/index.js +60 -25
  38. package/lib/plugins/column-virtualization/index.js.map +1 -1
  39. package/lib/plugins/context-menu/index.js +51 -16
  40. package/lib/plugins/context-menu/index.js.map +1 -1
  41. package/lib/plugins/editing/EditingPlugin.d.ts +117 -0
  42. package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -0
  43. package/lib/{core/internal → plugins/editing}/editors.d.ts +1 -1
  44. package/lib/plugins/editing/editors.d.ts.map +1 -0
  45. package/lib/plugins/editing/index.d.ts +8 -0
  46. package/lib/plugins/editing/index.d.ts.map +1 -0
  47. package/lib/plugins/editing/index.js +705 -0
  48. package/lib/plugins/editing/index.js.map +1 -0
  49. package/lib/plugins/editing/types.d.ts +45 -0
  50. package/lib/plugins/editing/types.d.ts.map +1 -0
  51. package/lib/plugins/export/ExportPlugin.d.ts.map +1 -1
  52. package/lib/plugins/export/index.js +74 -39
  53. package/lib/plugins/export/index.js.map +1 -1
  54. package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
  55. package/lib/plugins/filtering/index.js +87 -50
  56. package/lib/plugins/filtering/index.js.map +1 -1
  57. package/lib/plugins/grouping-columns/grouping-columns.d.ts +4 -4
  58. package/lib/plugins/grouping-columns/grouping-columns.d.ts.map +1 -1
  59. package/lib/plugins/grouping-columns/index.js +59 -24
  60. package/lib/plugins/grouping-columns/index.js.map +1 -1
  61. package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts.map +1 -1
  62. package/lib/plugins/grouping-rows/grouping-rows.d.ts.map +1 -1
  63. package/lib/plugins/grouping-rows/index.js +46 -11
  64. package/lib/plugins/grouping-rows/index.js.map +1 -1
  65. package/lib/plugins/master-detail/MasterDetailPlugin.d.ts +2 -2
  66. package/lib/plugins/master-detail/MasterDetailPlugin.d.ts.map +1 -1
  67. package/lib/plugins/master-detail/index.js +140 -102
  68. package/lib/plugins/master-detail/index.js.map +1 -1
  69. package/lib/plugins/master-detail/types.d.ts +12 -2
  70. package/lib/plugins/master-detail/types.d.ts.map +1 -1
  71. package/lib/plugins/multi-sort/MultiSortPlugin.d.ts.map +1 -1
  72. package/lib/plugins/multi-sort/index.js +59 -22
  73. package/lib/plugins/multi-sort/index.js.map +1 -1
  74. package/lib/plugins/pinned-columns/index.js +41 -6
  75. package/lib/plugins/pinned-columns/index.js.map +1 -1
  76. package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts.map +1 -1
  77. package/lib/plugins/pinned-rows/index.js +45 -9
  78. package/lib/plugins/pinned-rows/index.js.map +1 -1
  79. package/lib/plugins/pivot/index.js +42 -7
  80. package/lib/plugins/pivot/index.js.map +1 -1
  81. package/lib/plugins/reorder/ReorderPlugin.d.ts.map +1 -1
  82. package/lib/plugins/reorder/index.js +59 -19
  83. package/lib/plugins/reorder/index.js.map +1 -1
  84. package/lib/plugins/selection/index.js +41 -6
  85. package/lib/plugins/selection/index.js.map +1 -1
  86. package/lib/plugins/server-side/index.js +48 -13
  87. package/lib/plugins/server-side/index.js.map +1 -1
  88. package/lib/plugins/tree/TreePlugin.d.ts +3 -3
  89. package/lib/plugins/tree/TreePlugin.d.ts.map +1 -1
  90. package/lib/plugins/tree/index.js +165 -126
  91. package/lib/plugins/tree/index.js.map +1 -1
  92. package/lib/plugins/tree/tree-data.d.ts +6 -6
  93. package/lib/plugins/tree/tree-data.d.ts.map +1 -1
  94. package/lib/plugins/tree/tree-detect.d.ts +5 -9
  95. package/lib/plugins/tree/tree-detect.d.ts.map +1 -1
  96. package/lib/plugins/tree/types.d.ts +16 -4
  97. package/lib/plugins/tree/types.d.ts.map +1 -1
  98. package/lib/plugins/undo-redo/index.js +46 -11
  99. package/lib/plugins/undo-redo/index.js.map +1 -1
  100. package/lib/plugins/visibility/index.js +37 -2
  101. package/lib/plugins/visibility/index.js.map +1 -1
  102. package/package.json +1 -1
  103. package/public.d.ts +104 -13
  104. package/public.d.ts.map +1 -1
  105. package/umd/grid.all.umd.js +31 -19
  106. package/umd/grid.all.umd.js.map +1 -1
  107. package/umd/grid.umd.js +18 -6
  108. package/umd/grid.umd.js.map +1 -1
  109. package/umd/plugins/clipboard.umd.js +1 -1
  110. package/umd/plugins/clipboard.umd.js.map +1 -1
  111. package/umd/plugins/editing.umd.js +2 -0
  112. package/umd/plugins/editing.umd.js.map +1 -0
  113. package/umd/plugins/export.umd.js +2 -2
  114. package/umd/plugins/export.umd.js.map +1 -1
  115. package/umd/plugins/filtering.umd.js +1 -1
  116. package/umd/plugins/filtering.umd.js.map +1 -1
  117. package/umd/plugins/grouping-columns.umd.js +1 -1
  118. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  119. package/umd/plugins/grouping-rows.umd.js +1 -1
  120. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  121. package/umd/plugins/master-detail.umd.js +1 -1
  122. package/umd/plugins/master-detail.umd.js.map +1 -1
  123. package/umd/plugins/multi-sort.umd.js +1 -1
  124. package/umd/plugins/multi-sort.umd.js.map +1 -1
  125. package/umd/plugins/pinned-rows.umd.js +1 -1
  126. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  127. package/umd/plugins/reorder.umd.js +1 -1
  128. package/umd/plugins/reorder.umd.js.map +1 -1
  129. package/umd/plugins/tree.umd.js +1 -1
  130. package/umd/plugins/tree.umd.js.map +1 -1
  131. package/lib/core/internal/column-state.d.ts +0 -124
  132. package/lib/core/internal/column-state.d.ts.map +0 -1
  133. package/lib/core/internal/editing.d.ts +0 -76
  134. package/lib/core/internal/editing.d.ts.map +0 -1
  135. package/lib/core/internal/editors.d.ts.map +0 -1
  136. package/lib/core/internal/grid-internals.d.ts +0 -83
  137. package/lib/core/internal/grid-internals.d.ts.map +0 -1
@@ -1,2 +1,2 @@
1
- (function(f,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("../../core/internal/sanitize"),require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/sanitize","../../core/plugin/base-plugin"],g):(f=typeof globalThis<"u"?globalThis:f||self,g(f.TbwGridPlugin_masterDetail={},f.TbwGrid,f.TbwGrid))})(this,(function(f,g,m){"use strict";function w(p,e){const t=new Set(p);return t.has(e)?t.delete(e):t.add(e),t}function x(p,e){const t=new Set(p);return t.add(e),t}function R(p,e){const t=new Set(p);return t.delete(e),t}function b(p,e){return p.has(e)}function E(p,e,t,i){const s=document.createElement("div");s.className="master-detail-row",s.setAttribute("data-detail-for",String(e)),s.setAttribute("role","row");const r=document.createElement("div");r.className="master-detail-cell",r.setAttribute("role","cell"),r.style.gridColumn=`1 / ${i+1}`;const a=t(p,e);return typeof a=="string"?r.innerHTML=a:a instanceof HTMLElement&&r.appendChild(a),s.appendChild(r),s}const y=".master-detail-cell-wrapper{display:flex;align-items:center;gap:4px}.master-detail-toggle{cursor:pointer;opacity:.7;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center}.master-detail-toggle:hover{opacity:1}.master-detail-row{grid-column:1 / -1;display:grid;background:var(--tbw-master-detail-bg, var(--tbw-color-row-alt));border-bottom:1px solid var(--tbw-master-detail-border, var(--tbw-color-border));overflow:hidden}.master-detail-cell{padding:16px;overflow:auto}.master-detail-row.tbw-expanding{animation:tbw-detail-expand var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}.master-detail-row.tbw-collapsing{animation:tbw-detail-collapse var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-detail-expand{0%{opacity:0;max-height:0;padding-top:0;padding-bottom:0}to{opacity:1;max-height:500px;padding-top:16px;padding-bottom:16px}}@keyframes tbw-detail-collapse{0%{opacity:1;max-height:500px}to{opacity:0;max-height:0}}";class v extends m.BaseGridPlugin{name="masterDetail";version="1.0.0";get defaultConfig(){return{detailHeight:"auto",expandOnRowClick:!1,collapseOnClickOutside:!1,showExpandColumn:!0,animation:"slide"}}attach(e){super.attach(e),this.parseLightDomDetail()}parseLightDomDetail(){const e=this.grid;if(!e||typeof e.querySelector!="function")return;const t=e.querySelector("tbw-grid-detail");if(!t)return;const i=e;if(i.__frameworkAdapter?.parseDetailElement){const l=i.__frameworkAdapter.parseDetailElement(t);if(l){this.config={...this.config,detailRenderer:l};return}}const s=t.getAttribute("animation"),r=t.getAttribute("show-expand-column"),a=t.getAttribute("expand-on-row-click"),o=t.getAttribute("collapse-on-click-outside"),n=t.getAttribute("height"),d={};s!==null&&(d.animation=s==="false"?!1:s),r!==null&&(d.showExpandColumn=r!=="false"),a!==null&&(d.expandOnRowClick=a==="true"),o!==null&&(d.collapseOnClickOutside=o==="true"),n!==null&&(d.detailHeight=n==="auto"?"auto":parseInt(n,10));const c=t.innerHTML.trim();c&&!this.config.detailRenderer&&(d.detailRenderer=(l,h)=>{const u=g.evalTemplateString(c,{value:l,row:l});return g.sanitizeHTML(u)}),Object.keys(d).length>0&&(this.config={...this.config,...d})}get isAnimationEnabled(){const t=this.grid.effectiveConfig?.animation?.mode??"reduced-motion";if(t===!1||t==="off")return!1;if(t===!0||t==="on")return!0;const i=this.shadowRoot?.host;return i?getComputedStyle(i).getPropertyValue("--tbw-animation-enabled").trim()!=="0":!0}get animationStyle(){return this.isAnimationEnabled?this.config.animation??"slide":!1}get animationDuration(){const e=this.shadowRoot?.host;if(e){const t=getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(),i=parseInt(t,10);if(!isNaN(i))return i}return 200}animateExpand(e){!this.isAnimationEnabled||this.animationStyle===!1||(e.classList.add("tbw-expanding"),e.addEventListener("animationend",()=>{e.classList.remove("tbw-expanding")},{once:!0}))}animateCollapse(e,t){if(!this.isAnimationEnabled||this.animationStyle===!1){t();return}e.classList.add("tbw-collapsing");const i=()=>{e.classList.remove("tbw-collapsing"),t()};e.addEventListener("animationend",i,{once:!0}),setTimeout(i,this.animationDuration+50)}expandedRows=new Set;detailElements=new Map;detach(){this.expandedRows.clear(),this.detailElements.clear()}processColumns(e){if(!this.config.detailRenderer)return[...e];const t=[...e];if(t.length>0){const i={...t[0]},s=i.viewRenderer;if(s?.__masterDetailWrapped)return t;const r=a=>{const{value:o,row:n}=a,d=this.expandedRows.has(n),c=document.createElement("span");c.className="master-detail-cell-wrapper";const l=document.createElement("span");l.className=`master-detail-toggle${d?" expanded":""}`,this.setIcon(l,this.resolveIcon(d?"collapse":"expand")),l.setAttribute("role","button"),l.setAttribute("tabindex","0"),l.setAttribute("aria-expanded",String(d)),l.setAttribute("aria-label",d?"Collapse details":"Expand details"),l.addEventListener("click",u=>{u.stopPropagation();const C=this.rows.indexOf(n);this.expandedRows=w(this.expandedRows,n),this.emit("detail-expand",{rowIndex:C,row:n,expanded:this.expandedRows.has(n)}),this.requestRender()}),c.appendChild(l);const h=document.createElement("span");if(s){const u=s(a);u instanceof Node?h.appendChild(u):h.textContent=String(u??o??"")}else h.textContent=String(o??"");return c.appendChild(h),c};r.__masterDetailWrapped=!0,i.viewRenderer=r,t[0]=i}return t}onRowClick(e){if(!(!this.config.expandOnRowClick||!this.config.detailRenderer))return this.expandedRows=w(this.expandedRows,e.row),this.emit("detail-expand",{rowIndex:e.rowIndex,row:e.row,expanded:this.expandedRows.has(e.row)}),this.requestRender(),!1}onCellClick(){this.expandedRows.size>0&&queueMicrotask(()=>this.#e())}afterRender(){this.#e()}onScrollRender(){!this.config.detailRenderer||this.expandedRows.size===0||this.#e()}#e(){if(!this.config.detailRenderer)return;const e=this.shadowRoot?.querySelector(".rows");if(!e)return;const t=new Map,i=e.querySelectorAll(".data-grid-row"),s=this.columns.length;for(const a of i){const o=a.querySelector(".cell[data-row]"),n=o?parseInt(o.getAttribute("data-row")??"-1",10):-1;n>=0&&t.set(n,a)}const r=e.querySelectorAll(".master-detail-row");for(const a of r){const o=parseInt(a.getAttribute("data-detail-for")??"-1",10),n=o>=0?this.rows[o]:void 0,d=n&&this.expandedRows.has(n),c=t.has(o);(!d||!c)&&(a.remove(),n&&this.detailElements.delete(n))}for(const[a,o]of t){const n=this.rows[a];if(!n||!this.expandedRows.has(n))continue;const d=this.detailElements.get(n);if(d){d.previousElementSibling!==o&&o.after(d);continue}const c=E(n,a,this.config.detailRenderer,s);typeof this.config.detailHeight=="number"&&(c.style.height=`${this.config.detailHeight}px`),o.after(c),this.detailElements.set(n,c),this.animateExpand(c)}}getExtraHeight(){let e=0;for(const t of this.expandedRows){const i=this.detailElements.get(t);if(i)e+=i.offsetHeight;else{const s=this.config?.detailHeight;e+=typeof s=="number"?s:150}}return e}getExtraHeightBefore(e){let t=0;for(const i of this.expandedRows){const s=this.rows.indexOf(i);if(s>=0&&s<e){const r=this.detailElements.get(i);if(r)t+=r.offsetHeight;else{const a=this.config?.detailHeight;t+=typeof a=="number"?a:150}}}return t}adjustVirtualStart(e,t,i){if(this.expandedRows.size===0)return e;const s=[];for(const o of this.expandedRows){const n=this.rows.indexOf(o);n>=0&&s.push({index:n,row:o})}s.sort((o,n)=>o.index-n.index);let r=e,a=0;for(const{index:o,row:n}of s){const d=o*i+a,l=this.detailElements.get(n)?.offsetHeight??(typeof this.config?.detailHeight=="number"?this.config.detailHeight:150),h=d+i+l;a+=l,!(o>=e)&&h>t&&o<r&&(r=o)}return r}expand(e){const t=this.rows[e];t&&(this.expandedRows=x(this.expandedRows,t),this.requestRender())}collapse(e){const t=this.rows[e];t&&(this.expandedRows=R(this.expandedRows,t),this.requestRender())}toggle(e){const t=this.rows[e];t&&(this.expandedRows=w(this.expandedRows,t),this.requestRender())}isExpanded(e){const t=this.rows[e];return t?b(this.expandedRows,t):!1}expandAll(){for(const e of this.rows)this.expandedRows.add(e);this.requestRender()}collapseAll(){this.expandedRows.clear(),this.requestRender()}getExpandedRows(){const e=[];for(const t of this.expandedRows){const i=this.rows.indexOf(t);i>=0&&e.push(i)}return e}getDetailElement(e){const t=this.rows[e];return t?this.detailElements.get(t):void 0}refreshDetailRenderer(){const e=this.config.detailRenderer;this.config={...this.config,detailRenderer:void 0},this.parseLightDomDetail(),!this.config.detailRenderer&&e&&(this.config={...this.config,detailRenderer:e})}styles=y}f.MasterDetailPlugin=v,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(h,p){typeof exports=="object"&&typeof module<"u"?p(exports,require("../../core/internal/sanitize"),require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/sanitize","../../core/plugin/base-plugin"],p):(h=typeof globalThis<"u"?globalThis:h||self,p(h.TbwGridPlugin_masterDetail={},h.TbwGrid,h.TbwGrid))})(this,(function(h,p,m){"use strict";function w(f,e){const t=new Set(f);return t.has(e)?t.delete(e):t.add(e),t}function x(f,e){const t=new Set(f);return t.add(e),t}function R(f,e){const t=new Set(f);return t.delete(e),t}function b(f,e){return f.has(e)}function E(f,e,t,i){const n=document.createElement("div");n.className="master-detail-row",n.setAttribute("data-detail-for",String(e)),n.setAttribute("role","row");const r=document.createElement("div");r.className="master-detail-cell",r.setAttribute("role","cell"),r.style.gridColumn=`1 / ${i+1}`;const a=t(f,e);return typeof a=="string"?r.innerHTML=a:a instanceof HTMLElement&&r.appendChild(a),n.appendChild(r),n}const y=".master-detail-cell-wrapper{display:flex;align-items:center;gap:4px}.master-detail-toggle{cursor:pointer;opacity:.7;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center}.master-detail-toggle:hover{opacity:1}.master-detail-row{grid-column:1 / -1;display:grid;background:var(--tbw-master-detail-bg, var(--tbw-color-row-alt));border-bottom:1px solid var(--tbw-master-detail-border, var(--tbw-color-border));overflow:hidden}.master-detail-cell{padding:16px;overflow:auto}.master-detail-row.tbw-expanding{animation:tbw-detail-expand var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}.master-detail-row.tbw-collapsing{animation:tbw-detail-collapse var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-detail-expand{0%{opacity:0;max-height:0;padding-top:0;padding-bottom:0}to{opacity:1;max-height:500px;padding-top:16px;padding-bottom:16px}}@keyframes tbw-detail-collapse{0%{opacity:1;max-height:500px}to{opacity:0;max-height:0}}";class C extends m.BaseGridPlugin{name="masterDetail";version="1.0.0";get defaultConfig(){return{detailHeight:"auto",expandOnRowClick:!1,collapseOnClickOutside:!1,showExpandColumn:!0,animation:"slide"}}attach(e){super.attach(e),this.parseLightDomDetail()}parseLightDomDetail(){const e=this.grid;if(!e||typeof e.querySelector!="function")return;const t=e.querySelector("tbw-grid-detail");if(!t)return;const i=e;if(i.__frameworkAdapter?.parseDetailElement){const l=i.__frameworkAdapter.parseDetailElement(t);if(l){this.config={...this.config,detailRenderer:l};return}}const n=t.getAttribute("animation"),r=t.getAttribute("show-expand-column"),a=t.getAttribute("expand-on-row-click"),s=t.getAttribute("collapse-on-click-outside"),o=t.getAttribute("height"),d={};n!==null&&(d.animation=n==="false"?!1:n),r!==null&&(d.showExpandColumn=r!=="false"),a!==null&&(d.expandOnRowClick=a==="true"),s!==null&&(d.collapseOnClickOutside=s==="true"),o!==null&&(d.detailHeight=o==="auto"?"auto":parseInt(o,10));const c=t.innerHTML.trim();c&&!this.config.detailRenderer&&(d.detailRenderer=(l,u)=>{const g=p.evalTemplateString(c,{value:l,row:l});return p.sanitizeHTML(g)}),Object.keys(d).length>0&&(this.config={...this.config,...d})}get isAnimationEnabled(){const t=this.grid.effectiveConfig?.animation?.mode??"reduced-motion";if(t===!1||t==="off")return!1;if(t===!0||t==="on")return!0;const i=this.shadowRoot?.host;return i?getComputedStyle(i).getPropertyValue("--tbw-animation-enabled").trim()!=="0":!0}get animationStyle(){return this.isAnimationEnabled?this.config.animation??"slide":!1}get animationDuration(){const e=this.shadowRoot?.host;if(e){const t=getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(),i=parseInt(t,10);if(!isNaN(i))return i}return 200}animateExpand(e){!this.isAnimationEnabled||this.animationStyle===!1||(e.classList.add("tbw-expanding"),e.addEventListener("animationend",()=>{e.classList.remove("tbw-expanding")},{once:!0}))}animateCollapse(e,t){if(!this.isAnimationEnabled||this.animationStyle===!1){t();return}e.classList.add("tbw-collapsing");const i=()=>{e.classList.remove("tbw-collapsing"),t()};e.addEventListener("animationend",i,{once:!0}),setTimeout(i,this.animationDuration+50)}expandedRows=new Set;detailElements=new Map;detach(){this.expandedRows.clear(),this.detailElements.clear()}processColumns(e){if(!this.config.detailRenderer)return[...e];const t=[...e];if(t.length>0){const i={...t[0]},n=i.viewRenderer;if(n?.__masterDetailWrapped)return t;const r=a=>{const{value:s,row:o}=a,d=this.expandedRows.has(o),c=document.createElement("span");c.className="master-detail-cell-wrapper";const l=document.createElement("span");l.className=`master-detail-toggle${d?" expanded":""}`,this.setIcon(l,this.resolveIcon(d?"collapse":"expand")),l.setAttribute("role","button"),l.setAttribute("tabindex","0"),l.setAttribute("aria-expanded",String(d)),l.setAttribute("aria-label",d?"Collapse details":"Expand details"),c.appendChild(l);const u=document.createElement("span");if(n){const g=n(a);g instanceof Node?u.appendChild(g):u.textContent=String(g??s??"")}else u.textContent=String(s??"");return c.appendChild(u),c};r.__masterDetailWrapped=!0,i.viewRenderer=r,t[0]=i}return t}onRowClick(e){if(!(!this.config.expandOnRowClick||!this.config.detailRenderer))return this.expandedRows=w(this.expandedRows,e.row),this.emit("detail-expand",{rowIndex:e.rowIndex,row:e.row,expanded:this.expandedRows.has(e.row)}),this.requestRender(),!1}onCellClick(e){if(e.originalEvent?.target?.classList.contains("master-detail-toggle")){const i=e.row,n=e.rowIndex;return this.expandedRows=w(this.expandedRows,i),this.emit("detail-expand",{rowIndex:n,row:i,expanded:this.expandedRows.has(i)}),this.requestRender(),!0}this.expandedRows.size>0&&queueMicrotask(()=>this.#e())}afterRender(){this.#e()}onScrollRender(){!this.config.detailRenderer||this.expandedRows.size===0||this.#e()}#e(){if(!this.config.detailRenderer)return;const e=this.shadowRoot?.querySelector(".rows");if(!e)return;const t=new Map,i=e.querySelectorAll(".data-grid-row"),n=this.columns.length;for(const a of i){const s=a.querySelector(".cell[data-row]"),o=s?parseInt(s.getAttribute("data-row")??"-1",10):-1;o>=0&&t.set(o,a)}const r=e.querySelectorAll(".master-detail-row");for(const a of r){const s=parseInt(a.getAttribute("data-detail-for")??"-1",10),o=s>=0?this.rows[s]:void 0,d=o&&this.expandedRows.has(o),c=t.has(s);(!d||!c)&&(a.remove(),o&&this.detailElements.delete(o))}for(const[a,s]of t){const o=this.rows[a];if(!o||!this.expandedRows.has(o))continue;const d=this.detailElements.get(o);if(d){d.previousElementSibling!==s&&s.after(d);continue}const c=E(o,a,this.config.detailRenderer,n);typeof this.config.detailHeight=="number"&&(c.style.height=`${this.config.detailHeight}px`),s.after(c),this.detailElements.set(o,c),this.animateExpand(c)}}getExtraHeight(){let e=0;for(const t of this.expandedRows){const i=this.detailElements.get(t);if(i)e+=i.offsetHeight;else{const n=this.config?.detailHeight;e+=typeof n=="number"?n:150}}return e}getExtraHeightBefore(e){let t=0;for(const i of this.expandedRows){const n=this.rows.indexOf(i);if(n>=0&&n<e){const r=this.detailElements.get(i);if(r)t+=r.offsetHeight;else{const a=this.config?.detailHeight;t+=typeof a=="number"?a:150}}}return t}adjustVirtualStart(e,t,i){if(this.expandedRows.size===0)return e;const n=[];for(const s of this.expandedRows){const o=this.rows.indexOf(s);o>=0&&n.push({index:o,row:s})}n.sort((s,o)=>s.index-o.index);let r=e,a=0;for(const{index:s,row:o}of n){const d=s*i+a,l=this.detailElements.get(o)?.offsetHeight??(typeof this.config?.detailHeight=="number"?this.config.detailHeight:150),u=d+i+l;a+=l,!(s>=e)&&u>t&&s<r&&(r=s)}return r}expand(e){const t=this.rows[e];t&&(this.expandedRows=x(this.expandedRows,t),this.requestRender())}collapse(e){const t=this.rows[e];t&&(this.expandedRows=R(this.expandedRows,t),this.requestRender())}toggle(e){const t=this.rows[e];t&&(this.expandedRows=w(this.expandedRows,t),this.requestRender())}isExpanded(e){const t=this.rows[e];return t?b(this.expandedRows,t):!1}expandAll(){for(const e of this.rows)this.expandedRows.add(e);this.requestRender()}collapseAll(){this.expandedRows.clear(),this.requestRender()}getExpandedRows(){const e=[];for(const t of this.expandedRows){const i=this.rows.indexOf(t);i>=0&&e.push(i)}return e}getDetailElement(e){const t=this.rows[e];return t?this.detailElements.get(t):void 0}refreshDetailRenderer(){const e=this.config.detailRenderer;if(this.config={...this.config,detailRenderer:void 0},this.parseLightDomDetail(),!this.config.detailRenderer&&e&&(this.config={...this.config,detailRenderer:e}),this.config.detailRenderer){const t=this.grid;typeof t.refreshColumns=="function"?t.refreshColumns():this.requestRender()}}styles=y}h.MasterDetailPlugin=C,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
2
2
  //# sourceMappingURL=master-detail.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"master-detail.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/master-detail/master-detail.ts","../../../../../libs/grid/src/lib/plugins/master-detail/MasterDetailPlugin.ts"],"sourcesContent":["/**\n * Master/Detail Core Logic\n *\n * Pure functions for managing detail row expansion state.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// Uses `any` for maximum flexibility with user-defined row types.\n\n/**\n * Toggle the expansion state of a detail row.\n * Returns a new Set with the updated state.\n */\nexport function toggleDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n if (newExpanded.has(row)) {\n newExpanded.delete(row);\n } else {\n newExpanded.add(row);\n }\n return newExpanded;\n}\n\n/**\n * Expand a detail row.\n * Returns a new Set with the row added.\n */\nexport function expandDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.add(row);\n return newExpanded;\n}\n\n/**\n * Collapse a detail row.\n * Returns a new Set with the row removed.\n */\nexport function collapseDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.delete(row);\n return newExpanded;\n}\n\n/**\n * Check if a detail row is expanded.\n */\nexport function isDetailExpanded(expandedRows: Set<object>, row: object): boolean {\n return expandedRows.has(row);\n}\n\n/**\n * Create a detail element for a given row.\n * The element spans all columns and contains the rendered content.\n */\nexport function createDetailElement(\n row: any,\n rowIndex: number,\n renderer: (row: any, rowIndex: number) => HTMLElement | string,\n columnCount: number\n): HTMLElement {\n const detailRow = document.createElement('div');\n detailRow.className = 'master-detail-row';\n detailRow.setAttribute('data-detail-for', String(rowIndex));\n detailRow.setAttribute('role', 'row');\n\n const detailCell = document.createElement('div');\n detailCell.className = 'master-detail-cell';\n detailCell.setAttribute('role', 'cell');\n detailCell.style.gridColumn = `1 / ${columnCount + 1}`;\n\n const content = renderer(row, rowIndex);\n if (typeof content === 'string') {\n detailCell.innerHTML = content;\n } else if (content instanceof HTMLElement) {\n detailCell.appendChild(content);\n }\n\n detailRow.appendChild(detailCell);\n return detailRow;\n}\n","/**\n * Master/Detail Plugin (Class-based)\n *\n * Enables expandable detail rows showing additional content for each row.\n * Animation style is plugin-configured; respects grid-level animation.mode.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { evalTemplateString, sanitizeHTML } from '../../core/internal/sanitize';\nimport { BaseGridPlugin, GridElement, RowClickEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, GridConfig } from '../../core/types';\nimport {\n collapseDetailRow,\n createDetailElement,\n expandDetailRow,\n isDetailExpanded,\n toggleDetailRow,\n} from './master-detail';\nimport styles from './master-detail.css?inline';\nimport type { DetailExpandDetail, ExpandCollapseAnimation, MasterDetailConfig } from './types';\n\n/** Extended grid interface for accessing effective config */\ninterface GridWithConfig {\n effectiveConfig?: GridConfig;\n}\n\n/**\n * Master/Detail Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new MasterDetailPlugin({\n * enabled: true,\n * detailRenderer: (row) => `<div>Details for ${row.name}</div>`,\n * expandOnRowClick: true,\n * })\n * ```\n */\nexport class MasterDetailPlugin extends BaseGridPlugin<MasterDetailConfig> {\n readonly name = 'masterDetail';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<MasterDetailConfig> {\n return {\n detailHeight: 'auto',\n expandOnRowClick: false,\n collapseOnClickOutside: false,\n showExpandColumn: true,\n animation: 'slide', // Plugin's own default\n };\n }\n\n // #region Light DOM Parsing\n\n /**\n * Called when plugin is attached to the grid.\n * Parses light DOM for `<tbw-grid-detail>` elements to configure detail templates.\n */\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.parseLightDomDetail();\n }\n\n /**\n * Parse `<tbw-grid-detail>` elements from the grid's light DOM.\n *\n * Allows declarative configuration:\n * ```html\n * <tbw-grid [rows]=\"data\">\n * <tbw-grid-detail>\n * <div class=\"detail-content\">\n * <p>Name: {{ row.name }}</p>\n * <p>Email: {{ row.email }}</p>\n * </div>\n * </tbw-grid-detail>\n * </tbw-grid>\n * ```\n *\n * Attributes:\n * - `animation`: 'slide' | 'fade' | 'false' (default: 'slide')\n * - `show-expand-column`: 'true' | 'false' (default: 'true')\n * - `expand-on-row-click`: 'true' | 'false' (default: 'false')\n * - `collapse-on-click-outside`: 'true' | 'false' (default: 'false')\n * - `height`: number (pixels) or 'auto' (default: 'auto')\n */\n private parseLightDomDetail(): void {\n const gridEl = this.grid as unknown as Element;\n if (!gridEl || typeof gridEl.querySelector !== 'function') return;\n\n const detailEl = gridEl.querySelector('tbw-grid-detail');\n if (!detailEl) return;\n\n // Check if a framework adapter wants to handle this element\n // (e.g., Angular adapter intercepts for ng-template rendering)\n const gridWithAdapter = gridEl as unknown as {\n __frameworkAdapter?: {\n parseDetailElement?: (el: Element) => ((row: any, rowIndex: number) => HTMLElement | string) | undefined;\n };\n };\n if (gridWithAdapter.__frameworkAdapter?.parseDetailElement) {\n const adapterRenderer = gridWithAdapter.__frameworkAdapter.parseDetailElement(detailEl);\n if (adapterRenderer) {\n this.config = { ...this.config, detailRenderer: adapterRenderer };\n return;\n }\n }\n\n // Parse attributes for configuration\n const animation = detailEl.getAttribute('animation');\n const showExpandColumn = detailEl.getAttribute('show-expand-column');\n const expandOnRowClick = detailEl.getAttribute('expand-on-row-click');\n const collapseOnClickOutside = detailEl.getAttribute('collapse-on-click-outside');\n const heightAttr = detailEl.getAttribute('height');\n\n const configUpdates: Partial<MasterDetailConfig> = {};\n\n if (animation !== null) {\n configUpdates.animation = animation === 'false' ? false : (animation as 'slide' | 'fade');\n }\n if (showExpandColumn !== null) {\n configUpdates.showExpandColumn = showExpandColumn !== 'false';\n }\n if (expandOnRowClick !== null) {\n configUpdates.expandOnRowClick = expandOnRowClick === 'true';\n }\n if (collapseOnClickOutside !== null) {\n configUpdates.collapseOnClickOutside = collapseOnClickOutside === 'true';\n }\n if (heightAttr !== null) {\n configUpdates.detailHeight = heightAttr === 'auto' ? 'auto' : parseInt(heightAttr, 10);\n }\n\n // Get template content from innerHTML\n const templateHTML = detailEl.innerHTML.trim();\n if (templateHTML && !this.config.detailRenderer) {\n // Create a template-based renderer using the inner HTML\n configUpdates.detailRenderer = (row: any, _rowIndex: number): string => {\n // Evaluate template expressions like {{ row.field }}\n const evaluated = evalTemplateString(templateHTML, { value: row, row });\n // Sanitize the result to prevent XSS\n return sanitizeHTML(evaluated);\n };\n }\n\n // Merge updates into config\n if (Object.keys(configUpdates).length > 0) {\n this.config = { ...this.config, ...configUpdates };\n }\n }\n\n // #endregion\n\n // #region Animation Helpers\n\n /**\n * Check if animations are enabled at the grid level.\n * Respects gridConfig.animation.mode and CSS variable.\n */\n private get isAnimationEnabled(): boolean {\n const gridEl = this.grid as unknown as GridWithConfig;\n const mode = gridEl.effectiveConfig?.animation?.mode ?? 'reduced-motion';\n\n if (mode === false || mode === 'off') return false;\n if (mode === true || mode === 'on') return true;\n\n // reduced-motion: check CSS variable\n const host = this.shadowRoot?.host as HTMLElement | undefined;\n if (host) {\n const enabled = getComputedStyle(host).getPropertyValue('--tbw-animation-enabled').trim();\n return enabled !== '0';\n }\n return true;\n }\n\n /**\n * Get expand/collapse animation style from plugin config.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide'; // Plugin default\n }\n\n /**\n * Get animation duration from CSS variable (set by grid).\n */\n private get animationDuration(): number {\n const host = this.shadowRoot?.host as HTMLElement | undefined;\n if (host) {\n const durationStr = getComputedStyle(host).getPropertyValue('--tbw-animation-duration').trim();\n const parsed = parseInt(durationStr, 10);\n if (!isNaN(parsed)) return parsed;\n }\n return 200; // Default\n }\n\n /**\n * Apply expand animation to a detail element.\n */\n private animateExpand(detailEl: HTMLElement): void {\n if (!this.isAnimationEnabled || this.animationStyle === false) return;\n\n detailEl.classList.add('tbw-expanding');\n detailEl.addEventListener(\n 'animationend',\n () => {\n detailEl.classList.remove('tbw-expanding');\n },\n { once: true },\n );\n }\n\n /**\n * Apply collapse animation to a detail element and remove after animation.\n */\n private animateCollapse(detailEl: HTMLElement, onComplete: () => void): void {\n if (!this.isAnimationEnabled || this.animationStyle === false) {\n onComplete();\n return;\n }\n\n detailEl.classList.add('tbw-collapsing');\n const cleanup = () => {\n detailEl.classList.remove('tbw-collapsing');\n onComplete();\n };\n detailEl.addEventListener('animationend', cleanup, { once: true });\n // Fallback timeout in case animation doesn't fire\n setTimeout(cleanup, this.animationDuration + 50);\n }\n\n // #endregion\n\n // #region Internal State\n private expandedRows: Set<any> = new Set();\n private detailElements: Map<any, HTMLElement> = new Map();\n // #endregion\n\n // #region Lifecycle\n\n override detach(): void {\n this.expandedRows.clear();\n this.detailElements.clear();\n }\n // #endregion\n\n // #region Hooks\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.config.detailRenderer) {\n return [...columns];\n }\n\n // Wrap first column's renderer to add expand/collapse toggle\n const cols = [...columns];\n if (cols.length > 0) {\n const firstCol = { ...cols[0] };\n const originalRenderer = firstCol.viewRenderer;\n\n // Skip if already wrapped by this plugin (prevents double-wrapping on re-render)\n if ((originalRenderer as any)?.__masterDetailWrapped) {\n return cols;\n }\n\n const wrappedRenderer = (renderCtx: Parameters<NonNullable<typeof originalRenderer>>[0]) => {\n const { value, row } = renderCtx;\n const isExpanded = this.expandedRows.has(row);\n\n const container = document.createElement('span');\n container.className = 'master-detail-cell-wrapper';\n\n // Expand/collapse toggle icon\n const toggle = document.createElement('span');\n toggle.className = `master-detail-toggle${isExpanded ? ' expanded' : ''}`;\n // Use grid-level icons (fall back to defaults)\n this.setIcon(toggle, this.resolveIcon(isExpanded ? 'collapse' : 'expand'));\n // role=\"button\" is required for aria-expanded to be valid\n toggle.setAttribute('role', 'button');\n toggle.setAttribute('tabindex', '0');\n toggle.setAttribute('aria-expanded', String(isExpanded));\n toggle.setAttribute('aria-label', isExpanded ? 'Collapse details' : 'Expand details');\n toggle.addEventListener('click', (e) => {\n e.stopPropagation();\n const rowIndex = this.rows.indexOf(row);\n this.expandedRows = toggleDetailRow(this.expandedRows, row);\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex,\n row,\n expanded: this.expandedRows.has(row),\n });\n this.requestRender();\n });\n container.appendChild(toggle);\n\n // Cell content\n const content = document.createElement('span');\n if (originalRenderer) {\n const rendered = originalRenderer(renderCtx);\n if (rendered instanceof Node) {\n content.appendChild(rendered);\n } else {\n content.textContent = String(rendered ?? value ?? '');\n }\n } else {\n content.textContent = String(value ?? '');\n }\n container.appendChild(content);\n\n return container;\n };\n\n // Mark renderer as wrapped to prevent double-wrapping\n (wrappedRenderer as any).__masterDetailWrapped = true;\n firstCol.viewRenderer = wrappedRenderer;\n\n cols[0] = firstCol;\n }\n\n return cols;\n }\n\n override onRowClick(event: RowClickEvent): boolean | void {\n if (!this.config.expandOnRowClick || !this.config.detailRenderer) return;\n\n this.expandedRows = toggleDetailRow(this.expandedRows, event.row as object);\n\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex: event.rowIndex,\n row: event.row,\n expanded: this.expandedRows.has(event.row as object),\n });\n\n this.requestRender();\n return false;\n }\n\n override onCellClick(): boolean | void {\n // Sync detail rows after cell click triggers refreshVirtualWindow\n // This runs in microtask to ensure DOM updates are complete\n if (this.expandedRows.size > 0) {\n queueMicrotask(() => this.#syncDetailRows());\n }\n return; // Don't prevent default\n }\n\n override afterRender(): void {\n this.#syncDetailRows();\n }\n\n /**\n * Called on scroll to sync detail elements with visible rows.\n * Removes details for rows that scrolled out of view and reattaches for visible rows.\n */\n override onScrollRender(): void {\n if (!this.config.detailRenderer || this.expandedRows.size === 0) return;\n // Full sync needed on scroll to clean up orphaned details\n this.#syncDetailRows();\n }\n\n /**\n * Full sync of detail rows - cleans up stale elements and creates new ones.\n * Detail rows are inserted as siblings AFTER their master row to survive row rebuilds.\n */\n #syncDetailRows(): void {\n if (!this.config.detailRenderer) return;\n\n const body = this.shadowRoot?.querySelector('.rows');\n if (!body) return;\n\n // Build a map of row index -> row element for visible rows\n const visibleRowMap = new Map<number, Element>();\n const dataRows = body.querySelectorAll('.data-grid-row');\n const columnCount = this.columns.length;\n\n for (const rowEl of dataRows) {\n const firstCell = rowEl.querySelector('.cell[data-row]');\n const rowIndex = firstCell ? parseInt(firstCell.getAttribute('data-row') ?? '-1', 10) : -1;\n if (rowIndex >= 0) {\n visibleRowMap.set(rowIndex, rowEl);\n }\n }\n\n // Remove detail rows whose parent row is no longer visible or no longer expanded\n const existingDetails = body.querySelectorAll('.master-detail-row');\n for (const detailEl of existingDetails) {\n const forIndex = parseInt(detailEl.getAttribute('data-detail-for') ?? '-1', 10);\n const row = forIndex >= 0 ? this.rows[forIndex] : undefined;\n const isStillExpanded = row && this.expandedRows.has(row);\n const isRowVisible = visibleRowMap.has(forIndex);\n\n // Remove detail if not expanded or if parent row scrolled out\n if (!isStillExpanded || !isRowVisible) {\n detailEl.remove();\n if (row) this.detailElements.delete(row);\n }\n }\n\n // Insert detail rows for expanded rows that are visible\n for (const [rowIndex, rowEl] of visibleRowMap) {\n const row = this.rows[rowIndex];\n if (!row || !this.expandedRows.has(row)) continue;\n\n // Check if detail already exists for this row\n const existingDetail = this.detailElements.get(row);\n if (existingDetail) {\n // Ensure it's positioned correctly (as next sibling of row element)\n if (existingDetail.previousElementSibling !== rowEl) {\n rowEl.after(existingDetail);\n }\n continue;\n }\n\n // Create new detail element\n const detailEl = createDetailElement(row, rowIndex, this.config.detailRenderer, columnCount);\n\n if (typeof this.config.detailHeight === 'number') {\n detailEl.style.height = `${this.config.detailHeight}px`;\n }\n\n // Insert as sibling after the row element (not as child)\n rowEl.after(detailEl);\n this.detailElements.set(row, detailEl);\n\n // Apply expand animation\n this.animateExpand(detailEl);\n }\n }\n\n /**\n * Return total extra height from all expanded detail rows.\n * Used by grid virtualization to adjust scrollbar height.\n */\n override getExtraHeight(): number {\n let totalHeight = 0;\n for (const row of this.expandedRows) {\n const detailEl = this.detailElements.get(row);\n if (detailEl) {\n totalHeight += detailEl.offsetHeight;\n } else {\n // Detail not yet rendered - estimate based on config or default\n const configHeight = this.config?.detailHeight;\n totalHeight += typeof configHeight === 'number' ? configHeight : 150;\n }\n }\n return totalHeight;\n }\n\n /**\n * Return extra height that appears before a given row index.\n * This is the sum of heights of all expanded details whose parent row is before the given index.\n */\n override getExtraHeightBefore(beforeRowIndex: number): number {\n let totalHeight = 0;\n for (const row of this.expandedRows) {\n const rowIndex = this.rows.indexOf(row);\n // Include detail if it's for a row before the given index\n if (rowIndex >= 0 && rowIndex < beforeRowIndex) {\n const detailEl = this.detailElements.get(row);\n if (detailEl) {\n totalHeight += detailEl.offsetHeight;\n } else {\n const configHeight = this.config?.detailHeight;\n totalHeight += typeof configHeight === 'number' ? configHeight : 150;\n }\n }\n }\n return totalHeight;\n }\n\n /**\n * Adjust the virtualization start index to keep expanded row visible while its detail is visible.\n * This ensures the detail scrolls smoothly out of view instead of disappearing abruptly.\n */\n override adjustVirtualStart(start: number, scrollTop: number, rowHeight: number): number {\n if (this.expandedRows.size === 0) return start;\n\n // Build sorted list of expanded row indices for cumulative height calculation\n const expandedIndices: Array<{ index: number; row: any }> = [];\n for (const row of this.expandedRows) {\n const index = this.rows.indexOf(row);\n if (index >= 0) {\n expandedIndices.push({ index, row });\n }\n }\n expandedIndices.sort((a, b) => a.index - b.index);\n\n let minStart = start;\n\n // Calculate actual scroll position for each expanded row,\n // accounting for cumulative detail heights before it\n let cumulativeExtraHeight = 0;\n\n for (const { index: rowIndex, row } of expandedIndices) {\n // Actual position includes all detail heights before this row\n const actualRowTop = rowIndex * rowHeight + cumulativeExtraHeight;\n const detailEl = this.detailElements.get(row);\n const detailHeight =\n detailEl?.offsetHeight ?? (typeof this.config?.detailHeight === 'number' ? this.config.detailHeight : 150);\n const actualDetailBottom = actualRowTop + rowHeight + detailHeight;\n\n // Update cumulative height for next iteration\n cumulativeExtraHeight += detailHeight;\n\n // Skip rows that are at or after the calculated start\n if (rowIndex >= start) continue;\n\n // If any part of the detail is still visible (below the scroll position),\n // we need to keep the parent row in the render range\n if (actualDetailBottom > scrollTop) {\n if (rowIndex < minStart) {\n minStart = rowIndex;\n }\n }\n }\n\n return minStart;\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Expand the detail row at the given index.\n * @param rowIndex - Index of the row to expand\n */\n expand(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = expandDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Collapse the detail row at the given index.\n * @param rowIndex - Index of the row to collapse\n */\n collapse(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = collapseDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Toggle the detail row at the given index.\n * @param rowIndex - Index of the row to toggle\n */\n toggle(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = toggleDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Check if the detail row at the given index is expanded.\n * @param rowIndex - Index of the row to check\n * @returns Whether the detail row is expanded\n */\n isExpanded(rowIndex: number): boolean {\n const row = this.rows[rowIndex];\n return row ? isDetailExpanded(this.expandedRows, row) : false;\n }\n\n /**\n * Expand all detail rows.\n */\n expandAll(): void {\n for (const row of this.rows) {\n this.expandedRows.add(row);\n }\n this.requestRender();\n }\n\n /**\n * Collapse all detail rows.\n */\n collapseAll(): void {\n this.expandedRows.clear();\n this.requestRender();\n }\n\n /**\n * Get the indices of all expanded rows.\n * @returns Array of row indices that are expanded\n */\n getExpandedRows(): number[] {\n const indices: number[] = [];\n for (const row of this.expandedRows) {\n const idx = this.rows.indexOf(row);\n if (idx >= 0) indices.push(idx);\n }\n return indices;\n }\n\n /**\n * Get the detail element for a specific row.\n * @param rowIndex - Index of the row\n * @returns The detail HTMLElement or undefined\n */\n getDetailElement(rowIndex: number): HTMLElement | undefined {\n const row = this.rows[rowIndex];\n return row ? this.detailElements.get(row) : undefined;\n }\n\n /**\n * Re-parse light DOM to refresh the detail renderer.\n * Call this after framework templates are registered (e.g., Angular ngAfterContentInit).\n *\n * This allows frameworks to register templates asynchronously and then\n * update the plugin's detailRenderer.\n */\n refreshDetailRenderer(): void {\n // Force re-parse by temporarily clearing the renderer\n const currentRenderer = this.config.detailRenderer;\n this.config = { ...this.config, detailRenderer: undefined };\n this.parseLightDomDetail();\n\n // If no new renderer was found, restore the original\n if (!this.config.detailRenderer && currentRenderer) {\n this.config = { ...this.config, detailRenderer: currentRenderer };\n }\n }\n // #endregion\n\n // #region Styles\n override readonly styles = styles;\n // #endregion\n}\n"],"names":["toggleDetailRow","expandedRows","row","newExpanded","expandDetailRow","collapseDetailRow","isDetailExpanded","createDetailElement","rowIndex","renderer","columnCount","detailRow","detailCell","content","MasterDetailPlugin","BaseGridPlugin","grid","gridEl","detailEl","gridWithAdapter","adapterRenderer","animation","showExpandColumn","expandOnRowClick","collapseOnClickOutside","heightAttr","configUpdates","templateHTML","_rowIndex","evaluated","evalTemplateString","sanitizeHTML","mode","host","durationStr","parsed","onComplete","cleanup","columns","cols","firstCol","originalRenderer","wrappedRenderer","renderCtx","value","isExpanded","container","toggle","e","rendered","event","#syncDetailRows","body","visibleRowMap","dataRows","rowEl","firstCell","existingDetails","forIndex","isStillExpanded","isRowVisible","existingDetail","totalHeight","configHeight","beforeRowIndex","start","scrollTop","rowHeight","expandedIndices","index","a","b","minStart","cumulativeExtraHeight","actualRowTop","detailHeight","actualDetailBottom","indices","idx","currentRenderer","styles"],"mappings":"2ZAaO,SAASA,EAAgBC,EAA2BC,EAA0B,CACnF,MAAMC,EAAc,IAAI,IAAIF,CAAY,EACxC,OAAIE,EAAY,IAAID,CAAG,EACrBC,EAAY,OAAOD,CAAG,EAEtBC,EAAY,IAAID,CAAG,EAEdC,CACT,CAMO,SAASC,EAAgBH,EAA2BC,EAA0B,CACnF,MAAMC,EAAc,IAAI,IAAIF,CAAY,EACxC,OAAAE,EAAY,IAAID,CAAG,EACZC,CACT,CAMO,SAASE,EAAkBJ,EAA2BC,EAA0B,CACrF,MAAMC,EAAc,IAAI,IAAIF,CAAY,EACxC,OAAAE,EAAY,OAAOD,CAAG,EACfC,CACT,CAKO,SAASG,EAAiBL,EAA2BC,EAAsB,CAChF,OAAOD,EAAa,IAAIC,CAAG,CAC7B,CAMO,SAASK,EACdL,EACAM,EACAC,EACAC,EACa,CACb,MAAMC,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,UAAY,oBACtBA,EAAU,aAAa,kBAAmB,OAAOH,CAAQ,CAAC,EAC1DG,EAAU,aAAa,OAAQ,KAAK,EAEpC,MAAMC,EAAa,SAAS,cAAc,KAAK,EAC/CA,EAAW,UAAY,qBACvBA,EAAW,aAAa,OAAQ,MAAM,EACtCA,EAAW,MAAM,WAAa,OAAOF,EAAc,CAAC,GAEpD,MAAMG,EAAUJ,EAASP,EAAKM,CAAQ,EACtC,OAAI,OAAOK,GAAY,SACrBD,EAAW,UAAYC,EACdA,aAAmB,aAC5BD,EAAW,YAAYC,CAAO,EAGhCF,EAAU,YAAYC,CAAU,EACzBD,CACT,qiCCxCO,MAAMG,UAA2BC,EAAAA,cAAmC,CAChE,KAAO,eACE,QAAU,QAE5B,IAAuB,eAA6C,CAClE,MAAO,CACL,aAAc,OACd,iBAAkB,GAClB,uBAAwB,GACxB,iBAAkB,GAClB,UAAW,OAAA,CAEf,CAQS,OAAOC,EAAyB,CACvC,MAAM,OAAOA,CAAI,EACjB,KAAK,oBAAA,CACP,CAwBQ,qBAA4B,CAClC,MAAMC,EAAS,KAAK,KACpB,GAAI,CAACA,GAAU,OAAOA,EAAO,eAAkB,WAAY,OAE3D,MAAMC,EAAWD,EAAO,cAAc,iBAAiB,EACvD,GAAI,CAACC,EAAU,OAIf,MAAMC,EAAkBF,EAKxB,GAAIE,EAAgB,oBAAoB,mBAAoB,CAC1D,MAAMC,EAAkBD,EAAgB,mBAAmB,mBAAmBD,CAAQ,EACtF,GAAIE,EAAiB,CACnB,KAAK,OAAS,CAAE,GAAG,KAAK,OAAQ,eAAgBA,CAAA,EAChD,MACF,CACF,CAGA,MAAMC,EAAYH,EAAS,aAAa,WAAW,EAC7CI,EAAmBJ,EAAS,aAAa,oBAAoB,EAC7DK,EAAmBL,EAAS,aAAa,qBAAqB,EAC9DM,EAAyBN,EAAS,aAAa,2BAA2B,EAC1EO,EAAaP,EAAS,aAAa,QAAQ,EAE3CQ,EAA6C,CAAA,EAE/CL,IAAc,OAChBK,EAAc,UAAYL,IAAc,QAAU,GAASA,GAEzDC,IAAqB,OACvBI,EAAc,iBAAmBJ,IAAqB,SAEpDC,IAAqB,OACvBG,EAAc,iBAAmBH,IAAqB,QAEpDC,IAA2B,OAC7BE,EAAc,uBAAyBF,IAA2B,QAEhEC,IAAe,OACjBC,EAAc,aAAeD,IAAe,OAAS,OAAS,SAASA,EAAY,EAAE,GAIvF,MAAME,EAAeT,EAAS,UAAU,KAAA,EACpCS,GAAgB,CAAC,KAAK,OAAO,iBAE/BD,EAAc,eAAiB,CAACxB,EAAU0B,IAA8B,CAEtE,MAAMC,EAAYC,EAAAA,mBAAmBH,EAAc,CAAE,MAAOzB,EAAK,IAAAA,EAAK,EAEtE,OAAO6B,EAAAA,aAAaF,CAAS,CAC/B,GAIE,OAAO,KAAKH,CAAa,EAAE,OAAS,IACtC,KAAK,OAAS,CAAE,GAAG,KAAK,OAAQ,GAAGA,CAAA,EAEvC,CAUA,IAAY,oBAA8B,CAExC,MAAMM,EADS,KAAK,KACA,iBAAiB,WAAW,MAAQ,iBAExD,GAAIA,IAAS,IAASA,IAAS,MAAO,MAAO,GAC7C,GAAIA,IAAS,IAAQA,IAAS,KAAM,MAAO,GAG3C,MAAMC,EAAO,KAAK,YAAY,KAC9B,OAAIA,EACc,iBAAiBA,CAAI,EAAE,iBAAiB,yBAAyB,EAAE,KAAA,IAChE,IAEd,EACT,CAKA,IAAY,gBAA0C,CACpD,OAAK,KAAK,mBACH,KAAK,OAAO,WAAa,QADK,EAEvC,CAKA,IAAY,mBAA4B,CACtC,MAAMA,EAAO,KAAK,YAAY,KAC9B,GAAIA,EAAM,CACR,MAAMC,EAAc,iBAAiBD,CAAI,EAAE,iBAAiB,0BAA0B,EAAE,KAAA,EAClFE,EAAS,SAASD,EAAa,EAAE,EACvC,GAAI,CAAC,MAAMC,CAAM,EAAG,OAAOA,CAC7B,CACA,MAAO,IACT,CAKQ,cAAcjB,EAA6B,CAC7C,CAAC,KAAK,oBAAsB,KAAK,iBAAmB,KAExDA,EAAS,UAAU,IAAI,eAAe,EACtCA,EAAS,iBACP,eACA,IAAM,CACJA,EAAS,UAAU,OAAO,eAAe,CAC3C,EACA,CAAE,KAAM,EAAA,CAAK,EAEjB,CAKQ,gBAAgBA,EAAuBkB,EAA8B,CAC3E,GAAI,CAAC,KAAK,oBAAsB,KAAK,iBAAmB,GAAO,CAC7DA,EAAA,EACA,MACF,CAEAlB,EAAS,UAAU,IAAI,gBAAgB,EACvC,MAAMmB,EAAU,IAAM,CACpBnB,EAAS,UAAU,OAAO,gBAAgB,EAC1CkB,EAAA,CACF,EACAlB,EAAS,iBAAiB,eAAgBmB,EAAS,CAAE,KAAM,GAAM,EAEjE,WAAWA,EAAS,KAAK,kBAAoB,EAAE,CACjD,CAKQ,iBAA6B,IAC7B,mBAA4C,IAK3C,QAAe,CACtB,KAAK,aAAa,MAAA,EAClB,KAAK,eAAe,MAAA,CACtB,CAKS,eAAeC,EAAkD,CACxE,GAAI,CAAC,KAAK,OAAO,eACf,MAAO,CAAC,GAAGA,CAAO,EAIpB,MAAMC,EAAO,CAAC,GAAGD,CAAO,EACxB,GAAIC,EAAK,OAAS,EAAG,CACnB,MAAMC,EAAW,CAAE,GAAGD,EAAK,CAAC,CAAA,EACtBE,EAAmBD,EAAS,aAGlC,GAAKC,GAA0B,sBAC7B,OAAOF,EAGT,MAAMG,EAAmBC,GAAmE,CAC1F,KAAM,CAAE,MAAAC,EAAO,IAAA1C,CAAA,EAAQyC,EACjBE,EAAa,KAAK,aAAa,IAAI3C,CAAG,EAEtC4C,EAAY,SAAS,cAAc,MAAM,EAC/CA,EAAU,UAAY,6BAGtB,MAAMC,EAAS,SAAS,cAAc,MAAM,EAC5CA,EAAO,UAAY,uBAAuBF,EAAa,YAAc,EAAE,GAEvE,KAAK,QAAQE,EAAQ,KAAK,YAAYF,EAAa,WAAa,QAAQ,CAAC,EAEzEE,EAAO,aAAa,OAAQ,QAAQ,EACpCA,EAAO,aAAa,WAAY,GAAG,EACnCA,EAAO,aAAa,gBAAiB,OAAOF,CAAU,CAAC,EACvDE,EAAO,aAAa,aAAcF,EAAa,mBAAqB,gBAAgB,EACpFE,EAAO,iBAAiB,QAAUC,GAAM,CACtCA,EAAE,gBAAA,EACF,MAAMxC,EAAW,KAAK,KAAK,QAAQN,CAAG,EACtC,KAAK,aAAeF,EAAgB,KAAK,aAAcE,CAAG,EAC1D,KAAK,KAAyB,gBAAiB,CAC7C,SAAAM,EACA,IAAAN,EACA,SAAU,KAAK,aAAa,IAAIA,CAAG,CAAA,CACpC,EACD,KAAK,cAAA,CACP,CAAC,EACD4C,EAAU,YAAYC,CAAM,EAG5B,MAAMlC,EAAU,SAAS,cAAc,MAAM,EAC7C,GAAI4B,EAAkB,CACpB,MAAMQ,EAAWR,EAAiBE,CAAS,EACvCM,aAAoB,KACtBpC,EAAQ,YAAYoC,CAAQ,EAE5BpC,EAAQ,YAAc,OAAOoC,GAAYL,GAAS,EAAE,CAExD,MACE/B,EAAQ,YAAc,OAAO+B,GAAS,EAAE,EAE1C,OAAAE,EAAU,YAAYjC,CAAO,EAEtBiC,CACT,EAGCJ,EAAwB,sBAAwB,GACjDF,EAAS,aAAeE,EAExBH,EAAK,CAAC,EAAIC,CACZ,CAEA,OAAOD,CACT,CAES,WAAWW,EAAsC,CACxD,GAAI,GAAC,KAAK,OAAO,kBAAoB,CAAC,KAAK,OAAO,gBAElD,YAAK,aAAelD,EAAgB,KAAK,aAAckD,EAAM,GAAa,EAE1E,KAAK,KAAyB,gBAAiB,CAC7C,SAAUA,EAAM,SAChB,IAAKA,EAAM,IACX,SAAU,KAAK,aAAa,IAAIA,EAAM,GAAa,CAAA,CACpD,EAED,KAAK,cAAA,EACE,EACT,CAES,aAA8B,CAGjC,KAAK,aAAa,KAAO,GAC3B,eAAe,IAAM,KAAKC,IAAiB,CAG/C,CAES,aAAoB,CAC3B,KAAKA,GAAA,CACP,CAMS,gBAAuB,CAC1B,CAAC,KAAK,OAAO,gBAAkB,KAAK,aAAa,OAAS,GAE9D,KAAKA,GAAA,CACP,CAMAA,IAAwB,CACtB,GAAI,CAAC,KAAK,OAAO,eAAgB,OAEjC,MAAMC,EAAO,KAAK,YAAY,cAAc,OAAO,EACnD,GAAI,CAACA,EAAM,OAGX,MAAMC,MAAoB,IACpBC,EAAWF,EAAK,iBAAiB,gBAAgB,EACjD1C,EAAc,KAAK,QAAQ,OAEjC,UAAW6C,KAASD,EAAU,CAC5B,MAAME,EAAYD,EAAM,cAAc,iBAAiB,EACjD/C,EAAWgD,EAAY,SAASA,EAAU,aAAa,UAAU,GAAK,KAAM,EAAE,EAAI,GACpFhD,GAAY,GACd6C,EAAc,IAAI7C,EAAU+C,CAAK,CAErC,CAGA,MAAME,EAAkBL,EAAK,iBAAiB,oBAAoB,EAClE,UAAWlC,KAAYuC,EAAiB,CACtC,MAAMC,EAAW,SAASxC,EAAS,aAAa,iBAAiB,GAAK,KAAM,EAAE,EACxEhB,EAAMwD,GAAY,EAAI,KAAK,KAAKA,CAAQ,EAAI,OAC5CC,EAAkBzD,GAAO,KAAK,aAAa,IAAIA,CAAG,EAClD0D,EAAeP,EAAc,IAAIK,CAAQ,GAG3C,CAACC,GAAmB,CAACC,KACvB1C,EAAS,OAAA,EACLhB,GAAK,KAAK,eAAe,OAAOA,CAAG,EAE3C,CAGA,SAAW,CAACM,EAAU+C,CAAK,IAAKF,EAAe,CAC7C,MAAMnD,EAAM,KAAK,KAAKM,CAAQ,EAC9B,GAAI,CAACN,GAAO,CAAC,KAAK,aAAa,IAAIA,CAAG,EAAG,SAGzC,MAAM2D,EAAiB,KAAK,eAAe,IAAI3D,CAAG,EAClD,GAAI2D,EAAgB,CAEdA,EAAe,yBAA2BN,GAC5CA,EAAM,MAAMM,CAAc,EAE5B,QACF,CAGA,MAAM3C,EAAWX,EAAoBL,EAAKM,EAAU,KAAK,OAAO,eAAgBE,CAAW,EAEvF,OAAO,KAAK,OAAO,cAAiB,WACtCQ,EAAS,MAAM,OAAS,GAAG,KAAK,OAAO,YAAY,MAIrDqC,EAAM,MAAMrC,CAAQ,EACpB,KAAK,eAAe,IAAIhB,EAAKgB,CAAQ,EAGrC,KAAK,cAAcA,CAAQ,CAC7B,CACF,CAMS,gBAAyB,CAChC,IAAI4C,EAAc,EAClB,UAAW5D,KAAO,KAAK,aAAc,CACnC,MAAMgB,EAAW,KAAK,eAAe,IAAIhB,CAAG,EAC5C,GAAIgB,EACF4C,GAAe5C,EAAS,iBACnB,CAEL,MAAM6C,EAAe,KAAK,QAAQ,aAClCD,GAAe,OAAOC,GAAiB,SAAWA,EAAe,GACnE,CACF,CACA,OAAOD,CACT,CAMS,qBAAqBE,EAAgC,CAC5D,IAAIF,EAAc,EAClB,UAAW5D,KAAO,KAAK,aAAc,CACnC,MAAMM,EAAW,KAAK,KAAK,QAAQN,CAAG,EAEtC,GAAIM,GAAY,GAAKA,EAAWwD,EAAgB,CAC9C,MAAM9C,EAAW,KAAK,eAAe,IAAIhB,CAAG,EAC5C,GAAIgB,EACF4C,GAAe5C,EAAS,iBACnB,CACL,MAAM6C,EAAe,KAAK,QAAQ,aAClCD,GAAe,OAAOC,GAAiB,SAAWA,EAAe,GACnE,CACF,CACF,CACA,OAAOD,CACT,CAMS,mBAAmBG,EAAeC,EAAmBC,EAA2B,CACvF,GAAI,KAAK,aAAa,OAAS,EAAG,OAAOF,EAGzC,MAAMG,EAAsD,CAAA,EAC5D,UAAWlE,KAAO,KAAK,aAAc,CACnC,MAAMmE,EAAQ,KAAK,KAAK,QAAQnE,CAAG,EAC/BmE,GAAS,GACXD,EAAgB,KAAK,CAAE,MAAAC,EAAO,IAAAnE,CAAA,CAAK,CAEvC,CACAkE,EAAgB,KAAK,CAACE,EAAGC,IAAMD,EAAE,MAAQC,EAAE,KAAK,EAEhD,IAAIC,EAAWP,EAIXQ,EAAwB,EAE5B,SAAW,CAAE,MAAOjE,EAAU,IAAAN,CAAA,IAASkE,EAAiB,CAEtD,MAAMM,EAAelE,EAAW2D,EAAYM,EAEtCE,EADW,KAAK,eAAe,IAAIzE,CAAG,GAEhC,eAAiB,OAAO,KAAK,QAAQ,cAAiB,SAAW,KAAK,OAAO,aAAe,KAClG0E,EAAqBF,EAAeP,EAAYQ,EAGtDF,GAAyBE,EAGrB,EAAAnE,GAAYyD,IAIZW,EAAqBV,GACnB1D,EAAWgE,IACbA,EAAWhE,EAGjB,CAEA,OAAOgE,CACT,CASA,OAAOhE,EAAwB,CAC7B,MAAMN,EAAM,KAAK,KAAKM,CAAQ,EAC1BN,IACF,KAAK,aAAeE,EAAgB,KAAK,aAAcF,CAAG,EAC1D,KAAK,cAAA,EAET,CAMA,SAASM,EAAwB,CAC/B,MAAMN,EAAM,KAAK,KAAKM,CAAQ,EAC1BN,IACF,KAAK,aAAeG,EAAkB,KAAK,aAAcH,CAAG,EAC5D,KAAK,cAAA,EAET,CAMA,OAAOM,EAAwB,CAC7B,MAAMN,EAAM,KAAK,KAAKM,CAAQ,EAC1BN,IACF,KAAK,aAAeF,EAAgB,KAAK,aAAcE,CAAG,EAC1D,KAAK,cAAA,EAET,CAOA,WAAWM,EAA2B,CACpC,MAAMN,EAAM,KAAK,KAAKM,CAAQ,EAC9B,OAAON,EAAMI,EAAiB,KAAK,aAAcJ,CAAG,EAAI,EAC1D,CAKA,WAAkB,CAChB,UAAWA,KAAO,KAAK,KACrB,KAAK,aAAa,IAAIA,CAAG,EAE3B,KAAK,cAAA,CACP,CAKA,aAAoB,CAClB,KAAK,aAAa,MAAA,EAClB,KAAK,cAAA,CACP,CAMA,iBAA4B,CAC1B,MAAM2E,EAAoB,CAAA,EAC1B,UAAW3E,KAAO,KAAK,aAAc,CACnC,MAAM4E,EAAM,KAAK,KAAK,QAAQ5E,CAAG,EAC7B4E,GAAO,GAAGD,EAAQ,KAAKC,CAAG,CAChC,CACA,OAAOD,CACT,CAOA,iBAAiBrE,EAA2C,CAC1D,MAAMN,EAAM,KAAK,KAAKM,CAAQ,EAC9B,OAAON,EAAM,KAAK,eAAe,IAAIA,CAAG,EAAI,MAC9C,CASA,uBAA8B,CAE5B,MAAM6E,EAAkB,KAAK,OAAO,eACpC,KAAK,OAAS,CAAE,GAAG,KAAK,OAAQ,eAAgB,MAAA,EAChD,KAAK,oBAAA,EAGD,CAAC,KAAK,OAAO,gBAAkBA,IACjC,KAAK,OAAS,CAAE,GAAG,KAAK,OAAQ,eAAgBA,CAAA,EAEpD,CAIkB,OAASC,CAE7B"}
1
+ {"version":3,"file":"master-detail.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/master-detail/master-detail.ts","../../../../../libs/grid/src/lib/plugins/master-detail/MasterDetailPlugin.ts"],"sourcesContent":["/**\n * Master/Detail Core Logic\n *\n * Pure functions for managing detail row expansion state.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// Uses `any` for maximum flexibility with user-defined row types.\n\n/**\n * Toggle the expansion state of a detail row.\n * Returns a new Set with the updated state.\n */\nexport function toggleDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n if (newExpanded.has(row)) {\n newExpanded.delete(row);\n } else {\n newExpanded.add(row);\n }\n return newExpanded;\n}\n\n/**\n * Expand a detail row.\n * Returns a new Set with the row added.\n */\nexport function expandDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.add(row);\n return newExpanded;\n}\n\n/**\n * Collapse a detail row.\n * Returns a new Set with the row removed.\n */\nexport function collapseDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.delete(row);\n return newExpanded;\n}\n\n/**\n * Check if a detail row is expanded.\n */\nexport function isDetailExpanded(expandedRows: Set<object>, row: object): boolean {\n return expandedRows.has(row);\n}\n\n/**\n * Create a detail element for a given row.\n * The element spans all columns and contains the rendered content.\n */\nexport function createDetailElement(\n row: any,\n rowIndex: number,\n renderer: (row: any, rowIndex: number) => HTMLElement | string,\n columnCount: number\n): HTMLElement {\n const detailRow = document.createElement('div');\n detailRow.className = 'master-detail-row';\n detailRow.setAttribute('data-detail-for', String(rowIndex));\n detailRow.setAttribute('role', 'row');\n\n const detailCell = document.createElement('div');\n detailCell.className = 'master-detail-cell';\n detailCell.setAttribute('role', 'cell');\n detailCell.style.gridColumn = `1 / ${columnCount + 1}`;\n\n const content = renderer(row, rowIndex);\n if (typeof content === 'string') {\n detailCell.innerHTML = content;\n } else if (content instanceof HTMLElement) {\n detailCell.appendChild(content);\n }\n\n detailRow.appendChild(detailCell);\n return detailRow;\n}\n","/**\n * Master/Detail Plugin (Class-based)\n *\n * Enables expandable detail rows showing additional content for each row.\n * Animation style is plugin-configured; respects grid-level animation.mode.\n */\n\nimport { evalTemplateString, sanitizeHTML } from '../../core/internal/sanitize';\nimport { BaseGridPlugin, CellClickEvent, GridElement, RowClickEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, GridConfig } from '../../core/types';\nimport {\n collapseDetailRow,\n createDetailElement,\n expandDetailRow,\n isDetailExpanded,\n toggleDetailRow,\n} from './master-detail';\nimport styles from './master-detail.css?inline';\nimport type {\n DetailExpandDetail,\n ExpandCollapseAnimation,\n MasterDetailConfig,\n MasterDetailWrappedRenderer,\n} from './types';\n\n/** Extended grid interface for accessing effective config */\ninterface GridWithConfig {\n effectiveConfig?: GridConfig;\n}\n\n/**\n * Master/Detail Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new MasterDetailPlugin({\n * enabled: true,\n * detailRenderer: (row) => `<div>Details for ${row.name}</div>`,\n * expandOnRowClick: true,\n * })\n * ```\n */\nexport class MasterDetailPlugin extends BaseGridPlugin<MasterDetailConfig> {\n readonly name = 'masterDetail';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<MasterDetailConfig> {\n return {\n detailHeight: 'auto',\n expandOnRowClick: false,\n collapseOnClickOutside: false,\n showExpandColumn: true,\n animation: 'slide', // Plugin's own default\n };\n }\n\n // #region Light DOM Parsing\n\n /**\n * Called when plugin is attached to the grid.\n * Parses light DOM for `<tbw-grid-detail>` elements to configure detail templates.\n */\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.parseLightDomDetail();\n }\n\n /**\n * Parse `<tbw-grid-detail>` elements from the grid's light DOM.\n *\n * Allows declarative configuration:\n * ```html\n * <tbw-grid [rows]=\"data\">\n * <tbw-grid-detail>\n * <div class=\"detail-content\">\n * <p>Name: {{ row.name }}</p>\n * <p>Email: {{ row.email }}</p>\n * </div>\n * </tbw-grid-detail>\n * </tbw-grid>\n * ```\n *\n * Attributes:\n * - `animation`: 'slide' | 'fade' | 'false' (default: 'slide')\n * - `show-expand-column`: 'true' | 'false' (default: 'true')\n * - `expand-on-row-click`: 'true' | 'false' (default: 'false')\n * - `collapse-on-click-outside`: 'true' | 'false' (default: 'false')\n * - `height`: number (pixels) or 'auto' (default: 'auto')\n */\n private parseLightDomDetail(): void {\n const gridEl = this.grid as unknown as Element;\n if (!gridEl || typeof gridEl.querySelector !== 'function') return;\n\n const detailEl = gridEl.querySelector('tbw-grid-detail');\n if (!detailEl) return;\n\n // Check if a framework adapter wants to handle this element\n // (e.g., Angular adapter intercepts for ng-template rendering)\n const gridWithAdapter = gridEl as unknown as {\n __frameworkAdapter?: {\n parseDetailElement?: (el: Element) => ((row: any, rowIndex: number) => HTMLElement | string) | undefined;\n };\n };\n if (gridWithAdapter.__frameworkAdapter?.parseDetailElement) {\n const adapterRenderer = gridWithAdapter.__frameworkAdapter.parseDetailElement(detailEl);\n if (adapterRenderer) {\n this.config = { ...this.config, detailRenderer: adapterRenderer };\n return;\n }\n }\n\n // Parse attributes for configuration\n const animation = detailEl.getAttribute('animation');\n const showExpandColumn = detailEl.getAttribute('show-expand-column');\n const expandOnRowClick = detailEl.getAttribute('expand-on-row-click');\n const collapseOnClickOutside = detailEl.getAttribute('collapse-on-click-outside');\n const heightAttr = detailEl.getAttribute('height');\n\n const configUpdates: Partial<MasterDetailConfig> = {};\n\n if (animation !== null) {\n configUpdates.animation = animation === 'false' ? false : (animation as 'slide' | 'fade');\n }\n if (showExpandColumn !== null) {\n configUpdates.showExpandColumn = showExpandColumn !== 'false';\n }\n if (expandOnRowClick !== null) {\n configUpdates.expandOnRowClick = expandOnRowClick === 'true';\n }\n if (collapseOnClickOutside !== null) {\n configUpdates.collapseOnClickOutside = collapseOnClickOutside === 'true';\n }\n if (heightAttr !== null) {\n configUpdates.detailHeight = heightAttr === 'auto' ? 'auto' : parseInt(heightAttr, 10);\n }\n\n // Get template content from innerHTML\n const templateHTML = detailEl.innerHTML.trim();\n if (templateHTML && !this.config.detailRenderer) {\n // Create a template-based renderer using the inner HTML\n configUpdates.detailRenderer = (row: any, _rowIndex: number): string => {\n // Evaluate template expressions like {{ row.field }}\n const evaluated = evalTemplateString(templateHTML, { value: row, row });\n // Sanitize the result to prevent XSS\n return sanitizeHTML(evaluated);\n };\n }\n\n // Merge updates into config\n if (Object.keys(configUpdates).length > 0) {\n this.config = { ...this.config, ...configUpdates };\n }\n }\n\n // #endregion\n\n // #region Animation Helpers\n\n /**\n * Check if animations are enabled at the grid level.\n * Respects gridConfig.animation.mode and CSS variable.\n */\n private get isAnimationEnabled(): boolean {\n const gridEl = this.grid as unknown as GridWithConfig;\n const mode = gridEl.effectiveConfig?.animation?.mode ?? 'reduced-motion';\n\n if (mode === false || mode === 'off') return false;\n if (mode === true || mode === 'on') return true;\n\n // reduced-motion: check CSS variable\n const host = this.shadowRoot?.host as HTMLElement | undefined;\n if (host) {\n const enabled = getComputedStyle(host).getPropertyValue('--tbw-animation-enabled').trim();\n return enabled !== '0';\n }\n return true;\n }\n\n /**\n * Get expand/collapse animation style from plugin config.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide'; // Plugin default\n }\n\n /**\n * Get animation duration from CSS variable (set by grid).\n */\n private get animationDuration(): number {\n const host = this.shadowRoot?.host as HTMLElement | undefined;\n if (host) {\n const durationStr = getComputedStyle(host).getPropertyValue('--tbw-animation-duration').trim();\n const parsed = parseInt(durationStr, 10);\n if (!isNaN(parsed)) return parsed;\n }\n return 200; // Default\n }\n\n /**\n * Apply expand animation to a detail element.\n */\n private animateExpand(detailEl: HTMLElement): void {\n if (!this.isAnimationEnabled || this.animationStyle === false) return;\n\n detailEl.classList.add('tbw-expanding');\n detailEl.addEventListener(\n 'animationend',\n () => {\n detailEl.classList.remove('tbw-expanding');\n },\n { once: true },\n );\n }\n\n /**\n * Apply collapse animation to a detail element and remove after animation.\n */\n private animateCollapse(detailEl: HTMLElement, onComplete: () => void): void {\n if (!this.isAnimationEnabled || this.animationStyle === false) {\n onComplete();\n return;\n }\n\n detailEl.classList.add('tbw-collapsing');\n const cleanup = () => {\n detailEl.classList.remove('tbw-collapsing');\n onComplete();\n };\n detailEl.addEventListener('animationend', cleanup, { once: true });\n // Fallback timeout in case animation doesn't fire\n setTimeout(cleanup, this.animationDuration + 50);\n }\n\n // #endregion\n\n // #region Internal State\n private expandedRows: Set<any> = new Set();\n private detailElements: Map<any, HTMLElement> = new Map();\n // #endregion\n\n // #region Lifecycle\n\n override detach(): void {\n this.expandedRows.clear();\n this.detailElements.clear();\n }\n // #endregion\n\n // #region Hooks\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.config.detailRenderer) {\n return [...columns];\n }\n\n // Wrap first column's renderer to add expand/collapse toggle\n const cols = [...columns];\n if (cols.length > 0) {\n const firstCol = { ...cols[0] };\n const originalRenderer = firstCol.viewRenderer as MasterDetailWrappedRenderer | undefined;\n\n // Skip if already wrapped by this plugin (prevents double-wrapping on re-render)\n if (originalRenderer?.__masterDetailWrapped) {\n return cols;\n }\n\n const wrappedRenderer: MasterDetailWrappedRenderer = (renderCtx) => {\n const { value, row } = renderCtx;\n const isExpanded = this.expandedRows.has(row as object);\n\n const container = document.createElement('span');\n container.className = 'master-detail-cell-wrapper';\n\n // Expand/collapse toggle icon\n const toggle = document.createElement('span');\n toggle.className = `master-detail-toggle${isExpanded ? ' expanded' : ''}`;\n // Use grid-level icons (fall back to defaults)\n this.setIcon(toggle, this.resolveIcon(isExpanded ? 'collapse' : 'expand'));\n // role=\"button\" is required for aria-expanded to be valid\n toggle.setAttribute('role', 'button');\n toggle.setAttribute('tabindex', '0');\n toggle.setAttribute('aria-expanded', String(isExpanded));\n toggle.setAttribute('aria-label', isExpanded ? 'Collapse details' : 'Expand details');\n // Click handling is done via onCellClick hook (not direct addEventListener)\n // This ensures proper event delegation through the grid's event system\n container.appendChild(toggle);\n\n // Cell content\n const content = document.createElement('span');\n if (originalRenderer) {\n const rendered = originalRenderer(renderCtx);\n if (rendered instanceof Node) {\n content.appendChild(rendered);\n } else {\n content.textContent = String(rendered ?? value ?? '');\n }\n } else {\n content.textContent = String(value ?? '');\n }\n container.appendChild(content);\n\n return container;\n };\n\n // Mark renderer as wrapped to prevent double-wrapping\n wrappedRenderer.__masterDetailWrapped = true;\n firstCol.viewRenderer = wrappedRenderer;\n\n cols[0] = firstCol;\n }\n\n return cols;\n }\n\n override onRowClick(event: RowClickEvent): boolean | void {\n if (!this.config.expandOnRowClick || !this.config.detailRenderer) return;\n\n this.expandedRows = toggleDetailRow(this.expandedRows, event.row as object);\n\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex: event.rowIndex,\n row: event.row as Record<string, unknown>,\n expanded: this.expandedRows.has(event.row as object),\n });\n\n this.requestRender();\n return false;\n }\n\n override onCellClick(event: CellClickEvent): boolean | void {\n // Handle click on master-detail toggle icon (same pattern as TreePlugin)\n const target = event.originalEvent?.target as HTMLElement;\n if (target?.classList.contains('master-detail-toggle')) {\n const row = event.row;\n const rowIndex = event.rowIndex;\n this.expandedRows = toggleDetailRow(this.expandedRows, row as object);\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex,\n row: row as Record<string, unknown>,\n expanded: this.expandedRows.has(row as object),\n });\n this.requestRender();\n return true; // Prevent default handling\n }\n\n // Sync detail rows after cell click triggers refreshVirtualWindow\n // This runs in microtask to ensure DOM updates are complete\n if (this.expandedRows.size > 0) {\n queueMicrotask(() => this.#syncDetailRows());\n }\n return; // Don't prevent default\n }\n\n override afterRender(): void {\n this.#syncDetailRows();\n }\n\n /**\n * Called on scroll to sync detail elements with visible rows.\n * Removes details for rows that scrolled out of view and reattaches for visible rows.\n */\n override onScrollRender(): void {\n if (!this.config.detailRenderer || this.expandedRows.size === 0) return;\n // Full sync needed on scroll to clean up orphaned details\n this.#syncDetailRows();\n }\n\n /**\n * Full sync of detail rows - cleans up stale elements and creates new ones.\n * Detail rows are inserted as siblings AFTER their master row to survive row rebuilds.\n */\n #syncDetailRows(): void {\n if (!this.config.detailRenderer) return;\n\n const body = this.shadowRoot?.querySelector('.rows');\n if (!body) return;\n\n // Build a map of row index -> row element for visible rows\n const visibleRowMap = new Map<number, Element>();\n const dataRows = body.querySelectorAll('.data-grid-row');\n const columnCount = this.columns.length;\n\n for (const rowEl of dataRows) {\n const firstCell = rowEl.querySelector('.cell[data-row]');\n const rowIndex = firstCell ? parseInt(firstCell.getAttribute('data-row') ?? '-1', 10) : -1;\n if (rowIndex >= 0) {\n visibleRowMap.set(rowIndex, rowEl);\n }\n }\n\n // Remove detail rows whose parent row is no longer visible or no longer expanded\n const existingDetails = body.querySelectorAll('.master-detail-row');\n for (const detailEl of existingDetails) {\n const forIndex = parseInt(detailEl.getAttribute('data-detail-for') ?? '-1', 10);\n const row = forIndex >= 0 ? this.rows[forIndex] : undefined;\n const isStillExpanded = row && this.expandedRows.has(row);\n const isRowVisible = visibleRowMap.has(forIndex);\n\n // Remove detail if not expanded or if parent row scrolled out\n if (!isStillExpanded || !isRowVisible) {\n detailEl.remove();\n if (row) this.detailElements.delete(row);\n }\n }\n\n // Insert detail rows for expanded rows that are visible\n for (const [rowIndex, rowEl] of visibleRowMap) {\n const row = this.rows[rowIndex];\n if (!row || !this.expandedRows.has(row)) continue;\n\n // Check if detail already exists for this row\n const existingDetail = this.detailElements.get(row);\n if (existingDetail) {\n // Ensure it's positioned correctly (as next sibling of row element)\n if (existingDetail.previousElementSibling !== rowEl) {\n rowEl.after(existingDetail);\n }\n continue;\n }\n\n // Create new detail element\n const detailEl = createDetailElement(row, rowIndex, this.config.detailRenderer, columnCount);\n\n if (typeof this.config.detailHeight === 'number') {\n detailEl.style.height = `${this.config.detailHeight}px`;\n }\n\n // Insert as sibling after the row element (not as child)\n rowEl.after(detailEl);\n this.detailElements.set(row, detailEl);\n\n // Apply expand animation\n this.animateExpand(detailEl);\n }\n }\n\n /**\n * Return total extra height from all expanded detail rows.\n * Used by grid virtualization to adjust scrollbar height.\n */\n override getExtraHeight(): number {\n let totalHeight = 0;\n for (const row of this.expandedRows) {\n const detailEl = this.detailElements.get(row);\n if (detailEl) {\n totalHeight += detailEl.offsetHeight;\n } else {\n // Detail not yet rendered - estimate based on config or default\n const configHeight = this.config?.detailHeight;\n totalHeight += typeof configHeight === 'number' ? configHeight : 150;\n }\n }\n return totalHeight;\n }\n\n /**\n * Return extra height that appears before a given row index.\n * This is the sum of heights of all expanded details whose parent row is before the given index.\n */\n override getExtraHeightBefore(beforeRowIndex: number): number {\n let totalHeight = 0;\n for (const row of this.expandedRows) {\n const rowIndex = this.rows.indexOf(row);\n // Include detail if it's for a row before the given index\n if (rowIndex >= 0 && rowIndex < beforeRowIndex) {\n const detailEl = this.detailElements.get(row);\n if (detailEl) {\n totalHeight += detailEl.offsetHeight;\n } else {\n const configHeight = this.config?.detailHeight;\n totalHeight += typeof configHeight === 'number' ? configHeight : 150;\n }\n }\n }\n return totalHeight;\n }\n\n /**\n * Adjust the virtualization start index to keep expanded row visible while its detail is visible.\n * This ensures the detail scrolls smoothly out of view instead of disappearing abruptly.\n */\n override adjustVirtualStart(start: number, scrollTop: number, rowHeight: number): number {\n if (this.expandedRows.size === 0) return start;\n\n // Build sorted list of expanded row indices for cumulative height calculation\n const expandedIndices: Array<{ index: number; row: any }> = [];\n for (const row of this.expandedRows) {\n const index = this.rows.indexOf(row);\n if (index >= 0) {\n expandedIndices.push({ index, row });\n }\n }\n expandedIndices.sort((a, b) => a.index - b.index);\n\n let minStart = start;\n\n // Calculate actual scroll position for each expanded row,\n // accounting for cumulative detail heights before it\n let cumulativeExtraHeight = 0;\n\n for (const { index: rowIndex, row } of expandedIndices) {\n // Actual position includes all detail heights before this row\n const actualRowTop = rowIndex * rowHeight + cumulativeExtraHeight;\n const detailEl = this.detailElements.get(row);\n const detailHeight =\n detailEl?.offsetHeight ?? (typeof this.config?.detailHeight === 'number' ? this.config.detailHeight : 150);\n const actualDetailBottom = actualRowTop + rowHeight + detailHeight;\n\n // Update cumulative height for next iteration\n cumulativeExtraHeight += detailHeight;\n\n // Skip rows that are at or after the calculated start\n if (rowIndex >= start) continue;\n\n // If any part of the detail is still visible (below the scroll position),\n // we need to keep the parent row in the render range\n if (actualDetailBottom > scrollTop) {\n if (rowIndex < minStart) {\n minStart = rowIndex;\n }\n }\n }\n\n return minStart;\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Expand the detail row at the given index.\n * @param rowIndex - Index of the row to expand\n */\n expand(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = expandDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Collapse the detail row at the given index.\n * @param rowIndex - Index of the row to collapse\n */\n collapse(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = collapseDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Toggle the detail row at the given index.\n * @param rowIndex - Index of the row to toggle\n */\n toggle(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = toggleDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Check if the detail row at the given index is expanded.\n * @param rowIndex - Index of the row to check\n * @returns Whether the detail row is expanded\n */\n isExpanded(rowIndex: number): boolean {\n const row = this.rows[rowIndex];\n return row ? isDetailExpanded(this.expandedRows, row) : false;\n }\n\n /**\n * Expand all detail rows.\n */\n expandAll(): void {\n for (const row of this.rows) {\n this.expandedRows.add(row);\n }\n this.requestRender();\n }\n\n /**\n * Collapse all detail rows.\n */\n collapseAll(): void {\n this.expandedRows.clear();\n this.requestRender();\n }\n\n /**\n * Get the indices of all expanded rows.\n * @returns Array of row indices that are expanded\n */\n getExpandedRows(): number[] {\n const indices: number[] = [];\n for (const row of this.expandedRows) {\n const idx = this.rows.indexOf(row);\n if (idx >= 0) indices.push(idx);\n }\n return indices;\n }\n\n /**\n * Get the detail element for a specific row.\n * @param rowIndex - Index of the row\n * @returns The detail HTMLElement or undefined\n */\n getDetailElement(rowIndex: number): HTMLElement | undefined {\n const row = this.rows[rowIndex];\n return row ? this.detailElements.get(row) : undefined;\n }\n\n /**\n * Re-parse light DOM to refresh the detail renderer.\n * Call this after framework templates are registered (e.g., Angular ngAfterContentInit).\n *\n * This allows frameworks to register templates asynchronously and then\n * update the plugin's detailRenderer.\n */\n refreshDetailRenderer(): void {\n // Force re-parse by temporarily clearing the renderer\n const currentRenderer = this.config.detailRenderer;\n this.config = { ...this.config, detailRenderer: undefined };\n this.parseLightDomDetail();\n\n // If no new renderer was found, restore the original\n if (!this.config.detailRenderer && currentRenderer) {\n this.config = { ...this.config, detailRenderer: currentRenderer };\n }\n\n // Request a COLUMNS phase re-render so processColumns runs again with the new detailRenderer\n // This ensures the expand toggle is added to the first column.\n // Must use refreshColumns() (COLUMNS phase) not requestRender() (ROWS phase)\n // because processColumns only runs at COLUMNS phase or higher.\n if (this.config.detailRenderer) {\n const grid = this.grid as unknown as { refreshColumns?: () => void };\n if (typeof grid.refreshColumns === 'function') {\n grid.refreshColumns();\n } else {\n // Fallback to requestRender if refreshColumns not available\n this.requestRender();\n }\n }\n }\n // #endregion\n\n // #region Styles\n override readonly styles = styles;\n // #endregion\n}\n"],"names":["toggleDetailRow","expandedRows","row","newExpanded","expandDetailRow","collapseDetailRow","isDetailExpanded","createDetailElement","rowIndex","renderer","columnCount","detailRow","detailCell","content","MasterDetailPlugin","BaseGridPlugin","grid","gridEl","detailEl","gridWithAdapter","adapterRenderer","animation","showExpandColumn","expandOnRowClick","collapseOnClickOutside","heightAttr","configUpdates","templateHTML","_rowIndex","evaluated","evalTemplateString","sanitizeHTML","mode","host","durationStr","parsed","onComplete","cleanup","columns","cols","firstCol","originalRenderer","wrappedRenderer","renderCtx","value","isExpanded","container","toggle","rendered","event","#syncDetailRows","body","visibleRowMap","dataRows","rowEl","firstCell","existingDetails","forIndex","isStillExpanded","isRowVisible","existingDetail","totalHeight","configHeight","beforeRowIndex","start","scrollTop","rowHeight","expandedIndices","index","a","b","minStart","cumulativeExtraHeight","actualRowTop","detailHeight","actualDetailBottom","indices","idx","currentRenderer","styles"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- (function(s,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],l):(s=typeof globalThis<"u"?globalThis:s||self,l(s.TbwGridPlugin_multiSort={},s.TbwGrid))})(this,(function(s,l){"use strict";function m(e,t,i){return t.length?[...e].sort((n,o)=>{for(const r of t){const c=i.find(a=>a.field===r.field)?.sortComparator??x,g=n[r.field],p=o[r.field],d=c(g,p,n,o);if(d!==0)return r.direction==="asc"?d:-d}return 0}):[...e]}function x(e,t){return e==null&&t==null?0:e==null?1:t==null?-1:typeof e=="number"&&typeof t=="number"?e-t:e instanceof Date&&t instanceof Date?e.getTime()-t.getTime():typeof e=="boolean"&&typeof t=="boolean"?e===t?0:e?-1:1:String(e).localeCompare(String(t))}function M(e,t,i,n){const o=e.find(r=>r.field===t);return i?o?o.direction==="asc"?e.map(r=>r.field===t?{...r,direction:"desc"}:r):e.filter(r=>r.field!==t):e.length<n?[...e,{field:t,direction:"asc"}]:e:o?.direction==="asc"?[{field:t,direction:"desc"}]:o?.direction==="desc"?[]:[{field:t,direction:"asc"}]}function f(e,t){const i=e.findIndex(n=>n.field===t);return i>=0?i+1:void 0}function h(e,t){return e.find(i=>i.field===t)?.direction}const y='.header-cell[data-sort=asc]:after{content:"↑";margin-left:4px;opacity:.8}.header-cell[data-sort=desc]:after{content:"↓";margin-left:4px;opacity:.8}.sort-indicator{margin-left:4px;opacity:.8}.sort-index{font-size:10px;background:var(--tbw-multi-sort-badge-bg, var(--tbw-color-panel-bg));color:var(--tbw-multi-sort-badge-color, var(--tbw-color-fg));border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;margin-left:2px;font-weight:600}';class S extends l.BaseGridPlugin{name="multiSort";version="1.0.0";get defaultConfig(){return{maxSortColumns:3,showSortIndex:!0}}sortModel=[];detach(){this.sortModel=[]}processRows(t){return this.sortModel.length===0?[...t]:m([...t],this.sortModel,[...this.columns])}onHeaderClick(t){if(!this.columns.find(r=>r.field===t.field)?.sortable)return!1;const n=t.originalEvent.shiftKey,o=this.config.maxSortColumns??3;return this.sortModel=M(this.sortModel,t.field,n,o),this.emit("sort-change",{sortModel:[...this.sortModel]}),this.requestRender(),!0}afterRender(){const t=this.shadowRoot;if(!t)return;const i=this.config.showSortIndex!==!1;t.querySelectorAll(".header-row .cell[data-field]").forEach(o=>{const r=o.getAttribute("data-field");if(!r)return;const u=f(this.sortModel,r),c=h(this.sortModel,r);if(o.querySelector(".sort-index")?.remove(),c){o.querySelector('[part~="sort-indicator"], .sort-indicator')?.remove(),o.setAttribute("data-sort",c);const d=document.createElement("span");if(d.className="sort-indicator",this.setIcon(d,this.resolveIcon(c==="asc"?"sortAsc":"sortDesc")),o.appendChild(d),i&&this.sortModel.length>1&&u!==void 0){const a=document.createElement("span");a.className="sort-index",a.textContent=String(u),o.appendChild(a)}}else o.removeAttribute("data-sort")})}getSortModel(){return[...this.sortModel]}setSortModel(t){this.sortModel=[...t],this.emit("sort-change",{sortModel:[...t]}),this.requestRender()}clearSort(){this.sortModel=[],this.emit("sort-change",{sortModel:[]}),this.requestRender()}getSortIndex(t){return f(this.sortModel,t)}getSortDirection(t){return h(this.sortModel,t)}getColumnState(t){const i=this.sortModel.findIndex(o=>o.field===t);return i===-1?void 0:{sort:{direction:this.sortModel[i].direction,priority:i}}}applyColumnState(t,i){if(!i.sort){this.sortModel=this.sortModel.filter(r=>r.field!==t);return}const n=this.sortModel.findIndex(r=>r.field===t),o={field:t,direction:i.sort.direction};n!==-1?this.sortModel[n]=o:this.sortModel.splice(i.sort.priority,0,o)}styles=y}s.MultiSortPlugin=S,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(d,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],l):(d=typeof globalThis<"u"?globalThis:d||self,l(d.TbwGridPlugin_multiSort={},d.TbwGrid))})(this,(function(d,l){"use strict";function S(o,t,i){return t.length?[...o].sort((n,e)=>{for(const r of t){const c=i.find(u=>u.field===r.field)?.sortComparator??M,p=n[r.field],m=e[r.field],s=c(p,m,n,e);if(s!==0)return r.direction==="asc"?s:-s}return 0}):[...o]}function M(o,t){return o==null&&t==null?0:o==null?1:t==null?-1:typeof o=="number"&&typeof t=="number"?o-t:o instanceof Date&&t instanceof Date?o.getTime()-t.getTime():typeof o=="boolean"&&typeof t=="boolean"?o===t?0:o?-1:1:String(o).localeCompare(String(t))}function y(o,t,i,n){const e=o.find(r=>r.field===t);return i?e?e.direction==="asc"?o.map(r=>r.field===t?{...r,direction:"desc"}:r):o.filter(r=>r.field!==t):o.length<n?[...o,{field:t,direction:"asc"}]:o:e?.direction==="asc"?[{field:t,direction:"desc"}]:e?.direction==="desc"?[]:[{field:t,direction:"asc"}]}function h(o,t){const i=o.findIndex(n=>n.field===t);return i>=0?i+1:void 0}function g(o,t){return o.find(i=>i.field===t)?.direction}const b='.header-cell[data-sort=asc]:after{content:"↑";margin-left:4px;opacity:.8}.header-cell[data-sort=desc]:after{content:"↓";margin-left:4px;opacity:.8}.sort-indicator{margin-left:4px;opacity:.8}.sort-index{font-size:10px;background:var(--tbw-multi-sort-badge-bg, var(--tbw-color-panel-bg));color:var(--tbw-multi-sort-badge-color, var(--tbw-color-fg));border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;margin-left:2px;font-weight:600}';class w extends l.BaseGridPlugin{name="multiSort";version="1.0.0";get defaultConfig(){return{maxSortColumns:3,showSortIndex:!0}}sortModel=[];detach(){this.sortModel=[]}processRows(t){return this.sortModel.length===0?[...t]:S([...t],this.sortModel,[...this.columns])}onHeaderClick(t){if(!this.columns.find(r=>r.field===t.field)?.sortable)return!1;const n=t.originalEvent.shiftKey,e=this.config.maxSortColumns??3;return this.sortModel=y(this.sortModel,t.field,n,e),this.emit("sort-change",{sortModel:[...this.sortModel]}),this.requestRender(),!0}afterRender(){const t=this.shadowRoot;if(!t)return;const i=this.config.showSortIndex!==!1;t.querySelectorAll(".header-row .cell[data-field]").forEach(e=>{const r=e.getAttribute("data-field");if(!r)return;const f=h(this.sortModel,r),c=g(this.sortModel,r);if(e.querySelector(".sort-index")?.remove(),c){e.querySelector('[part~="sort-indicator"], .sort-indicator')?.remove(),e.setAttribute("data-sort",c);const s=document.createElement("span");s.className="sort-indicator",this.setIcon(s,this.resolveIcon(c==="asc"?"sortAsc":"sortDesc"));const u=e.querySelector(".tbw-filter-btn"),C=e.querySelector(".resize-handle"),x=u??C;if(x?e.insertBefore(s,x):e.appendChild(s),i&&this.sortModel.length>1&&f!==void 0){const a=document.createElement("span");a.className="sort-index",a.textContent=String(f),s.nextSibling?e.insertBefore(a,s.nextSibling):e.appendChild(a)}}else e.removeAttribute("data-sort")})}getSortModel(){return[...this.sortModel]}setSortModel(t){this.sortModel=[...t],this.emit("sort-change",{sortModel:[...t]}),this.requestRender()}clearSort(){this.sortModel=[],this.emit("sort-change",{sortModel:[]}),this.requestRender()}getSortIndex(t){return h(this.sortModel,t)}getSortDirection(t){return g(this.sortModel,t)}getColumnState(t){const i=this.sortModel.findIndex(e=>e.field===t);return i===-1?void 0:{sort:{direction:this.sortModel[i].direction,priority:i}}}applyColumnState(t,i){if(!i.sort){this.sortModel=this.sortModel.filter(r=>r.field!==t);return}const n=this.sortModel.findIndex(r=>r.field===t),e={field:t,direction:i.sort.direction};n!==-1?this.sortModel[n]=e:this.sortModel.splice(i.sort.priority,0,e)}styles=b}d.MultiSortPlugin=w,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
2
2
  //# sourceMappingURL=multi-sort.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"multi-sort.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/multi-sort/multi-sort.ts","../../../../../libs/grid/src/lib/plugins/multi-sort/MultiSortPlugin.ts"],"sourcesContent":["/**\n * Multi-Sort Core Logic\n *\n * Pure functions for multi-column sorting operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { SortModel } from './types';\n\n/**\n * Apply multiple sort columns to a row array.\n * Sorts are applied in order - first sort has highest priority.\n *\n * @param rows - Array of row objects to sort\n * @param sorts - Ordered array of sort configurations\n * @param columns - Column configurations (for custom comparators)\n * @returns New sorted array (does not mutate original)\n */\nexport function applySorts<TRow = unknown>(rows: TRow[], sorts: SortModel[], columns: ColumnConfig<TRow>[]): TRow[] {\n if (!sorts.length) return [...rows];\n\n return [...rows].sort((a, b) => {\n for (const sort of sorts) {\n const col = columns.find((c) => c.field === sort.field);\n const comparator = col?.sortComparator ?? defaultComparator;\n const aVal = (a as Record<string, unknown>)[sort.field];\n const bVal = (b as Record<string, unknown>)[sort.field];\n const result = comparator(aVal, bVal, a, b);\n if (result !== 0) {\n return sort.direction === 'asc' ? result : -result;\n }\n }\n return 0;\n });\n}\n\n/**\n * Default comparator for sorting values.\n * Handles nulls, numbers, dates, and strings.\n *\n * @param a - First value\n * @param b - Second value\n * @returns Comparison result (-1, 0, 1)\n */\nexport function defaultComparator(a: unknown, b: unknown): number {\n // Handle nulls/undefined - push to end\n if (a == null && b == null) return 0;\n if (a == null) return 1;\n if (b == null) return -1;\n\n // Type-aware comparison\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b;\n }\n\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() - b.getTime();\n }\n\n // Boolean comparison\n if (typeof a === 'boolean' && typeof b === 'boolean') {\n return a === b ? 0 : a ? -1 : 1;\n }\n\n // String comparison (fallback)\n return String(a).localeCompare(String(b));\n}\n\n/**\n * Toggle sort state for a field.\n * With shift key: adds/toggles in multi-sort list\n * Without shift key: replaces entire sort with single column\n *\n * @param current - Current sort model\n * @param field - Field to toggle\n * @param shiftKey - Whether shift key is held (multi-sort mode)\n * @param maxColumns - Maximum columns allowed in sort\n * @returns New sort model\n */\nexport function toggleSort(current: SortModel[], field: string, shiftKey: boolean, maxColumns: number): SortModel[] {\n const existing = current.find((s) => s.field === field);\n\n if (shiftKey) {\n // Multi-sort: add/toggle in list\n if (existing) {\n if (existing.direction === 'asc') {\n // Flip to descending\n return current.map((s) => (s.field === field ? { ...s, direction: 'desc' as const } : s));\n } else {\n // Remove from sort\n return current.filter((s) => s.field !== field);\n }\n } else if (current.length < maxColumns) {\n // Add new sort column\n return [...current, { field, direction: 'asc' as const }];\n }\n // Max columns reached, return unchanged\n return current;\n } else {\n // Single sort: replace all\n if (existing?.direction === 'asc') {\n return [{ field, direction: 'desc' }];\n } else if (existing?.direction === 'desc') {\n return [];\n }\n return [{ field, direction: 'asc' }];\n }\n}\n\n/**\n * Get the sort index (1-based) for a field in the sort model.\n * Returns undefined if the field is not in the sort model.\n *\n * @param sortModel - Current sort model\n * @param field - Field to check\n * @returns 1-based index or undefined\n */\nexport function getSortIndex(sortModel: SortModel[], field: string): number | undefined {\n const index = sortModel.findIndex((s) => s.field === field);\n return index >= 0 ? index + 1 : undefined;\n}\n\n/**\n * Get the sort direction for a field in the sort model.\n *\n * @param sortModel - Current sort model\n * @param field - Field to check\n * @returns Sort direction or undefined if not sorted\n */\nexport function getSortDirection(sortModel: SortModel[], field: string): 'asc' | 'desc' | undefined {\n return sortModel.find((s) => s.field === field)?.direction;\n}\n","/**\n * Multi-Sort Plugin (Class-based)\n *\n * Provides multi-column sorting capabilities for tbw-grid.\n * Supports shift+click for adding secondary sort columns.\n */\n\nimport { BaseGridPlugin, HeaderClickEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnState } from '../../core/types';\nimport { applySorts, getSortDirection, getSortIndex, toggleSort } from './multi-sort';\nimport styles from './multi-sort.css?inline';\nimport type { MultiSortConfig, SortModel } from './types';\n\n/**\n * Multi-Sort Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new MultiSortPlugin({ maxSortColumns: 3, showSortIndex: true })\n * ```\n */\nexport class MultiSortPlugin extends BaseGridPlugin<MultiSortConfig> {\n readonly name = 'multiSort';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<MultiSortConfig> {\n return {\n maxSortColumns: 3,\n showSortIndex: true,\n };\n }\n\n // #region Internal State\n private sortModel: SortModel[] = [];\n // #endregion\n\n // #region Lifecycle\n\n override detach(): void {\n this.sortModel = [];\n }\n // #endregion\n\n // #region Hooks\n\n override processRows(rows: readonly unknown[]): unknown[] {\n if (this.sortModel.length === 0) {\n return [...rows];\n }\n return applySorts([...rows], this.sortModel, [...this.columns]);\n }\n\n override onHeaderClick(event: HeaderClickEvent): boolean {\n const column = this.columns.find((c) => c.field === event.field);\n if (!column?.sortable) return false;\n\n const shiftKey = event.originalEvent.shiftKey;\n const maxColumns = this.config.maxSortColumns ?? 3;\n\n this.sortModel = toggleSort(this.sortModel, event.field, shiftKey, maxColumns);\n\n this.emit('sort-change', { sortModel: [...this.sortModel] });\n this.requestRender();\n\n return true;\n }\n\n override afterRender(): void {\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n const showIndex = this.config.showSortIndex !== false;\n\n // Update all sortable header cells with sort indicators\n const headerCells = shadowRoot.querySelectorAll('.header-row .cell[data-field]');\n headerCells.forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n\n const sortIndex = getSortIndex(this.sortModel, field);\n const sortDir = getSortDirection(this.sortModel, field);\n\n // Remove existing sort index badge (always clean up)\n const existingBadge = cell.querySelector('.sort-index');\n existingBadge?.remove();\n\n if (sortDir) {\n // Column is sorted - remove base indicator and add our own\n const existingIndicator = cell.querySelector('[part~=\"sort-indicator\"], .sort-indicator');\n existingIndicator?.remove();\n\n cell.setAttribute('data-sort', sortDir);\n\n // Add sort arrow indicator\n const indicator = document.createElement('span');\n indicator.className = 'sort-indicator';\n // Use grid-level icons (fall back to defaults)\n this.setIcon(indicator, this.resolveIcon(sortDir === 'asc' ? 'sortAsc' : 'sortDesc'));\n cell.appendChild(indicator);\n\n // Add sort index badge if multiple columns sorted and showSortIndex is enabled\n if (showIndex && this.sortModel.length > 1 && sortIndex !== undefined) {\n const badge = document.createElement('span');\n badge.className = 'sort-index';\n badge.textContent = String(sortIndex);\n cell.appendChild(badge);\n }\n } else {\n cell.removeAttribute('data-sort');\n // For unsorted columns, leave the base indicator (⇅) alone\n }\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current sort model.\n * @returns Copy of the current sort model\n */\n getSortModel(): SortModel[] {\n return [...this.sortModel];\n }\n\n /**\n * Set the sort model programmatically.\n * @param model - New sort model to apply\n */\n setSortModel(model: SortModel[]): void {\n this.sortModel = [...model];\n this.emit('sort-change', { sortModel: [...model] });\n this.requestRender();\n }\n\n /**\n * Clear all sorting.\n */\n clearSort(): void {\n this.sortModel = [];\n this.emit('sort-change', { sortModel: [] });\n this.requestRender();\n }\n\n /**\n * Get the sort index (1-based) for a specific field.\n * @param field - Field to check\n * @returns 1-based index or undefined if not sorted\n */\n getSortIndex(field: string): number | undefined {\n return getSortIndex(this.sortModel, field);\n }\n\n /**\n * Get the sort direction for a specific field.\n * @param field - Field to check\n * @returns Sort direction or undefined if not sorted\n */\n getSortDirection(field: string): 'asc' | 'desc' | undefined {\n return getSortDirection(this.sortModel, field);\n }\n // #endregion\n\n // #region Column State Hooks\n\n /**\n * Return sort state for a column if it's in the sort model.\n */\n override getColumnState(field: string): Partial<ColumnState> | undefined {\n const index = this.sortModel.findIndex((s) => s.field === field);\n if (index === -1) return undefined;\n\n const sortEntry = this.sortModel[index];\n return {\n sort: {\n direction: sortEntry.direction,\n priority: index,\n },\n };\n }\n\n /**\n * Apply sort state from column state.\n * Rebuilds the sort model from all column states.\n */\n override applyColumnState(field: string, state: ColumnState): void {\n // Only process if the column has sort state\n if (!state.sort) {\n // Remove this field from sortModel if it exists\n this.sortModel = this.sortModel.filter((s) => s.field !== field);\n return;\n }\n\n // Find existing entry or add new one\n const existingIndex = this.sortModel.findIndex((s) => s.field === field);\n const newEntry: SortModel = {\n field,\n direction: state.sort.direction,\n };\n\n if (existingIndex !== -1) {\n // Update existing entry\n this.sortModel[existingIndex] = newEntry;\n } else {\n // Add at the correct priority position\n this.sortModel.splice(state.sort.priority, 0, newEntry);\n }\n\n // Re-sort the model by priority to ensure correct order\n // This is handled after all columns are processed, but we maintain order here\n }\n // #endregion\n\n // #region Styles\n\n override readonly styles = styles;\n // #endregion\n}\n"],"names":["applySorts","rows","sorts","columns","a","b","sort","comparator","c","defaultComparator","aVal","bVal","result","toggleSort","current","field","shiftKey","maxColumns","existing","s","getSortIndex","sortModel","index","getSortDirection","MultiSortPlugin","BaseGridPlugin","event","shadowRoot","showIndex","cell","sortIndex","sortDir","indicator","badge","model","state","existingIndex","newEntry","styles"],"mappings":"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"}
1
+ {"version":3,"file":"multi-sort.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/multi-sort/multi-sort.ts","../../../../../libs/grid/src/lib/plugins/multi-sort/MultiSortPlugin.ts"],"sourcesContent":["/**\n * Multi-Sort Core Logic\n *\n * Pure functions for multi-column sorting operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { SortModel } from './types';\n\n/**\n * Apply multiple sort columns to a row array.\n * Sorts are applied in order - first sort has highest priority.\n *\n * @param rows - Array of row objects to sort\n * @param sorts - Ordered array of sort configurations\n * @param columns - Column configurations (for custom comparators)\n * @returns New sorted array (does not mutate original)\n */\nexport function applySorts<TRow = unknown>(rows: TRow[], sorts: SortModel[], columns: ColumnConfig<TRow>[]): TRow[] {\n if (!sorts.length) return [...rows];\n\n return [...rows].sort((a, b) => {\n for (const sort of sorts) {\n const col = columns.find((c) => c.field === sort.field);\n const comparator = col?.sortComparator ?? defaultComparator;\n const aVal = (a as Record<string, unknown>)[sort.field];\n const bVal = (b as Record<string, unknown>)[sort.field];\n const result = comparator(aVal, bVal, a, b);\n if (result !== 0) {\n return sort.direction === 'asc' ? result : -result;\n }\n }\n return 0;\n });\n}\n\n/**\n * Default comparator for sorting values.\n * Handles nulls, numbers, dates, and strings.\n *\n * @param a - First value\n * @param b - Second value\n * @returns Comparison result (-1, 0, 1)\n */\nexport function defaultComparator(a: unknown, b: unknown): number {\n // Handle nulls/undefined - push to end\n if (a == null && b == null) return 0;\n if (a == null) return 1;\n if (b == null) return -1;\n\n // Type-aware comparison\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b;\n }\n\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() - b.getTime();\n }\n\n // Boolean comparison\n if (typeof a === 'boolean' && typeof b === 'boolean') {\n return a === b ? 0 : a ? -1 : 1;\n }\n\n // String comparison (fallback)\n return String(a).localeCompare(String(b));\n}\n\n/**\n * Toggle sort state for a field.\n * With shift key: adds/toggles in multi-sort list\n * Without shift key: replaces entire sort with single column\n *\n * @param current - Current sort model\n * @param field - Field to toggle\n * @param shiftKey - Whether shift key is held (multi-sort mode)\n * @param maxColumns - Maximum columns allowed in sort\n * @returns New sort model\n */\nexport function toggleSort(current: SortModel[], field: string, shiftKey: boolean, maxColumns: number): SortModel[] {\n const existing = current.find((s) => s.field === field);\n\n if (shiftKey) {\n // Multi-sort: add/toggle in list\n if (existing) {\n if (existing.direction === 'asc') {\n // Flip to descending\n return current.map((s) => (s.field === field ? { ...s, direction: 'desc' as const } : s));\n } else {\n // Remove from sort\n return current.filter((s) => s.field !== field);\n }\n } else if (current.length < maxColumns) {\n // Add new sort column\n return [...current, { field, direction: 'asc' as const }];\n }\n // Max columns reached, return unchanged\n return current;\n } else {\n // Single sort: replace all\n if (existing?.direction === 'asc') {\n return [{ field, direction: 'desc' }];\n } else if (existing?.direction === 'desc') {\n return [];\n }\n return [{ field, direction: 'asc' }];\n }\n}\n\n/**\n * Get the sort index (1-based) for a field in the sort model.\n * Returns undefined if the field is not in the sort model.\n *\n * @param sortModel - Current sort model\n * @param field - Field to check\n * @returns 1-based index or undefined\n */\nexport function getSortIndex(sortModel: SortModel[], field: string): number | undefined {\n const index = sortModel.findIndex((s) => s.field === field);\n return index >= 0 ? index + 1 : undefined;\n}\n\n/**\n * Get the sort direction for a field in the sort model.\n *\n * @param sortModel - Current sort model\n * @param field - Field to check\n * @returns Sort direction or undefined if not sorted\n */\nexport function getSortDirection(sortModel: SortModel[], field: string): 'asc' | 'desc' | undefined {\n return sortModel.find((s) => s.field === field)?.direction;\n}\n","/**\n * Multi-Sort Plugin (Class-based)\n *\n * Provides multi-column sorting capabilities for tbw-grid.\n * Supports shift+click for adding secondary sort columns.\n */\n\nimport { BaseGridPlugin, HeaderClickEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnState } from '../../core/types';\nimport { applySorts, getSortDirection, getSortIndex, toggleSort } from './multi-sort';\nimport styles from './multi-sort.css?inline';\nimport type { MultiSortConfig, SortModel } from './types';\n\n/**\n * Multi-Sort Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new MultiSortPlugin({ maxSortColumns: 3, showSortIndex: true })\n * ```\n */\nexport class MultiSortPlugin extends BaseGridPlugin<MultiSortConfig> {\n readonly name = 'multiSort';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<MultiSortConfig> {\n return {\n maxSortColumns: 3,\n showSortIndex: true,\n };\n }\n\n // #region Internal State\n private sortModel: SortModel[] = [];\n // #endregion\n\n // #region Lifecycle\n\n override detach(): void {\n this.sortModel = [];\n }\n // #endregion\n\n // #region Hooks\n\n override processRows(rows: readonly unknown[]): unknown[] {\n if (this.sortModel.length === 0) {\n return [...rows];\n }\n return applySorts([...rows], this.sortModel, [...this.columns]);\n }\n\n override onHeaderClick(event: HeaderClickEvent): boolean {\n const column = this.columns.find((c) => c.field === event.field);\n if (!column?.sortable) return false;\n\n const shiftKey = event.originalEvent.shiftKey;\n const maxColumns = this.config.maxSortColumns ?? 3;\n\n this.sortModel = toggleSort(this.sortModel, event.field, shiftKey, maxColumns);\n\n this.emit('sort-change', { sortModel: [...this.sortModel] });\n this.requestRender();\n\n return true;\n }\n\n override afterRender(): void {\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n const showIndex = this.config.showSortIndex !== false;\n\n // Update all sortable header cells with sort indicators\n const headerCells = shadowRoot.querySelectorAll('.header-row .cell[data-field]');\n headerCells.forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n\n const sortIndex = getSortIndex(this.sortModel, field);\n const sortDir = getSortDirection(this.sortModel, field);\n\n // Remove existing sort index badge (always clean up)\n const existingBadge = cell.querySelector('.sort-index');\n existingBadge?.remove();\n\n if (sortDir) {\n // Column is sorted - remove base indicator and add our own\n const existingIndicator = cell.querySelector('[part~=\"sort-indicator\"], .sort-indicator');\n existingIndicator?.remove();\n\n cell.setAttribute('data-sort', sortDir);\n\n // Add sort arrow indicator - insert BEFORE filter button and resize handle\n // to maintain consistent order: [label, sort-indicator, sort-index, filter-btn, resize-handle]\n const indicator = document.createElement('span');\n indicator.className = 'sort-indicator';\n // Use grid-level icons (fall back to defaults)\n this.setIcon(indicator, this.resolveIcon(sortDir === 'asc' ? 'sortAsc' : 'sortDesc'));\n\n // Find insertion point: before filter button or resize handle\n const filterBtn = cell.querySelector('.tbw-filter-btn');\n const resizeHandle = cell.querySelector('.resize-handle');\n const insertBefore = filterBtn ?? resizeHandle;\n if (insertBefore) {\n cell.insertBefore(indicator, insertBefore);\n } else {\n cell.appendChild(indicator);\n }\n\n // Add sort index badge if multiple columns sorted and showSortIndex is enabled\n if (showIndex && this.sortModel.length > 1 && sortIndex !== undefined) {\n const badge = document.createElement('span');\n badge.className = 'sort-index';\n badge.textContent = String(sortIndex);\n // Insert badge right after the indicator\n if (indicator.nextSibling) {\n cell.insertBefore(badge, indicator.nextSibling);\n } else {\n cell.appendChild(badge);\n }\n }\n } else {\n cell.removeAttribute('data-sort');\n // For unsorted columns, leave the base indicator (⇅) alone\n }\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current sort model.\n * @returns Copy of the current sort model\n */\n getSortModel(): SortModel[] {\n return [...this.sortModel];\n }\n\n /**\n * Set the sort model programmatically.\n * @param model - New sort model to apply\n */\n setSortModel(model: SortModel[]): void {\n this.sortModel = [...model];\n this.emit('sort-change', { sortModel: [...model] });\n this.requestRender();\n }\n\n /**\n * Clear all sorting.\n */\n clearSort(): void {\n this.sortModel = [];\n this.emit('sort-change', { sortModel: [] });\n this.requestRender();\n }\n\n /**\n * Get the sort index (1-based) for a specific field.\n * @param field - Field to check\n * @returns 1-based index or undefined if not sorted\n */\n getSortIndex(field: string): number | undefined {\n return getSortIndex(this.sortModel, field);\n }\n\n /**\n * Get the sort direction for a specific field.\n * @param field - Field to check\n * @returns Sort direction or undefined if not sorted\n */\n getSortDirection(field: string): 'asc' | 'desc' | undefined {\n return getSortDirection(this.sortModel, field);\n }\n // #endregion\n\n // #region Column State Hooks\n\n /**\n * Return sort state for a column if it's in the sort model.\n */\n override getColumnState(field: string): Partial<ColumnState> | undefined {\n const index = this.sortModel.findIndex((s) => s.field === field);\n if (index === -1) return undefined;\n\n const sortEntry = this.sortModel[index];\n return {\n sort: {\n direction: sortEntry.direction,\n priority: index,\n },\n };\n }\n\n /**\n * Apply sort state from column state.\n * Rebuilds the sort model from all column states.\n */\n override applyColumnState(field: string, state: ColumnState): void {\n // Only process if the column has sort state\n if (!state.sort) {\n // Remove this field from sortModel if it exists\n this.sortModel = this.sortModel.filter((s) => s.field !== field);\n return;\n }\n\n // Find existing entry or add new one\n const existingIndex = this.sortModel.findIndex((s) => s.field === field);\n const newEntry: SortModel = {\n field,\n direction: state.sort.direction,\n };\n\n if (existingIndex !== -1) {\n // Update existing entry\n this.sortModel[existingIndex] = newEntry;\n } else {\n // Add at the correct priority position\n this.sortModel.splice(state.sort.priority, 0, newEntry);\n }\n\n // Re-sort the model by priority to ensure correct order\n // This is handled after all columns are processed, but we maintain order here\n }\n // #endregion\n\n // #region Styles\n\n override readonly styles = styles;\n // #endregion\n}\n"],"names":["applySorts","rows","sorts","columns","a","b","sort","comparator","c","defaultComparator","aVal","bVal","result","toggleSort","current","field","shiftKey","maxColumns","existing","s","getSortIndex","sortModel","index","getSortDirection","MultiSortPlugin","BaseGridPlugin","event","shadowRoot","showIndex","cell","sortIndex","sortDir","indicator","filterBtn","resizeHandle","insertBefore","badge","model","state","existingIndex","newEntry","styles"],"mappings":"qUAkBO,SAASA,EAA2BC,EAAcC,EAAoBC,EAAuC,CAClH,OAAKD,EAAM,OAEJ,CAAC,GAAGD,CAAI,EAAE,KAAK,CAACG,EAAGC,IAAM,CAC9B,UAAWC,KAAQJ,EAAO,CAExB,MAAMK,EADMJ,EAAQ,KAAMK,GAAMA,EAAE,QAAUF,EAAK,KAAK,GAC9B,gBAAkBG,EACpCC,EAAQN,EAA8BE,EAAK,KAAK,EAChDK,EAAQN,EAA8BC,EAAK,KAAK,EAChDM,EAASL,EAAWG,EAAMC,EAAMP,EAAGC,CAAC,EAC1C,GAAIO,IAAW,EACb,OAAON,EAAK,YAAc,MAAQM,EAAS,CAACA,CAEhD,CACA,MAAO,EACT,CAAC,EAdyB,CAAC,GAAGX,CAAI,CAepC,CAUO,SAASQ,EAAkBL,EAAYC,EAAoB,CAEhE,OAAID,GAAK,MAAQC,GAAK,KAAa,EAC/BD,GAAK,KAAa,EAClBC,GAAK,KAAa,GAGlB,OAAOD,GAAM,UAAY,OAAOC,GAAM,SACjCD,EAAIC,EAGTD,aAAa,MAAQC,aAAa,KAC7BD,EAAE,UAAYC,EAAE,QAAA,EAIrB,OAAOD,GAAM,WAAa,OAAOC,GAAM,UAClCD,IAAMC,EAAI,EAAID,EAAI,GAAK,EAIzB,OAAOA,CAAC,EAAE,cAAc,OAAOC,CAAC,CAAC,CAC1C,CAaO,SAASQ,EAAWC,EAAsBC,EAAeC,EAAmBC,EAAiC,CAClH,MAAMC,EAAWJ,EAAQ,KAAMK,GAAMA,EAAE,QAAUJ,CAAK,EAEtD,OAAIC,EAEEE,EACEA,EAAS,YAAc,MAElBJ,EAAQ,IAAKK,GAAOA,EAAE,QAAUJ,EAAQ,CAAE,GAAGI,EAAG,UAAW,MAAA,EAAoBA,CAAE,EAGjFL,EAAQ,OAAQK,GAAMA,EAAE,QAAUJ,CAAK,EAEvCD,EAAQ,OAASG,EAEnB,CAAC,GAAGH,EAAS,CAAE,MAAAC,EAAO,UAAW,MAAgB,EAGnDD,EAGHI,GAAU,YAAc,MACnB,CAAC,CAAE,MAAAH,EAAO,UAAW,OAAQ,EAC3BG,GAAU,YAAc,OAC1B,CAAA,EAEF,CAAC,CAAE,MAAAH,EAAO,UAAW,MAAO,CAEvC,CAUO,SAASK,EAAaC,EAAwBN,EAAmC,CACtF,MAAMO,EAAQD,EAAU,UAAWF,GAAMA,EAAE,QAAUJ,CAAK,EAC1D,OAAOO,GAAS,EAAIA,EAAQ,EAAI,MAClC,CASO,SAASC,EAAiBF,EAAwBN,EAA2C,CAClG,OAAOM,EAAU,KAAMF,GAAMA,EAAE,QAAUJ,CAAK,GAAG,SACnD,8eC9GO,MAAMS,UAAwBC,EAAAA,cAAgC,CAC1D,KAAO,YACE,QAAU,QAE5B,IAAuB,eAA0C,CAC/D,MAAO,CACL,eAAgB,EAChB,cAAe,EAAA,CAEnB,CAGQ,UAAyB,CAAA,EAKxB,QAAe,CACtB,KAAK,UAAY,CAAA,CACnB,CAKS,YAAYxB,EAAqC,CACxD,OAAI,KAAK,UAAU,SAAW,EACrB,CAAC,GAAGA,CAAI,EAEVD,EAAW,CAAC,GAAGC,CAAI,EAAG,KAAK,UAAW,CAAC,GAAG,KAAK,OAAO,CAAC,CAChE,CAES,cAAcyB,EAAkC,CAEvD,GAAI,CADW,KAAK,QAAQ,KAAMlB,GAAMA,EAAE,QAAUkB,EAAM,KAAK,GAClD,SAAU,MAAO,GAE9B,MAAMV,EAAWU,EAAM,cAAc,SAC/BT,EAAa,KAAK,OAAO,gBAAkB,EAEjD,YAAK,UAAYJ,EAAW,KAAK,UAAWa,EAAM,MAAOV,EAAUC,CAAU,EAE7E,KAAK,KAAK,cAAe,CAAE,UAAW,CAAC,GAAG,KAAK,SAAS,EAAG,EAC3D,KAAK,cAAA,EAEE,EACT,CAES,aAAoB,CAC3B,MAAMU,EAAa,KAAK,WACxB,GAAI,CAACA,EAAY,OAEjB,MAAMC,EAAY,KAAK,OAAO,gBAAkB,GAG5BD,EAAW,iBAAiB,+BAA+B,EACnE,QAASE,GAAS,CAC5B,MAAMd,EAAQc,EAAK,aAAa,YAAY,EAC5C,GAAI,CAACd,EAAO,OAEZ,MAAMe,EAAYV,EAAa,KAAK,UAAWL,CAAK,EAC9CgB,EAAUR,EAAiB,KAAK,UAAWR,CAAK,EAMtD,GAHsBc,EAAK,cAAc,aAAa,GACvC,OAAA,EAEXE,EAAS,CAEeF,EAAK,cAAc,2CAA2C,GACrE,OAAA,EAEnBA,EAAK,aAAa,YAAaE,CAAO,EAItC,MAAMC,EAAY,SAAS,cAAc,MAAM,EAC/CA,EAAU,UAAY,iBAEtB,KAAK,QAAQA,EAAW,KAAK,YAAYD,IAAY,MAAQ,UAAY,UAAU,CAAC,EAGpF,MAAME,EAAYJ,EAAK,cAAc,iBAAiB,EAChDK,EAAeL,EAAK,cAAc,gBAAgB,EAClDM,EAAeF,GAAaC,EAQlC,GAPIC,EACFN,EAAK,aAAaG,EAAWG,CAAY,EAEzCN,EAAK,YAAYG,CAAS,EAIxBJ,GAAa,KAAK,UAAU,OAAS,GAAKE,IAAc,OAAW,CACrE,MAAMM,EAAQ,SAAS,cAAc,MAAM,EAC3CA,EAAM,UAAY,aAClBA,EAAM,YAAc,OAAON,CAAS,EAEhCE,EAAU,YACZH,EAAK,aAAaO,EAAOJ,EAAU,WAAW,EAE9CH,EAAK,YAAYO,CAAK,CAE1B,CACF,MACEP,EAAK,gBAAgB,WAAW,CAGpC,CAAC,CACH,CASA,cAA4B,CAC1B,MAAO,CAAC,GAAG,KAAK,SAAS,CAC3B,CAMA,aAAaQ,EAA0B,CACrC,KAAK,UAAY,CAAC,GAAGA,CAAK,EAC1B,KAAK,KAAK,cAAe,CAAE,UAAW,CAAC,GAAGA,CAAK,EAAG,EAClD,KAAK,cAAA,CACP,CAKA,WAAkB,CAChB,KAAK,UAAY,CAAA,EACjB,KAAK,KAAK,cAAe,CAAE,UAAW,CAAA,EAAI,EAC1C,KAAK,cAAA,CACP,CAOA,aAAatB,EAAmC,CAC9C,OAAOK,EAAa,KAAK,UAAWL,CAAK,CAC3C,CAOA,iBAAiBA,EAA2C,CAC1D,OAAOQ,EAAiB,KAAK,UAAWR,CAAK,CAC/C,CAQS,eAAeA,EAAiD,CACvE,MAAMO,EAAQ,KAAK,UAAU,UAAWH,GAAMA,EAAE,QAAUJ,CAAK,EAC/D,OAAIO,IAAU,GAAI,OAGX,CACL,KAAM,CACJ,UAHc,KAAK,UAAUA,CAAK,EAGb,UACrB,SAAUA,CAAA,CACZ,CAEJ,CAMS,iBAAiBP,EAAeuB,EAA0B,CAEjE,GAAI,CAACA,EAAM,KAAM,CAEf,KAAK,UAAY,KAAK,UAAU,OAAQnB,GAAMA,EAAE,QAAUJ,CAAK,EAC/D,MACF,CAGA,MAAMwB,EAAgB,KAAK,UAAU,UAAWpB,GAAMA,EAAE,QAAUJ,CAAK,EACjEyB,EAAsB,CAC1B,MAAAzB,EACA,UAAWuB,EAAM,KAAK,SAAA,EAGpBC,IAAkB,GAEpB,KAAK,UAAUA,CAAa,EAAIC,EAGhC,KAAK,UAAU,OAAOF,EAAM,KAAK,SAAU,EAAGE,CAAQ,CAK1D,CAKkB,OAASC,CAE7B"}
@@ -1,2 +1,2 @@
1
- (function(d,h){typeof exports=="object"&&typeof module<"u"?h(exports,require("../../core/plugin/base-plugin"),require("../../core/internal/aggregators")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin","../../core/internal/aggregators"],h):(d=typeof globalThis<"u"?globalThis:d||self,h(d.TbwGridPlugin_pinnedRows={},d.TbwGrid,d.TbwGrid))})(this,(function(d,h,w){"use strict";function v(n){return typeof n=="object"&&n!==null&&"aggFunc"in n}function u(n,t){const o=document.createElement("div");o.className="tbw-pinned-rows",o.setAttribute("role","presentation"),o.setAttribute("aria-live","polite");const r=document.createElement("div");r.className="tbw-pinned-rows-left";const s=document.createElement("div");s.className="tbw-pinned-rows-center";const i=document.createElement("div");if(i.className="tbw-pinned-rows-right",n.showRowCount!==!1){const e=document.createElement("span");e.className="tbw-status-panel tbw-status-panel-row-count",e.textContent=`Total: ${t.totalRows} rows`,r.appendChild(e)}if(n.showFilteredCount&&t.filteredRows!==t.totalRows){const e=document.createElement("span");e.className="tbw-status-panel tbw-status-panel-filtered-count",e.textContent=`Filtered: ${t.filteredRows}`,r.appendChild(e)}if(n.showSelectedCount&&t.selectedRows>0){const e=document.createElement("span");e.className="tbw-status-panel tbw-status-panel-selected-count",e.textContent=`Selected: ${t.selectedRows}`,i.appendChild(e)}if(n.customPanels)for(const e of n.customPanels){const a=A(e,t);switch(e.position){case"left":r.appendChild(a);break;case"center":s.appendChild(a);break;case"right":i.appendChild(a);break}}return o.appendChild(r),o.appendChild(s),o.appendChild(i),o}function m(n){const t=document.createElement("div");return t.className=`tbw-aggregation-rows tbw-aggregation-rows-${n}`,t.setAttribute("role","presentation"),t}function b(n,t,o,r){n.innerHTML="";for(const s of t){const i=document.createElement("div");if(i.className="tbw-aggregation-row",i.setAttribute("role","presentation"),s.id&&i.setAttribute("data-aggregation-id",s.id),s.fullWidth){const e=document.createElement("div");e.className="tbw-aggregation-cell tbw-aggregation-cell-full",e.style.gridColumn="1 / -1",e.textContent=s.label||"",i.appendChild(e)}else for(const e of o){const a=document.createElement("div");a.className="tbw-aggregation-cell",a.setAttribute("data-field",e.field);let l,p;const f=s.aggregators?.[e.field];if(f)if(v(f)){const g=w.getAggregator(f.aggFunc);g&&(l=g(r,e.field,e)),p=f.formatter}else{const g=w.getAggregator(f);g&&(l=g(r,e.field,e))}else if(s.cells&&Object.prototype.hasOwnProperty.call(s.cells,e.field)){const g=s.cells[e.field];typeof g=="function"?l=g(r,e.field,e):l=g}l!=null?a.textContent=p?p(l,e.field,e):String(l):a.textContent="",i.appendChild(a)}n.appendChild(i)}}function A(n,t){const o=document.createElement("div");o.className="tbw-status-panel tbw-status-panel-custom",o.id=`status-panel-${n.id}`;const r=n.render(t);return typeof r=="string"?o.innerHTML=r:o.appendChild(r),o}function C(n,t,o,r,s){return{totalRows:n.length,filteredRows:s?.cachedResult?.length??n.length,selectedRows:r?.selected?.size??0,columns:t,rows:n,grid:o}}const R=".tbw-footer{flex-shrink:0;z-index:var(--tbw-z-layer-pinned-rows, 20);background:var(--tbw-color-panel-bg)}.tbw-pinned-rows{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--tbw-pinned-rows-bg, var(--tbw-color-panel-bg));border-top:1px solid var(--tbw-pinned-rows-border, var(--tbw-color-border));font-size:12px;color:var(--tbw-pinned-rows-color, var(--tbw-color-fg-muted));min-height:32px;box-sizing:border-box;min-width:fit-content}.tbw-pinned-rows-left,.tbw-pinned-rows-center,.tbw-pinned-rows-right{display:flex;align-items:center;gap:16px}.tbw-pinned-rows-left{justify-content:flex-start}.tbw-pinned-rows-center{justify-content:center;flex:1}.tbw-pinned-rows-right{justify-content:flex-end}.tbw-status-panel{white-space:nowrap}.tbw-aggregation-rows{min-width:fit-content;background:var(--tbw-aggregation-bg, var(--tbw-color-header-bg))}.tbw-aggregation-rows-top{border-bottom:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-rows-bottom{border-top:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-row{display:grid;grid-template-columns:var(--tbw-column-template);font-size:var(--tbw-aggregation-font-size, .8em);font-weight:var(--tbw-aggregation-font-weight, 600)}.tbw-aggregation-cell{padding:var(--tbw-cell-padding, 2px 8px);min-height:var(--tbw-row-height, 28px);display:flex;align-items:center;border-right:1px solid var(--tbw-color-border-cell)}.tbw-aggregation-cell:last-child{border-right:0}.tbw-aggregation-cell-full{grid-column:1 / -1;border-right:0}";class E extends h.BaseGridPlugin{name="pinnedRows";version="1.0.0";get defaultConfig(){return{position:"bottom",showRowCount:!0,showSelectedCount:!0,showFilteredCount:!0}}infoBarElement=null;topAggregationContainer=null;bottomAggregationContainer=null;footerWrapper=null;detach(){this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null),this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null)}afterRender(){const t=this.shadowRoot;if(!t)return;const o=t.querySelector(".tbw-scroll-area")??t.querySelector(".tbw-grid-content")??t.children[0];if(!o)return;const r=this.getSelectionState(),s=this.getFilterState(),i=C(this.rows,this.columns,this.grid,r,s),e=this.config.aggregationRows||[],a=e.filter(c=>c.position==="top"),l=e.filter(c=>c.position!=="top");if(a.length>0){if(!this.topAggregationContainer){this.topAggregationContainer=m("top");const c=t.querySelector(".header");c&&c.nextSibling?o.insertBefore(this.topAggregationContainer,c.nextSibling):o.appendChild(this.topAggregationContainer)}b(this.topAggregationContainer,a,this.visibleColumns,this.rows)}else this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null);const p=this.config.showRowCount!==!1||this.config.showSelectedCount&&i.selectedRows>0||this.config.showFilteredCount&&i.filteredRows!==i.totalRows||this.config.customPanels&&this.config.customPanels.length>0,f=p&&this.config.position!=="top",g=l.length>0||f;if(p&&this.config.position==="top")if(!this.infoBarElement)this.infoBarElement=u(this.config,i),o.insertBefore(this.infoBarElement,o.firstChild);else{const c=u(this.config,i);this.infoBarElement.replaceWith(c),this.infoBarElement=c}else this.config.position==="top"&&this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null);g?(this.footerWrapper||(this.footerWrapper=document.createElement("div"),this.footerWrapper.className="tbw-footer",o.appendChild(this.footerWrapper)),this.footerWrapper.innerHTML="",l.length>0&&(this.bottomAggregationContainer||(this.bottomAggregationContainer=m("bottom")),this.footerWrapper.appendChild(this.bottomAggregationContainer),b(this.bottomAggregationContainer,l,this.visibleColumns,this.rows)),f&&(this.infoBarElement=u(this.config,i),this.footerWrapper.appendChild(this.infoBarElement))):this.cleanupFooter()}cleanup(){this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null),this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null)}cleanupFooter(){this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.infoBarElement&&this.config.position!=="top"&&(this.infoBarElement.remove(),this.infoBarElement=null)}getSelectionState(){try{return this.grid?.getPluginState?.("selection")??null}catch{return null}}getFilterState(){try{return this.grid?.getPluginState?.("filtering")??null}catch{return null}}refresh(){this.requestRender()}getContext(){const t=this.getSelectionState(),o=this.getFilterState();return C(this.rows,this.columns,this.grid,t,o)}addPanel(t){this.config.customPanels||(this.config.customPanels=[]),this.config.customPanels.push(t),this.requestRender()}removePanel(t){this.config.customPanels&&(this.config.customPanels=this.config.customPanels.filter(o=>o.id!==t),this.requestRender())}addAggregationRow(t){this.config.aggregationRows||(this.config.aggregationRows=[]),this.config.aggregationRows.push(t),this.requestRender()}removeAggregationRow(t){this.config.aggregationRows&&(this.config.aggregationRows=this.config.aggregationRows.filter(o=>o.id!==t),this.requestRender())}styles=R}d.PinnedRowsPlugin=E,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(f,p){typeof exports=="object"&&typeof module<"u"?p(exports,require("../../core/plugin/base-plugin"),require("../../core/internal/aggregators")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin","../../core/internal/aggregators"],p):(f=typeof globalThis<"u"?globalThis:f||self,p(f.TbwGridPlugin_pinnedRows={},f.TbwGrid,f.TbwGrid))})(this,(function(f,p,w){"use strict";function v(o){return typeof o=="object"&&o!==null&&"aggFunc"in o}function u(o,e){const n=document.createElement("div");n.className="tbw-pinned-rows",n.setAttribute("role","presentation"),n.setAttribute("aria-live","polite");const r=document.createElement("div");r.className="tbw-pinned-rows-left";const s=document.createElement("div");s.className="tbw-pinned-rows-center";const i=document.createElement("div");if(i.className="tbw-pinned-rows-right",o.showRowCount!==!1){const t=document.createElement("span");t.className="tbw-status-panel tbw-status-panel-row-count",t.textContent=`Total: ${e.totalRows} rows`,r.appendChild(t)}if(o.showFilteredCount&&e.filteredRows!==e.totalRows){const t=document.createElement("span");t.className="tbw-status-panel tbw-status-panel-filtered-count",t.textContent=`Filtered: ${e.filteredRows}`,r.appendChild(t)}if(o.showSelectedCount&&e.selectedRows>0){const t=document.createElement("span");t.className="tbw-status-panel tbw-status-panel-selected-count",t.textContent=`Selected: ${e.selectedRows}`,i.appendChild(t)}if(o.customPanels)for(const t of o.customPanels){const a=A(t,e);switch(t.position){case"left":r.appendChild(a);break;case"center":s.appendChild(a);break;case"right":i.appendChild(a);break}}return n.appendChild(r),n.appendChild(s),n.appendChild(i),n}function m(o){const e=document.createElement("div");return e.className=`tbw-aggregation-rows tbw-aggregation-rows-${o}`,e.setAttribute("role","presentation"),e}function b(o,e,n,r){o.innerHTML="";for(const s of e){const i=document.createElement("div");if(i.className="tbw-aggregation-row",i.setAttribute("role","presentation"),s.id&&i.setAttribute("data-aggregation-id",s.id),s.fullWidth){const t=document.createElement("div");t.className="tbw-aggregation-cell tbw-aggregation-cell-full",t.style.gridColumn="1 / -1",t.textContent=s.label||"",i.appendChild(t)}else for(const t of n){const a=document.createElement("div");a.className="tbw-aggregation-cell",a.setAttribute("data-field",t.field);let l,h;const d=s.aggregators?.[t.field];if(d)if(v(d)){const g=w.getAggregator(d.aggFunc);g&&(l=g(r,t.field,t)),h=d.formatter}else{const g=w.getAggregator(d);g&&(l=g(r,t.field,t))}else if(s.cells&&Object.prototype.hasOwnProperty.call(s.cells,t.field)){const g=s.cells[t.field];typeof g=="function"?l=g(r,t.field,t):l=g}l!=null?a.textContent=h?h(l,t.field,t):String(l):a.textContent="",i.appendChild(a)}o.appendChild(i)}}function A(o,e){const n=document.createElement("div");n.className="tbw-status-panel tbw-status-panel-custom",n.id=`status-panel-${o.id}`;const r=o.render(e);return typeof r=="string"?n.innerHTML=r:n.appendChild(r),n}function C(o,e,n,r,s){return{totalRows:o.length,filteredRows:s?.cachedResult?.length??o.length,selectedRows:r?.selected?.size??0,columns:e,rows:o,grid:n}}const E=".tbw-footer{flex-shrink:0;z-index:var(--tbw-z-layer-pinned-rows, 20);background:var(--tbw-color-panel-bg)}.tbw-pinned-rows{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--tbw-pinned-rows-bg, var(--tbw-color-panel-bg));border-top:1px solid var(--tbw-pinned-rows-border, var(--tbw-color-border));font-size:12px;color:var(--tbw-pinned-rows-color, var(--tbw-color-fg-muted));min-height:32px;box-sizing:border-box;min-width:fit-content}.tbw-pinned-rows-left,.tbw-pinned-rows-center,.tbw-pinned-rows-right{display:flex;align-items:center;gap:16px}.tbw-pinned-rows-left{justify-content:flex-start}.tbw-pinned-rows-center{justify-content:center;flex:1}.tbw-pinned-rows-right{justify-content:flex-end}.tbw-status-panel{white-space:nowrap}.tbw-aggregation-rows{min-width:fit-content;background:var(--tbw-aggregation-bg, var(--tbw-color-header-bg))}.tbw-aggregation-rows-top{border-bottom:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-rows-bottom{border-top:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-row{display:grid;grid-template-columns:var(--tbw-column-template);font-size:var(--tbw-aggregation-font-size, .8em);font-weight:var(--tbw-aggregation-font-weight, 600)}.tbw-aggregation-cell{padding:var(--tbw-cell-padding, 2px 8px);min-height:var(--tbw-row-height, 28px);display:flex;align-items:center;border-right:1px solid var(--tbw-color-border-cell)}.tbw-aggregation-cell:last-child{border-right:0}.tbw-aggregation-cell-full{grid-column:1 / -1;border-right:0}";class R extends p.BaseGridPlugin{name="pinnedRows";version="1.0.0";get defaultConfig(){return{position:"bottom",showRowCount:!0,showSelectedCount:!0,showFilteredCount:!0}}infoBarElement=null;topAggregationContainer=null;bottomAggregationContainer=null;footerWrapper=null;detach(){this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null),this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null)}afterRender(){const e=this.shadowRoot;if(!e)return;const n=e.querySelector(".tbw-scroll-area")??e.querySelector(".tbw-grid-content")??e.children[0];if(!n)return;this.footerWrapper&&!n.contains(this.footerWrapper)&&(this.footerWrapper=null,this.bottomAggregationContainer=null,this.infoBarElement=null),this.topAggregationContainer&&!n.contains(this.topAggregationContainer)&&(this.topAggregationContainer=null),this.infoBarElement&&!n.contains(this.infoBarElement)&&(this.infoBarElement=null);const r=this.getSelectionState(),s=this.getFilterState(),i=C(this.rows,this.columns,this.grid,r,s),t=this.config.aggregationRows||[],a=t.filter(c=>c.position==="top"),l=t.filter(c=>c.position!=="top");if(a.length>0){if(!this.topAggregationContainer){this.topAggregationContainer=m("top");const c=e.querySelector(".header");c&&c.nextSibling?n.insertBefore(this.topAggregationContainer,c.nextSibling):n.appendChild(this.topAggregationContainer)}b(this.topAggregationContainer,a,this.visibleColumns,this.rows)}else this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null);const h=this.config.showRowCount!==!1||this.config.showSelectedCount&&i.selectedRows>0||this.config.showFilteredCount&&i.filteredRows!==i.totalRows||this.config.customPanels&&this.config.customPanels.length>0,d=h&&this.config.position!=="top",g=l.length>0||d;if(h&&this.config.position==="top")if(!this.infoBarElement)this.infoBarElement=u(this.config,i),n.insertBefore(this.infoBarElement,n.firstChild);else{const c=u(this.config,i);this.infoBarElement.replaceWith(c),this.infoBarElement=c}else this.config.position==="top"&&this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null);g?(this.footerWrapper||(this.footerWrapper=document.createElement("div"),this.footerWrapper.className="tbw-footer",n.appendChild(this.footerWrapper)),this.footerWrapper.innerHTML="",l.length>0&&(this.bottomAggregationContainer||(this.bottomAggregationContainer=m("bottom")),this.footerWrapper.appendChild(this.bottomAggregationContainer),b(this.bottomAggregationContainer,l,this.visibleColumns,this.rows)),d&&(this.infoBarElement=u(this.config,i),this.footerWrapper.appendChild(this.infoBarElement))):this.cleanupFooter()}cleanup(){this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null),this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null)}cleanupFooter(){this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.infoBarElement&&this.config.position!=="top"&&(this.infoBarElement.remove(),this.infoBarElement=null)}getSelectionState(){try{return this.grid?.getPluginState?.("selection")??null}catch{return null}}getFilterState(){try{return this.grid?.getPluginState?.("filtering")??null}catch{return null}}refresh(){this.requestRender()}getContext(){const e=this.getSelectionState(),n=this.getFilterState();return C(this.rows,this.columns,this.grid,e,n)}addPanel(e){this.config.customPanels||(this.config.customPanels=[]),this.config.customPanels.push(e),this.requestRender()}removePanel(e){this.config.customPanels&&(this.config.customPanels=this.config.customPanels.filter(n=>n.id!==e),this.requestRender())}addAggregationRow(e){this.config.aggregationRows||(this.config.aggregationRows=[]),this.config.aggregationRows.push(e),this.requestRender()}removeAggregationRow(e){this.config.aggregationRows&&(this.config.aggregationRows=this.config.aggregationRows.filter(n=>n.id!==e),this.requestRender())}styles=E}f.PinnedRowsPlugin=R,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})}));
2
2
  //# sourceMappingURL=pinned-rows.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pinned-rows.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/pinned-rows/pinned-rows.ts","../../../../../libs/grid/src/lib/plugins/pinned-rows/PinnedRowsPlugin.ts"],"sourcesContent":["/**\n * Status Bar Rendering Logic\n *\n * Pure functions for creating and updating the status bar UI.\n * Includes both info bar and aggregation row rendering.\n */\n\nimport { getAggregator } from '../../core/internal/aggregators';\nimport type { ColumnConfig } from '../../core/types';\nimport type {\n AggregationRowConfig,\n AggregatorConfig,\n AggregatorDefinition,\n PinnedRowsConfig,\n PinnedRowsContext,\n PinnedRowsPanel,\n} from './types';\n\n/**\n * Check if an aggregator definition is a full config object (with aggFunc and optional formatter).\n */\nfunction isAggregatorConfig(def: AggregatorDefinition): def is AggregatorConfig {\n return typeof def === 'object' && def !== null && 'aggFunc' in def;\n}\n\n/**\n * Creates the info bar DOM element with all configured panels.\n *\n * @param config - The status bar configuration\n * @param context - The current grid context for rendering\n * @returns The complete info bar element\n */\nexport function createInfoBarElement(config: PinnedRowsConfig, context: PinnedRowsContext): HTMLElement {\n const pinnedRows = document.createElement('div');\n pinnedRows.className = 'tbw-pinned-rows';\n pinnedRows.setAttribute('role', 'presentation');\n pinnedRows.setAttribute('aria-live', 'polite');\n\n const left = document.createElement('div');\n left.className = 'tbw-pinned-rows-left';\n\n const center = document.createElement('div');\n center.className = 'tbw-pinned-rows-center';\n\n const right = document.createElement('div');\n right.className = 'tbw-pinned-rows-right';\n\n // Default panels - row count\n if (config.showRowCount !== false) {\n const rowCount = document.createElement('span');\n rowCount.className = 'tbw-status-panel tbw-status-panel-row-count';\n rowCount.textContent = `Total: ${context.totalRows} rows`;\n left.appendChild(rowCount);\n }\n\n // Filtered count panel (only shows when filter is active)\n if (config.showFilteredCount && context.filteredRows !== context.totalRows) {\n const filteredCount = document.createElement('span');\n filteredCount.className = 'tbw-status-panel tbw-status-panel-filtered-count';\n filteredCount.textContent = `Filtered: ${context.filteredRows}`;\n left.appendChild(filteredCount);\n }\n\n // Selected count panel (only shows when rows are selected)\n if (config.showSelectedCount && context.selectedRows > 0) {\n const selectedCount = document.createElement('span');\n selectedCount.className = 'tbw-status-panel tbw-status-panel-selected-count';\n selectedCount.textContent = `Selected: ${context.selectedRows}`;\n right.appendChild(selectedCount);\n }\n\n // Render custom panels\n if (config.customPanels) {\n for (const panel of config.customPanels) {\n const panelEl = renderCustomPanel(panel, context);\n switch (panel.position) {\n case 'left':\n left.appendChild(panelEl);\n break;\n case 'center':\n center.appendChild(panelEl);\n break;\n case 'right':\n right.appendChild(panelEl);\n break;\n }\n }\n }\n\n pinnedRows.appendChild(left);\n pinnedRows.appendChild(center);\n pinnedRows.appendChild(right);\n\n return pinnedRows;\n}\n\n/**\n * Creates a container for aggregation rows at top or bottom.\n *\n * @param position - 'top' or 'bottom'\n * @returns The container element\n */\nexport function createAggregationContainer(position: 'top' | 'bottom'): HTMLElement {\n const container = document.createElement('div');\n container.className = `tbw-aggregation-rows tbw-aggregation-rows-${position}`;\n // Use presentation role since aggregation rows are outside the role=\"grid\" element for layout reasons\n container.setAttribute('role', 'presentation');\n return container;\n}\n\n/**\n * Renders aggregation rows into a container.\n *\n * @param container - The container to render into\n * @param rows - Aggregation row configurations\n * @param columns - Current column configuration\n * @param dataRows - Current row data for aggregation calculations\n */\nexport function renderAggregationRows(\n container: HTMLElement,\n rows: AggregationRowConfig[],\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\n container.innerHTML = '';\n\n for (const rowConfig of rows) {\n const rowEl = document.createElement('div');\n rowEl.className = 'tbw-aggregation-row';\n // Use presentation role since aggregation rows are outside the role=\"grid\" element\n rowEl.setAttribute('role', 'presentation');\n if (rowConfig.id) {\n rowEl.setAttribute('data-aggregation-id', rowConfig.id);\n }\n\n if (rowConfig.fullWidth) {\n // Full-width mode: single cell spanning all columns\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell tbw-aggregation-cell-full';\n cell.style.gridColumn = '1 / -1';\n cell.textContent = rowConfig.label || '';\n rowEl.appendChild(cell);\n } else {\n // Per-column mode: one cell per column with aggregated/static values\n for (const col of columns) {\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell';\n cell.setAttribute('data-field', col.field);\n\n let value: unknown;\n let formatter: ((value: unknown, field: string, column?: ColumnConfig) => string) | undefined;\n\n // Check for aggregator first\n const aggDef = rowConfig.aggregators?.[col.field];\n if (aggDef) {\n // Handle both simple ref and full config object\n if (isAggregatorConfig(aggDef)) {\n const aggFn = getAggregator(aggDef.aggFunc);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n formatter = aggDef.formatter;\n } else {\n const aggFn = getAggregator(aggDef);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n }\n } else if (rowConfig.cells && Object.prototype.hasOwnProperty.call(rowConfig.cells, col.field)) {\n // Static or computed cell value\n const staticVal = rowConfig.cells[col.field];\n if (typeof staticVal === 'function') {\n value = staticVal(dataRows, col.field, col);\n } else {\n value = staticVal;\n }\n }\n\n // Apply formatter if provided, otherwise convert to string\n if (value != null) {\n cell.textContent = formatter ? formatter(value, col.field, col) : String(value);\n } else {\n cell.textContent = '';\n }\n rowEl.appendChild(cell);\n }\n }\n\n container.appendChild(rowEl);\n }\n}\n\n/**\n * Renders a custom panel element.\n *\n * @param panel - The panel definition\n * @param context - The current grid context\n * @returns The panel DOM element\n */\nfunction renderCustomPanel(panel: PinnedRowsPanel, context: PinnedRowsContext): HTMLElement {\n const panelEl = document.createElement('div');\n panelEl.className = 'tbw-status-panel tbw-status-panel-custom';\n panelEl.id = `status-panel-${panel.id}`;\n\n const content = panel.render(context);\n\n if (typeof content === 'string') {\n panelEl.innerHTML = content;\n } else {\n panelEl.appendChild(content);\n }\n\n return panelEl;\n}\n\n/**\n * Builds the status bar context from grid state and plugin states.\n *\n * @param rows - Current row data\n * @param columns - Current column configuration\n * @param grid - Grid element reference\n * @param selectionState - Optional selection plugin state\n * @param filterState - Optional filtering plugin state\n * @returns The status bar context\n */\nexport function buildContext(\n rows: unknown[],\n columns: unknown[],\n grid: HTMLElement,\n selectionState?: { selected: Set<number> } | null,\n filterState?: { cachedResult: unknown[] | null } | null,\n): PinnedRowsContext {\n return {\n totalRows: rows.length,\n filteredRows: filterState?.cachedResult?.length ?? rows.length,\n selectedRows: selectionState?.selected?.size ?? 0,\n columns: columns as PinnedRowsContext['columns'],\n rows,\n grid,\n };\n}\n\n// Keep old name as alias for backwards compatibility\nexport const createPinnedRowsElement = createInfoBarElement;\n","/**\n * Pinned Rows Plugin (Class-based)\n *\n * Adds info bars and aggregation rows to the grid.\n * - Info bar: Shows row counts, selection info, and custom panels\n * - Aggregation rows: Footer/header rows with computed values (sum, avg, etc.)\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { buildContext, createAggregationContainer, createInfoBarElement, renderAggregationRows } from './pinned-rows';\nimport styles from './pinned-rows.css?inline';\nimport type { AggregationRowConfig, PinnedRowsConfig, PinnedRowsContext, PinnedRowsPanel } from './types';\n\n/**\n * Pinned Rows Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new PinnedRowsPlugin({\n * enabled: true,\n * position: 'bottom',\n * showRowCount: true,\n * showSelectedCount: true,\n * aggregationRows: [\n * { id: 'totals', position: 'bottom', values: { amount: 'sum' } },\n * ],\n * })\n * ```\n */\nexport class PinnedRowsPlugin extends BaseGridPlugin<PinnedRowsConfig> {\n readonly name = 'pinnedRows';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<PinnedRowsConfig> {\n return {\n position: 'bottom',\n showRowCount: true,\n showSelectedCount: true,\n showFilteredCount: true,\n };\n }\n\n // #region Internal State\n private infoBarElement: HTMLElement | null = null;\n private topAggregationContainer: HTMLElement | null = null;\n private bottomAggregationContainer: HTMLElement | null = null;\n private footerWrapper: HTMLElement | null = null;\n // #endregion\n\n // #region Lifecycle\n override detach(): void {\n if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n // #endregion\n\n // #region Hooks\n override afterRender(): void {\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n // Use .tbw-scroll-area so footer is inside the horizontal scroll area,\n // otherwise fall back to .tbw-grid-content or root container\n const container =\n shadowRoot.querySelector('.tbw-scroll-area') ??\n shadowRoot.querySelector('.tbw-grid-content') ??\n shadowRoot.children[0];\n if (!container) return;\n\n // Build context with plugin states\n const selectionState = this.getSelectionState();\n const filterState = this.getFilterState();\n\n const context = buildContext(\n this.rows as unknown[],\n this.columns as unknown[],\n this.grid as unknown as HTMLElement,\n selectionState,\n filterState,\n );\n\n // #region Handle Aggregation Rows\n const aggregationRows = this.config.aggregationRows || [];\n const topRows = aggregationRows.filter((r) => r.position === 'top');\n const bottomRows = aggregationRows.filter((r) => r.position !== 'top');\n\n // Top aggregation rows\n if (topRows.length > 0) {\n if (!this.topAggregationContainer) {\n this.topAggregationContainer = createAggregationContainer('top');\n const header = shadowRoot.querySelector('.header');\n if (header && header.nextSibling) {\n container.insertBefore(this.topAggregationContainer, header.nextSibling);\n } else {\n container.appendChild(this.topAggregationContainer);\n }\n }\n renderAggregationRows(\n this.topAggregationContainer,\n topRows,\n this.visibleColumns as ColumnConfig[],\n this.rows as unknown[],\n );\n } else if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n\n // Handle footer\n const hasInfoContent =\n this.config.showRowCount !== false ||\n (this.config.showSelectedCount && context.selectedRows > 0) ||\n (this.config.showFilteredCount && context.filteredRows !== context.totalRows) ||\n (this.config.customPanels && this.config.customPanels.length > 0);\n const hasBottomInfoBar = hasInfoContent && this.config.position !== 'top';\n const needsFooter = bottomRows.length > 0 || hasBottomInfoBar;\n\n // Handle top info bar\n if (hasInfoContent && this.config.position === 'top') {\n if (!this.infoBarElement) {\n this.infoBarElement = createInfoBarElement(this.config, context);\n container.insertBefore(this.infoBarElement, container.firstChild);\n } else {\n const newInfoBar = createInfoBarElement(this.config, context);\n this.infoBarElement.replaceWith(newInfoBar);\n this.infoBarElement = newInfoBar;\n }\n } else if (this.config.position === 'top' && this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n\n // Create/manage footer wrapper\n if (needsFooter) {\n if (!this.footerWrapper) {\n this.footerWrapper = document.createElement('div');\n this.footerWrapper.className = 'tbw-footer';\n container.appendChild(this.footerWrapper);\n }\n\n this.footerWrapper.innerHTML = '';\n\n if (bottomRows.length > 0) {\n if (!this.bottomAggregationContainer) {\n this.bottomAggregationContainer = createAggregationContainer('bottom');\n }\n this.footerWrapper.appendChild(this.bottomAggregationContainer);\n renderAggregationRows(\n this.bottomAggregationContainer,\n bottomRows,\n this.visibleColumns as ColumnConfig[],\n this.rows as unknown[],\n );\n }\n\n if (hasBottomInfoBar) {\n this.infoBarElement = createInfoBarElement(this.config, context);\n this.footerWrapper.appendChild(this.infoBarElement);\n }\n } else {\n this.cleanupFooter();\n }\n // #endregion\n }\n // #endregion\n\n // #region Private Methods\n private cleanup(): void {\n if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n\n private cleanupFooter(): void {\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.infoBarElement && this.config.position !== 'top') {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n }\n\n private getSelectionState(): { selected: Set<number> } | null {\n // Try to get selection plugin state\n try {\n const grid = this.grid as any;\n return grid?.getPluginState?.('selection') ?? null;\n } catch {\n return null;\n }\n }\n\n private getFilterState(): { cachedResult: unknown[] | null } | null {\n try {\n const grid = this.grid as any;\n return grid?.getPluginState?.('filtering') ?? null;\n } catch {\n return null;\n }\n }\n // #endregion\n\n // #region Public API\n /**\n * Refresh the status bar to reflect current grid state.\n */\n refresh(): void {\n this.requestRender();\n }\n\n /**\n * Get the current status bar context.\n * @returns The context with row counts and other info\n */\n getContext(): PinnedRowsContext {\n const selectionState = this.getSelectionState();\n const filterState = this.getFilterState();\n\n return buildContext(\n this.rows as unknown[],\n this.columns as unknown[],\n this.grid as unknown as HTMLElement,\n selectionState,\n filterState,\n );\n }\n\n /**\n * Add a custom panel to the info bar.\n * @param panel - The panel configuration to add\n */\n addPanel(panel: PinnedRowsPanel): void {\n if (!this.config.customPanels) {\n this.config.customPanels = [];\n }\n this.config.customPanels.push(panel);\n this.requestRender();\n }\n\n /**\n * Remove a custom panel by ID.\n * @param id - The panel ID to remove\n */\n removePanel(id: string): void {\n if (this.config.customPanels) {\n this.config.customPanels = this.config.customPanels.filter((p) => p.id !== id);\n this.requestRender();\n }\n }\n\n /**\n * Add an aggregation row.\n * @param row - The aggregation row configuration\n */\n addAggregationRow(row: AggregationRowConfig): void {\n if (!this.config.aggregationRows) {\n this.config.aggregationRows = [];\n }\n this.config.aggregationRows.push(row);\n this.requestRender();\n }\n\n /**\n * Remove an aggregation row by ID.\n * @param id - The aggregation row ID to remove\n */\n removeAggregationRow(id: string): void {\n if (this.config.aggregationRows) {\n this.config.aggregationRows = this.config.aggregationRows.filter((r) => r.id !== id);\n this.requestRender();\n }\n }\n // #endregion\n\n // #region Styles\n override readonly styles = styles;\n // #endregion\n}\n"],"names":["isAggregatorConfig","def","createInfoBarElement","config","context","pinnedRows","left","center","right","rowCount","filteredCount","selectedCount","panel","panelEl","renderCustomPanel","createAggregationContainer","position","container","renderAggregationRows","rows","columns","dataRows","rowConfig","rowEl","cell","col","value","formatter","aggDef","aggFn","getAggregator","staticVal","content","buildContext","grid","selectionState","filterState","PinnedRowsPlugin","BaseGridPlugin","shadowRoot","aggregationRows","topRows","r","bottomRows","header","hasInfoContent","hasBottomInfoBar","needsFooter","newInfoBar","id","p","row","styles"],"mappings":"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"}
1
+ {"version":3,"file":"pinned-rows.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/pinned-rows/pinned-rows.ts","../../../../../libs/grid/src/lib/plugins/pinned-rows/PinnedRowsPlugin.ts"],"sourcesContent":["/**\n * Status Bar Rendering Logic\n *\n * Pure functions for creating and updating the status bar UI.\n * Includes both info bar and aggregation row rendering.\n */\n\nimport { getAggregator } from '../../core/internal/aggregators';\nimport type { ColumnConfig } from '../../core/types';\nimport type {\n AggregationRowConfig,\n AggregatorConfig,\n AggregatorDefinition,\n PinnedRowsConfig,\n PinnedRowsContext,\n PinnedRowsPanel,\n} from './types';\n\n/**\n * Check if an aggregator definition is a full config object (with aggFunc and optional formatter).\n */\nfunction isAggregatorConfig(def: AggregatorDefinition): def is AggregatorConfig {\n return typeof def === 'object' && def !== null && 'aggFunc' in def;\n}\n\n/**\n * Creates the info bar DOM element with all configured panels.\n *\n * @param config - The status bar configuration\n * @param context - The current grid context for rendering\n * @returns The complete info bar element\n */\nexport function createInfoBarElement(config: PinnedRowsConfig, context: PinnedRowsContext): HTMLElement {\n const pinnedRows = document.createElement('div');\n pinnedRows.className = 'tbw-pinned-rows';\n pinnedRows.setAttribute('role', 'presentation');\n pinnedRows.setAttribute('aria-live', 'polite');\n\n const left = document.createElement('div');\n left.className = 'tbw-pinned-rows-left';\n\n const center = document.createElement('div');\n center.className = 'tbw-pinned-rows-center';\n\n const right = document.createElement('div');\n right.className = 'tbw-pinned-rows-right';\n\n // Default panels - row count\n if (config.showRowCount !== false) {\n const rowCount = document.createElement('span');\n rowCount.className = 'tbw-status-panel tbw-status-panel-row-count';\n rowCount.textContent = `Total: ${context.totalRows} rows`;\n left.appendChild(rowCount);\n }\n\n // Filtered count panel (only shows when filter is active)\n if (config.showFilteredCount && context.filteredRows !== context.totalRows) {\n const filteredCount = document.createElement('span');\n filteredCount.className = 'tbw-status-panel tbw-status-panel-filtered-count';\n filteredCount.textContent = `Filtered: ${context.filteredRows}`;\n left.appendChild(filteredCount);\n }\n\n // Selected count panel (only shows when rows are selected)\n if (config.showSelectedCount && context.selectedRows > 0) {\n const selectedCount = document.createElement('span');\n selectedCount.className = 'tbw-status-panel tbw-status-panel-selected-count';\n selectedCount.textContent = `Selected: ${context.selectedRows}`;\n right.appendChild(selectedCount);\n }\n\n // Render custom panels\n if (config.customPanels) {\n for (const panel of config.customPanels) {\n const panelEl = renderCustomPanel(panel, context);\n switch (panel.position) {\n case 'left':\n left.appendChild(panelEl);\n break;\n case 'center':\n center.appendChild(panelEl);\n break;\n case 'right':\n right.appendChild(panelEl);\n break;\n }\n }\n }\n\n pinnedRows.appendChild(left);\n pinnedRows.appendChild(center);\n pinnedRows.appendChild(right);\n\n return pinnedRows;\n}\n\n/**\n * Creates a container for aggregation rows at top or bottom.\n *\n * @param position - 'top' or 'bottom'\n * @returns The container element\n */\nexport function createAggregationContainer(position: 'top' | 'bottom'): HTMLElement {\n const container = document.createElement('div');\n container.className = `tbw-aggregation-rows tbw-aggregation-rows-${position}`;\n // Use presentation role since aggregation rows are outside the role=\"grid\" element for layout reasons\n container.setAttribute('role', 'presentation');\n return container;\n}\n\n/**\n * Renders aggregation rows into a container.\n *\n * @param container - The container to render into\n * @param rows - Aggregation row configurations\n * @param columns - Current column configuration\n * @param dataRows - Current row data for aggregation calculations\n */\nexport function renderAggregationRows(\n container: HTMLElement,\n rows: AggregationRowConfig[],\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\n container.innerHTML = '';\n\n for (const rowConfig of rows) {\n const rowEl = document.createElement('div');\n rowEl.className = 'tbw-aggregation-row';\n // Use presentation role since aggregation rows are outside the role=\"grid\" element\n rowEl.setAttribute('role', 'presentation');\n if (rowConfig.id) {\n rowEl.setAttribute('data-aggregation-id', rowConfig.id);\n }\n\n if (rowConfig.fullWidth) {\n // Full-width mode: single cell spanning all columns\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell tbw-aggregation-cell-full';\n cell.style.gridColumn = '1 / -1';\n cell.textContent = rowConfig.label || '';\n rowEl.appendChild(cell);\n } else {\n // Per-column mode: one cell per column with aggregated/static values\n for (const col of columns) {\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell';\n cell.setAttribute('data-field', col.field);\n\n let value: unknown;\n let formatter: ((value: unknown, field: string, column?: ColumnConfig) => string) | undefined;\n\n // Check for aggregator first\n const aggDef = rowConfig.aggregators?.[col.field];\n if (aggDef) {\n // Handle both simple ref and full config object\n if (isAggregatorConfig(aggDef)) {\n const aggFn = getAggregator(aggDef.aggFunc);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n formatter = aggDef.formatter;\n } else {\n const aggFn = getAggregator(aggDef);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n }\n } else if (rowConfig.cells && Object.prototype.hasOwnProperty.call(rowConfig.cells, col.field)) {\n // Static or computed cell value\n const staticVal = rowConfig.cells[col.field];\n if (typeof staticVal === 'function') {\n value = staticVal(dataRows, col.field, col);\n } else {\n value = staticVal;\n }\n }\n\n // Apply formatter if provided, otherwise convert to string\n if (value != null) {\n cell.textContent = formatter ? formatter(value, col.field, col) : String(value);\n } else {\n cell.textContent = '';\n }\n rowEl.appendChild(cell);\n }\n }\n\n container.appendChild(rowEl);\n }\n}\n\n/**\n * Renders a custom panel element.\n *\n * @param panel - The panel definition\n * @param context - The current grid context\n * @returns The panel DOM element\n */\nfunction renderCustomPanel(panel: PinnedRowsPanel, context: PinnedRowsContext): HTMLElement {\n const panelEl = document.createElement('div');\n panelEl.className = 'tbw-status-panel tbw-status-panel-custom';\n panelEl.id = `status-panel-${panel.id}`;\n\n const content = panel.render(context);\n\n if (typeof content === 'string') {\n panelEl.innerHTML = content;\n } else {\n panelEl.appendChild(content);\n }\n\n return panelEl;\n}\n\n/**\n * Builds the status bar context from grid state and plugin states.\n *\n * @param rows - Current row data\n * @param columns - Current column configuration\n * @param grid - Grid element reference\n * @param selectionState - Optional selection plugin state\n * @param filterState - Optional filtering plugin state\n * @returns The status bar context\n */\nexport function buildContext(\n rows: unknown[],\n columns: unknown[],\n grid: HTMLElement,\n selectionState?: { selected: Set<number> } | null,\n filterState?: { cachedResult: unknown[] | null } | null,\n): PinnedRowsContext {\n return {\n totalRows: rows.length,\n filteredRows: filterState?.cachedResult?.length ?? rows.length,\n selectedRows: selectionState?.selected?.size ?? 0,\n columns: columns as PinnedRowsContext['columns'],\n rows,\n grid,\n };\n}\n\n// Keep old name as alias for backwards compatibility\nexport const createPinnedRowsElement = createInfoBarElement;\n","/**\n * Pinned Rows Plugin (Class-based)\n *\n * Adds info bars and aggregation rows to the grid.\n * - Info bar: Shows row counts, selection info, and custom panels\n * - Aggregation rows: Footer/header rows with computed values (sum, avg, etc.)\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { buildContext, createAggregationContainer, createInfoBarElement, renderAggregationRows } from './pinned-rows';\nimport styles from './pinned-rows.css?inline';\nimport type { AggregationRowConfig, PinnedRowsConfig, PinnedRowsContext, PinnedRowsPanel } from './types';\n\n/**\n * Pinned Rows Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new PinnedRowsPlugin({\n * enabled: true,\n * position: 'bottom',\n * showRowCount: true,\n * showSelectedCount: true,\n * aggregationRows: [\n * { id: 'totals', position: 'bottom', values: { amount: 'sum' } },\n * ],\n * })\n * ```\n */\nexport class PinnedRowsPlugin extends BaseGridPlugin<PinnedRowsConfig> {\n readonly name = 'pinnedRows';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<PinnedRowsConfig> {\n return {\n position: 'bottom',\n showRowCount: true,\n showSelectedCount: true,\n showFilteredCount: true,\n };\n }\n\n // #region Internal State\n private infoBarElement: HTMLElement | null = null;\n private topAggregationContainer: HTMLElement | null = null;\n private bottomAggregationContainer: HTMLElement | null = null;\n private footerWrapper: HTMLElement | null = null;\n // #endregion\n\n // #region Lifecycle\n override detach(): void {\n if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n // #endregion\n\n // #region Hooks\n override afterRender(): void {\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n // Use .tbw-scroll-area so footer is inside the horizontal scroll area,\n // otherwise fall back to .tbw-grid-content or root container\n const container =\n shadowRoot.querySelector('.tbw-scroll-area') ??\n shadowRoot.querySelector('.tbw-grid-content') ??\n shadowRoot.children[0];\n if (!container) return;\n\n // Clear orphaned element references if they were removed from the DOM\n // (e.g., by buildGridDOMIntoShadow calling replaceChildren())\n // We check if the element is still inside the container rather than isConnected,\n // because in unit tests the mock grid may not be attached to document.body\n if (this.footerWrapper && !container.contains(this.footerWrapper)) {\n this.footerWrapper = null;\n this.bottomAggregationContainer = null;\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer && !container.contains(this.topAggregationContainer)) {\n this.topAggregationContainer = null;\n }\n if (this.infoBarElement && !container.contains(this.infoBarElement)) {\n this.infoBarElement = null;\n }\n\n // Build context with plugin states\n const selectionState = this.getSelectionState();\n const filterState = this.getFilterState();\n\n const context = buildContext(\n this.rows as unknown[],\n this.columns as unknown[],\n this.grid as unknown as HTMLElement,\n selectionState,\n filterState,\n );\n\n // #region Handle Aggregation Rows\n const aggregationRows = this.config.aggregationRows || [];\n const topRows = aggregationRows.filter((r) => r.position === 'top');\n const bottomRows = aggregationRows.filter((r) => r.position !== 'top');\n\n // Top aggregation rows\n if (topRows.length > 0) {\n if (!this.topAggregationContainer) {\n this.topAggregationContainer = createAggregationContainer('top');\n const header = shadowRoot.querySelector('.header');\n if (header && header.nextSibling) {\n container.insertBefore(this.topAggregationContainer, header.nextSibling);\n } else {\n container.appendChild(this.topAggregationContainer);\n }\n }\n renderAggregationRows(\n this.topAggregationContainer,\n topRows,\n this.visibleColumns as ColumnConfig[],\n this.rows as unknown[],\n );\n } else if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n\n // Handle footer\n const hasInfoContent =\n this.config.showRowCount !== false ||\n (this.config.showSelectedCount && context.selectedRows > 0) ||\n (this.config.showFilteredCount && context.filteredRows !== context.totalRows) ||\n (this.config.customPanels && this.config.customPanels.length > 0);\n const hasBottomInfoBar = hasInfoContent && this.config.position !== 'top';\n const needsFooter = bottomRows.length > 0 || hasBottomInfoBar;\n\n // Handle top info bar\n if (hasInfoContent && this.config.position === 'top') {\n if (!this.infoBarElement) {\n this.infoBarElement = createInfoBarElement(this.config, context);\n container.insertBefore(this.infoBarElement, container.firstChild);\n } else {\n const newInfoBar = createInfoBarElement(this.config, context);\n this.infoBarElement.replaceWith(newInfoBar);\n this.infoBarElement = newInfoBar;\n }\n } else if (this.config.position === 'top' && this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n\n // Create/manage footer wrapper\n if (needsFooter) {\n if (!this.footerWrapper) {\n this.footerWrapper = document.createElement('div');\n this.footerWrapper.className = 'tbw-footer';\n container.appendChild(this.footerWrapper);\n }\n\n this.footerWrapper.innerHTML = '';\n\n if (bottomRows.length > 0) {\n if (!this.bottomAggregationContainer) {\n this.bottomAggregationContainer = createAggregationContainer('bottom');\n }\n this.footerWrapper.appendChild(this.bottomAggregationContainer);\n renderAggregationRows(\n this.bottomAggregationContainer,\n bottomRows,\n this.visibleColumns as ColumnConfig[],\n this.rows as unknown[],\n );\n }\n\n if (hasBottomInfoBar) {\n this.infoBarElement = createInfoBarElement(this.config, context);\n this.footerWrapper.appendChild(this.infoBarElement);\n }\n } else {\n this.cleanupFooter();\n }\n // #endregion\n }\n // #endregion\n\n // #region Private Methods\n private cleanup(): void {\n if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n\n private cleanupFooter(): void {\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.infoBarElement && this.config.position !== 'top') {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n }\n\n private getSelectionState(): { selected: Set<number> } | null {\n // Try to get selection plugin state\n try {\n return (this.grid?.getPluginState?.('selection') as { selected: Set<number> } | null) ?? null;\n } catch {\n return null;\n }\n }\n\n private getFilterState(): { cachedResult: unknown[] | null } | null {\n try {\n return (this.grid?.getPluginState?.('filtering') as { cachedResult: unknown[] | null } | null) ?? null;\n } catch {\n return null;\n }\n }\n // #endregion\n\n // #region Public API\n /**\n * Refresh the status bar to reflect current grid state.\n */\n refresh(): void {\n this.requestRender();\n }\n\n /**\n * Get the current status bar context.\n * @returns The context with row counts and other info\n */\n getContext(): PinnedRowsContext {\n const selectionState = this.getSelectionState();\n const filterState = this.getFilterState();\n\n return buildContext(\n this.rows as unknown[],\n this.columns as unknown[],\n this.grid as unknown as HTMLElement,\n selectionState,\n filterState,\n );\n }\n\n /**\n * Add a custom panel to the info bar.\n * @param panel - The panel configuration to add\n */\n addPanel(panel: PinnedRowsPanel): void {\n if (!this.config.customPanels) {\n this.config.customPanels = [];\n }\n this.config.customPanels.push(panel);\n this.requestRender();\n }\n\n /**\n * Remove a custom panel by ID.\n * @param id - The panel ID to remove\n */\n removePanel(id: string): void {\n if (this.config.customPanels) {\n this.config.customPanels = this.config.customPanels.filter((p) => p.id !== id);\n this.requestRender();\n }\n }\n\n /**\n * Add an aggregation row.\n * @param row - The aggregation row configuration\n */\n addAggregationRow(row: AggregationRowConfig): void {\n if (!this.config.aggregationRows) {\n this.config.aggregationRows = [];\n }\n this.config.aggregationRows.push(row);\n this.requestRender();\n }\n\n /**\n * Remove an aggregation row by ID.\n * @param id - The aggregation row ID to remove\n */\n removeAggregationRow(id: string): void {\n if (this.config.aggregationRows) {\n this.config.aggregationRows = this.config.aggregationRows.filter((r) => r.id !== id);\n this.requestRender();\n }\n }\n // #endregion\n\n // #region Styles\n override readonly styles = styles;\n // #endregion\n}\n"],"names":["isAggregatorConfig","def","createInfoBarElement","config","context","pinnedRows","left","center","right","rowCount","filteredCount","selectedCount","panel","panelEl","renderCustomPanel","createAggregationContainer","position","container","renderAggregationRows","rows","columns","dataRows","rowConfig","rowEl","cell","col","value","formatter","aggDef","aggFn","getAggregator","staticVal","content","buildContext","grid","selectionState","filterState","PinnedRowsPlugin","BaseGridPlugin","shadowRoot","aggregationRows","topRows","r","bottomRows","header","hasInfoContent","hasBottomInfoBar","needsFooter","newInfoBar","id","p","row","styles"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- (function(g,h){typeof exports=="object"&&typeof module<"u"?h(exports,require("../../core/internal/keyboard"),require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/keyboard","../../core/plugin/base-plugin"],h):(g=typeof globalThis<"u"?globalThis:g||self,h(g.TbwGridPlugin_reorder={},g.TbwGrid,g.TbwGrid))})(this,(function(g,h,b){"use strict";function w(c){const e=c.meta??{};return e.lockPosition!==!0&&e.suppressMovable!==!0}function v(c,e,t){if(e===t||e<0||e>=c.length||t<0||t>c.length)return c;const n=[...c],[r]=n.splice(e,1);return n.splice(t,0,r),n}const y='.header-row>.cell[draggable=true]{cursor:grab;position:relative}.header-row>.cell.dragging{opacity:.5;cursor:grabbing}.header-row>.cell.drop-before:before{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent));z-index:1}.header-row>.cell.drop-after:after{content:"";position:absolute;right:0;top:0;bottom:0;width:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent));z-index:1}.cell.flip-animating{transition:transform var(--tbw-animation-duration, .2s) ease-out;will-change:transform;z-index:1}@keyframes reorder-fade-in{0%{opacity:0}to{opacity:1}}.cell.fade-animating{animation:reorder-fade-in var(--tbw-animation-duration, .2s) ease-out backwards}';class O extends b.BaseGridPlugin{name="reorder";version="1.0.0";get defaultConfig(){return{animation:"flip"}}get animationType(){return this.isAnimationEnabled?this.config.animation!==void 0?this.config.animation:this.config.viewTransition===!1?!1:(this.config.viewTransition===!0,"flip"):!1}get isAnimationEnabled(){const t=this.grid.effectiveConfig?.animation?.mode??"reduced-motion";if(t===!1||t==="off")return!1;if(t===!0||t==="on")return!0;const n=this.shadowRoot?.host;return n?getComputedStyle(n).getPropertyValue("--tbw-animation-enabled").trim()!=="0":!0}get animationDuration(){if(this.config.animationDuration!==void 0)return this.config.animationDuration;const e=this.shadowRoot?.host;if(e){const t=getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(),n=parseInt(t,10);if(!isNaN(n))return n}return 200}isDragging=!1;draggedField=null;draggedIndex=null;dropIndex=null;attach(e){super.attach(e),e.addEventListener("column-reorder-request",t=>{const n=t.detail;n?.field&&typeof n.toIndex=="number"&&this.moveColumn(n.field,n.toIndex)},{signal:this.disconnectSignal})}detach(){this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null}afterRender(){const e=this.shadowRoot;if(!e)return;e.querySelectorAll(".header-row > .cell").forEach(n=>{const r=n,i=r.getAttribute("data-field");if(!i)return;const s=this.columns.find(o=>o.field===i),l=!this.grid.queryPlugins({type:b.PLUGIN_QUERIES.CAN_MOVE_COLUMN,context:s}).includes(!1);if(!s||!w(s)||!l){r.draggable=!1;return}r.draggable=!0,!r.getAttribute("data-dragstart-bound")&&(r.setAttribute("data-dragstart-bound","true"),r.addEventListener("dragstart",o=>{const u=this.getColumnOrder().indexOf(i);this.isDragging=!0,this.draggedField=i,this.draggedIndex=u,o.dataTransfer&&(o.dataTransfer.effectAllowed="move",o.dataTransfer.setData("text/plain",i)),r.classList.add("dragging")}),r.addEventListener("dragend",()=>{this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null,e.querySelectorAll(".header-row > .cell").forEach(o=>{o.classList.remove("dragging","drop-target","drop-before","drop-after")})}),r.addEventListener("dragover",o=>{if(o.preventDefault(),!this.isDragging||this.draggedField===i)return;const f=r.getBoundingClientRect(),u=f.left+f.width/2,p=this.getColumnOrder().indexOf(i);this.dropIndex=o.clientX<u?p:p+1,r.classList.add("drop-target"),r.classList.toggle("drop-before",o.clientX<u),r.classList.toggle("drop-after",o.clientX>=u)}),r.addEventListener("dragleave",()=>{r.classList.remove("drop-target","drop-before","drop-after")}),r.addEventListener("drop",o=>{o.preventDefault();const f=this.draggedField,u=this.draggedIndex,m=this.dropIndex;if(!this.isDragging||f===null||u===null||m===null)return;const p=m>u?m-1:m,E=this.getColumnOrder(),C=v(E,u,p),x={field:f,fromIndex:u,toIndex:p,columnOrder:C};this.updateColumnOrder(C),this.emit("column-move",x)}))})}onKeyDown(e){if(!e.altKey||e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=this.grid,n=t._focusCol,r=t._visibleColumns;if(n<0||n>=r.length)return;const i=r[n];if(!i||!w(i))return;const s=this.grid;if(s.queryPlugins({type:b.PLUGIN_QUERIES.CAN_MOVE_COLUMN,context:i}).includes(!1))return;const a=this.getColumnOrder(),l=a.indexOf(i.field);if(l===-1)return;const o=e.key==="ArrowLeft"?l-1:l+1;if(o<0||o>=a.length)return;const f=r.find(u=>u.field===a[o]);if(!(f&&s.queryPlugins({type:b.PLUGIN_QUERIES.CAN_MOVE_COLUMN,context:f}).includes(!1)))return this.moveColumn(i.field,o),t._focusCol=o,h.ensureCellVisible(this.grid),e.preventDefault(),e.stopPropagation(),!0}getColumnOrder(){return this.grid.getColumnOrder()}moveColumn(e,t){const n=this.getColumnOrder(),r=n.indexOf(e);if(r===-1)return;const i=v(n,r,t);this.updateColumnOrder(i),this.emit("column-move",{field:e,fromIndex:r,toIndex:t,columnOrder:i})}setColumnOrder(e){this.updateColumnOrder(e)}resetColumnOrder(){const e=this.columns.map(t=>t.field);this.updateColumnOrder(e)}captureHeaderPositions(){const e=new Map;return this.shadowRoot?.querySelectorAll(".header-row > .cell[data-field]").forEach(t=>{const n=t.getAttribute("data-field");n&&e.set(n,t.getBoundingClientRect().left)}),e}animateFLIP(e){const t=this.shadowRoot;if(!t||e.size===0)return;const n=new Map;if(t.querySelectorAll(".header-row > .cell[data-field]").forEach(s=>{const d=s.getAttribute("data-field");if(!d)return;const a=e.get(d);if(a===void 0)return;const l=a-s.getBoundingClientRect().left;Math.abs(l)>1&&n.set(d,l)}),n.size===0)return;const r=[];if(t.querySelectorAll(".cell[data-field]").forEach(s=>{const d=n.get(s.getAttribute("data-field")??"");if(d!==void 0){const a=s;a.style.transform=`translateX(${d}px)`,r.push(a)}}),r.length===0)return;t.host.offsetHeight;const i=this.animationDuration;requestAnimationFrame(()=>{r.forEach(s=>{s.classList.add("flip-animating"),s.style.transform=""}),setTimeout(()=>{r.forEach(s=>{s.style.transform="",s.classList.remove("flip-animating")})},i+50)})}animateFade(e){const t=this.shadowRoot;if(!t){e();return}const n=this.captureHeaderPositions();e();const r=new Set;if(t.querySelectorAll(".header-row > .cell[data-field]").forEach(d=>{const a=d.getAttribute("data-field");if(!a)return;const l=n.get(a);if(l===void 0)return;const o=d.getBoundingClientRect().left;Math.abs(l-o)>1&&r.add(a)}),r.size===0)return;const i=[];if(t.querySelectorAll(".cell[data-field]").forEach(d=>{const a=d.getAttribute("data-field");if(a&&r.has(a)){const l=d;l.classList.add("fade-animating"),i.push(l)}}),i.length===0)return;const s=this.animationDuration;setTimeout(()=>{i.forEach(d=>d.classList.remove("fade-animating"))},s+50)}updateColumnOrder(e){const t=this.grid,n=this.animationType;if(n==="flip"&&this.shadowRoot){const r=this.captureHeaderPositions();t.setColumnOrder(e),this.shadowRoot.host.offsetHeight,this.animateFLIP(r)}else n==="fade"?this.animateFade(()=>t.setColumnOrder(e)):t.setColumnOrder(e);t.requestStateChange?.()}styles=y}g.ReorderPlugin=O,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(g,h){typeof exports=="object"&&typeof module<"u"?h(exports,require("../../core/internal/keyboard"),require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/keyboard","../../core/plugin/base-plugin"],h):(g=typeof globalThis<"u"?globalThis:g||self,h(g.TbwGridPlugin_reorder={},g.TbwGrid,g.TbwGrid))})(this,(function(g,h,b){"use strict";function w(c){const e=c.meta??{};return e.lockPosition!==!0&&e.suppressMovable!==!0}function v(c,e,t){if(e===t||e<0||e>=c.length||t<0||t>c.length)return c;const n=[...c],[r]=n.splice(e,1);return n.splice(t,0,r),n}const y='.header-row>.cell[draggable=true]{cursor:grab;position:relative}.header-row>.cell.dragging{opacity:.5;cursor:grabbing}.header-row>.cell.drop-before:before{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent));z-index:1}.header-row>.cell.drop-after:after{content:"";position:absolute;right:0;top:0;bottom:0;width:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent));z-index:1}.cell.flip-animating{transition:transform var(--tbw-animation-duration, .2s) ease-out;will-change:transform;z-index:1}@keyframes reorder-fade-in{0%{opacity:0}to{opacity:1}}.cell.fade-animating{animation:reorder-fade-in var(--tbw-animation-duration, .2s) ease-out backwards}';class O extends b.BaseGridPlugin{name="reorder";version="1.0.0";get defaultConfig(){return{animation:"flip"}}get animationType(){return this.isAnimationEnabled?this.config.animation!==void 0?this.config.animation:this.config.viewTransition===!1?!1:(this.config.viewTransition===!0,"flip"):!1}get isAnimationEnabled(){const t=this.grid.effectiveConfig?.animation?.mode??"reduced-motion";if(t===!1||t==="off")return!1;if(t===!0||t==="on")return!0;const n=this.shadowRoot?.host;return n?getComputedStyle(n).getPropertyValue("--tbw-animation-enabled").trim()!=="0":!0}get animationDuration(){if(this.config.animationDuration!==void 0)return this.config.animationDuration;const e=this.shadowRoot?.host;if(e){const t=getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(),n=parseInt(t,10);if(!isNaN(n))return n}return 200}isDragging=!1;draggedField=null;draggedIndex=null;dropIndex=null;attach(e){super.attach(e),e.addEventListener("column-reorder-request",t=>{const n=t.detail;n?.field&&typeof n.toIndex=="number"&&this.moveColumn(n.field,n.toIndex)},{signal:this.disconnectSignal})}detach(){this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null}afterRender(){const e=this.shadowRoot;if(!e)return;e.querySelectorAll(".header-row > .cell").forEach(n=>{const r=n,i=r.getAttribute("data-field");if(!i)return;const s=this.columns.find(o=>o.field===i),l=!this.grid.queryPlugins({type:b.PLUGIN_QUERIES.CAN_MOVE_COLUMN,context:s}).includes(!1);if(!s||!w(s)||!l){r.draggable=!1;return}r.draggable=!0,!r.getAttribute("data-dragstart-bound")&&(r.setAttribute("data-dragstart-bound","true"),r.addEventListener("dragstart",o=>{const u=this.getColumnOrder().indexOf(i);this.isDragging=!0,this.draggedField=i,this.draggedIndex=u,o.dataTransfer&&(o.dataTransfer.effectAllowed="move",o.dataTransfer.setData("text/plain",i)),r.classList.add("dragging")}),r.addEventListener("dragend",()=>{this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null,e.querySelectorAll(".header-row > .cell").forEach(o=>{o.classList.remove("dragging","drop-target","drop-before","drop-after")})}),r.addEventListener("dragover",o=>{if(o.preventDefault(),!this.isDragging||this.draggedField===i)return;const f=r.getBoundingClientRect(),u=f.left+f.width/2,p=this.getColumnOrder().indexOf(i);this.dropIndex=o.clientX<u?p:p+1,r.classList.add("drop-target"),r.classList.toggle("drop-before",o.clientX<u),r.classList.toggle("drop-after",o.clientX>=u)}),r.addEventListener("dragleave",()=>{r.classList.remove("drop-target","drop-before","drop-after")}),r.addEventListener("drop",o=>{o.preventDefault();const f=this.draggedField,u=this.draggedIndex,m=this.dropIndex;if(!this.isDragging||f===null||u===null||m===null)return;const p=m>u?m-1:m,E=this.getColumnOrder(),C=v(E,u,p),x={field:f,fromIndex:u,toIndex:p,columnOrder:C};this.updateColumnOrder(C),this.emit("column-move",x)}))})}onKeyDown(e){if(!e.altKey||e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=this.grid,n=t._focusCol,r=t._visibleColumns;if(n<0||n>=r.length)return;const i=r[n];if(!i||!w(i))return;const s=this.grid;if(s.queryPlugins({type:b.PLUGIN_QUERIES.CAN_MOVE_COLUMN,context:i}).includes(!1))return;const a=this.getColumnOrder(),l=a.indexOf(i.field);if(l===-1)return;const o=e.key==="ArrowLeft"?l-1:l+1;if(o<0||o>=a.length)return;const f=r.find(u=>u.field===a[o]);if(!(f&&s.queryPlugins({type:b.PLUGIN_QUERIES.CAN_MOVE_COLUMN,context:f}).includes(!1)))return this.moveColumn(i.field,o),t._focusCol=o,h.ensureCellVisible(this.grid),e.preventDefault(),e.stopPropagation(),!0}getColumnOrder(){return this.grid.getColumnOrder()}moveColumn(e,t){const n=this.getColumnOrder(),r=n.indexOf(e);if(r===-1)return;const i=v(n,r,t);this.updateColumnOrder(i),this.emit("column-move",{field:e,fromIndex:r,toIndex:t,columnOrder:i})}setColumnOrder(e){this.updateColumnOrder(e)}resetColumnOrder(){const e=this.columns.map(t=>t.field);this.updateColumnOrder(e)}captureHeaderPositions(){const e=new Map;return this.shadowRoot?.querySelectorAll(".header-row > .cell[data-field]").forEach(t=>{const n=t.getAttribute("data-field");n&&e.set(n,t.getBoundingClientRect().left)}),e}animateFLIP(e){const t=this.shadowRoot;if(!t||e.size===0)return;const n=new Map;if(t.querySelectorAll(".header-row > .cell[data-field]").forEach(s=>{const d=s.getAttribute("data-field");if(!d)return;const a=e.get(d);if(a===void 0)return;const l=a-s.getBoundingClientRect().left;Math.abs(l)>1&&n.set(d,l)}),n.size===0)return;const r=[];if(t.querySelectorAll(".cell[data-field]").forEach(s=>{const d=n.get(s.getAttribute("data-field")??"");if(d!==void 0){const a=s;a.style.transform=`translateX(${d}px)`,r.push(a)}}),r.length===0)return;t.host.offsetHeight;const i=this.animationDuration;requestAnimationFrame(()=>{r.forEach(s=>{s.classList.add("flip-animating"),s.style.transform=""}),setTimeout(()=>{r.forEach(s=>{s.style.transform="",s.classList.remove("flip-animating")})},i+50)})}animateFade(e){const t=this.shadowRoot;if(!t){e();return}const n=this.captureHeaderPositions();e();const r=new Set;if(t.querySelectorAll(".header-row > .cell[data-field]").forEach(d=>{const a=d.getAttribute("data-field");if(!a)return;const l=n.get(a);if(l===void 0)return;const o=d.getBoundingClientRect().left;Math.abs(l-o)>1&&r.add(a)}),r.size===0)return;const i=[];if(t.querySelectorAll(".cell[data-field]").forEach(d=>{const a=d.getAttribute("data-field");if(a&&r.has(a)){const l=d;l.classList.add("fade-animating"),i.push(l)}}),i.length===0)return;const s=this.animationDuration;setTimeout(()=>{i.forEach(d=>d.classList.remove("fade-animating"))},s+50)}updateColumnOrder(e){const t=this.grid,n=this.animationType;if(n==="flip"&&this.shadowRoot){const r=this.captureHeaderPositions();t.setColumnOrder(e),requestAnimationFrame(()=>{this.shadowRoot?.host?.offsetHeight,this.animateFLIP(r)})}else n==="fade"?this.animateFade(()=>t.setColumnOrder(e)):t.setColumnOrder(e);t.requestStateChange?.()}styles=y}g.ReorderPlugin=O,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})}));
2
2
  //# sourceMappingURL=reorder.umd.js.map