@toolbox-web/grid 1.3.1 → 1.5.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 (102) hide show
  1. package/all.d.ts +1 -0
  2. package/all.d.ts.map +1 -1
  3. package/all.js +1034 -706
  4. package/all.js.map +1 -1
  5. package/index.js +641 -600
  6. package/index.js.map +1 -1
  7. package/lib/core/grid.d.ts.map +1 -1
  8. package/lib/core/internal/header.d.ts +7 -0
  9. package/lib/core/internal/header.d.ts.map +1 -1
  10. package/lib/core/types.d.ts +147 -0
  11. package/lib/core/types.d.ts.map +1 -1
  12. package/lib/plugins/clipboard/index.js +2 -1
  13. package/lib/plugins/clipboard/index.js.map +1 -1
  14. package/lib/plugins/column-virtualization/index.js +6 -5
  15. package/lib/plugins/column-virtualization/index.js.map +1 -1
  16. package/lib/plugins/context-menu/index.js +2 -1
  17. package/lib/plugins/context-menu/index.js.map +1 -1
  18. package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
  19. package/lib/plugins/editing/index.js +9 -6
  20. package/lib/plugins/editing/index.js.map +1 -1
  21. package/lib/plugins/export/index.d.ts +1 -1
  22. package/lib/plugins/export/index.d.ts.map +1 -1
  23. package/lib/plugins/export/index.js +2 -1
  24. package/lib/plugins/export/index.js.map +1 -1
  25. package/lib/plugins/filtering/index.d.ts +1 -1
  26. package/lib/plugins/filtering/index.d.ts.map +1 -1
  27. package/lib/plugins/filtering/index.js +60 -59
  28. package/lib/plugins/filtering/index.js.map +1 -1
  29. package/lib/plugins/grouping-columns/index.js +2 -1
  30. package/lib/plugins/grouping-columns/index.js.map +1 -1
  31. package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts +12 -3
  32. package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts.map +1 -1
  33. package/lib/plugins/grouping-rows/grouping-rows.d.ts +13 -2
  34. package/lib/plugins/grouping-rows/grouping-rows.d.ts.map +1 -1
  35. package/lib/plugins/grouping-rows/index.d.ts +1 -1
  36. package/lib/plugins/grouping-rows/index.d.ts.map +1 -1
  37. package/lib/plugins/grouping-rows/index.js +200 -125
  38. package/lib/plugins/grouping-rows/index.js.map +1 -1
  39. package/lib/plugins/grouping-rows/types.d.ts +24 -2
  40. package/lib/plugins/grouping-rows/types.d.ts.map +1 -1
  41. package/lib/plugins/master-detail/index.d.ts +1 -1
  42. package/lib/plugins/master-detail/index.d.ts.map +1 -1
  43. package/lib/plugins/master-detail/index.js +2 -1
  44. package/lib/plugins/master-detail/index.js.map +1 -1
  45. package/lib/plugins/multi-sort/index.js +2 -1
  46. package/lib/plugins/multi-sort/index.js.map +1 -1
  47. package/lib/plugins/pinned-columns/index.js +2 -1
  48. package/lib/plugins/pinned-columns/index.js.map +1 -1
  49. package/lib/plugins/pinned-rows/index.js +3 -2
  50. package/lib/plugins/pinned-rows/index.js.map +1 -1
  51. package/lib/plugins/pivot/index.js +2 -1
  52. package/lib/plugins/pivot/index.js.map +1 -1
  53. package/lib/plugins/print/PrintPlugin.d.ts +98 -0
  54. package/lib/plugins/print/PrintPlugin.d.ts.map +1 -0
  55. package/lib/plugins/print/index.d.ts +10 -0
  56. package/lib/plugins/print/index.d.ts.map +1 -0
  57. package/lib/plugins/print/index.js +626 -0
  58. package/lib/plugins/print/index.js.map +1 -0
  59. package/lib/plugins/print/print-isolated.d.ts +26 -0
  60. package/lib/plugins/print/print-isolated.d.ts.map +1 -0
  61. package/lib/plugins/print/types.d.ts +147 -0
  62. package/lib/plugins/print/types.d.ts.map +1 -0
  63. package/lib/plugins/reorder/index.js +2 -1
  64. package/lib/plugins/reorder/index.js.map +1 -1
  65. package/lib/plugins/responsive/index.js +2 -1
  66. package/lib/plugins/responsive/index.js.map +1 -1
  67. package/lib/plugins/row-reorder/index.js +2 -1
  68. package/lib/plugins/row-reorder/index.js.map +1 -1
  69. package/lib/plugins/selection/SelectionPlugin.d.ts.map +1 -1
  70. package/lib/plugins/selection/index.js +109 -97
  71. package/lib/plugins/selection/index.js.map +1 -1
  72. package/lib/plugins/server-side/index.js +2 -1
  73. package/lib/plugins/server-side/index.js.map +1 -1
  74. package/lib/plugins/tree/index.js +2 -1
  75. package/lib/plugins/tree/index.js.map +1 -1
  76. package/lib/plugins/undo-redo/index.d.ts +1 -1
  77. package/lib/plugins/undo-redo/index.d.ts.map +1 -1
  78. package/lib/plugins/undo-redo/index.js +2 -1
  79. package/lib/plugins/undo-redo/index.js.map +1 -1
  80. package/lib/plugins/visibility/index.d.ts +1 -1
  81. package/lib/plugins/visibility/index.d.ts.map +1 -1
  82. package/lib/plugins/visibility/index.js +2 -1
  83. package/lib/plugins/visibility/index.js.map +1 -1
  84. package/package.json +1 -1
  85. package/public.d.ts +1 -1
  86. package/public.d.ts.map +1 -1
  87. package/umd/grid.all.umd.js +101 -24
  88. package/umd/grid.all.umd.js.map +1 -1
  89. package/umd/grid.umd.js +11 -11
  90. package/umd/grid.umd.js.map +1 -1
  91. package/umd/plugins/editing.umd.js +1 -1
  92. package/umd/plugins/editing.umd.js.map +1 -1
  93. package/umd/plugins/filtering.umd.js +1 -1
  94. package/umd/plugins/filtering.umd.js.map +1 -1
  95. package/umd/plugins/grouping-rows.umd.js +4 -1
  96. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  97. package/umd/plugins/pinned-rows.umd.js +1 -1
  98. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  99. package/umd/plugins/print.umd.js +76 -0
  100. package/umd/plugins/print.umd.js.map +1 -0
  101. package/umd/plugins/selection.umd.js +2 -2
  102. package/umd/plugins/selection.umd.js.map +1 -1
