@toolbox-web/grid 2.14.0 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all.js +2 -2
- package/all.js.map +1 -1
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/lib/core/constants.d.ts +6 -6
- package/lib/core/plugin/base-plugin.d.ts +1 -1
- package/lib/core/types.d.ts +125 -0
- package/lib/features/registry.js +1 -1
- package/lib/features/registry.js.map +1 -1
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/editing/index.js +1 -1
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/editing/internal/helpers.d.ts +13 -0
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/FilteringPlugin.d.ts +22 -2
- package/lib/plugins/filtering/index.js +1 -1
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts +8 -0
- package/lib/plugins/pinned-rows/index.js +1 -1
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pinned-rows/pinned-rows.d.ts +7 -1
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/print/index.js.map +1 -1
- package/lib/plugins/reorder-columns/index.js.map +1 -1
- package/lib/plugins/reorder-rows/index.js.map +1 -1
- package/lib/plugins/responsive/index.js.map +1 -1
- package/lib/plugins/row-drag-drop/index.js.map +1 -1
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/sticky-rows/index.js.map +1 -1
- package/lib/plugins/tooltip/index.js.map +1 -1
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +1 -1
- package/public.d.ts +1 -1
- package/umd/grid.all.umd.js +1 -1
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +1 -1
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/editing.umd.js +1 -1
- package/umd/plugins/editing.umd.js.map +1 -1
- package/umd/plugins/filtering.umd.js +1 -1
- package/umd/plugins/filtering.umd.js.map +1 -1
- package/umd/plugins/pinned-rows.umd.js +1 -1
- package/umd/plugins/pinned-rows.umd.js.map +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("../../core/internal/aggregators"),require("../../core/internal/sanitize"),require("../../core/plugin/base-plugin")):"function"==typeof define&&define.amd?define(["exports","../../core/internal/aggregators","../../core/internal/sanitize","../../core/plugin/base-plugin"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TbwGridPlugin_pinnedRows={},t.TbwGrid,t.TbwGrid,t.TbwGrid)}(this,function(t,e,n,o){"use strict";function r(t,e){const n=document.createElement("div");n.className="tbw-pinned-rows",n.setAttribute("role","presentation"),n.setAttribute("aria-live","polite");const o=document.createElement("div");o.className="tbw-pinned-rows-left";const r=document.createElement("div");r.className="tbw-pinned-rows-center";const i=document.createElement("div");if(i.className="tbw-pinned-rows-right",!1!==t.showRowCount){const t=document.createElement("span");t.className="tbw-status-panel tbw-status-panel-row-count",t.textContent=`Total: ${e.totalRows} rows`,o.appendChild(t)}if(t.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}`,o.appendChild(t)}if(t.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(t.customPanels)for(const a of t.customPanels){const t=c(a,e);switch(a.position){case"left":o.appendChild(t);break;case"center":r.appendChild(t);break;case"right":i.appendChild(t)}}return n.appendChild(o),n.appendChild(r),n.appendChild(i),n}function i(t){const e=document.createElement("div");return e.className=`tbw-aggregation-rows tbw-aggregation-rows-${t}`,e.setAttribute("role","presentation"),e}function a(t,e,n,o,r=!1){t.innerHTML="";for(const i of e){const e=document.createElement("div");e.className="tbw-aggregation-row",e.setAttribute("role","presentation"),i.id&&e.setAttribute("data-aggregation-id",i.id);i.fullWidth??r?s(e,i,n,o):l(e,i,n,o),t.appendChild(e)}}function s(t,e,n,o){const r=document.createElement("div");r.className="tbw-aggregation-cell tbw-aggregation-cell-full",r.style.gridColumn="1 / -1";const i="function"==typeof e.label?e.label(o,n):e.label;if(i){const t=document.createElement("span");t.className="tbw-aggregation-label",t.textContent=i,r.appendChild(t)}const a=function(t,e,n){const o=t.aggregators&&Object.keys(t.aggregators).length>0,r=t.cells&&Object.keys(t.cells).length>0;if(!o&&!r)return null;const i=document.createElement("span");i.className="tbw-aggregation-aggregates";for(const a of e){const{value:e,formatter:o}=g(t,a,n);if(null!=e){const t=document.createElement("span");t.className="tbw-aggregation-aggregate",t.setAttribute("data-field",a.field);const n=a.header??a.field,r=o?o(e,a.field,a):String(e);t.textContent=`${n}: ${r}`,i.appendChild(t)}}return i.children.length>0?i:null}(e,n,o);a&&r.appendChild(a),t.appendChild(r)}function l(t,e,n,o){for(const i of n){const n=document.createElement("div");n.className="tbw-aggregation-cell",n.setAttribute("data-field",i.field);const{value:r,formatter:a}=g(e,i,o);n.textContent=null!=r?a?a(r,i.field,i):String(r):"",t.appendChild(n)}const r="function"==typeof e.label?e.label(o,n):e.label;if(r){const e=document.createElement("span");e.className="tbw-aggregation-label",e.textContent=r,t.appendChild(e)}}function g(t,n,o){let r,i;const a=t.aggregators?.[n.field];if(a)if("object"==typeof(s=a)&&null!==s&&"aggFunc"in s){const t=e.aggregatorRegistry.get(a.aggFunc);t&&(r=t(o,n.field,n)),i=a.formatter}else{const t=e.aggregatorRegistry.get(a);t&&(r=t(o,n.field,n))}else if(t.cells&&Object.prototype.hasOwnProperty.call(t.cells,n.field)){const e=t.cells[n.field];r="function"==typeof e?e(o,n.field,n):e}var s;return{value:r,formatter:i}}function c(t,e){const o=document.createElement("div");o.className="tbw-status-panel tbw-status-panel-custom",o.id=`status-panel-${t.id}`;const r=t.render(e);return"string"==typeof r?o.innerHTML=n.sanitizeHTML(r):o.appendChild(r),o}function p(t,e,n,o,r){const i=n?.sourceRows,a=n?.rows,s=Array.isArray(i)?i.length:t.length,l=Array.isArray(a)?a.length:t.length,g=s>0?s:l;return{totalRows:g,filteredRows:r?.cachedResult?.length??(l<s?l:g),selectedRows:o?.selected?.size??0,columns:e,rows:t,grid:n}}function d(t){const e=document.createElement("div");return e.className="tbw-status-panel tbw-status-panel-custom",e.appendChild(t),e}function h(t,e){const n=document.createElement("div");n.className="tbw-pinned-rows",n.setAttribute("role","presentation"),n.setAttribute("aria-live","polite"),t.id&&n.setAttribute("data-pinned-row-id",t.id);const o=document.createElement("div");o.className="tbw-pinned-rows-left";const r=document.createElement("div");r.className="tbw-pinned-rows-center";const i=document.createElement("div");i.className="tbw-pinned-rows-right";const a={left:o,center:r,right:i},s=Array.isArray(t.render)?t.render:[{zone:"left",render:t.render}];for(const l of s){const t=l.zone??"left",n=l.render(e);if(null==n)continue;const o=n.classList?.contains("tbw-status-panel");a[t].appendChild(o?n:d(n))}return 0===o.children.length&&0===r.children.length&&0===i.children.length?null:(n.appendChild(o),n.appendChild(r),n.appendChild(i),n)}function u(t,e,n,o,r=!1){const s=i(e);return a(s,[t],n,o,r),s}class f extends o.BaseGridPlugin{name="pinnedRows";styles="@layer tbw-plugins{.tbw-scroll-area{container-type:inline-size}.tbw-footer,.tbw-header-pinned{flex-shrink:0;z-index:var(--tbw-z-layer-pinned-rows, 20);background:var(--tbw-color-panel-bg);min-width:fit-content}.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;position:sticky;left:0;min-width:0;width:100cqi}.tbw-pinned-rows+.tbw-pinned-rows{border-top:0}.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);position:relative;background:inherit}.tbw-aggregation-row>.tbw-aggregation-label{position:sticky;left:0;grid-row:1;grid-column:1;display:flex;align-items:center;padding:var(--tbw-cell-padding, .125rem .5rem);background:inherit;z-index:1;pointer-events:none}.tbw-aggregation-row>.tbw-aggregation-cell:first-child{grid-column:1;grid-row:1}.tbw-aggregation-cell:not(:empty){position:relative;z-index:2;background:inherit}.tbw-aggregation-cell{padding:var(--tbw-cell-padding, .125rem .5rem);min-height:var(--tbw-row-height, 1.75rem);display:block;align-items:center;align-content: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;display:flex;align-items:center;gap:var(--tbw-spacing-lg, .75rem)}.tbw-aggregation-label{white-space:nowrap}.tbw-aggregation-aggregates{display:flex;align-items:center;gap:var(--tbw-spacing-lg, .75rem);font-weight:400;opacity:.85}.tbw-aggregation-aggregate{white-space:nowrap}}";get defaultConfig(){return{position:"bottom",showRowCount:!0,showSelectedCount:!0,showFilteredCount:!0}}infoBarElement=null;topAggregationContainer=null;bottomAggregationContainer=null;footerWrapper=null;headerWrapper=null;lastModeWasSlots=!1;detach(){this.cleanup()}afterRender(){const t=this.gridElement;if(!t)return;const e=t.querySelector(".tbw-scroll-area")??t.querySelector(".tbw-grid-content")??t.querySelector(".tbw-grid-root");if(!e)return;this.footerWrapper&&!e.contains(this.footerWrapper)&&(this.footerWrapper=null,this.bottomAggregationContainer=null,this.infoBarElement=null),this.topAggregationContainer&&!e.contains(this.topAggregationContainer)&&(this.topAggregationContainer=null),this.infoBarElement&&!e.contains(this.infoBarElement)&&(this.infoBarElement=null),this.headerWrapper&&!e.contains(this.headerWrapper)&&(this.headerWrapper=null);const n=this.getSelectionState(),o=this.getFilterState(),r=p(this.sourceRows,this.columns,this.gridElement,n,o);this.config.slots?this.renderSlotMode(e,t,r):this.renderLegacyMode(e,t,r)}renderSlotMode(t,e,n){this.lastModeWasSlots||this.detachLegacyOnly(),this.lastModeWasSlots=!0;const o=this.config.slots??[],r=o.filter(t=>"top"===t.position),i=o.filter(t=>"top"!==t.position);r.length>0?(this.headerWrapper||(this.headerWrapper=document.createElement("div"),this.headerWrapper.className="tbw-header-pinned",t.insertBefore(this.headerWrapper,t.firstChild)),this.populateSlotWrapper(this.headerWrapper,r,"top",n)):this.headerWrapper&&(this.headerWrapper.remove(),this.headerWrapper=null),i.length>0?(this.footerWrapper||(this.footerWrapper=document.createElement("div"),this.footerWrapper.className="tbw-footer",t.appendChild(this.footerWrapper)),this.populateSlotWrapper(this.footerWrapper,i,"bottom",n)):this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null)}populateSlotWrapper(t,e,n,o){t.innerHTML="";for(const r of e){const e="render"in r&&null!=r.render?h(r,o):u(r,n,this.visibleColumns,this.sourceRows,this.config.fullWidth);e&&t.appendChild(e)}}renderLegacyMode(t,e,n){this.lastModeWasSlots&&(this.headerWrapper&&(this.headerWrapper.remove(),this.headerWrapper=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.bottomAggregationContainer=null,this.infoBarElement=null),this.lastModeWasSlots=!1;const o=this.config.aggregationRows||[],s=o.filter(t=>"top"===t.position),l=o.filter(t=>"top"!==t.position);s.length>0?(this.topAggregationContainer||(this.topAggregationContainer=i("top"),t.insertBefore(this.topAggregationContainer,t.firstChild)),a(this.topAggregationContainer,s,this.visibleColumns,this.sourceRows,this.config.fullWidth)):this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null);const g=!1!==this.config.showRowCount||this.config.showSelectedCount&&n.selectedRows>0||this.config.showFilteredCount&&n.filteredRows!==n.totalRows||this.config.customPanels&&this.config.customPanels.length>0,c=g&&"top"!==this.config.position,p=l.length>0||c;if(g&&"top"===this.config.position)if(this.infoBarElement){const t=r(this.config,n);this.infoBarElement.replaceWith(t),this.infoBarElement=t}else this.infoBarElement=r(this.config,n),t.insertBefore(this.infoBarElement,t.firstChild);else"top"===this.config.position&&this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null);p?(this.footerWrapper||(this.footerWrapper=document.createElement("div"),this.footerWrapper.className="tbw-footer",t.appendChild(this.footerWrapper)),this.footerWrapper.innerHTML="",l.length>0&&(this.bottomAggregationContainer||(this.bottomAggregationContainer=i("bottom")),this.footerWrapper.appendChild(this.bottomAggregationContainer),a(this.bottomAggregationContainer,l,this.visibleColumns,this.sourceRows,this.config.fullWidth)),c&&(this.infoBarElement=r(this.config,n),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),this.headerWrapper&&(this.headerWrapper.remove(),this.headerWrapper=null)}detachLegacyOnly(){this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null)}cleanupFooter(){this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.infoBarElement&&"top"!==this.config.position&&(this.infoBarElement.remove(),this.infoBarElement=null)}getSelectionState(){try{return this.grid?.getPluginState?.("selection")??null}catch{return null}}getFilterState(){try{return this.grid?.getPluginState?.("filtering")??null}catch{return null}}refresh(){this.requestRender()}getContext(){const t=this.getSelectionState(),e=this.getFilterState();return p(this.rows,this.columns,this.gridElement,t,e)}addPanel(t){this.config.customPanels||(this.config.customPanels=[]),this.config.customPanels.push(t),this.requestRender()}removePanel(t){this.config.customPanels&&(this.config.customPanels=this.config.customPanels.filter(e=>e.id!==t),this.requestRender())}addAggregationRow(t){this.config.aggregationRows||(this.config.aggregationRows=[]),this.config.aggregationRows.push(t),this.requestRender()}removeAggregationRow(t){this.config.aggregationRows&&(this.config.aggregationRows=this.config.aggregationRows.filter(e=>e.id!==t),this.requestRender())}}t.PinnedRowsPlugin=f,t.filteredCountPanel=function(){return t=>{if(t.filteredRows===t.totalRows)return null;const e=document.createElement("span");return e.className="tbw-status-panel tbw-status-panel-filtered-count",e.textContent=`Filtered: ${t.filteredRows}`,e}},t.rowCountPanel=function(){return t=>{const e=document.createElement("span");return e.className="tbw-status-panel tbw-status-panel-row-count",e.textContent=`Total: ${t.totalRows} rows`,e}},t.selectedCountPanel=function(){return t=>{if(t.selectedRows<=0)return null;const e=document.createElement("span");return e.className="tbw-status-panel tbw-status-panel-selected-count",e.textContent=`Selected: ${t.selectedRows}`,e}},Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/internal/aggregators"),require("../../core/internal/sanitize"),require("../../core/plugin/base-plugin")):"function"==typeof define&&define.amd?define(["exports","../../core/internal/aggregators","../../core/internal/sanitize","../../core/plugin/base-plugin"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_pinnedRows={},e.TbwGrid,e.TbwGrid,e.TbwGrid)}(this,function(e,t,n,o){"use strict";function r(e,t){const n=document.createElement("div");n.className="tbw-pinned-rows",n.setAttribute("role","presentation"),n.setAttribute("aria-live","polite");const o=document.createElement("div");o.className="tbw-pinned-rows-left";const r=document.createElement("div");r.className="tbw-pinned-rows-center";const i=document.createElement("div");if(i.className="tbw-pinned-rows-right",!1!==e.showRowCount){const e=document.createElement("span");e.className="tbw-status-panel tbw-status-panel-row-count",e.textContent=`Total: ${t.totalRows} rows`,o.appendChild(e)}if(e.showFilteredCount&&t.filteredRows!==t.totalRows){const e=document.createElement("span");e.className="tbw-status-panel tbw-status-panel-filtered-count",e.textContent=`Filtered: ${t.filteredRows}`,o.appendChild(e)}if(e.showSelectedCount&&t.selectedRows>0){const e=document.createElement("span");e.className="tbw-status-panel tbw-status-panel-selected-count",e.textContent=`Selected: ${t.selectedRows}`,i.appendChild(e)}if(e.customPanels)for(const a of e.customPanels){const e=c(a,t);switch(a.position){case"left":o.appendChild(e);break;case"center":r.appendChild(e);break;case"right":i.appendChild(e)}}return n.appendChild(o),n.appendChild(r),n.appendChild(i),n}function i(e){const t=document.createElement("div");return t.className=`tbw-aggregation-rows tbw-aggregation-rows-${e}`,t.setAttribute("role","presentation"),t}function a(e,t,n,o,r=!1){e.innerHTML="";for(const i of t){const t=document.createElement("div");t.className="tbw-aggregation-row",t.setAttribute("role","presentation"),i.id&&t.setAttribute("data-aggregation-id",i.id);i.fullWidth??r?s(t,i,n,o):l(t,i,n,o),e.appendChild(t)}}function s(e,t,n,o){const r=document.createElement("div");r.className="tbw-aggregation-cell tbw-aggregation-cell-full",r.style.gridColumn="1 / -1";const i="function"==typeof t.label?t.label(o,n):t.label;if(i){const e=document.createElement("span");e.className="tbw-aggregation-label",e.textContent=i,r.appendChild(e)}const a=function(e,t,n){const o=e.aggregators&&Object.keys(e.aggregators).length>0,r=e.cells&&Object.keys(e.cells).length>0;if(!o&&!r)return null;const i=document.createElement("span");i.className="tbw-aggregation-aggregates";for(const a of t){const{value:t,formatter:o}=g(e,a,n);if(null!=t){const e=document.createElement("span");e.className="tbw-aggregation-aggregate",e.setAttribute("data-field",a.field);const n=a.header??a.field,r=o?o(t,a.field,a):String(t);e.textContent=`${n}: ${r}`,i.appendChild(e)}}return i.children.length>0?i:null}(t,n,o);a&&r.appendChild(a),e.appendChild(r)}function l(e,t,n,o){for(const i of n){const n=document.createElement("div");n.className="tbw-aggregation-cell",n.setAttribute("data-field",i.field);const{value:r,formatter:a}=g(t,i,o);n.textContent=null!=r?a?a(r,i.field,i):String(r):"",e.appendChild(n)}const r="function"==typeof t.label?t.label(o,n):t.label;if(r){const t=document.createElement("span");t.className="tbw-aggregation-label",t.textContent=r,e.appendChild(t)}}function g(e,n,o){let r,i;const a=e.aggregators?.[n.field];if(a)if("object"==typeof(s=a)&&null!==s&&"aggFunc"in s){const e=t.aggregatorRegistry.get(a.aggFunc);e&&(r=e(o,n.field,n)),i=a.formatter}else{const e=t.aggregatorRegistry.get(a);e&&(r=e(o,n.field,n))}else if(e.cells&&Object.prototype.hasOwnProperty.call(e.cells,n.field)){const t=e.cells[n.field];r="function"==typeof t?t(o,n.field,n):t}var s;return{value:r,formatter:i}}function c(e,t){const o=document.createElement("div");o.className="tbw-status-panel tbw-status-panel-custom",o.id=`status-panel-${e.id}`;const r=e.render(t);return"string"==typeof r?o.innerHTML=n.sanitizeHTML(r):o.appendChild(r),o}function d(e,t,n,o,r){const i=n?.sourceRows,a=n?.rows,s=Array.isArray(i)?i.length:e.length,l=Array.isArray(a)?a.length:e.length,g=s>0?s:l;return{totalRows:g,filteredRows:r?.cachedResult?.length??(l<s?l:g),selectedRows:o?.selected?.size??0,columns:t,rows:e,grid:n}}function p(e){const t=document.createElement("div");return t.className="tbw-status-panel tbw-status-panel-custom",t.appendChild(e),t}const h=new WeakMap;function u(e,t,n){const o=Array.isArray(e.render)?e.render:[{zone:"left",render:e.render}],r=o.map(e=>e.render(t)??null);if(n){const e=h.get(n);if(e&&e.length===r.length&&e.every((e,t)=>e===r[t]))return n}const i=document.createElement("div");i.className="tbw-pinned-rows",i.setAttribute("role","presentation"),i.setAttribute("aria-live","polite"),e.id&&i.setAttribute("data-pinned-row-id",e.id);const a=document.createElement("div");a.className="tbw-pinned-rows-left";const s=document.createElement("div");s.className="tbw-pinned-rows-center";const l=document.createElement("div");l.className="tbw-pinned-rows-right";const g={left:a,center:s,right:l};for(let c=0;c<o.length;c++){const e=o[c],t=r[c];if(null==t)continue;const n=e.zone??"left",i=t.classList?.contains("tbw-status-panel");g[n].appendChild(i?t:p(t))}return 0===a.children.length&&0===s.children.length&&0===l.children.length?null:(i.appendChild(a),i.appendChild(s),i.appendChild(l),h.set(i,r),i)}function f(e,t,n,o,r=!1){const s=i(t);return a(s,[e],n,o,r),s}class w extends o.BaseGridPlugin{name="pinnedRows";styles="@layer tbw-plugins{.tbw-scroll-area{container-type:inline-size}.tbw-footer,.tbw-header-pinned{flex-shrink:0;z-index:var(--tbw-z-layer-pinned-rows, 20);background:var(--tbw-color-panel-bg);min-width:fit-content}.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;position:sticky;left:0;min-width:0;width:100cqi}.tbw-pinned-rows+.tbw-pinned-rows{border-top:0}.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);position:relative;background:inherit}.tbw-aggregation-row>.tbw-aggregation-label{position:sticky;left:0;grid-row:1;grid-column:1;display:flex;align-items:center;padding:var(--tbw-cell-padding, .125rem .5rem);background:inherit;z-index:1;pointer-events:none}.tbw-aggregation-row>.tbw-aggregation-cell:first-child{grid-column:1;grid-row:1}.tbw-aggregation-cell:not(:empty){position:relative;z-index:2;background:inherit}.tbw-aggregation-cell{padding:var(--tbw-cell-padding, .125rem .5rem);min-height:var(--tbw-row-height, 1.75rem);display:block;align-items:center;align-content: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;display:flex;align-items:center;gap:var(--tbw-spacing-lg, .75rem)}.tbw-aggregation-label{white-space:nowrap}.tbw-aggregation-aggregates{display:flex;align-items:center;gap:var(--tbw-spacing-lg, .75rem);font-weight:400;opacity:.85}.tbw-aggregation-aggregate{white-space:nowrap}}";get defaultConfig(){return{position:"bottom",showRowCount:!0,showSelectedCount:!0,showFilteredCount:!0}}infoBarElement=null;topAggregationContainer=null;bottomAggregationContainer=null;footerWrapper=null;headerWrapper=null;lastModeWasSlots=!1;detach(){this.cleanup()}afterRender(){const e=this.gridElement;if(!e)return;const t=e.querySelector(".tbw-scroll-area")??e.querySelector(".tbw-grid-content")??e.querySelector(".tbw-grid-root");if(!t)return;this.footerWrapper&&!t.contains(this.footerWrapper)&&(this.footerWrapper=null,this.bottomAggregationContainer=null,this.infoBarElement=null),this.topAggregationContainer&&!t.contains(this.topAggregationContainer)&&(this.topAggregationContainer=null),this.infoBarElement&&!t.contains(this.infoBarElement)&&(this.infoBarElement=null),this.headerWrapper&&!t.contains(this.headerWrapper)&&(this.headerWrapper=null);const n=this.getSelectionState(),o=this.getFilterState(),r=d(this.sourceRows,this.columns,this.gridElement,n,o);this.config.slots?this.renderSlotMode(t,e,r):this.renderLegacyMode(t,e,r)}renderSlotMode(e,t,n){this.lastModeWasSlots||this.detachLegacyOnly(),this.lastModeWasSlots=!0;const o=this.config.slots??[],r=o.filter(e=>"top"===e.position),i=o.filter(e=>"top"!==e.position);r.length>0?(this.headerWrapper||(this.headerWrapper=document.createElement("div"),this.headerWrapper.className="tbw-header-pinned",e.insertBefore(this.headerWrapper,e.firstChild)),this.populateSlotWrapper(this.headerWrapper,r,"top",n)):this.headerWrapper&&(this.headerWrapper.remove(),this.headerWrapper=null),i.length>0?(this.footerWrapper||(this.footerWrapper=document.createElement("div"),this.footerWrapper.className="tbw-footer",e.appendChild(this.footerWrapper)),this.populateSlotWrapper(this.footerWrapper,i,"bottom",n)):this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null)}populateSlotWrapper(e,t,n,o){const r=new Map;for(let s=0;s<e.children.length;s++){const t=e.children[s],n=t.getAttribute("data-pinned-row-id");n&&r.set(n,t)}const i=[];for(const s of t){const e="render"in s&&null!=s.render?u(s,o,s.id?r.get(s.id)??null:null):f(s,n,this.visibleColumns,this.sourceRows,this.config.fullWidth);e&&i.push(e)}const a=e.children;if(a.length===i.length){let e=!0;for(let t=0;t<i.length;t++)if(a[t]!==i[t]){e=!1;break}if(e)return}e.replaceChildren(...i)}renderLegacyMode(e,t,n){this.lastModeWasSlots&&(this.headerWrapper&&(this.headerWrapper.remove(),this.headerWrapper=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.bottomAggregationContainer=null,this.infoBarElement=null),this.lastModeWasSlots=!1;const o=this.config.aggregationRows||[],s=o.filter(e=>"top"===e.position),l=o.filter(e=>"top"!==e.position);s.length>0?(this.topAggregationContainer||(this.topAggregationContainer=i("top"),e.insertBefore(this.topAggregationContainer,e.firstChild)),a(this.topAggregationContainer,s,this.visibleColumns,this.sourceRows,this.config.fullWidth)):this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null);const g=!1!==this.config.showRowCount||this.config.showSelectedCount&&n.selectedRows>0||this.config.showFilteredCount&&n.filteredRows!==n.totalRows||this.config.customPanels&&this.config.customPanels.length>0,c=g&&"top"!==this.config.position,d=l.length>0||c;if(g&&"top"===this.config.position)if(this.infoBarElement){const e=r(this.config,n);this.infoBarElement.replaceWith(e),this.infoBarElement=e}else this.infoBarElement=r(this.config,n),e.insertBefore(this.infoBarElement,e.firstChild);else"top"===this.config.position&&this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null);d?(this.footerWrapper||(this.footerWrapper=document.createElement("div"),this.footerWrapper.className="tbw-footer",e.appendChild(this.footerWrapper)),this.footerWrapper.innerHTML="",l.length>0&&(this.bottomAggregationContainer||(this.bottomAggregationContainer=i("bottom")),this.footerWrapper.appendChild(this.bottomAggregationContainer),a(this.bottomAggregationContainer,l,this.visibleColumns,this.sourceRows,this.config.fullWidth)),c&&(this.infoBarElement=r(this.config,n),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),this.headerWrapper&&(this.headerWrapper.remove(),this.headerWrapper=null)}detachLegacyOnly(){this.topAggregationContainer&&(this.topAggregationContainer.remove(),this.topAggregationContainer=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.infoBarElement&&(this.infoBarElement.remove(),this.infoBarElement=null)}cleanupFooter(){this.footerWrapper&&(this.footerWrapper.remove(),this.footerWrapper=null),this.bottomAggregationContainer&&(this.bottomAggregationContainer.remove(),this.bottomAggregationContainer=null),this.infoBarElement&&"top"!==this.config.position&&(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(),t=this.getFilterState();return d(this.rows,this.columns,this.gridElement,e,t)}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(t=>t.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(t=>t.id!==e),this.requestRender())}}e.PinnedRowsPlugin=w,e.filteredCountPanel=function(){return e=>{if(e.filteredRows===e.totalRows)return null;const t=document.createElement("span");return t.className="tbw-status-panel tbw-status-panel-filtered-count",t.textContent=`Filtered: ${e.filteredRows}`,t}},e.rowCountPanel=function(){return e=>{const t=document.createElement("span");return t.className="tbw-status-panel tbw-status-panel-row-count",t.textContent=`Total: ${e.totalRows} rows`,t}},e.selectedCountPanel=function(){return e=>{if(e.selectedRows<=0)return null;const t=document.createElement("span");return t.className="tbw-status-panel tbw-status-panel-selected-count",t.textContent=`Selected: ${e.selectedRows}`,t}},Object.defineProperty(e,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 { aggregatorRegistry } from '../../core/internal/aggregators';\nimport { sanitizeHTML } from '../../core/internal/sanitize';\nimport type { ColumnConfig } from '../../core/types';\nimport type {\n AggregationRowConfig,\n AggregatorConfig,\n AggregatorDefinition,\n PanelRender,\n PanelSlot,\n PanelZone,\n PinnedRowsConfig,\n PinnedRowsContext,\n PinnedRowsPanel,\n ZonedPanelRender,\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 * @param globalFullWidth - Global fullWidth default from PinnedRowsConfig (default: false)\n */\nexport function renderAggregationRows(\n container: HTMLElement,\n rows: AggregationRowConfig[],\n columns: ColumnConfig[],\n dataRows: unknown[],\n globalFullWidth = false,\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 // Per-row fullWidth overrides global default\n const isFullWidth = rowConfig.fullWidth ?? globalFullWidth;\n\n if (isFullWidth) {\n renderFullWidthAggregationRow(rowEl, rowConfig, columns, dataRows);\n } else {\n renderPerColumnAggregationRow(rowEl, rowConfig, columns, dataRows);\n }\n\n container.appendChild(rowEl);\n }\n}\n\n/**\n * Renders a full-width aggregation row: single spanning cell with label and inline aggregated values.\n */\nfunction renderFullWidthAggregationRow(\n rowEl: HTMLElement,\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell tbw-aggregation-cell-full';\n cell.style.gridColumn = '1 / -1';\n\n // Label (static string or dynamic function)\n const labelValue = typeof rowConfig.label === 'function' ? rowConfig.label(dataRows, columns) : rowConfig.label;\n if (labelValue) {\n const labelSpan = document.createElement('span');\n labelSpan.className = 'tbw-aggregation-label';\n labelSpan.textContent = labelValue;\n cell.appendChild(labelSpan);\n }\n\n // Inline aggregated values\n const aggregatesContainer = renderInlineAggregates(rowConfig, columns, dataRows);\n if (aggregatesContainer) {\n cell.appendChild(aggregatesContainer);\n }\n\n // If nothing was added (no label, no aggregates), ensure cell is empty but present\n rowEl.appendChild(cell);\n}\n\n/**\n * Renders per-column aggregation cells aligned to the grid template.\n */\nfunction renderPerColumnAggregationRow(\n rowEl: HTMLElement,\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\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 const { value, formatter } = resolveAggregatedValue(rowConfig, col, dataRows);\n\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 // Overlay label: positioned at the left edge, independent of column alignment\n const labelValue = typeof rowConfig.label === 'function' ? rowConfig.label(dataRows, columns) : rowConfig.label;\n if (labelValue) {\n const labelEl = document.createElement('span');\n labelEl.className = 'tbw-aggregation-label';\n labelEl.textContent = labelValue;\n rowEl.appendChild(labelEl);\n }\n}\n\n/**\n * Resolves the aggregated value for a single column in an aggregation row.\n * Returns the computed value and an optional formatter function.\n */\nfunction resolveAggregatedValue(\n rowConfig: AggregationRowConfig,\n col: ColumnConfig,\n dataRows: unknown[],\n): { value: unknown; formatter?: (value: unknown, field: string, column?: ColumnConfig) => string } {\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 if (isAggregatorConfig(aggDef)) {\n const aggFn = aggregatorRegistry.get(aggDef.aggFunc);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n formatter = aggDef.formatter;\n } else {\n const aggFn = aggregatorRegistry.get(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 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 return { value, formatter };\n}\n\n/**\n * Renders inline aggregate values for a full-width aggregation row.\n * Returns a container element with aggregate spans, or null if no aggregates are defined.\n */\nfunction renderInlineAggregates(\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): HTMLElement | null {\n // Collect fields that have aggregators or cell values\n const hasAggregators = rowConfig.aggregators && Object.keys(rowConfig.aggregators).length > 0;\n const hasCells = rowConfig.cells && Object.keys(rowConfig.cells).length > 0;\n if (!hasAggregators && !hasCells) return null;\n\n const container = document.createElement('span');\n container.className = 'tbw-aggregation-aggregates';\n\n for (const col of columns) {\n const { value, formatter } = resolveAggregatedValue(rowConfig, col, dataRows);\n if (value != null) {\n const span = document.createElement('span');\n span.className = 'tbw-aggregation-aggregate';\n span.setAttribute('data-field', col.field);\n const header = col.header ?? col.field;\n const displayValue = formatter ? formatter(value, col.field, col) : String(value);\n span.textContent = `${header}: ${displayValue}`;\n container.appendChild(span);\n }\n }\n\n return container.children.length > 0 ? container : null;\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 = sanitizeHTML(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 // Prefer live counts from the grid element so filteredRows reflects the\n // actual processed row count regardless of which mechanism did the\n // filtering (built-in filter plugin, column filters, custom pipeline, etc.).\n // Fall back to the passed `rows` when the grid element does not expose\n // these properties (e.g. in unit tests using a plain <div>).\n //\n // When `sourceRows` is empty (e.g. ServerSidePlugin owns the data and the\n // user never assigned `grid.rows = ...`), fall back to the processed count\n // so we report a meaningful total instead of 0.\n const gridSourceRows = (grid as unknown as { sourceRows?: unknown[] })?.sourceRows;\n const gridProcessedRows = (grid as unknown as { rows?: unknown[] })?.rows;\n const sourceLen = Array.isArray(gridSourceRows) ? gridSourceRows.length : rows.length;\n const processedCount = Array.isArray(gridProcessedRows) ? gridProcessedRows.length : rows.length;\n const totalRows = sourceLen > 0 ? sourceLen : processedCount;\n\n // filteredRows resolution (in priority order):\n // 1. Plugin filter state's cachedResult (authoritative when filtering plugin owns the data)\n // 2. Custom pipeline signal: processed < source means the host filtered rows itself\n // 3. Default to totalRows so the renderer's `filteredRows !== totalRows` check\n // hides the panel when no filter is active. This is critical for the\n // server-side case where processedCount > sourceLen (placeholders inflate\n // grid.rows beyond grid.sourceRows.length) and would otherwise show a\n // spurious \"Filtered: N\" panel.\n const filteredRows = filterState?.cachedResult?.length ?? (processedCount < sourceLen ? processedCount : totalRows);\n\n return {\n totalRows,\n filteredRows,\n selectedRows: selectionState?.selected?.size ?? 0,\n columns: columns as PinnedRowsContext['columns'],\n rows,\n grid,\n };\n}\n\n// #region Slot-aware (issue #255) renderers\n\n/**\n * Built-in panel renderer: total row count.\n * Always renders. Output: `<span class=\"tbw-status-panel tbw-status-panel-row-count\">Total: N rows</span>`.\n * @since 2.6.0\n */\nexport function rowCountPanel(): PanelRender {\n return (ctx) => {\n const el = document.createElement('span');\n el.className = 'tbw-status-panel tbw-status-panel-row-count';\n el.textContent = `Total: ${ctx.totalRows} rows`;\n return el;\n };\n}\n\n/**\n * Built-in panel renderer: selected row count.\n * Returns `null` (skipped) when no rows are selected.\n * @since 2.6.0\n */\nexport function selectedCountPanel(): PanelRender {\n return (ctx) => {\n if (ctx.selectedRows <= 0) return null;\n const el = document.createElement('span');\n el.className = 'tbw-status-panel tbw-status-panel-selected-count';\n el.textContent = `Selected: ${ctx.selectedRows}`;\n return el;\n };\n}\n\n/**\n * Built-in panel renderer: filtered row count.\n * Returns `null` (skipped) when the filtered count equals the total (no filter active).\n * @since 2.6.0\n */\nexport function filteredCountPanel(): PanelRender {\n return (ctx) => {\n if (ctx.filteredRows === ctx.totalRows) return null;\n const el = document.createElement('span');\n el.className = 'tbw-status-panel tbw-status-panel-filtered-count';\n el.textContent = `Filtered: ${ctx.filteredRows}`;\n return el;\n };\n}\n\n/**\n * Wraps the user's panel HTMLElement in the standard `.tbw-status-panel` envelope.\n * Mirrors the legacy custom-panel DOM shape so consumer CSS keeps working.\n *\n * Intentionally does NOT set an `id` — a panel slot may produce multiple\n * wrappers (one per `ZonedPanelRender` entry) and duplicating the slot id\n * across them would violate HTML id uniqueness. The owning row already\n * carries `data-pinned-row-id={slot.id}` for identification.\n */\nfunction wrapCustomPanelElement(element: HTMLElement): HTMLElement {\n const panelEl = document.createElement('div');\n panelEl.className = 'tbw-status-panel tbw-status-panel-custom';\n panelEl.appendChild(element);\n return panelEl;\n}\n\n/**\n * Renders a single {@link PanelSlot} as a `.tbw-pinned-rows` row with three zones.\n * Returns `null` if no panel content was produced (all renders returned null).\n */\nexport function renderPanelSlot(slot: PanelSlot, context: PinnedRowsContext): HTMLElement | null {\n // Build the three zones up-front so we can drop the row entirely if everything is null.\n const row = document.createElement('div');\n row.className = 'tbw-pinned-rows';\n row.setAttribute('role', 'presentation');\n row.setAttribute('aria-live', 'polite');\n if (slot.id) row.setAttribute('data-pinned-row-id', slot.id);\n\n const left = document.createElement('div');\n left.className = 'tbw-pinned-rows-left';\n const center = document.createElement('div');\n center.className = 'tbw-pinned-rows-center';\n const right = document.createElement('div');\n right.className = 'tbw-pinned-rows-right';\n\n const zoneOf: Record<PanelZone, HTMLElement> = { left, center, right };\n\n const renderers: ZonedPanelRender[] = Array.isArray(slot.render)\n ? slot.render\n : [{ zone: 'left', render: slot.render }];\n\n for (const entry of renderers) {\n const zone = entry.zone ?? 'left';\n const out = entry.render(context);\n if (out == null) continue;\n // Built-in panels return their element ready-to-append; wrap others\n // (user-supplied via render array) consistently in .tbw-status-panel only\n // when not already a status panel, to preserve existing semantics.\n const isAlreadyStatusPanel = out.classList?.contains('tbw-status-panel');\n zoneOf[zone].appendChild(isAlreadyStatusPanel ? out : wrapCustomPanelElement(out));\n }\n\n if (left.children.length === 0 && center.children.length === 0 && right.children.length === 0) {\n return null;\n }\n\n row.appendChild(left);\n row.appendChild(center);\n row.appendChild(right);\n return row;\n}\n\n/**\n * Renders a single {@link AggregationRowConfig} (slot variant) as one\n * `.tbw-aggregation-rows` container holding one `.tbw-aggregation-row`.\n *\n * The container class includes `-top` or `-bottom` to preserve existing CSS\n * (e.g. border-top vs border-bottom).\n */\nexport function renderAggregationSlot(\n slot: AggregationRowConfig,\n position: 'top' | 'bottom',\n columns: ColumnConfig[],\n dataRows: unknown[],\n globalFullWidth = false,\n): HTMLElement {\n const container = createAggregationContainer(position);\n renderAggregationRows(container, [slot], columns, dataRows, globalFullWidth);\n return container;\n}\n\n// #endregion\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 {\n buildContext,\n createAggregationContainer,\n createInfoBarElement,\n renderAggregationRows,\n renderAggregationSlot,\n renderPanelSlot,\n} from './pinned-rows';\nimport styles from './pinned-rows.css?inline';\nimport type {\n AggregationRowConfig,\n PinnedRowSlot,\n PinnedRowsConfig,\n PinnedRowsContext,\n PinnedRowsPanel,\n} 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 * ## 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 {\n * PinnedRowsPlugin,\n * rowCountPanel,\n * } 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 * slots: [\n * {\n * id: 'totals',\n * position: 'bottom',\n * aggregators: { quantity: 'sum', price: 'sum' },\n * cells: { product: 'Totals:' },\n * },\n * { id: 'count', position: 'bottom', render: rowCountPanel() },\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 * @since 0.1.1\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 /** Slot-mode wrapper: holds top slot rows when `config.slots` is provided. */\n private headerWrapper: HTMLElement | null = null;\n /** Tracks whether the last render used slot mode, so we can clean up the\n * opposite mode's DOM if the user toggles between APIs at runtime. */\n private lastModeWasSlots = false;\n // #endregion\n\n // #region Lifecycle\n /** @internal */\n override detach(): void {\n this.cleanup();\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') ??\n gridEl.querySelector('.tbw-grid-content') ??\n gridEl.querySelector('.tbw-grid-root');\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 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 if (this.headerWrapper && !container.contains(this.headerWrapper)) {\n this.headerWrapper = 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.sourceRows as unknown[],\n this.columns as unknown[],\n this.gridElement,\n selectionState,\n filterState,\n );\n\n // Mode dispatch: explicit `slots[]` ⇒ slot-driven layout.\n // Otherwise: legacy layout (preserves byte-identical DOM for existing consumers).\n if (this.config.slots) {\n this.renderSlotMode(container as HTMLElement, gridEl, context);\n } else {\n this.renderLegacyMode(container as HTMLElement, gridEl, context);\n }\n }\n // #endregion\n\n // #region Slot-mode rendering (issue #255)\n /**\n * Slot-driven render path: iterates `config.slots` in declared order and emits\n * one DOM row per slot inside `.tbw-header-pinned` (top) or `.tbw-footer` (bottom).\n * The slot order in the array is preserved as the visual top→bottom order\n * within each area.\n */\n private renderSlotMode(container: HTMLElement, gridEl: HTMLElement, context: PinnedRowsContext): void {\n // If we just switched into slot mode from legacy mode, tear down the\n // legacy DOM elements so we don't end up with both rendered side-by-side.\n if (!this.lastModeWasSlots) {\n this.detachLegacyOnly();\n }\n this.lastModeWasSlots = true;\n\n const slots = this.config.slots ?? [];\n const topSlots = slots.filter((s) => s.position === 'top');\n const bottomSlots = slots.filter((s) => s.position !== 'top');\n\n // Top wrapper sits AFTER the header (mirrors the legacy top-aggregation\n // insertion site); see the DECIDED entry in grid-plugins knowledge.\n if (topSlots.length > 0) {\n if (!this.headerWrapper) {\n this.headerWrapper = document.createElement('div');\n this.headerWrapper.className = 'tbw-header-pinned';\n // Insert at the top of the scroll area, before the rows body wrapper.\n // We cannot use `header.nextSibling` as a reference because `.header`\n // lives inside `.rows-body` (not directly in `container`), so passing\n // its sibling to `insertBefore` would throw NotFoundError. The header\n // is `position: sticky` within `.rows-body`, so it visually stays on\n // top regardless of where this wrapper sits in `container`.\n container.insertBefore(this.headerWrapper, container.firstChild);\n }\n this.populateSlotWrapper(this.headerWrapper, topSlots, 'top', context);\n } else if (this.headerWrapper) {\n this.headerWrapper.remove();\n this.headerWrapper = null;\n }\n\n if (bottomSlots.length > 0) {\n if (!this.footerWrapper) {\n this.footerWrapper = document.createElement('div');\n this.footerWrapper.className = 'tbw-footer';\n container.appendChild(this.footerWrapper);\n }\n this.populateSlotWrapper(this.footerWrapper, bottomSlots, 'bottom', context);\n } else if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n\n /**\n * Replaces the contents of a top/bottom wrapper with one DOM row per slot,\n * in array order. Drops slots that emit nothing (panel slot whose renderers\n * all returned null).\n */\n private populateSlotWrapper(\n wrapper: HTMLElement,\n slots: PinnedRowSlot[],\n position: 'top' | 'bottom',\n context: PinnedRowsContext,\n ): void {\n wrapper.innerHTML = '';\n for (const slot of slots) {\n const isPanel = 'render' in slot && (slot as { render?: unknown }).render != null;\n const rowEl = isPanel\n ? renderPanelSlot(slot as Parameters<typeof renderPanelSlot>[0], context)\n : renderAggregationSlot(\n slot as AggregationRowConfig,\n position,\n this.visibleColumns as ColumnConfig[],\n this.sourceRows as unknown[],\n this.config.fullWidth,\n );\n if (rowEl) wrapper.appendChild(rowEl);\n }\n }\n // #endregion\n\n // #region Legacy-mode rendering (DOM byte-identical to pre-#255 behavior)\n private renderLegacyMode(container: HTMLElement, gridEl: HTMLElement, context: PinnedRowsContext): void {\n if (this.lastModeWasSlots) {\n // Switched out of slot mode — drop the slot-mode wrappers.\n if (this.headerWrapper) {\n this.headerWrapper.remove();\n this.headerWrapper = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n this.bottomAggregationContainer = null;\n this.infoBarElement = null;\n }\n this.lastModeWasSlots = false;\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 // See note in renderSlotMode: `.header` is nested in `.rows-body`,\n // not in `container`, so its `nextSibling` is not a valid reference\n // for `container.insertBefore`. Insert at the top of `container`.\n container.insertBefore(this.topAggregationContainer, container.firstChild);\n }\n renderAggregationRows(\n this.topAggregationContainer,\n topRows,\n this.visibleColumns as ColumnConfig[],\n this.sourceRows as unknown[],\n this.config.fullWidth,\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.sourceRows as unknown[],\n this.config.fullWidth,\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 if (this.headerWrapper) {\n this.headerWrapper.remove();\n this.headerWrapper = null;\n }\n }\n\n /** Detach only the legacy-mode DOM (used when switching legacy → slot mode). */\n private detachLegacyOnly(): void {\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 if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = 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.gridElement,\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":["createInfoBarElement","config","context","pinnedRows","document","createElement","className","setAttribute","left","center","right","showRowCount","rowCount","textContent","totalRows","appendChild","showFilteredCount","filteredRows","filteredCount","showSelectedCount","selectedRows","selectedCount","customPanels","panel","panelEl","renderCustomPanel","position","createAggregationContainer","container","renderAggregationRows","rows","columns","dataRows","globalFullWidth","innerHTML","rowConfig","rowEl","id","fullWidth","renderFullWidthAggregationRow","renderPerColumnAggregationRow","cell","style","gridColumn","labelValue","label","labelSpan","aggregatesContainer","hasAggregators","aggregators","Object","keys","length","hasCells","cells","col","value","formatter","resolveAggregatedValue","span","field","header","displayValue","String","children","renderInlineAggregates","labelEl","aggDef","def","aggFn","aggregatorRegistry","get","aggFunc","prototype","hasOwnProperty","call","staticVal","content","render","sanitizeHTML","buildContext","grid","selectionState","filterState","gridSourceRows","sourceRows","gridProcessedRows","sourceLen","Array","isArray","processedCount","cachedResult","selected","size","wrapCustomPanelElement","element","renderPanelSlot","slot","row","zoneOf","renderers","zone","entry","out","isAlreadyStatusPanel","classList","contains","renderAggregationSlot","PinnedRowsPlugin","BaseGridPlugin","name","styles","defaultConfig","infoBarElement","topAggregationContainer","bottomAggregationContainer","footerWrapper","headerWrapper","lastModeWasSlots","detach","this","cleanup","afterRender","gridEl","gridElement","querySelector","getSelectionState","getFilterState","slots","renderSlotMode","renderLegacyMode","detachLegacyOnly","topSlots","filter","s","bottomSlots","insertBefore","firstChild","populateSlotWrapper","remove","wrapper","visibleColumns","aggregationRows","topRows","r","bottomRows","hasInfoContent","hasBottomInfoBar","needsFooter","newInfoBar","replaceWith","cleanupFooter","getPluginState","refresh","requestRender","getContext","addPanel","push","removePanel","p","addAggregationRow","removeAggregationRow","ctx","el"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pinned-rows.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/pinned-rows/pinned-rows.ts","../../../../../libs/grid/src/lib/plugins/pinned-rows/PinnedRowsPlugin.ts"],"sourcesContent":["/**\n * Status Bar Rendering Logic\n *\n * Pure functions for creating and updating the status bar UI.\n * Includes both info bar and aggregation row rendering.\n */\n\nimport { aggregatorRegistry } from '../../core/internal/aggregators';\nimport { sanitizeHTML } from '../../core/internal/sanitize';\nimport type { ColumnConfig } from '../../core/types';\nimport type {\n AggregationRowConfig,\n AggregatorConfig,\n AggregatorDefinition,\n PanelRender,\n PanelSlot,\n PanelZone,\n PinnedRowsConfig,\n PinnedRowsContext,\n PinnedRowsPanel,\n ZonedPanelRender,\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 * @param globalFullWidth - Global fullWidth default from PinnedRowsConfig (default: false)\n */\nexport function renderAggregationRows(\n container: HTMLElement,\n rows: AggregationRowConfig[],\n columns: ColumnConfig[],\n dataRows: unknown[],\n globalFullWidth = false,\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 // Per-row fullWidth overrides global default\n const isFullWidth = rowConfig.fullWidth ?? globalFullWidth;\n\n if (isFullWidth) {\n renderFullWidthAggregationRow(rowEl, rowConfig, columns, dataRows);\n } else {\n renderPerColumnAggregationRow(rowEl, rowConfig, columns, dataRows);\n }\n\n container.appendChild(rowEl);\n }\n}\n\n/**\n * Renders a full-width aggregation row: single spanning cell with label and inline aggregated values.\n */\nfunction renderFullWidthAggregationRow(\n rowEl: HTMLElement,\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell tbw-aggregation-cell-full';\n cell.style.gridColumn = '1 / -1';\n\n // Label (static string or dynamic function)\n const labelValue = typeof rowConfig.label === 'function' ? rowConfig.label(dataRows, columns) : rowConfig.label;\n if (labelValue) {\n const labelSpan = document.createElement('span');\n labelSpan.className = 'tbw-aggregation-label';\n labelSpan.textContent = labelValue;\n cell.appendChild(labelSpan);\n }\n\n // Inline aggregated values\n const aggregatesContainer = renderInlineAggregates(rowConfig, columns, dataRows);\n if (aggregatesContainer) {\n cell.appendChild(aggregatesContainer);\n }\n\n // If nothing was added (no label, no aggregates), ensure cell is empty but present\n rowEl.appendChild(cell);\n}\n\n/**\n * Renders per-column aggregation cells aligned to the grid template.\n */\nfunction renderPerColumnAggregationRow(\n rowEl: HTMLElement,\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\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 const { value, formatter } = resolveAggregatedValue(rowConfig, col, dataRows);\n\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 // Overlay label: positioned at the left edge, independent of column alignment\n const labelValue = typeof rowConfig.label === 'function' ? rowConfig.label(dataRows, columns) : rowConfig.label;\n if (labelValue) {\n const labelEl = document.createElement('span');\n labelEl.className = 'tbw-aggregation-label';\n labelEl.textContent = labelValue;\n rowEl.appendChild(labelEl);\n }\n}\n\n/**\n * Resolves the aggregated value for a single column in an aggregation row.\n * Returns the computed value and an optional formatter function.\n */\nfunction resolveAggregatedValue(\n rowConfig: AggregationRowConfig,\n col: ColumnConfig,\n dataRows: unknown[],\n): { value: unknown; formatter?: (value: unknown, field: string, column?: ColumnConfig) => string } {\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 if (isAggregatorConfig(aggDef)) {\n const aggFn = aggregatorRegistry.get(aggDef.aggFunc);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n formatter = aggDef.formatter;\n } else {\n const aggFn = aggregatorRegistry.get(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 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 return { value, formatter };\n}\n\n/**\n * Renders inline aggregate values for a full-width aggregation row.\n * Returns a container element with aggregate spans, or null if no aggregates are defined.\n */\nfunction renderInlineAggregates(\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): HTMLElement | null {\n // Collect fields that have aggregators or cell values\n const hasAggregators = rowConfig.aggregators && Object.keys(rowConfig.aggregators).length > 0;\n const hasCells = rowConfig.cells && Object.keys(rowConfig.cells).length > 0;\n if (!hasAggregators && !hasCells) return null;\n\n const container = document.createElement('span');\n container.className = 'tbw-aggregation-aggregates';\n\n for (const col of columns) {\n const { value, formatter } = resolveAggregatedValue(rowConfig, col, dataRows);\n if (value != null) {\n const span = document.createElement('span');\n span.className = 'tbw-aggregation-aggregate';\n span.setAttribute('data-field', col.field);\n const header = col.header ?? col.field;\n const displayValue = formatter ? formatter(value, col.field, col) : String(value);\n span.textContent = `${header}: ${displayValue}`;\n container.appendChild(span);\n }\n }\n\n return container.children.length > 0 ? container : null;\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 = sanitizeHTML(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 // Prefer live counts from the grid element so filteredRows reflects the\n // actual processed row count regardless of which mechanism did the\n // filtering (built-in filter plugin, column filters, custom pipeline, etc.).\n // Fall back to the passed `rows` when the grid element does not expose\n // these properties (e.g. in unit tests using a plain <div>).\n //\n // When `sourceRows` is empty (e.g. ServerSidePlugin owns the data and the\n // user never assigned `grid.rows = ...`), fall back to the processed count\n // so we report a meaningful total instead of 0.\n const gridSourceRows = (grid as unknown as { sourceRows?: unknown[] })?.sourceRows;\n const gridProcessedRows = (grid as unknown as { rows?: unknown[] })?.rows;\n const sourceLen = Array.isArray(gridSourceRows) ? gridSourceRows.length : rows.length;\n const processedCount = Array.isArray(gridProcessedRows) ? gridProcessedRows.length : rows.length;\n const totalRows = sourceLen > 0 ? sourceLen : processedCount;\n\n // filteredRows resolution (in priority order):\n // 1. Plugin filter state's cachedResult (authoritative when filtering plugin owns the data)\n // 2. Custom pipeline signal: processed < source means the host filtered rows itself\n // 3. Default to totalRows so the renderer's `filteredRows !== totalRows` check\n // hides the panel when no filter is active. This is critical for the\n // server-side case where processedCount > sourceLen (placeholders inflate\n // grid.rows beyond grid.sourceRows.length) and would otherwise show a\n // spurious \"Filtered: N\" panel.\n const filteredRows = filterState?.cachedResult?.length ?? (processedCount < sourceLen ? processedCount : totalRows);\n\n return {\n totalRows,\n filteredRows,\n selectedRows: selectionState?.selected?.size ?? 0,\n columns: columns as PinnedRowsContext['columns'],\n rows,\n grid,\n };\n}\n\n// #region Slot-aware (issue #255) renderers\n\n/**\n * Built-in panel renderer: total row count.\n * Always renders. Output: `<span class=\"tbw-status-panel tbw-status-panel-row-count\">Total: N rows</span>`.\n * @since 2.6.0\n */\nexport function rowCountPanel(): PanelRender {\n return (ctx) => {\n const el = document.createElement('span');\n el.className = 'tbw-status-panel tbw-status-panel-row-count';\n el.textContent = `Total: ${ctx.totalRows} rows`;\n return el;\n };\n}\n\n/**\n * Built-in panel renderer: selected row count.\n * Returns `null` (skipped) when no rows are selected.\n * @since 2.6.0\n */\nexport function selectedCountPanel(): PanelRender {\n return (ctx) => {\n if (ctx.selectedRows <= 0) return null;\n const el = document.createElement('span');\n el.className = 'tbw-status-panel tbw-status-panel-selected-count';\n el.textContent = `Selected: ${ctx.selectedRows}`;\n return el;\n };\n}\n\n/**\n * Built-in panel renderer: filtered row count.\n * Returns `null` (skipped) when the filtered count equals the total (no filter active).\n * @since 2.6.0\n */\nexport function filteredCountPanel(): PanelRender {\n return (ctx) => {\n if (ctx.filteredRows === ctx.totalRows) return null;\n const el = document.createElement('span');\n el.className = 'tbw-status-panel tbw-status-panel-filtered-count';\n el.textContent = `Filtered: ${ctx.filteredRows}`;\n return el;\n };\n}\n\n/**\n * Wraps the user's panel HTMLElement in the standard `.tbw-status-panel` envelope.\n * Mirrors the legacy custom-panel DOM shape so consumer CSS keeps working.\n *\n * Intentionally does NOT set an `id` — a panel slot may produce multiple\n * wrappers (one per `ZonedPanelRender` entry) and duplicating the slot id\n * across them would violate HTML id uniqueness. The owning row already\n * carries `data-pinned-row-id={slot.id}` for identification.\n */\nfunction wrapCustomPanelElement(element: HTMLElement): HTMLElement {\n const panelEl = document.createElement('div');\n panelEl.className = 'tbw-status-panel tbw-status-panel-custom';\n panelEl.appendChild(element);\n return panelEl;\n}\n\n/**\n * Per-row memo of the per-renderer outputs that were last appended into the\n * row. Lets {@link renderPanelSlot} reuse a previously-built row when the\n * user's render callbacks return the same element references on subsequent\n * grid renders — avoiding a wipe + re-append that would otherwise force\n * consumers (Angular/React/Vue component mounts) to be torn down on every\n * grid render and cause feedback loops via ResizeObserver-based row-height\n * autosizers.\n *\n * Keyed by row element so the memo is GC'd with the row.\n */\nconst rowOutputsCache = new WeakMap<HTMLElement, (HTMLElement | null)[]>();\n\n/**\n * Renders a single {@link PanelSlot} as a `.tbw-pinned-rows` row with three zones.\n * Returns `null` if no panel content was produced (all renders returned null).\n *\n * When `previousRow` is supplied and every per-renderer output is reference-equal\n * to the outputs that built `previousRow`, the cached row is returned unchanged\n * so the caller can skip DOM mutation. Built-in panels (e.g. row-count) create a\n * fresh element on every call so this short-circuit is naturally bypassed for\n * them; consumer-supplied panels that return a stable element benefit.\n */\nexport function renderPanelSlot(\n slot: PanelSlot,\n context: PinnedRowsContext,\n previousRow?: HTMLElement | null,\n): HTMLElement | null {\n const renderers: ZonedPanelRender[] = Array.isArray(slot.render)\n ? slot.render\n : [{ zone: 'left', render: slot.render }];\n\n // Collect outputs in renderer order; we compare these to the previous row's\n // outputs to decide whether to reuse the row as-is.\n const outputs: (HTMLElement | null)[] = renderers.map((entry) => entry.render(context) ?? null);\n\n if (previousRow) {\n const prevOutputs = rowOutputsCache.get(previousRow);\n if (prevOutputs && prevOutputs.length === outputs.length && prevOutputs.every((out, i) => out === outputs[i])) {\n return previousRow;\n }\n }\n\n // Build a fresh row.\n const row = document.createElement('div');\n row.className = 'tbw-pinned-rows';\n row.setAttribute('role', 'presentation');\n row.setAttribute('aria-live', 'polite');\n if (slot.id) row.setAttribute('data-pinned-row-id', slot.id);\n\n const left = document.createElement('div');\n left.className = 'tbw-pinned-rows-left';\n const center = document.createElement('div');\n center.className = 'tbw-pinned-rows-center';\n const right = document.createElement('div');\n right.className = 'tbw-pinned-rows-right';\n\n const zoneOf: Record<PanelZone, HTMLElement> = { left, center, right };\n\n for (let i = 0; i < renderers.length; i++) {\n const entry = renderers[i];\n const out = outputs[i];\n if (out == null) continue;\n const zone = entry.zone ?? 'left';\n // Built-in panels return their element ready-to-append; wrap others\n // (user-supplied via render array) consistently in .tbw-status-panel only\n // when not already a status panel, to preserve existing semantics.\n const isAlreadyStatusPanel = out.classList?.contains('tbw-status-panel');\n zoneOf[zone].appendChild(isAlreadyStatusPanel ? out : wrapCustomPanelElement(out));\n }\n\n if (left.children.length === 0 && center.children.length === 0 && right.children.length === 0) {\n return null;\n }\n\n row.appendChild(left);\n row.appendChild(center);\n row.appendChild(right);\n rowOutputsCache.set(row, outputs);\n return row;\n}\n\n/**\n * Renders a single {@link AggregationRowConfig} (slot variant) as one\n * `.tbw-aggregation-rows` container holding one `.tbw-aggregation-row`.\n *\n * The container class includes `-top` or `-bottom` to preserve existing CSS\n * (e.g. border-top vs border-bottom).\n */\nexport function renderAggregationSlot(\n slot: AggregationRowConfig,\n position: 'top' | 'bottom',\n columns: ColumnConfig[],\n dataRows: unknown[],\n globalFullWidth = false,\n): HTMLElement {\n const container = createAggregationContainer(position);\n renderAggregationRows(container, [slot], columns, dataRows, globalFullWidth);\n return container;\n}\n\n// #endregion\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 {\n buildContext,\n createAggregationContainer,\n createInfoBarElement,\n renderAggregationRows,\n renderAggregationSlot,\n renderPanelSlot,\n} from './pinned-rows';\nimport styles from './pinned-rows.css?inline';\nimport type {\n AggregationRowConfig,\n PinnedRowSlot,\n PinnedRowsConfig,\n PinnedRowsContext,\n PinnedRowsPanel,\n} 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 * ## 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 {\n * PinnedRowsPlugin,\n * rowCountPanel,\n * } 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 * slots: [\n * {\n * id: 'totals',\n * position: 'bottom',\n * aggregators: { quantity: 'sum', price: 'sum' },\n * cells: { product: 'Totals:' },\n * },\n * { id: 'count', position: 'bottom', render: rowCountPanel() },\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 * @since 0.1.1\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 /** Slot-mode wrapper: holds top slot rows when `config.slots` is provided. */\n private headerWrapper: HTMLElement | null = null;\n /** Tracks whether the last render used slot mode, so we can clean up the\n * opposite mode's DOM if the user toggles between APIs at runtime. */\n private lastModeWasSlots = false;\n // #endregion\n\n // #region Lifecycle\n /** @internal */\n override detach(): void {\n this.cleanup();\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') ??\n gridEl.querySelector('.tbw-grid-content') ??\n gridEl.querySelector('.tbw-grid-root');\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 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 if (this.headerWrapper && !container.contains(this.headerWrapper)) {\n this.headerWrapper = 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.sourceRows as unknown[],\n this.columns as unknown[],\n this.gridElement,\n selectionState,\n filterState,\n );\n\n // Mode dispatch: explicit `slots[]` ⇒ slot-driven layout.\n // Otherwise: legacy layout (preserves byte-identical DOM for existing consumers).\n if (this.config.slots) {\n this.renderSlotMode(container as HTMLElement, gridEl, context);\n } else {\n this.renderLegacyMode(container as HTMLElement, gridEl, context);\n }\n }\n // #endregion\n\n // #region Slot-mode rendering (issue #255)\n /**\n * Slot-driven render path: iterates `config.slots` in declared order and emits\n * one DOM row per slot inside `.tbw-header-pinned` (top) or `.tbw-footer` (bottom).\n * The slot order in the array is preserved as the visual top→bottom order\n * within each area.\n */\n private renderSlotMode(container: HTMLElement, gridEl: HTMLElement, context: PinnedRowsContext): void {\n // If we just switched into slot mode from legacy mode, tear down the\n // legacy DOM elements so we don't end up with both rendered side-by-side.\n if (!this.lastModeWasSlots) {\n this.detachLegacyOnly();\n }\n this.lastModeWasSlots = true;\n\n const slots = this.config.slots ?? [];\n const topSlots = slots.filter((s) => s.position === 'top');\n const bottomSlots = slots.filter((s) => s.position !== 'top');\n\n // Top wrapper sits AFTER the header (mirrors the legacy top-aggregation\n // insertion site); see the DECIDED entry in grid-plugins knowledge.\n if (topSlots.length > 0) {\n if (!this.headerWrapper) {\n this.headerWrapper = document.createElement('div');\n this.headerWrapper.className = 'tbw-header-pinned';\n // Insert at the top of the scroll area, before the rows body wrapper.\n // We cannot use `header.nextSibling` as a reference because `.header`\n // lives inside `.rows-body` (not directly in `container`), so passing\n // its sibling to `insertBefore` would throw NotFoundError. The header\n // is `position: sticky` within `.rows-body`, so it visually stays on\n // top regardless of where this wrapper sits in `container`.\n container.insertBefore(this.headerWrapper, container.firstChild);\n }\n this.populateSlotWrapper(this.headerWrapper, topSlots, 'top', context);\n } else if (this.headerWrapper) {\n this.headerWrapper.remove();\n this.headerWrapper = null;\n }\n\n if (bottomSlots.length > 0) {\n if (!this.footerWrapper) {\n this.footerWrapper = document.createElement('div');\n this.footerWrapper.className = 'tbw-footer';\n container.appendChild(this.footerWrapper);\n }\n this.populateSlotWrapper(this.footerWrapper, bottomSlots, 'bottom', context);\n } else if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n\n /**\n * Replaces the contents of a top/bottom wrapper with one DOM row per slot,\n * in array order. Drops slots that emit nothing (panel slot whose renderers\n * all returned null).\n *\n * Diffs the new rows against the wrapper's current children by reference\n * and skips DOM mutation when nothing changed. Combined with the\n * memoization in {@link renderPanelSlot}, this prevents consumer-supplied\n * panels from being torn down and re-mounted on every grid render — which\n * caused tight feedback loops with Angular/React/Vue framework adapters\n * whose mount/unmount cycles bounced viewport height through\n * ResizeObserver-driven row-height autosizers.\n */\n private populateSlotWrapper(\n wrapper: HTMLElement,\n slots: PinnedRowSlot[],\n position: 'top' | 'bottom',\n context: PinnedRowsContext,\n ): void {\n // Index existing children by data-pinned-row-id so panel slots with a\n // stable id can be reused. Aggregation slots have no id and always rebuild.\n const previousById = new Map<string, HTMLElement>();\n for (let i = 0; i < wrapper.children.length; i++) {\n const child = wrapper.children[i] as HTMLElement;\n const id = child.getAttribute('data-pinned-row-id');\n if (id) previousById.set(id, child);\n }\n\n const newRows: HTMLElement[] = [];\n for (const slot of slots) {\n const isPanel = 'render' in slot && (slot as { render?: unknown }).render != null;\n const rowEl = isPanel\n ? renderPanelSlot(\n slot as Parameters<typeof renderPanelSlot>[0],\n context,\n slot.id ? (previousById.get(slot.id) ?? null) : null,\n )\n : renderAggregationSlot(\n slot as AggregationRowConfig,\n position,\n this.visibleColumns as ColumnConfig[],\n this.sourceRows as unknown[],\n this.config.fullWidth,\n );\n if (rowEl) newRows.push(rowEl);\n }\n\n // Fast path: identical refs in identical order → no DOM mutation.\n const current = wrapper.children;\n if (current.length === newRows.length) {\n let same = true;\n for (let i = 0; i < newRows.length; i++) {\n if (current[i] !== newRows[i]) {\n same = false;\n break;\n }\n }\n if (same) return;\n }\n\n wrapper.replaceChildren(...newRows);\n }\n // #endregion\n\n // #region Legacy-mode rendering (DOM byte-identical to pre-#255 behavior)\n private renderLegacyMode(container: HTMLElement, gridEl: HTMLElement, context: PinnedRowsContext): void {\n if (this.lastModeWasSlots) {\n // Switched out of slot mode — drop the slot-mode wrappers.\n if (this.headerWrapper) {\n this.headerWrapper.remove();\n this.headerWrapper = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n this.bottomAggregationContainer = null;\n this.infoBarElement = null;\n }\n this.lastModeWasSlots = false;\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 // See note in renderSlotMode: `.header` is nested in `.rows-body`,\n // not in `container`, so its `nextSibling` is not a valid reference\n // for `container.insertBefore`. Insert at the top of `container`.\n container.insertBefore(this.topAggregationContainer, container.firstChild);\n }\n renderAggregationRows(\n this.topAggregationContainer,\n topRows,\n this.visibleColumns as ColumnConfig[],\n this.sourceRows as unknown[],\n this.config.fullWidth,\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.sourceRows as unknown[],\n this.config.fullWidth,\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 if (this.headerWrapper) {\n this.headerWrapper.remove();\n this.headerWrapper = null;\n }\n }\n\n /** Detach only the legacy-mode DOM (used when switching legacy → slot mode). */\n private detachLegacyOnly(): void {\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 if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = 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.gridElement,\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":["createInfoBarElement","config","context","pinnedRows","document","createElement","className","setAttribute","left","center","right","showRowCount","rowCount","textContent","totalRows","appendChild","showFilteredCount","filteredRows","filteredCount","showSelectedCount","selectedRows","selectedCount","customPanels","panel","panelEl","renderCustomPanel","position","createAggregationContainer","container","renderAggregationRows","rows","columns","dataRows","globalFullWidth","innerHTML","rowConfig","rowEl","id","fullWidth","renderFullWidthAggregationRow","renderPerColumnAggregationRow","cell","style","gridColumn","labelValue","label","labelSpan","aggregatesContainer","hasAggregators","aggregators","Object","keys","length","hasCells","cells","col","value","formatter","resolveAggregatedValue","span","field","header","displayValue","String","children","renderInlineAggregates","labelEl","aggDef","def","aggFn","aggregatorRegistry","get","aggFunc","prototype","hasOwnProperty","call","staticVal","content","render","sanitizeHTML","buildContext","grid","selectionState","filterState","gridSourceRows","sourceRows","gridProcessedRows","sourceLen","Array","isArray","processedCount","cachedResult","selected","size","wrapCustomPanelElement","element","rowOutputsCache","WeakMap","renderPanelSlot","slot","previousRow","renderers","zone","outputs","map","entry","prevOutputs","every","out","i","row","zoneOf","isAlreadyStatusPanel","classList","contains","set","renderAggregationSlot","PinnedRowsPlugin","BaseGridPlugin","name","styles","defaultConfig","infoBarElement","topAggregationContainer","bottomAggregationContainer","footerWrapper","headerWrapper","lastModeWasSlots","detach","this","cleanup","afterRender","gridEl","gridElement","querySelector","getSelectionState","getFilterState","slots","renderSlotMode","renderLegacyMode","detachLegacyOnly","topSlots","filter","s","bottomSlots","insertBefore","firstChild","populateSlotWrapper","remove","wrapper","previousById","Map","child","getAttribute","newRows","visibleColumns","push","current","same","replaceChildren","aggregationRows","topRows","r","bottomRows","hasInfoContent","hasBottomInfoBar","needsFooter","newInfoBar","replaceWith","cleanupFooter","getPluginState","refresh","requestRender","getContext","addPanel","removePanel","p","addAggregationRow","removeAggregationRow","ctx","el"],"mappings":"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"}
|