@@ -1,2 +1,2 @@
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,m){"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 w(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=m.getAggregator(d.aggFunc);g&&(l=g(r,t.field,t)),h=d.formatter}else{const g=m.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="@layer tbw-plugins{.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:var(--tbw-button-padding, var(--tbw-spacing-md, .5rem) var(--tbw-spacing-lg, .75rem));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:var(--tbw-font-size-xs, .75rem);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:var(--tbw-spacing-xl, 1rem)}.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, .125rem .5rem);min-height:var(--tbw-row-height, 1.75rem);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";styles=E;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.gridElement;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=w("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=w("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())}}f.PinnedRowsPlugin=R,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(f,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):(f=typeof globalThis<"u"?globalThis:f||self,h(f.TbwGridPlugin_pinnedRows={},f.TbwGrid,f.TbwGrid))})(this,(function(f,h,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,p;const d=s.aggregators?.[t.field];if(d)if(v(d)){const g=w.getAggregator(d.aggFunc);g&&(l=g(r,t.field,t)),p=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=p?p(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="@layer tbw-plugins{.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:var(--tbw-button-padding, var(--tbw-spacing-md, .5rem) var(--tbw-spacing-lg, .75rem));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:var(--tbw-font-size-xs, .75rem);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:var(--tbw-spacing-xl, 1rem)}.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, .125rem .5rem);min-height:var(--tbw-row-height, 1.75rem);display:block;align-items:center;border-right:1px solid var(--tbw-color-border-cell);overflow:hidden;text-overflow:ellipsis;white-space:var(--tbw-cell-white-space, nowrap)}.tbw-aggregation-cell:last-child{border-right:0}.tbw-aggregation-cell-full{grid-column:1 / -1;border-right:0}}";class R extends h.BaseGridPlugin{name="pinnedRows";styles=E;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.gridElement;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 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,d=p&&this.config.position!=="top",g=l.length>0||d;if(p&&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())}}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 (Status Bar) Plugin for tbw-grid\n *\n * Creates fixed status bars at the top or bottom of the grid for displaying aggregations,\n * row counts, or custom content. Think of it as the \"totals row\" you'd see in a spreadsheet—\n * always visible regardless of scroll position.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedRowsPlugin } from '@toolbox-web/grid/plugins/pinned-rows';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `position` | `'top' \\| 'bottom'` | `'bottom'` | Status bar position |\n * | `showRowCount` | `boolean` | `true` | Show total row count |\n * | `showSelectedCount` | `boolean` | `true` | Show selected row count |\n * | `showFilteredCount` | `boolean` | `true` | Show filtered row count |\n * | `aggregationRows` | `AggregationRowConfig[]` | - | Aggregation row configs |\n *\n * ## Built-in Aggregation Functions\n *\n * | Function | Description |\n * |----------|-------------|\n * | `sum` | Sum of values |\n * | `avg` | Average of values |\n * | `count` | Count of rows |\n * | `min` | Minimum value |\n * | `max` | Maximum value |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-rows-bg` | `var(--tbw-color-panel-bg)` | Status bar background |\n * | `--tbw-pinned-rows-border` | `var(--tbw-color-border)` | Status bar border |\n *\n * @example Status Bar with Aggregation\n * ```ts\n * import '@toolbox-web/grid';\n * import { PinnedRowsPlugin } from '@toolbox-web/grid/plugins/pinned-rows';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'product', header: 'Product' },\n * { field: 'quantity', header: 'Qty', type: 'number' },\n * { field: 'price', header: 'Price', type: 'currency' },\n * ],\n * plugins: [\n * new PinnedRowsPlugin({\n * position: 'bottom',\n * showRowCount: true,\n * aggregationRows: [\n * {\n * id: 'totals',\n * aggregators: { quantity: 'sum', price: 'sum' },\n * cells: { product: 'Totals:' },\n * },\n * ],\n * }),\n * ],\n * };\n * ```\n *\n * @see {@link PinnedRowsConfig} for all configuration options\n * @see {@link AggregationRowConfig} for aggregation row structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedRowsPlugin extends BaseGridPlugin<PinnedRowsConfig> {\n /** @internal */\n readonly name = 'pinnedRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\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 /** @internal */\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 /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) 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 gridEl.querySelector('.tbw-scroll-area') ?? gridEl.querySelector('.tbw-grid-content') ?? gridEl.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 = gridEl.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"],"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","styles","gridEl","aggregationRows","topRows","r","bottomRows","header","hasInfoContent","hasBottomInfoBar","needsFooter","newInfoBar","id","p","row"],"mappings":"+ZAqBA,SAASA,EAAmBC,EAAoD,CAC9E,OAAO,OAAOA,GAAQ,UAAYA,IAAQ,MAAQ,YAAaA,CACjE,CASO,SAASC,EAAqBC,EAA0BC,EAAyC,CACtG,MAAMC,EAAa,SAAS,cAAc,KAAK,EAC/CA,EAAW,UAAY,kBACvBA,EAAW,aAAa,OAAQ,cAAc,EAC9CA,EAAW,aAAa,YAAa,QAAQ,EAE7C,MAAMC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY,uBAEjB,MAAMC,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAY,yBAEnB,MAAMC,EAAQ,SAAS,cAAc,KAAK,EAI1C,GAHAA,EAAM,UAAY,wBAGdL,EAAO,eAAiB,GAAO,CACjC,MAAMM,EAAW,SAAS,cAAc,MAAM,EAC9CA,EAAS,UAAY,8CACrBA,EAAS,YAAc,UAAUL,EAAQ,SAAS,QAClDE,EAAK,YAAYG,CAAQ,CAC3B,CAGA,GAAIN,EAAO,mBAAqBC,EAAQ,eAAiBA,EAAQ,UAAW,CAC1E,MAAMM,EAAgB,SAAS,cAAc,MAAM,EACnDA,EAAc,UAAY,mDAC1BA,EAAc,YAAc,aAAaN,EAAQ,YAAY,GAC7DE,EAAK,YAAYI,CAAa,CAChC,CAGA,GAAIP,EAAO,mBAAqBC,EAAQ,aAAe,EAAG,CACxD,MAAMO,EAAgB,SAAS,cAAc,MAAM,EACnDA,EAAc,UAAY,mDAC1BA,EAAc,YAAc,aAAaP,EAAQ,YAAY,GAC7DI,EAAM,YAAYG,CAAa,CACjC,CAGA,GAAIR,EAAO,aACT,UAAWS,KAAST,EAAO,aAAc,CACvC,MAAMU,EAAUC,EAAkBF,EAAOR,CAAO,EAChD,OAAQQ,EAAM,SAAA,CACZ,IAAK,OACHN,EAAK,YAAYO,CAAO,EACxB,MACF,IAAK,SACHN,EAAO,YAAYM,CAAO,EAC1B,MACF,IAAK,QACHL,EAAM,YAAYK,CAAO,EACzB,KAAA,CAEN,CAGF,OAAAR,EAAW,YAAYC,CAAI,EAC3BD,EAAW,YAAYE,CAAM,EAC7BF,EAAW,YAAYG,CAAK,EAErBH,CACT,CAQO,SAASU,EAA2BC,EAAyC,CAClF,MAAMC,EAAY,SAAS,cAAc,KAAK,EAC9C,OAAAA,EAAU,UAAY,6CAA6CD,CAAQ,GAE3EC,EAAU,aAAa,OAAQ,cAAc,EACtCA,CACT,CAUO,SAASC,EACdD,EACAE,EACAC,EACAC,EACM,CACNJ,EAAU,UAAY,GAEtB,UAAWK,KAAaH,EAAM,CAC5B,MAAMI,EAAQ,SAAS,cAAc,KAAK,EAQ1C,GAPAA,EAAM,UAAY,sBAElBA,EAAM,aAAa,OAAQ,cAAc,EACrCD,EAAU,IACZC,EAAM,aAAa,sBAAuBD,EAAU,EAAE,EAGpDA,EAAU,UAAW,CAEvB,MAAME,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY,iDACjBA,EAAK,MAAM,WAAa,SACxBA,EAAK,YAAcF,EAAU,OAAS,GACtCC,EAAM,YAAYC,CAAI,CACxB,KAEE,WAAWC,KAAOL,EAAS,CACzB,MAAMI,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY,uBACjBA,EAAK,aAAa,aAAcC,EAAI,KAAK,EAEzC,IAAIC,EACAC,EAGJ,MAAMC,EAASN,EAAU,cAAcG,EAAI,KAAK,EAChD,GAAIG,EAEF,GAAI5B,EAAmB4B,CAAM,EAAG,CAC9B,MAAMC,EAAQC,EAAAA,cAAcF,EAAO,OAAO,EACtCC,IACFH,EAAQG,EAAMR,EAAUI,EAAI,MAAOA,CAAG,GAExCE,EAAYC,EAAO,SACrB,KAAO,CACL,MAAMC,EAAQC,EAAAA,cAAcF,CAAM,EAC9BC,IACFH,EAAQG,EAAMR,EAAUI,EAAI,MAAOA,CAAG,EAE1C,SACSH,EAAU,OAAS,OAAO,UAAU,eAAe,KAAKA,EAAU,MAAOG,EAAI,KAAK,EAAG,CAE9F,MAAMM,EAAYT,EAAU,MAAMG,EAAI,KAAK,EACvC,OAAOM,GAAc,WACvBL,EAAQK,EAAUV,EAAUI,EAAI,MAAOA,CAAG,EAE1CC,EAAQK,CAEZ,CAGIL,GAAS,KACXF,EAAK,YAAcG,EAAYA,EAAUD,EAAOD,EAAI,MAAOA,CAAG,EAAI,OAAOC,CAAK,EAE9EF,EAAK,YAAc,GAErBD,EAAM,YAAYC,CAAI,CACxB,CAGFP,EAAU,YAAYM,CAAK,CAC7B,CACF,CASA,SAAST,EAAkBF,EAAwBR,EAAyC,CAC1F,MAAMS,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAY,2CACpBA,EAAQ,GAAK,gBAAgBD,EAAM,EAAE,GAErC,MAAMoB,EAAUpB,EAAM,OAAOR,CAAO,EAEpC,OAAI,OAAO4B,GAAY,SACrBnB,EAAQ,UAAYmB,EAEpBnB,EAAQ,YAAYmB,CAAO,EAGtBnB,CACT,CAYO,SAASoB,EACdd,EACAC,EACAc,EACAC,EACAC,EACmB,CACnB,MAAO,CACL,UAAWjB,EAAK,OAChB,aAAciB,GAAa,cAAc,QAAUjB,EAAK,OACxD,aAAcgB,GAAgB,UAAU,MAAQ,EAChD,QAAAf,EACA,KAAAD,EACA,KAAAe,CAAA,CAEJ,+sDC1JO,MAAMG,UAAyBC,EAAAA,cAAiC,CAE5D,KAAO,aAEE,OAASC,EAG3B,IAAuB,eAA2C,CAChE,MAAO,CACL,SAAU,SACV,aAAc,GACd,kBAAmB,GACnB,kBAAmB,EAAA,CAEvB,CAGQ,eAAqC,KACrC,wBAA8C,KAC9C,2BAAiD,KACjD,cAAoC,KAKnC,QAAe,CAClB,KAAK,iBACP,KAAK,eAAe,OAAA,EACpB,KAAK,eAAiB,MAEpB,KAAK,0BACP,KAAK,wBAAwB,OAAA,EAC7B,KAAK,wBAA0B,MAE7B,KAAK,6BACP,KAAK,2BAA2B,OAAA,EAChC,KAAK,2BAA6B,MAEhC,KAAK,gBACP,KAAK,cAAc,OAAA,EACnB,KAAK,cAAgB,KAEzB,CAKS,aAAoB,CAC3B,MAAMC,EAAS,KAAK,YACpB,GAAI,CAACA,EAAQ,OAIb,MAAMvB,EACJuB,EAAO,cAAc,kBAAkB,GAAKA,EAAO,cAAc,mBAAmB,GAAKA,EAAO,SAAS,CAAC,EAC5G,GAAI,CAACvB,EAAW,OAMZ,KAAK,eAAiB,CAACA,EAAU,SAAS,KAAK,aAAa,IAC9D,KAAK,cAAgB,KACrB,KAAK,2BAA6B,KAClC,KAAK,eAAiB,MAEpB,KAAK,yBAA2B,CAACA,EAAU,SAAS,KAAK,uBAAuB,IAClF,KAAK,wBAA0B,MAE7B,KAAK,gBAAkB,CAACA,EAAU,SAAS,KAAK,cAAc,IAChE,KAAK,eAAiB,MAIxB,MAAMkB,EAAiB,KAAK,kBAAA,EACtBC,EAAc,KAAK,eAAA,EAEnBhC,EAAU6B,EACd,KAAK,KACL,KAAK,QACL,KAAK,KACLE,EACAC,CAAA,EAIIK,EAAkB,KAAK,OAAO,iBAAmB,CAAA,EACjDC,EAAUD,EAAgB,OAAQE,GAAMA,EAAE,WAAa,KAAK,EAC5DC,EAAaH,EAAgB,OAAQE,GAAMA,EAAE,WAAa,KAAK,EAGrE,GAAID,EAAQ,OAAS,EAAG,CACtB,GAAI,CAAC,KAAK,wBAAyB,CACjC,KAAK,wBAA0B3B,EAA2B,KAAK,EAC/D,MAAM8B,EAASL,EAAO,cAAc,SAAS,EACzCK,GAAUA,EAAO,YACnB5B,EAAU,aAAa,KAAK,wBAAyB4B,EAAO,WAAW,EAEvE5B,EAAU,YAAY,KAAK,uBAAuB,CAEtD,CACAC,EACE,KAAK,wBACLwB,EACA,KAAK,eACL,KAAK,IAAA,CAET,MAAW,KAAK,0BACd,KAAK,wBAAwB,OAAA,EAC7B,KAAK,wBAA0B,MAIjC,MAAMI,EACJ,KAAK,OAAO,eAAiB,IAC5B,KAAK,OAAO,mBAAqB1C,EAAQ,aAAe,GACxD,KAAK,OAAO,mBAAqBA,EAAQ,eAAiBA,EAAQ,WAClE,KAAK,OAAO,cAAgB,KAAK,OAAO,aAAa,OAAS,EAC3D2C,EAAmBD,GAAkB,KAAK,OAAO,WAAa,MAC9DE,EAAcJ,EAAW,OAAS,GAAKG,EAG7C,GAAID,GAAkB,KAAK,OAAO,WAAa,MAC7C,GAAI,CAAC,KAAK,eACR,KAAK,eAAiB5C,EAAqB,KAAK,OAAQE,CAAO,EAC/Da,EAAU,aAAa,KAAK,eAAgBA,EAAU,UAAU,MAC3D,CACL,MAAMgC,EAAa/C,EAAqB,KAAK,OAAQE,CAAO,EAC5D,KAAK,eAAe,YAAY6C,CAAU,EAC1C,KAAK,eAAiBA,CACxB,MACS,KAAK,OAAO,WAAa,OAAS,KAAK,iBAChD,KAAK,eAAe,OAAA,EACpB,KAAK,eAAiB,MAIpBD,GACG,KAAK,gBACR,KAAK,cAAgB,SAAS,cAAc,KAAK,EACjD,KAAK,cAAc,UAAY,aAC/B/B,EAAU,YAAY,KAAK,aAAa,GAG1C,KAAK,cAAc,UAAY,GAE3B2B,EAAW,OAAS,IACjB,KAAK,6BACR,KAAK,2BAA6B7B,EAA2B,QAAQ,GAEvE,KAAK,cAAc,YAAY,KAAK,0BAA0B,EAC9DG,EACE,KAAK,2BACL0B,EACA,KAAK,eACL,KAAK,IAAA,GAILG,IACF,KAAK,eAAiB7C,EAAqB,KAAK,OAAQE,CAAO,EAC/D,KAAK,cAAc,YAAY,KAAK,cAAc,IAGpD,KAAK,cAAA,CAGT,CAIQ,SAAgB,CAClB,KAAK,iBACP,KAAK,eAAe,OAAA,EACpB,KAAK,eAAiB,MAEpB,KAAK,0BACP,KAAK,wBAAwB,OAAA,EAC7B,KAAK,wBAA0B,MAE7B,KAAK,6BACP,KAAK,2BAA2B,OAAA,EAChC,KAAK,2BAA6B,MAEhC,KAAK,gBACP,KAAK,cAAc,OAAA,EACnB,KAAK,cAAgB,KAEzB,CAEQ,eAAsB,CACxB,KAAK,gBACP,KAAK,cAAc,OAAA,EACnB,KAAK,cAAgB,MAEnB,KAAK,6BACP,KAAK,2BAA2B,OAAA,EAChC,KAAK,2BAA6B,MAEhC,KAAK,gBAAkB,KAAK,OAAO,WAAa,QAClD,KAAK,eAAe,OAAA,EACpB,KAAK,eAAiB,KAE1B,CAEQ,mBAAsD,CAE5D,GAAI,CACF,OAAQ,KAAK,MAAM,iBAAiB,WAAW,GAA0C,IAC3F,MAAQ,CACN,OAAO,IACT,CACF,CAEQ,gBAA4D,CAClE,GAAI,CACF,OAAQ,KAAK,MAAM,iBAAiB,WAAW,GAAmD,IACpG,MAAQ,CACN,OAAO,IACT,CACF,CAOA,SAAgB,CACd,KAAK,cAAA,CACP,CAMA,YAAgC,CAC9B,MAAM+B,EAAiB,KAAK,kBAAA,EACtBC,EAAc,KAAK,eAAA,EAEzB,OAAOH,EACL,KAAK,KACL,KAAK,QACL,KAAK,KACLE,EACAC,CAAA,CAEJ,CAMA,SAASxB,EAA8B,CAChC,KAAK,OAAO,eACf,KAAK,OAAO,aAAe,CAAA,GAE7B,KAAK,OAAO,aAAa,KAAKA,CAAK,EACnC,KAAK,cAAA,CACP,CAMA,YAAYsC,EAAkB,CACxB,KAAK,OAAO,eACd,KAAK,OAAO,aAAe,KAAK,OAAO,aAAa,OAAQC,GAAMA,EAAE,KAAOD,CAAE,EAC7E,KAAK,cAAA,EAET,CAMA,kBAAkBE,EAAiC,CAC5C,KAAK,OAAO,kBACf,KAAK,OAAO,gBAAkB,CAAA,GAEhC,KAAK,OAAO,gBAAgB,KAAKA,CAAG,EACpC,KAAK,cAAA,CACP,CAMA,qBAAqBF,EAAkB,CACjC,KAAK,OAAO,kBACd,KAAK,OAAO,gBAAkB,KAAK,OAAO,gBAAgB,OAAQP,GAAMA,EAAE,KAAOO,CAAE,EACnF,KAAK,cAAA,EAET,CAEF"}
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 (Status Bar) Plugin for tbw-grid\n *\n * Creates fixed status bars at the top or bottom of the grid for displaying aggregations,\n * row counts, or custom content. Think of it as the \"totals row\" you'd see in a spreadsheet—\n * always visible regardless of scroll position.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedRowsPlugin } from '@toolbox-web/grid/plugins/pinned-rows';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `position` | `'top' \\| 'bottom'` | `'bottom'` | Status bar position |\n * | `showRowCount` | `boolean` | `true` | Show total row count |\n * | `showSelectedCount` | `boolean` | `true` | Show selected row count |\n * | `showFilteredCount` | `boolean` | `true` | Show filtered row count |\n * | `aggregationRows` | `AggregationRowConfig[]` | - | Aggregation row configs |\n *\n * ## Built-in Aggregation Functions\n *\n * | Function | Description |\n * |----------|-------------|\n * | `sum` | Sum of values |\n * | `avg` | Average of values |\n * | `count` | Count of rows |\n * | `min` | Minimum value |\n * | `max` | Maximum value |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-rows-bg` | `var(--tbw-color-panel-bg)` | Status bar background |\n * | `--tbw-pinned-rows-border` | `var(--tbw-color-border)` | Status bar border |\n *\n * @example Status Bar with Aggregation\n * ```ts\n * import '@toolbox-web/grid';\n * import { PinnedRowsPlugin } from '@toolbox-web/grid/plugins/pinned-rows';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'product', header: 'Product' },\n * { field: 'quantity', header: 'Qty', type: 'number' },\n * { field: 'price', header: 'Price', type: 'currency' },\n * ],\n * plugins: [\n * new PinnedRowsPlugin({\n * position: 'bottom',\n * showRowCount: true,\n * aggregationRows: [\n * {\n * id: 'totals',\n * aggregators: { quantity: 'sum', price: 'sum' },\n * cells: { product: 'Totals:' },\n * },\n * ],\n * }),\n * ],\n * };\n * ```\n *\n * @see {@link PinnedRowsConfig} for all configuration options\n * @see {@link AggregationRowConfig} for aggregation row structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedRowsPlugin extends BaseGridPlugin<PinnedRowsConfig> {\n /** @internal */\n readonly name = 'pinnedRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\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 /** @internal */\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 /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) 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 gridEl.querySelector('.tbw-scroll-area') ?? gridEl.querySelector('.tbw-grid-content') ?? gridEl.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 = gridEl.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"],"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","styles","gridEl","aggregationRows","topRows","r","bottomRows","header","hasInfoContent","hasBottomInfoBar","needsFooter","newInfoBar","id","p","row"],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ (function(o,p){typeof exports=="object"&&typeof module<"u"?p(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],p):(o=typeof globalThis<"u"?globalThis:o||self,p(o.TbwGridPlugin_print={},o.TbwGrid))})(this,(function(o,p){"use strict";const h="tbw-print-isolation-style";function u(n,e){const t=document.createElement("style");return t.id=h,t.textContent=`
2
+ /* Print isolation: hide everything except the target grid */
3
+ @media print {
4
+ /* Hide all body children by default */
5
+ body > *:not(#${n}) {
6
+ display: none !important;
7
+ }
8
+
9
+ /* But show the grid and ensure it's not hidden by ancestor rules */
10
+ #${n} {
11
+ display: block !important;
12
+ position: static !important;
13
+ visibility: visible !important;
14
+ opacity: 1 !important;
15
+ overflow: visible !important;
16
+ height: auto !important;
17
+ width: 100% !important;
18
+ max-height: none !important;
19
+ margin: 0 !important;
20
+ padding: 0 !important;
21
+ transform: none !important;
22
+ }
23
+
24
+ /* If grid is nested, we need to show its ancestors too */
25
+ #${n},
26
+ #${n} * {
27
+ visibility: visible !important;
28
+ }
29
+
30
+ /* Walk up the DOM and show all ancestors of the grid */
31
+ body *:has(> #${n}),
32
+ body *:has(#${n}) {
33
+ display: block !important;
34
+ visibility: visible !important;
35
+ opacity: 1 !important;
36
+ overflow: visible !important;
37
+ height: auto !important;
38
+ position: static !important;
39
+ transform: none !important;
40
+ background: transparent !important;
41
+ border: none !important;
42
+ padding: 0 !important;
43
+ margin: 0 !important;
44
+ }
45
+
46
+ /* Hide siblings of ancestors (everything that's not in the path to the grid) */
47
+ body *:has(#${n}) > *:not(:has(#${n})):not(#${n}) {
48
+ display: none !important;
49
+ }
50
+
51
+ /* Page settings */
52
+ @page {
53
+ size: ${e};
54
+ margin: 1cm;
55
+ }
56
+
57
+ /* Ensure proper print styling */
58
+ body {
59
+ margin: 0 !important;
60
+ padding: 0 !important;
61
+ background: white !important;
62
+ color-scheme: light !important;
63
+ }
64
+ }
65
+
66
+ /* Screen: also apply isolation for print preview */
67
+ @media screen {
68
+ /* When this stylesheet is active, we're about to print */
69
+ /* No screen-specific rules needed - isolation only applies to print */
70
+ }
71
+ `,t}async function c(n,e={}){const{orientation:t="landscape"}=e,i=n.id;document.querySelectorAll(`#${CSS.escape(i)}`).length>1&&console.warn(`[tbw-grid:print] Multiple elements found with id="${i}". Print isolation may not work correctly. Ensure each grid has a unique ID.`),document.getElementById(h)?.remove();const a=u(i,t);return document.head.appendChild(a),new Promise(l=>{const d=()=>{window.removeEventListener("afterprint",d),document.getElementById(h)?.remove(),l()};window.addEventListener("afterprint",d),window.print(),setTimeout(()=>{window.removeEventListener("afterprint",d),document.getElementById(h)?.remove(),l()},5e3)})}const g=".tbw-print-header,.tbw-print-footer{display:none}@media print{tbw-grid{overflow:visible!important;height:auto!important;border:none!important;border-radius:0!important;color-scheme:light only;-webkit-print-color-adjust:exact;print-color-adjust:exact}tbw-grid .tbw-grid-content{overflow:visible!important;height:auto!important;max-height:none!important}tbw-grid .tbw-scroll-area{overflow:visible!important;height:auto!important;max-height:none!important}tbw-grid .rows-body{overflow:visible!important;height:auto!important;max-height:none!important}tbw-grid .rows-container,tbw-grid .rows-viewport,tbw-grid .rows{overflow:visible!important;height:auto!important;max-height:none!important;transform:none!important}tbw-grid .rows-viewport .rows{position:static!important}tbw-grid .resize-handle,tbw-grid [part=sort-indicator],tbw-grid .tbw-filter-btn,tbw-grid .tool-panel,tbw-grid .tool-panel-content,tbw-grid .tbw-shell-header,tbw-grid .shell-toolbar,tbw-grid .tool-panel-toggle,tbw-grid [data-print-hide],tbw-grid .expander-cell,tbw-grid .tree-toggle,tbw-grid .context-menu,tbw-grid .faux-vscroll{display:none!important}tbw-grid .tbw-print-header{display:flex;justify-content:space-between;align-items:baseline;padding:.5em 0;margin-bottom:.5em;border-bottom:2px solid var(--tbw-color-border, #333);font-family:inherit}.tbw-print-header-title{font-size:1.25em;font-weight:700}.tbw-print-header-timestamp{font-size:.875em;color:var(--tbw-color-fg-muted, #666)}tbw-grid .tbw-print-footer{display:block;margin-top:.5em;padding-top:.5em;border-top:1px solid var(--tbw-color-border, #ccc);font-size:.75em;color:var(--tbw-color-fg-muted, #666);text-align:right}tbw-grid .data-grid-row{break-inside:avoid;page-break-inside:avoid}tbw-grid .cell{border:1px solid var(--tbw-color-border, #ddd)!important}tbw-grid .header-row,tbw-grid .data-grid-row{padding-right:1px}tbw-grid .data-grid-row:hover,tbw-grid .cell:hover{background:inherit!important}@page{margin:1cm}@page{tbw-grid.print-landscape{size:landscape}}@page{tbw-grid.print-portrait{size:portrait}}}",b={button:!1,orientation:"landscape",warnThreshold:500,maxRows:0,includeTitle:!0,includeTimestamp:!0,title:"",isolate:!1};class f extends p.BaseGridPlugin{name="print";version="1.0.0";styles=g;#n=!1;#e=null;#r=null;#o=null;#t=null;#i=null;#a=null;get#s(){return this.grid}isPrinting(){return this.#n}async print(e){if(this.#n){console.warn("[PrintPlugin] Print already in progress");return}const t=this.gridElement;if(!t){console.warn("[PrintPlugin] Grid not available");return}const i={...b,...this.config,...e},a=this.rows.length;let l=a,d=!1;if(i.warnThreshold>0&&a>i.warnThreshold){const m=i.maxRows>0?`
72
+
73
+ Note: Output will be limited to ${i.maxRows.toLocaleString()} rows.`:"";if(!confirm(`This grid has ${a.toLocaleString()} rows. Printing large datasets may cause performance issues or browser slowdowns.${m}
74
+
75
+ Click OK to continue, or Cancel to abort.`))return}i.maxRows>0&&a>i.maxRows&&(l=i.maxRows,d=!0),this.#n=!0;const w=performance.now();this.emit("print-start",{rowCount:l,limitApplied:d,originalRowCount:a});try{const m=this.#s;this.#r={bypassThreshold:m._virtualization?.bypassThreshold??24},this.#c(),d&&(this.#o=this.sourceRows,this.grid.rows=this.sourceRows.slice(0,l),await new Promise(r=>setTimeout(r,50))),(i.includeTitle||i.includeTimestamp)&&this.#d(i),await this.#p(),await new Promise(r=>requestAnimationFrame(r)),await new Promise(r=>requestAnimationFrame(r)),t.classList.add(`print-${i.orientation}`),await new Promise(r=>requestAnimationFrame(r)),await new Promise(r=>requestAnimationFrame(r)),i.isolate?await this.#h(i):await this.#m(),this.emit("print-complete",{success:!0,rowCount:l,duration:Math.round(performance.now()-w)})}catch(m){console.error("[PrintPlugin] Print failed:",m),this.emit("print-complete",{success:!1,rowCount:0,duration:Math.round(performance.now()-w)})}finally{this.#u(),this.#n=!1}}#d(e){const t=this.gridElement;if(t){if(this.#t=document.createElement("div"),this.#t.className="tbw-print-header",e.includeTitle){const i=e.title||this.grid.effectiveConfig?.shell?.header?.title||"Grid Data",s=document.createElement("div");s.className="tbw-print-header-title",s.textContent=i,this.#t.appendChild(s)}if(e.includeTimestamp){const i=document.createElement("div");i.className="tbw-print-header-timestamp",i.textContent=`Printed: ${new Date().toLocaleString()}`,this.#t.appendChild(i)}t.insertBefore(this.#t,t.firstChild),this.#i=document.createElement("div"),this.#i.className="tbw-print-footer",this.#i.textContent=`Page generated from ${window.location.hostname}`,t.appendChild(this.#i)}}async#p(){const e=this.#s;if(!e._virtualization)return;const t=this.rows.length;e._virtualization.bypassThreshold=t+100,e.refreshVirtualWindow(!0),await new Promise(i=>setTimeout(i,100))}async#m(){return new Promise(e=>{const t=()=>{window.removeEventListener("afterprint",t),e()};window.addEventListener("afterprint",t),window.print(),setTimeout(()=>{window.removeEventListener("afterprint",t),e()},1e3)})}async#h(e){const t=this.gridElement;t&&await c(t,{orientation:e.orientation})}#c(){const e=this.columns;if(e){this.#e=new Map;for(const t of e)t.printHidden&&t.field&&(this.#e.set(t.field,!t.hidden),this.grid.setColumnVisible(t.field,!1))}}#w(){if(this.#e){for(const[e,t]of this.#e)this.grid.setColumnVisible(e,t);this.#e=null}}#u(){const e=this.gridElement;if(!e)return;this.#w(),e.classList.remove("print-portrait","print-landscape"),this.#a!==null&&(e.style.transform="",e.style.transformOrigin="",e.style.width="",this.#a=null),this.#t&&(this.#t.remove(),this.#t=null),this.#i&&(this.#i.remove(),this.#i=null);const t=this.#s;this.#r&&t._virtualization&&(t._virtualization.bypassThreshold=this.#r.bypassThreshold,t.refreshVirtualWindow(!0),this.#r=null),this.#o!==null&&(this.grid.rows=this.#o,this.#o=null)}afterRender(){this.config?.button&&!this.#l&&(this.#g(),this.#l=!0)}#l=!1;#g(){this.#s.registerToolbarContent?.({id:"print-button",order:900,render:t=>{const i=document.createElement("button");i.className="tbw-toolbar-btn tbw-print-btn",i.title="Print grid",i.type="button";const s=this.resolveIcon("print")||"🖨️";this.setIcon(i,s),i.addEventListener("click",()=>{this.print()},{signal:this.disconnectSignal}),t.appendChild(i)}})}}o.PrintPlugin=f,o.printGridIsolated=c,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})}));
76
+ //# sourceMappingURL=print.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"print.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/print/print-isolated.ts","../../../../../libs/grid/src/lib/plugins/print/PrintPlugin.ts"],"sourcesContent":["/**\n * Utility for printing a grid in isolation by hiding all other page content.\n *\n * This approach keeps the grid in place (with virtualization disabled by PrintPlugin)\n * and uses CSS to hide everything else on the page during printing.\n */\n\nimport type { PrintOrientation } from './types';\n\nexport interface PrintIsolatedOptions {\n /** Page orientation hint */\n orientation?: PrintOrientation;\n}\n\n/** ID for the isolation stylesheet */\nconst ISOLATION_STYLE_ID = 'tbw-print-isolation-style';\n\n/**\n * Create a stylesheet that hides everything except the target grid.\n * Uses the grid's ID to target it specifically.\n */\nfunction createIsolationStylesheet(gridId: string, orientation: PrintOrientation): HTMLStyleElement {\n const style = document.createElement('style');\n style.id = ISOLATION_STYLE_ID;\n style.textContent = `\n /* Print isolation: hide everything except the target grid */\n @media print {\n /* Hide all body children by default */\n body > *:not(#${gridId}) {\n display: none !important;\n }\n\n /* But show the grid and ensure it's not hidden by ancestor rules */\n #${gridId} {\n display: block !important;\n position: static !important;\n visibility: visible !important;\n opacity: 1 !important;\n overflow: visible !important;\n height: auto !important;\n width: 100% !important;\n max-height: none !important;\n margin: 0 !important;\n padding: 0 !important;\n transform: none !important;\n }\n\n /* If grid is nested, we need to show its ancestors too */\n #${gridId},\n #${gridId} * {\n visibility: visible !important;\n }\n\n /* Walk up the DOM and show all ancestors of the grid */\n body *:has(> #${gridId}),\n body *:has(#${gridId}) {\n display: block !important;\n visibility: visible !important;\n opacity: 1 !important;\n overflow: visible !important;\n height: auto !important;\n position: static !important;\n transform: none !important;\n background: transparent !important;\n border: none !important;\n padding: 0 !important;\n margin: 0 !important;\n }\n\n /* Hide siblings of ancestors (everything that's not in the path to the grid) */\n body *:has(#${gridId}) > *:not(:has(#${gridId})):not(#${gridId}) {\n display: none !important;\n }\n\n /* Page settings */\n @page {\n size: ${orientation};\n margin: 1cm;\n }\n\n /* Ensure proper print styling */\n body {\n margin: 0 !important;\n padding: 0 !important;\n background: white !important;\n color-scheme: light !important;\n }\n }\n\n /* Screen: also apply isolation for print preview */\n @media screen {\n /* When this stylesheet is active, we're about to print */\n /* No screen-specific rules needed - isolation only applies to print */\n }\n `;\n return style;\n}\n\n/**\n * Print a grid in isolation by hiding all other page content.\n *\n * This function adds a temporary stylesheet that uses CSS to hide everything\n * on the page except the target grid during printing. The grid stays in place\n * with all its data (virtualization should be disabled separately).\n *\n * @param gridElement - The tbw-grid element to print (must have an ID)\n * @param options - Optional configuration\n * @returns Promise that resolves when the print dialog closes\n *\n * @example\n * ```typescript\n * import { printGridIsolated } from '@toolbox-web/grid/plugins/print';\n *\n * const grid = document.querySelector('tbw-grid');\n * await printGridIsolated(grid, { orientation: 'landscape' });\n * ```\n */\nexport async function printGridIsolated(gridElement: HTMLElement, options: PrintIsolatedOptions = {}): Promise<void> {\n const { orientation = 'landscape' } = options;\n\n const gridId = gridElement.id;\n\n // Warn if multiple elements share this ID (user-set IDs could collide)\n const elementsWithId = document.querySelectorAll(`#${CSS.escape(gridId)}`);\n if (elementsWithId.length > 1) {\n console.warn(\n `[tbw-grid:print] Multiple elements found with id=\"${gridId}\". ` +\n `Print isolation may not work correctly. Ensure each grid has a unique ID.`,\n );\n }\n\n // Remove any existing isolation stylesheet\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n\n // Add the isolation stylesheet\n const isolationStyle = createIsolationStylesheet(gridId, orientation);\n document.head.appendChild(isolationStyle);\n\n return new Promise((resolve) => {\n // Listen for afterprint event to cleanup\n const onAfterPrint = () => {\n window.removeEventListener('afterprint', onAfterPrint);\n // Remove isolation stylesheet\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n resolve();\n };\n window.addEventListener('afterprint', onAfterPrint);\n\n // Trigger print\n window.print();\n\n // Fallback timeout in case afterprint doesn't fire (some browsers)\n setTimeout(() => {\n window.removeEventListener('afterprint', onAfterPrint);\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n resolve();\n }, 5000);\n });\n}\n","/**\n * Print Plugin (Class-based)\n *\n * Provides print layout functionality for tbw-grid.\n * Temporarily disables virtualization to render all rows and uses\n * @media print CSS for print-optimized styling.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { InternalGrid, ToolbarContentDefinition } from '../../core/types';\nimport { printGridIsolated } from './print-isolated';\nimport styles from './print.css?inline';\nimport type { PrintCompleteDetail, PrintConfig, PrintParams, PrintStartDetail } from './types';\n\n/**\n * Extended grid interface for PrintPlugin internal access.\n * Includes registerToolbarContent which is available on the grid class\n * but not exposed in the standard plugin API.\n */\ninterface PrintGridRef extends InternalGrid {\n registerToolbarContent?(content: ToolbarContentDefinition): void;\n unregisterToolbarContent?(contentId: string): void;\n}\n\n/** Default configuration */\nconst DEFAULT_CONFIG: Required<PrintConfig> = {\n button: false,\n orientation: 'landscape',\n warnThreshold: 500,\n maxRows: 0,\n includeTitle: true,\n includeTimestamp: true,\n title: '',\n isolate: false,\n};\n\n/**\n * Print Plugin for tbw-grid\n *\n * Enables printing the full grid content by temporarily disabling virtualization\n * and applying print-optimized styles. Handles large datasets gracefully with\n * configurable row limits.\n *\n * ## Installation\n *\n * ```ts\n * import { PrintPlugin } from '@toolbox-web/grid/plugins/print';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `button` | `boolean` | `false` | Show print button in toolbar |\n * | `orientation` | `'portrait' \\| 'landscape'` | `'landscape'` | Page orientation |\n * | `warnThreshold` | `number` | `500` | Show confirmation dialog when rows exceed this (0 = no warning) |\n * | `maxRows` | `number` | `0` | Hard limit on printed rows (0 = unlimited) |\n * | `includeTitle` | `boolean` | `true` | Include grid title in print |\n * | `includeTimestamp` | `boolean` | `true` | Include timestamp in footer |\n * | `title` | `string` | `''` | Custom print title |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `print` | `(params?) => Promise<void>` | Trigger print dialog |\n * | `isPrinting` | `() => boolean` | Check if print is in progress |\n *\n * ## Events\n *\n * | Event | Detail | Description |\n * |-------|--------|-------------|\n * | `print-start` | `PrintStartDetail` | Fired when print begins |\n * | `print-complete` | `PrintCompleteDetail` | Fired when print completes |\n *\n * @example Basic Print\n * ```ts\n * import { PrintPlugin } from '@toolbox-web/grid/plugins/print';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * plugins: [new PrintPlugin()],\n * };\n *\n * // Trigger print\n * const printPlugin = grid.getPlugin(PrintPlugin);\n * await printPlugin.print();\n * ```\n *\n * @example With Toolbar Button\n * ```ts\n * grid.gridConfig = {\n * plugins: [new PrintPlugin({ button: true, orientation: 'landscape' })],\n * };\n * ```\n *\n * @see {@link PrintConfig} for all configuration options\n */\nexport class PrintPlugin extends BaseGridPlugin<PrintConfig> {\n /** @internal */\n readonly name = 'print';\n\n /** @internal */\n override readonly version = '1.0.0';\n\n /** CSS styles for print mode */\n override readonly styles = styles;\n\n /** Current print state */\n #printing = false;\n\n /** Saved column visibility state */\n #savedHiddenColumns: Map<string, boolean> | null = null;\n\n /** Saved virtualization state */\n #savedVirtualization: { bypassThreshold: number } | null = null;\n\n /** Saved rows when maxRows limit is applied */\n #savedRows: unknown[] | null = null;\n\n /** Print header element */\n #printHeader: HTMLElement | null = null;\n\n /** Print footer element */\n #printFooter: HTMLElement | null = null;\n\n /** Applied scale factor (legacy, used for cleanup) */\n #appliedScale: number | null = null;\n\n /**\n * Get the grid typed as PrintGridRef for internal access.\n */\n get #internalGrid(): PrintGridRef {\n return this.grid as unknown as PrintGridRef;\n }\n\n /**\n * Check if print is currently in progress\n */\n isPrinting(): boolean {\n return this.#printing;\n }\n\n /**\n * Trigger the browser print dialog\n *\n * This method:\n * 1. Validates row count against maxRows limit\n * 2. Disables virtualization to render all rows\n * 3. Applies print-specific CSS classes\n * 4. Opens the browser print dialog (or isolated window if `isolate: true`)\n * 5. Restores normal state after printing\n *\n * @param params - Optional parameters to override config for this print\n * @param params.isolate - If true, prints in an isolated window containing only the grid\n * @returns Promise that resolves when print dialog closes\n */\n async print(params?: PrintParams): Promise<void> {\n if (this.#printing) {\n console.warn('[PrintPlugin] Print already in progress');\n return;\n }\n\n const grid = this.gridElement;\n if (!grid) {\n console.warn('[PrintPlugin] Grid not available');\n return;\n }\n\n const config = { ...DEFAULT_CONFIG, ...this.config, ...params };\n const rows = this.rows;\n const originalRowCount = rows.length;\n let rowCount = originalRowCount;\n let limitApplied = false;\n\n // Check if we should warn about large datasets\n if (config.warnThreshold > 0 && originalRowCount > config.warnThreshold) {\n const limitInfo =\n config.maxRows > 0 ? `\\n\\nNote: Output will be limited to ${config.maxRows.toLocaleString()} rows.` : '';\n const proceed = confirm(\n `This grid has ${originalRowCount.toLocaleString()} rows. ` +\n `Printing large datasets may cause performance issues or browser slowdowns.${limitInfo}\\n\\n` +\n `Click OK to continue, or Cancel to abort.`,\n );\n if (!proceed) {\n return;\n }\n }\n\n // Apply hard row limit if configured\n if (config.maxRows > 0 && originalRowCount > config.maxRows) {\n rowCount = config.maxRows;\n limitApplied = true;\n }\n\n this.#printing = true;\n\n // Track timing for duration reporting\n const startTime = performance.now();\n\n // Emit print-start event\n this.emit<PrintStartDetail>('print-start', {\n rowCount,\n limitApplied,\n originalRowCount,\n });\n\n try {\n // Save current virtualization state\n const internalGrid = this.#internalGrid;\n this.#savedVirtualization = {\n bypassThreshold: internalGrid._virtualization?.bypassThreshold ?? 24,\n };\n\n // Hide columns marked with printHidden\n this.#hidePrintColumns();\n\n // Apply row limit if configured\n if (limitApplied) {\n this.#savedRows = this.sourceRows;\n // Set limited rows on the grid\n (this.grid as unknown as { rows: unknown[] }).rows = this.sourceRows.slice(0, rowCount);\n // Wait for grid to process new rows\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n\n // Add print header if configured\n if (config.includeTitle || config.includeTimestamp) {\n this.#addPrintHeader(config);\n }\n\n // Disable virtualization to render all rows\n // This forces the grid to render all rows in the DOM\n await this.#disableVirtualization();\n\n // Wait for next frame to ensure DOM is updated\n await new Promise((resolve) => requestAnimationFrame(resolve));\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n // Add orientation class for @page rules\n grid.classList.add(`print-${config.orientation}`);\n\n // Wait for next frame to ensure DOM is updated\n await new Promise((resolve) => requestAnimationFrame(resolve));\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n // Trigger browser print dialog (isolated or inline)\n if (config.isolate) {\n await this.#printInIsolatedWindow(config);\n } else {\n await this.#triggerPrint();\n }\n\n // Emit print-complete event\n this.emit<PrintCompleteDetail>('print-complete', {\n success: true,\n rowCount,\n duration: Math.round(performance.now() - startTime),\n });\n } catch (error) {\n console.error('[PrintPlugin] Print failed:', error);\n this.emit<PrintCompleteDetail>('print-complete', {\n success: false,\n rowCount: 0,\n duration: Math.round(performance.now() - startTime),\n });\n } finally {\n // Restore normal state\n this.#cleanup();\n this.#printing = false;\n }\n }\n\n /**\n * Add print header with title and timestamp\n */\n #addPrintHeader(config: Required<PrintConfig>): void {\n const grid = this.gridElement;\n if (!grid) return;\n\n // Create print header\n this.#printHeader = document.createElement('div');\n this.#printHeader.className = 'tbw-print-header';\n\n // Title\n if (config.includeTitle) {\n const title = config.title || this.grid.effectiveConfig?.shell?.header?.title || 'Grid Data';\n const titleEl = document.createElement('div');\n titleEl.className = 'tbw-print-header-title';\n titleEl.textContent = title;\n this.#printHeader.appendChild(titleEl);\n }\n\n // Timestamp\n if (config.includeTimestamp) {\n const timestampEl = document.createElement('div');\n timestampEl.className = 'tbw-print-header-timestamp';\n timestampEl.textContent = `Printed: ${new Date().toLocaleString()}`;\n this.#printHeader.appendChild(timestampEl);\n }\n\n // Insert at the beginning of the grid\n grid.insertBefore(this.#printHeader, grid.firstChild);\n\n // Create print footer\n this.#printFooter = document.createElement('div');\n this.#printFooter.className = 'tbw-print-footer';\n this.#printFooter.textContent = `Page generated from ${window.location.hostname}`;\n grid.appendChild(this.#printFooter);\n }\n\n /**\n * Disable virtualization to render all rows\n */\n async #disableVirtualization(): Promise<void> {\n const internalGrid = this.#internalGrid;\n if (!internalGrid._virtualization) return;\n\n // Set bypass threshold higher than total row count to disable virtualization\n // This makes the grid render all rows (up to maxRows) instead of just visible ones\n const totalRows = this.rows.length;\n internalGrid._virtualization.bypassThreshold = totalRows + 100;\n\n // Force a full refresh to re-render with virtualization disabled\n internalGrid.refreshVirtualWindow(true);\n\n // Wait for render to complete\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n\n /**\n * Trigger the browser print dialog\n */\n async #triggerPrint(): Promise<void> {\n return new Promise((resolve) => {\n // Listen for afterprint event\n const onAfterPrint = () => {\n window.removeEventListener('afterprint', onAfterPrint);\n resolve();\n };\n window.addEventListener('afterprint', onAfterPrint);\n\n // Trigger print\n window.print();\n\n // Fallback timeout in case afterprint doesn't fire (some browsers)\n setTimeout(() => {\n window.removeEventListener('afterprint', onAfterPrint);\n resolve();\n }, 1000);\n });\n }\n\n /**\n * Print in isolation by hiding all other page content.\n * This excludes navigation, sidebars, etc. while keeping the grid in place.\n */\n async #printInIsolatedWindow(config: Required<PrintConfig>): Promise<void> {\n const grid = this.gridElement;\n if (!grid) return;\n\n await printGridIsolated(grid, {\n orientation: config.orientation,\n });\n }\n\n /**\n * Hide columns marked with printHidden: true\n */\n #hidePrintColumns(): void {\n const columns = this.columns;\n if (!columns) return;\n\n // Save current hidden state and hide print columns\n this.#savedHiddenColumns = new Map();\n\n for (const col of columns) {\n if (col.printHidden && col.field) {\n // Save current visibility state (true = visible, false = hidden)\n this.#savedHiddenColumns.set(col.field, !col.hidden);\n // Hide the column for printing\n this.grid.setColumnVisible(col.field, false);\n }\n }\n }\n\n /**\n * Restore columns that were hidden for printing\n */\n #restorePrintColumns(): void {\n if (!this.#savedHiddenColumns) return;\n\n for (const [field, wasVisible] of this.#savedHiddenColumns) {\n // Restore original visibility\n this.grid.setColumnVisible(field, wasVisible);\n }\n\n this.#savedHiddenColumns = null;\n }\n\n /**\n * Cleanup after printing\n */\n #cleanup(): void {\n const grid = this.gridElement;\n if (!grid) return;\n\n // Restore columns that were hidden for printing\n this.#restorePrintColumns();\n\n // Remove orientation classes (both original and possibly switched)\n grid.classList.remove('print-portrait', 'print-landscape');\n\n // Remove scaling transform if applied (legacy)\n if (this.#appliedScale !== null) {\n grid.style.transform = '';\n grid.style.transformOrigin = '';\n grid.style.width = '';\n this.#appliedScale = null;\n }\n\n // Remove print header/footer\n if (this.#printHeader) {\n this.#printHeader.remove();\n this.#printHeader = null;\n }\n if (this.#printFooter) {\n this.#printFooter.remove();\n this.#printFooter = null;\n }\n\n // Restore virtualization\n const internalGrid = this.#internalGrid;\n if (this.#savedVirtualization && internalGrid._virtualization) {\n internalGrid._virtualization.bypassThreshold = this.#savedVirtualization.bypassThreshold;\n internalGrid.refreshVirtualWindow(true);\n this.#savedVirtualization = null;\n }\n\n // Restore original rows if they were limited\n if (this.#savedRows !== null) {\n (this.grid as unknown as { rows: unknown[] }).rows = this.#savedRows;\n this.#savedRows = null;\n }\n }\n\n /**\n * Register toolbar button if configured\n * @internal\n */\n override afterRender(): void {\n // Register toolbar on first render when button is enabled\n if (this.config?.button && !this.#toolbarRegistered) {\n this.#registerToolbarButton();\n this.#toolbarRegistered = true;\n }\n }\n\n /** Track if toolbar button is registered */\n #toolbarRegistered = false;\n\n /**\n * Register print button in toolbar\n */\n #registerToolbarButton(): void {\n const grid = this.#internalGrid;\n\n // Register toolbar content\n grid.registerToolbarContent?.({\n id: 'print-button',\n order: 900, // High order to appear at the end\n render: (container: HTMLElement) => {\n const button = document.createElement('button');\n button.className = 'tbw-toolbar-btn tbw-print-btn';\n button.title = 'Print grid';\n button.type = 'button';\n\n // Use print icon\n const icon = this.resolveIcon('print') || '🖨️';\n this.setIcon(button, icon);\n\n button.addEventListener(\n 'click',\n () => {\n this.print();\n },\n { signal: this.disconnectSignal },\n );\n\n container.appendChild(button);\n },\n });\n }\n}\n"],"names":["ISOLATION_STYLE_ID","createIsolationStylesheet","gridId","orientation","style","printGridIsolated","gridElement","options","isolationStyle","resolve","onAfterPrint","DEFAULT_CONFIG","PrintPlugin","BaseGridPlugin","styles","#printing","#savedHiddenColumns","#savedVirtualization","#savedRows","#printHeader","#printFooter","#appliedScale","#internalGrid","params","grid","config","originalRowCount","rowCount","limitApplied","limitInfo","startTime","internalGrid","#hidePrintColumns","#addPrintHeader","#disableVirtualization","#printInIsolatedWindow","#triggerPrint","error","#cleanup","title","titleEl","timestampEl","totalRows","columns","col","#restorePrintColumns","field","wasVisible","#toolbarRegistered","#registerToolbarButton","container","button","icon"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- (function(h,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("../../core/internal/utils"),require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/utils","../../core/plugin/base-plugin","../../core/plugin/expander-column"],g):(h=typeof globalThis<"u"?globalThis:h||self,g(h.TbwGridPlugin_selection={},h.TbwGrid,h.TbwGrid,h.TbwGrid))})(this,(function(h,g,y,f){"use strict";function w(r){return{startRow:Math.min(r.startRow,r.endRow),startCol:Math.min(r.startCol,r.endCol),endRow:Math.max(r.startRow,r.endRow),endCol:Math.max(r.startCol,r.endCol)}}function p(r){const e=w(r);return{from:{row:e.startRow,col:e.startCol},to:{row:e.endRow,col:e.endCol}}}function m(r){return r.map(p)}function A(r,e,t){const s=w(t);return r>=s.startRow&&r<=s.endRow&&e>=s.startCol&&e<=s.endCol}function R(r,e,t){return t.some(s=>A(r,e,s))}function S(r){const e=[],t=w(r);for(let s=t.startRow;s<=t.endRow;s++)for(let i=t.startCol;i<=t.endCol;i++)e.push({row:s,col:i});return e}function v(r){const e=new Map;for(const t of r)for(const s of S(t))e.set(`${s.row},${s.col}`,s);return[...e.values()]}function b(r,e){return{startRow:r.row,startCol:r.col,endRow:e.row,endCol:e.col}}const x="@layer tbw-plugins{tbw-grid.selecting .data-grid-row>.cell{-webkit-user-select:none;user-select:none}tbw-grid[data-has-focus] .data-grid-row.row-focus{background-color:var(--tbw-focus-background, rgba(from var(--tbw-color-accent) r g b / 12%))}tbw-grid[data-selection-mode=row] .cell-focus{outline:none}tbw-grid .data-grid-row>.cell.selected{background-color:var(--tbw-range-selection-bg)}tbw-grid .data-grid-row>.cell.selected.top{border-top:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.bottom{border-bottom:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.first{border-left:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.last{border-right:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row[data-selectable=false]{cursor:not-allowed;opacity:.6}tbw-grid .data-grid-row[data-selectable=false].row-focus{background-color:var(--tbw-color-row-alt)}tbw-grid .data-grid-row>.cell[data-selectable=false]{cursor:not-allowed;opacity:.6}tbw-grid .data-grid-row>.cell[data-selectable=false].selected{background-color:var(--tbw-color-warning-bg, rgba(255, 243, 205, .5))}tbw-grid .tbw-selection-summary{font-size:var(--tbw-font-size-sm, .8125rem);color:var(--tbw-color-fg-muted);white-space:nowrap}}";function I(r,e,t){if(r==="cell"&&e.selectedCell)return{mode:r,ranges:[{from:{row:e.selectedCell.row,col:e.selectedCell.col},to:{row:e.selectedCell.row,col:e.selectedCell.col}}]};if(r==="row"&&e.selected.size>0){const s=[...e.selected].map(i=>({from:{row:i,col:0},to:{row:i,col:t-1}}));return{mode:r,ranges:s}}return r==="range"&&e.ranges.length>0?{mode:r,ranges:m(e.ranges)}:{mode:r,ranges:[]}}class k extends y.BaseGridPlugin{static manifest={configRules:[{id:"selection/range-dblclick",severity:"warn",message:`"triggerOn: 'dblclick'" has no effect when mode is "range".
1
+ (function(h,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("../../core/internal/utils"),require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/utils","../../core/plugin/base-plugin","../../core/plugin/expander-column"],g):(h=typeof globalThis<"u"?globalThis:h||self,g(h.TbwGridPlugin_selection={},h.TbwGrid,h.TbwGrid,h.TbwGrid))})(this,(function(h,g,y,f){"use strict";function w(r){return{startRow:Math.min(r.startRow,r.endRow),startCol:Math.min(r.startCol,r.endCol),endRow:Math.max(r.startRow,r.endRow),endCol:Math.max(r.startCol,r.endCol)}}function A(r){const e=w(r);return{from:{row:e.startRow,col:e.startCol},to:{row:e.endRow,col:e.endCol}}}function C(r){return r.map(A)}function S(r,e,t){const s=w(t);return r>=s.startRow&&r<=s.endRow&&e>=s.startCol&&e<=s.endCol}function p(r,e,t){return t.some(s=>S(r,e,s))}function v(r){const e=[],t=w(r);for(let s=t.startRow;s<=t.endRow;s++)for(let n=t.startCol;n<=t.endCol;n++)e.push({row:s,col:n});return e}function x(r){const e=new Map;for(const t of r)for(const s of v(t))e.set(`${s.row},${s.col}`,s);return[...e.values()]}function R(r,e){return{startRow:r.row,startCol:r.col,endRow:e.row,endCol:e.col}}function m(r,e){const t=w(r),s=w(e);return t.startRow===s.startRow&&t.startCol===s.startCol&&t.endRow===s.endRow&&t.endCol===s.endCol}const I="@layer tbw-plugins{tbw-grid.selecting .data-grid-row>.cell{-webkit-user-select:none;user-select:none}tbw-grid[data-has-focus] .data-grid-row.row-focus{background-color:var(--tbw-focus-background, rgba(from var(--tbw-color-accent) r g b / 12%))}tbw-grid[data-selection-mode=row] .cell-focus{outline:none}tbw-grid .data-grid-row>.cell.selected{background-color:var(--tbw-range-selection-bg)}tbw-grid .data-grid-row>.cell.selected.top{border-top:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.bottom{border-bottom:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.first{border-left:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.last{border-right:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row[data-selectable=false]{cursor:not-allowed;opacity:.6}tbw-grid .data-grid-row[data-selectable=false].row-focus{background-color:var(--tbw-color-row-alt)}tbw-grid .data-grid-row>.cell[data-selectable=false]{cursor:not-allowed;opacity:.6}tbw-grid .data-grid-row>.cell[data-selectable=false].selected{background-color:var(--tbw-color-warning-bg, rgba(255, 243, 205, .5))}tbw-grid .tbw-selection-summary{font-size:var(--tbw-font-size-sm, .8125rem);color:var(--tbw-color-fg-muted);white-space:nowrap}}";function k(r,e,t){if(r==="cell"&&e.selectedCell)return{mode:r,ranges:[{from:{row:e.selectedCell.row,col:e.selectedCell.col},to:{row:e.selectedCell.row,col:e.selectedCell.col}}]};if(r==="row"&&e.selected.size>0){const s=[...e.selected].map(n=>({from:{row:n,col:0},to:{row:n,col:t-1}}));return{mode:r,ranges:s}}return r==="range"&&e.ranges.length>0?{mode:r,ranges:C(e.ranges)}:{mode:r,ranges:[]}}class K extends y.BaseGridPlugin{static manifest={configRules:[{id:"selection/range-dblclick",severity:"warn",message:`"triggerOn: 'dblclick'" has no effect when mode is "range".
2
2
  → Range selection uses drag interaction (mousedown → mousemove), not click events.
3
- → The "triggerOn" option only affects "cell" and "row" selection modes.`,check:e=>e.mode==="range"&&e.triggerOn==="dblclick"}]};name="selection";styles=x;get defaultConfig(){return{mode:"cell",triggerOn:"click"}}selected=new Set;lastSelected=null;anchor=null;ranges=[];activeRange=null;cellAnchor=null;isDragging=!1;pendingKeyboardUpdate=null;selectedCell=null;checkSelectable(e,t){const{isSelectable:s}=this.config;if(!s)return!0;const i=this.rows[e];if(!i)return!1;const o=t!==void 0?this.columns[t]:void 0;return s(i,e,o,t)}isRowSelectable(e){return this.checkSelectable(e)}isCellSelectable(e,t){return this.checkSelectable(e,t)}detach(){this.selected.clear(),this.ranges=[],this.activeRange=null,this.cellAnchor=null,this.isDragging=!1,this.selectedCell=null,this.pendingKeyboardUpdate=null}onCellClick(e){const{rowIndex:t,colIndex:s,originalEvent:i}=e,{mode:o,triggerOn:l="click"}=this.config;if(i.type!==l)return!1;const n=this.columns[s],d=n&&f.isUtilityColumn(n);if(o==="cell")return d||!this.isCellSelectable(t,s)||(this.selectedCell={row:t,col:s},this.emit("selection-change",this.#e()),this.requestAfterRender()),!1;if(o==="row")return this.isRowSelectable(t)&&(this.selected.clear(),this.selected.add(t),this.lastSelected=t,this.emit("selection-change",this.#e()),this.requestAfterRender()),!1;if(o==="range"){if(d||!this.isCellSelectable(t,s))return!1;const a=i.shiftKey,u=i.ctrlKey||i.metaKey;if(a&&this.cellAnchor){const c=b(this.cellAnchor,{row:t,col:s});u?this.ranges.length>0?this.ranges[this.ranges.length-1]=c:this.ranges.push(c):this.ranges=[c],this.activeRange=c}else if(u){const c={startRow:t,startCol:s,endRow:t,endCol:s};this.ranges.push(c),this.activeRange=c,this.cellAnchor={row:t,col:s}}else{const c={startRow:t,startCol:s,endRow:t,endCol:s};this.ranges=[c],this.activeRange=c,this.cellAnchor={row:t,col:s}}return this.emit("selection-change",this.#e()),this.requestAfterRender(),!1}return!1}onKeyDown(e){const{mode:t}=this.config,i=["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Tab","Home","End","PageUp","PageDown"].includes(e.key);if(e.key==="Escape")return t==="cell"?this.selectedCell=null:t==="row"?(this.selected.clear(),this.anchor=null):t==="range"&&(this.ranges=[],this.activeRange=null,this.cellAnchor=null),this.emit("selection-change",this.#e()),this.requestAfterRender(),!0;if(t==="cell"&&i)return queueMicrotask(()=>{const o=this.grid._focusRow,l=this.grid._focusCol;this.isCellSelectable(o,l)?this.selectedCell={row:o,col:l}:this.selectedCell=null,this.emit("selection-change",this.#e()),this.requestAfterRender()}),!1;if(t==="row"&&(e.key==="ArrowUp"||e.key==="ArrowDown"))return queueMicrotask(()=>{const o=this.grid._focusRow;this.isRowSelectable(o)?(this.selected.clear(),this.selected.add(o),this.lastSelected=o):this.selected.clear(),this.emit("selection-change",this.#e()),this.requestAfterRender()}),!1;if(t==="range"&&i){const o=e.key==="Tab",l=e.shiftKey&&!o;return l&&!this.cellAnchor&&(this.cellAnchor={row:this.grid._focusRow,col:this.grid._focusCol}),this.pendingKeyboardUpdate={shiftKey:l},queueMicrotask(()=>this.requestAfterRender()),!1}if(t==="range"&&e.key==="a"&&(e.ctrlKey||e.metaKey)){const o=this.rows.length,l=this.columns.length;if(o>0&&l>0){const n={startRow:0,startCol:0,endRow:o-1,endCol:l-1};return this.ranges=[n],this.activeRange=n,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0}}return!1}onCellMouseDown(e){if(this.config.mode!=="range"||e.rowIndex===void 0||e.colIndex===void 0||e.rowIndex<0)return;const t=this.columns[e.colIndex];if(t&&f.isUtilityColumn(t)||!this.isCellSelectable(e.rowIndex,e.colIndex)||e.originalEvent.shiftKey&&this.cellAnchor)return;this.isDragging=!0;const s=e.rowIndex,i=e.colIndex;this.cellAnchor={row:s,col:i},e.originalEvent.ctrlKey||e.originalEvent.metaKey||(this.ranges=[]);const l={startRow:s,startCol:i,endRow:s,endCol:i};return this.ranges.push(l),this.activeRange=l,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0}onCellMouseMove(e){if(this.config.mode!=="range"||!this.isDragging||!this.cellAnchor||e.rowIndex===void 0||e.colIndex===void 0||e.rowIndex<0)return;let t=e.colIndex;const s=this.columns[t];if(s&&f.isUtilityColumn(s)){const o=this.columns.findIndex(l=>!f.isUtilityColumn(l));o>=0&&(t=o)}const i=b(this.cellAnchor,{row:e.rowIndex,col:t});return this.ranges.length>0?this.ranges[this.ranges.length-1]=i:this.ranges.push(i),this.activeRange=i,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0}onCellMouseUp(e){if(this.config.mode==="range"&&this.isDragging)return this.isDragging=!1,!0}#t(){const e=this.gridElement;if(!e)return;const{mode:t}=this.config,s=!!this.config.isSelectable;e.querySelectorAll(".cell").forEach(l=>{l.classList.remove("selected","top","bottom","first","last"),s&&l.removeAttribute("data-selectable")});const o=e.querySelectorAll(".data-grid-row");if(o.forEach(l=>{l.classList.remove("selected","row-focus"),s&&l.removeAttribute("data-selectable")}),t==="row"&&(g.clearCellFocus(e),o.forEach(l=>{const n=l.querySelector(".cell[data-row]"),d=g.getRowIndexFromCell(n);d>=0&&(s&&!this.isRowSelectable(d)&&l.setAttribute("data-selectable","false"),this.selected.has(d)&&l.classList.add("selected","row-focus"))})),(t==="cell"||t==="range")&&s&&e.querySelectorAll(".cell[data-row][data-col]").forEach(n=>{const d=parseInt(n.getAttribute("data-row")??"-1",10),a=parseInt(n.getAttribute("data-col")??"-1",10);d>=0&&a>=0&&(this.isCellSelectable(d,a)||n.setAttribute("data-selectable","false"))}),t==="range"&&this.ranges.length>0){g.clearCellFocus(e);const l=this.activeRange?w(this.activeRange):null,n=this.columns.findIndex(a=>!f.isUtilityColumn(a));this.columns.length-1,e.querySelectorAll(".cell[data-row][data-col]").forEach(a=>{const u=parseInt(a.getAttribute("data-row")??"-1",10),c=parseInt(a.getAttribute("data-col")??"-1",10);if(u>=0&&c>=0){const C=this.columns[c];if(C&&f.isUtilityColumn(C))return;if(R(u,c,this.ranges)&&(a.classList.add("selected"),l)){u===l.startRow&&a.classList.add("top"),u===l.endRow&&a.classList.add("bottom");const K=Math.max(l.startCol,n);c===K&&a.classList.add("first"),c===l.endCol&&a.classList.add("last")}}})}}afterRender(){const e=this.gridElement;if(!e)return;const t=e.children[0],{mode:s}=this.config;if(this.pendingKeyboardUpdate&&s==="range"){const{shiftKey:i}=this.pendingKeyboardUpdate;this.pendingKeyboardUpdate=null;const o=this.grid._focusRow,l=this.grid._focusCol;if(i&&this.cellAnchor){const n=b(this.cellAnchor,{row:o,col:l});this.ranges=[n],this.activeRange=n}else i||(this.ranges=[],this.activeRange=null,this.cellAnchor={row:o,col:l});this.emit("selection-change",this.#e())}this.grid.setAttribute("data-selection-mode",s),t&&t.classList.toggle("selecting",this.isDragging),this.#t()}onScrollRender(){this.#t()}getSelection(){return{mode:this.config.mode,ranges:this.#e().ranges,anchor:this.cellAnchor}}getSelectedCells(){return v(this.ranges)}isCellSelected(e,t){return R(e,t,this.ranges)}clearSelection(){this.selectedCell=null,this.selected.clear(),this.anchor=null,this.ranges=[],this.activeRange=null,this.cellAnchor=null,this.emit("selection-change",{mode:this.config.mode,ranges:[]}),this.requestAfterRender()}setRanges(e){this.ranges=e.map(t=>({startRow:t.from.row,startCol:t.from.col,endRow:t.to.row,endCol:t.to.col})),this.activeRange=this.ranges.length>0?this.ranges[this.ranges.length-1]:null,this.emit("selection-change",{mode:this.config.mode,ranges:m(this.ranges)}),this.requestAfterRender()}#e(){return I(this.config.mode,{selectedCell:this.selectedCell,selected:this.selected,ranges:this.ranges},this.columns.length)}}h.SelectionPlugin=k,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
3
+ → The "triggerOn" option only affects "cell" and "row" selection modes.`,check:e=>e.mode==="range"&&e.triggerOn==="dblclick"}]};name="selection";styles=I;get defaultConfig(){return{mode:"cell",triggerOn:"click"}}selected=new Set;lastSelected=null;anchor=null;ranges=[];activeRange=null;cellAnchor=null;isDragging=!1;pendingKeyboardUpdate=null;selectedCell=null;checkSelectable(e,t){const{isSelectable:s}=this.config;if(!s)return!0;const n=this.rows[e];if(!n)return!1;const i=t!==void 0?this.columns[t]:void 0;return s(n,e,i,t)}isRowSelectable(e){return this.checkSelectable(e)}isCellSelectable(e,t){return this.checkSelectable(e,t)}detach(){this.selected.clear(),this.ranges=[],this.activeRange=null,this.cellAnchor=null,this.isDragging=!1,this.selectedCell=null,this.pendingKeyboardUpdate=null}onCellClick(e){const{rowIndex:t,colIndex:s,originalEvent:n}=e,{mode:i,triggerOn:l="click"}=this.config;if(n.type!==l)return!1;const o=this.columns[s],d=o&&f.isUtilityColumn(o);if(i==="cell"){if(d||!this.isCellSelectable(t,s))return!1;const c=this.selectedCell;return c&&c.row===t&&c.col===s||(this.selectedCell={row:t,col:s},this.emit("selection-change",this.#e()),this.requestAfterRender()),!1}if(i==="row")return!this.isRowSelectable(t)||this.selected.size===1&&this.selected.has(t)||(this.selected.clear(),this.selected.add(t),this.lastSelected=t,this.emit("selection-change",this.#e()),this.requestAfterRender()),!1;if(i==="range"){if(d||!this.isCellSelectable(t,s))return!1;const c=n.shiftKey,u=n.ctrlKey||n.metaKey;if(c&&this.cellAnchor){const a=R(this.cellAnchor,{row:t,col:s}),b=this.ranges.length>0?this.ranges[this.ranges.length-1]:null;if(b&&m(b,a))return!1;u?this.ranges.length>0?this.ranges[this.ranges.length-1]=a:this.ranges.push(a):this.ranges=[a],this.activeRange=a}else if(u){const a={startRow:t,startCol:s,endRow:t,endCol:s};this.ranges.push(a),this.activeRange=a,this.cellAnchor={row:t,col:s}}else{const a={startRow:t,startCol:s,endRow:t,endCol:s};if(this.ranges.length===1&&m(this.ranges[0],a))return!1;this.ranges=[a],this.activeRange=a,this.cellAnchor={row:t,col:s}}return this.emit("selection-change",this.#e()),this.requestAfterRender(),!1}return!1}onKeyDown(e){const{mode:t}=this.config,n=["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Tab","Home","End","PageUp","PageDown"].includes(e.key);if(e.key==="Escape")return t==="cell"?this.selectedCell=null:t==="row"?(this.selected.clear(),this.anchor=null):t==="range"&&(this.ranges=[],this.activeRange=null,this.cellAnchor=null),this.emit("selection-change",this.#e()),this.requestAfterRender(),!0;if(t==="cell"&&n)return queueMicrotask(()=>{const i=this.grid._focusRow,l=this.grid._focusCol;this.isCellSelectable(i,l)?this.selectedCell={row:i,col:l}:this.selectedCell=null,this.emit("selection-change",this.#e()),this.requestAfterRender()}),!1;if(t==="row"&&(e.key==="ArrowUp"||e.key==="ArrowDown"))return queueMicrotask(()=>{const i=this.grid._focusRow;this.isRowSelectable(i)?(this.selected.clear(),this.selected.add(i),this.lastSelected=i):this.selected.clear(),this.emit("selection-change",this.#e()),this.requestAfterRender()}),!1;if(t==="range"&&n){const i=e.key==="Tab",l=e.shiftKey&&!i;return l&&!this.cellAnchor&&(this.cellAnchor={row:this.grid._focusRow,col:this.grid._focusCol}),this.pendingKeyboardUpdate={shiftKey:l},queueMicrotask(()=>this.requestAfterRender()),!1}if(t==="range"&&e.key==="a"&&(e.ctrlKey||e.metaKey)){const i=this.rows.length,l=this.columns.length;if(i>0&&l>0){e.preventDefault(),e.stopPropagation();const o={startRow:0,startCol:0,endRow:i-1,endCol:l-1};return this.ranges=[o],this.activeRange=o,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0}}return!1}onCellMouseDown(e){if(this.config.mode!=="range"||e.rowIndex===void 0||e.colIndex===void 0||e.rowIndex<0)return;const t=this.columns[e.colIndex];if(t&&f.isUtilityColumn(t)||!this.isCellSelectable(e.rowIndex,e.colIndex)||e.originalEvent.shiftKey&&this.cellAnchor)return;this.isDragging=!0;const s=e.rowIndex,n=e.colIndex,i=e.originalEvent.ctrlKey||e.originalEvent.metaKey,l={startRow:s,startCol:n,endRow:s,endCol:n};return!i&&this.ranges.length===1&&m(this.ranges[0],l)?(this.cellAnchor={row:s,col:n},!0):(this.cellAnchor={row:s,col:n},i||(this.ranges=[]),this.ranges.push(l),this.activeRange=l,this.emit("selection-change",this.#e()),this.requestAfterRender(),!0)}onCellMouseMove(e){if(this.config.mode!=="range"||!this.isDragging||!this.cellAnchor||e.rowIndex===void 0||e.colIndex===void 0||e.rowIndex<0)return;let t=e.colIndex;const s=this.columns[t];if(s&&f.isUtilityColumn(s)){const l=this.columns.findIndex(o=>!f.isUtilityColumn(o));l>=0&&(t=l)}const n=R(this.cellAnchor,{row:e.rowIndex,col:t}),i=this.ranges.length>0?this.ranges[this.ranges.length-1]:null;return i&&m(i,n)||(this.ranges.length>0?this.ranges[this.ranges.length-1]=n:this.ranges.push(n),this.activeRange=n,this.emit("selection-change",this.#e()),this.requestAfterRender()),!0}onCellMouseUp(e){if(this.config.mode==="range"&&this.isDragging)return this.isDragging=!1,!0}#t(){const e=this.gridElement;if(!e)return;const{mode:t}=this.config,s=!!this.config.isSelectable;e.querySelectorAll(".cell").forEach(l=>{l.classList.remove("selected","top","bottom","first","last"),s&&l.removeAttribute("data-selectable")});const i=e.querySelectorAll(".data-grid-row");if(i.forEach(l=>{l.classList.remove("selected","row-focus"),s&&l.removeAttribute("data-selectable")}),t==="row"&&(g.clearCellFocus(e),i.forEach(l=>{const o=l.querySelector(".cell[data-row]"),d=g.getRowIndexFromCell(o);d>=0&&(s&&!this.isRowSelectable(d)&&l.setAttribute("data-selectable","false"),this.selected.has(d)&&l.classList.add("selected","row-focus"))})),(t==="cell"||t==="range")&&s&&e.querySelectorAll(".cell[data-row][data-col]").forEach(o=>{const d=parseInt(o.getAttribute("data-row")??"-1",10),c=parseInt(o.getAttribute("data-col")??"-1",10);d>=0&&c>=0&&(this.isCellSelectable(d,c)||o.setAttribute("data-selectable","false"))}),t==="range"&&this.ranges.length>0){g.clearCellFocus(e);const l=this.activeRange?w(this.activeRange):null,o=this.columns.findIndex(c=>!f.isUtilityColumn(c));this.columns.length-1,e.querySelectorAll(".cell[data-row][data-col]").forEach(c=>{const u=parseInt(c.getAttribute("data-row")??"-1",10),a=parseInt(c.getAttribute("data-col")??"-1",10);if(u>=0&&a>=0){const b=this.columns[a];if(b&&f.isUtilityColumn(b))return;if(p(u,a,this.ranges)&&(c.classList.add("selected"),l)){u===l.startRow&&c.classList.add("top"),u===l.endRow&&c.classList.add("bottom");const q=Math.max(l.startCol,o);a===q&&c.classList.add("first"),a===l.endCol&&c.classList.add("last")}}})}}afterRender(){const e=this.gridElement;if(!e)return;const t=e.children[0],{mode:s}=this.config;if(this.pendingKeyboardUpdate&&s==="range"){const{shiftKey:n}=this.pendingKeyboardUpdate;this.pendingKeyboardUpdate=null;const i=this.grid._focusRow,l=this.grid._focusCol;if(n&&this.cellAnchor){const o=R(this.cellAnchor,{row:i,col:l});this.ranges=[o],this.activeRange=o}else n||(this.ranges=[],this.activeRange=null,this.cellAnchor={row:i,col:l});this.emit("selection-change",this.#e())}this.grid.setAttribute("data-selection-mode",s),t&&t.classList.toggle("selecting",this.isDragging),this.#t()}onScrollRender(){this.#t()}getSelection(){return{mode:this.config.mode,ranges:this.#e().ranges,anchor:this.cellAnchor}}getSelectedCells(){return x(this.ranges)}isCellSelected(e,t){return p(e,t,this.ranges)}clearSelection(){this.selectedCell=null,this.selected.clear(),this.anchor=null,this.ranges=[],this.activeRange=null,this.cellAnchor=null,this.emit("selection-change",{mode:this.config.mode,ranges:[]}),this.requestAfterRender()}setRanges(e){this.ranges=e.map(t=>({startRow:t.from.row,startCol:t.from.col,endRow:t.to.row,endCol:t.to.col})),this.activeRange=this.ranges.length>0?this.ranges[this.ranges.length-1]:null,this.emit("selection-change",{mode:this.config.mode,ranges:C(this.ranges)}),this.requestAfterRender()}#e(){return k(this.config.mode,{selectedCell:this.selectedCell,selected:this.selected,ranges:this.ranges},this.columns.length)}}h.SelectionPlugin=K,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
4
4
  //# sourceMappingURL=selection.umd.js.map