@toolbox-web/grid 0.2.5 → 0.2.7
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.d.ts +486 -80
- package/all.js +1364 -1029
- package/all.js.map +1 -1
- package/index-DG2CZ_Zo.js +3229 -0
- package/index-DG2CZ_Zo.js.map +1 -0
- package/index.d.ts +222 -11
- package/index.js +25 -3143
- package/index.js.map +1 -1
- package/lib/plugins/clipboard/index.js +1 -1
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js +1 -1
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js +1 -1
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/export/index.js +1 -1
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/index.js +184 -149
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/index.js +46 -45
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/index.js +117 -83
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js +140 -82
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js +18 -18
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/index.js +1 -1
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/index.js +55 -47
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js +385 -351
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/reorder/index.js +278 -85
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/selection/index.js +28 -27
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js +2 -2
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/index.js +181 -170
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js +1 -1
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js +1 -1
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +1 -1
- package/umd/grid.all.umd.js +22 -22
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +12 -12
- package/umd/grid.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/grouping-columns.umd.js +1 -1
- package/umd/plugins/grouping-columns.umd.js.map +1 -1
- package/umd/plugins/grouping-rows.umd.js +1 -1
- package/umd/plugins/grouping-rows.umd.js.map +1 -1
- package/umd/plugins/master-detail.umd.js +1 -1
- package/umd/plugins/master-detail.umd.js.map +1 -1
- package/umd/plugins/multi-sort.umd.js +1 -1
- package/umd/plugins/multi-sort.umd.js.map +1 -1
- package/umd/plugins/pinned-rows.umd.js +1 -1
- package/umd/plugins/pinned-rows.umd.js.map +1 -1
- package/umd/plugins/pivot.umd.js +1 -1
- package/umd/plugins/pivot.umd.js.map +1 -1
- package/umd/plugins/reorder.umd.js +1 -1
- package/umd/plugins/reorder.umd.js.map +1 -1
- package/umd/plugins/selection.umd.js +1 -1
- package/umd/plugins/selection.umd.js.map +1 -1
- package/umd/plugins/server-side.umd.js +1 -1
- package/umd/plugins/server-side.umd.js.map +1 -1
- package/umd/plugins/tree.umd.js +1 -1
- package/umd/plugins/tree.umd.js.map +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(g,C){typeof exports=="object"&&typeof module<"u"?C(exports,require("../../core/internal/virtualization"),require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/virtualization","../../core/plugin/base-plugin"],C):(g=typeof globalThis<"u"?globalThis:g||self,C(g.TbwGridPlugin_filtering={},g.TbwGrid,g.TbwGrid))})(this,(function(g,C,M){"use strict";function V(w,e,r=!1){const t=w[e.field];if(e.operator==="blank")return t==null||t==="";if(e.operator==="notBlank")return t!=null&&t!=="";if(t==null)return!1;const l=String(t),o=r?l:l.toLowerCase(),i=r?String(e.value):String(e.value).toLowerCase();switch(e.operator){case"contains":return o.includes(i);case"notContains":return!o.includes(i);case"equals":return o===i;case"notEquals":return o!==i;case"startsWith":return o.startsWith(i);case"endsWith":return o.endsWith(i);case"lessThan":return Number(t)<Number(e.value);case"lessThanOrEqual":return Number(t)<=Number(e.value);case"greaterThan":return Number(t)>Number(e.value);case"greaterThanOrEqual":return Number(t)>=Number(e.value);case"between":return Number(t)>=Number(e.value)&&Number(t)<=Number(e.valueTo);case"in":return Array.isArray(e.value)&&e.value.includes(t);case"notIn":return Array.isArray(e.value)&&!e.value.includes(t);default:return!0}}function q(w,e,r=!1){return e.length?w.filter(t=>e.every(l=>V(t,l,r))):w}function z(w){return JSON.stringify(w.map(e=>({field:e.field,operator:e.operator,value:e.value,valueTo:e.valueTo})))}function L(w,e){const r=new Set;for(const t of w){const l=t[e];l!=null&&r.add(l)}return[...r].sort((t,l)=>typeof t=="number"&&typeof l=="number"?t-l:String(t).localeCompare(String(l)))}const B='.header-cell.filtered:before{content:"";position:absolute;top:4px;right:4px;width:6px;height:6px;background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));border-radius:50%}.tbw-filter-btn{display:inline-flex;align-items:center;justify-content:center;background:transparent;border:none;cursor:pointer;padding:2px;margin-left:4px;opacity:.4;transition:opacity .15s;color:inherit;vertical-align:middle}.tbw-filter-btn:hover,.tbw-filter-btn.active{opacity:1}.tbw-filter-btn.active{color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6))}',K=".tbw-filter-panel{position:fixed;background:var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));color:var(--tbw-filter-panel-fg, var(--tbw-color-fg, light-dark(#222222, #eeeeee)));border:1px solid var(--tbw-filter-panel-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-panel-radius, var(--tbw-border-radius, 4px));box-shadow:0 4px 16px var(--tbw-filter-panel-shadow, var(--tbw-color-shadow, light-dark(rgba(0, 0, 0, .1), rgba(0, 0, 0, .3))));padding:12px;z-index:10000;min-width:200px;max-width:280px;max-height:350px;display:flex;flex-direction:column;font-family:var(--tbw-font-family, system-ui, sans-serif);font-size:var(--tbw-font-size, 13px)}.tbw-filter-search{margin-bottom:8px}.tbw-filter-search-input{width:100%;padding:6px 10px;background:var(--tbw-filter-input-bg, var(--tbw-color-bg, transparent));color:inherit;border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-input-radius, 4px);font-size:inherit;box-sizing:border-box}.tbw-filter-search-input:focus{outline:none;border-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));box-shadow:0 0 0 2px rgba(from var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6)) r g b / 15%)}.tbw-filter-actions{display:flex;padding:4px 2px;margin-bottom:8px;border-bottom:1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)))}.tbw-filter-action-btn{background:transparent;border:none;color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));cursor:pointer;font-size:12px;padding:2px 0}.tbw-filter-action-btn:hover{text-decoration:underline}.tbw-filter-values{flex:1;overflow-y:auto;margin-bottom:8px;max-height:180px;position:relative}.tbw-filter-values-spacer{width:1px}.tbw-filter-values-content{position:absolute;top:0;left:0;right:0}.tbw-filter-value-item{display:flex;align-items:center;gap:8px;padding:4px 2px;cursor:pointer;border-radius:3px}.tbw-filter-value-item:hover{background:var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)))}.tbw-filter-checkbox{margin:0;cursor:pointer;accent-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6))}.tbw-filter-no-match{color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));padding:8px 0;text-align:center;font-style:italic}.tbw-filter-buttons{display:flex;gap:8px;padding-top:8px;border-top:1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)))}.tbw-filter-apply-btn{flex:1;padding:6px 12px;background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));color:var(--tbw-filter-accent-fg, var(--tbw-color-accent-fg, light-dark(#ffffff, #000000)));border:none;border-radius:4px;cursor:pointer;font-size:13px}.tbw-filter-apply-btn:hover{filter:brightness(.9)}.tbw-filter-clear-btn{flex:1;padding:6px 12px;background:transparent;color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:4px;cursor:pointer;font-size:13px}.tbw-filter-clear-btn:hover{background:var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)))}";class m extends M.BaseGridPlugin{name="filtering";version="1.0.0";get defaultConfig(){return{debounceMs:300,caseSensitive:!1,trimInput:!0,useWorker:!0}}filters=new Map;cachedResult=null;cacheKey=null;openPanelField=null;panelElement=null;searchText=new Map;excludedValues=new Map;panelAbortController=null;globalStylesInjected=!1;static LIST_ITEM_HEIGHT=28;static LIST_OVERSCAN=3;static LIST_BYPASS_THRESHOLD=50;attach(e){super.attach(e),this.injectGlobalStyles()}detach(){this.filters.clear(),this.cachedResult=null,this.cacheKey=null,this.openPanelField=null,this.panelElement&&(this.panelElement.remove(),this.panelElement=null),this.searchText.clear(),this.excludedValues.clear(),this.panelAbortController?.abort(),this.panelAbortController=null}processRows(e){const r=[...this.filters.values()];if(!r.length)return[...e];const t=z(r);if(this.cacheKey===t&&this.cachedResult)return this.cachedResult;const l=q([...e],r,this.config.caseSensitive);return this.cachedResult=l,this.cacheKey=t,l}afterRender(){const e=this.shadowRoot;if(!e)return;e.querySelectorAll('[part~="header-cell"]').forEach(t=>{const l=t.getAttribute("data-col");if(l===null)return;const o=this.columns[parseInt(l,10)];if(!o||o.filterable===!1||t.querySelector(".tbw-filter-btn"))return;const i=o.field;if(!i)return;const a=document.createElement("button");a.className="tbw-filter-btn",a.setAttribute("aria-label",`Filter ${o.header??i}`),a.innerHTML='<svg viewBox="0 0 16 16" width="12" height="12"><path fill="currentColor" d="M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>',this.filters.has(i)&&(a.classList.add("active"),t.classList.add("filtered")),a.addEventListener("click",v=>{v.stopPropagation(),this.toggleFilterPanel(i,o,a)}),t.appendChild(a)})}setFilter(e,r){r===null?this.filters.delete(e):this.filters.set(e,{...r,field:e}),this.cachedResult=null,this.cacheKey=null,this.emit("filter-change",{filters:[...this.filters.values()],filteredRowCount:0}),this.requestRender()}getFilter(e){return this.filters.get(e)}getFilters(){return[...this.filters.values()]}getFilterModel(){return this.getFilters()}setFilterModel(e){this.filters.clear();for(const r of e)this.filters.set(r.field,r);this.cachedResult=null,this.cacheKey=null,this.emit("filter-change",{filters:[...this.filters.values()],filteredRowCount:0}),this.requestRender()}clearAllFilters(){this.filters.clear(),this.excludedValues.clear(),this.searchText.clear(),this.cachedResult=null,this.cacheKey=null,this.emit("filter-change",{filters:[],filteredRowCount:this.rows.length}),this.requestRender()}clearFieldFilter(e){this.filters.delete(e),this.excludedValues.delete(e),this.searchText.delete(e),this.cachedResult=null,this.cacheKey=null,this.emit("filter-change",{filters:[...this.filters.values()],filteredRowCount:0}),this.requestRender()}isFieldFiltered(e){return this.filters.has(e)}getFilteredRowCount(){return this.cachedResult?.length??this.rows.length}getActiveFilters(){return this.getFilters()}getUniqueValues(e){return L(this.sourceRows,e)}injectGlobalStyles(){if(this.globalStylesInjected)return;if(document.getElementById("tbw-filter-panel-styles")){this.globalStylesInjected=!0;return}const e=document.createElement("style");e.id="tbw-filter-panel-styles",e.textContent=K,document.head.appendChild(e),this.globalStylesInjected=!0}toggleFilterPanel(e,r,t){if(this.openPanelField===e){this.closeFilterPanel();return}this.closeFilterPanel();const l=document.createElement("div");l.className="tbw-filter-panel",this.panelElement=l,this.openPanelField=e;const o=L(this.sourceRows,e);let i=this.excludedValues.get(e);i||(i=new Set,this.excludedValues.set(e,i));const a=this.searchText.get(e)??"",v={field:e,column:r,uniqueValues:o,excludedValues:i,searchText:a,applySetFilter:u=>{this.applySetFilter(e,u),this.closeFilterPanel()},applyTextFilter:(u,E,S)=>{this.applyTextFilter(e,u,E,S),this.closeFilterPanel()},clearFilter:()=>{this.clearFieldFilter(e),this.closeFilterPanel()},closePanel:()=>this.closeFilterPanel()};let f=!1;this.config.filterPanelRenderer&&(this.config.filterPanelRenderer(l,v),f=l.children.length>0),f||this.renderDefaultFilterPanel(l,v,o,i),document.body.appendChild(l),this.positionPanel(l,t),this.panelAbortController=new AbortController,setTimeout(()=>{document.addEventListener("click",u=>{!l.contains(u.target)&&u.target!==t&&this.closeFilterPanel()},{signal:this.panelAbortController?.signal})},0)}closeFilterPanel(){this.panelElement&&(this.panelElement.remove(),this.panelElement=null),this.openPanelField=null,this.panelAbortController?.abort(),this.panelAbortController=null}positionPanel(e,r){const t=r.getBoundingClientRect();e.style.position="fixed",e.style.top=`${t.bottom+4}px`,e.style.left=`${t.left}px`,requestAnimationFrame(()=>{const l=e.getBoundingClientRect();l.right>window.innerWidth-8&&(e.style.left=`${window.innerWidth-l.width-8}px`),l.bottom>window.innerHeight-8&&(e.style.top=`${t.top-l.height-4}px`)})}renderDefaultFilterPanel(e,r,t,l){const{field:o}=r,i=document.createElement("div");i.className="tbw-filter-search";const a=document.createElement("input");a.type="text",a.placeholder="Search...",a.className="tbw-filter-search-input",a.value=this.searchText.get(o)??"",i.appendChild(a),e.appendChild(i);const v=document.createElement("div");v.className="tbw-filter-actions";const f=document.createElement("label");f.className="tbw-filter-value-item",f.style.padding="0",f.style.margin="0";const u=document.createElement("input");u.type="checkbox",u.className="tbw-filter-checkbox";const E=document.createElement("span");E.textContent="Select All",f.appendChild(u),f.appendChild(E),v.appendChild(f);const S=()=>{const n=[...y.values()],d=n.every(c=>c),h=n.every(c=>!c);u.checked=d,u.indeterminate=!d&&!h};u.addEventListener("change",()=>{const n=u.checked;for(const d of y.keys())y.set(d,n);S(),N()}),e.appendChild(v);const x=document.createElement("div");x.className="tbw-filter-values";const k=document.createElement("div");k.className="tbw-filter-values-spacer",x.appendChild(k);const p=document.createElement("div");p.className="tbw-filter-values-content",x.appendChild(p);const y=new Map;t.forEach(n=>{const d=n==null?"__null__":String(n);y.set(d,!l.has(n))}),S();let T=[];const A=(n,d)=>{const h=n==null?"(Blank)":String(n),c=n==null?"__null__":String(n),s=document.createElement("label");s.className="tbw-filter-value-item",s.style.position="absolute",s.style.top=`${d*m.LIST_ITEM_HEIGHT}px`,s.style.left="0",s.style.right="0",s.style.height=`${m.LIST_ITEM_HEIGHT}px`,s.style.boxSizing="border-box";const b=document.createElement("input");b.type="checkbox",b.className="tbw-filter-checkbox",b.checked=y.get(c)??!0,b.dataset.value=c,b.addEventListener("change",()=>{y.set(c,b.checked),S()});const H=document.createElement("span");return H.textContent=h,s.appendChild(b),s.appendChild(H),s},N=()=>{const n=T.length,d=x.clientHeight,h=x.scrollTop;if(k.style.height=`${n*m.LIST_ITEM_HEIGHT}px`,C.shouldBypassVirtualization(n,m.LIST_BYPASS_THRESHOLD/3)){p.innerHTML="",p.style.transform="translateY(0px)",T.forEach((s,b)=>{p.appendChild(A(s,b))});return}const c=C.computeVirtualWindow({totalRows:n,viewportHeight:d,scrollTop:h,rowHeight:m.LIST_ITEM_HEIGHT,overscan:m.LIST_OVERSCAN});p.style.transform=`translateY(${c.offsetY}px)`,p.innerHTML="";for(let s=c.start;s<c.end;s++)p.appendChild(A(T[s],s-c.start))},P=n=>{const d=n.toLowerCase();if(T=t.filter(h=>{const c=h==null?"(Blank)":String(h);return!n||c.toLowerCase().includes(d)}),T.length===0){k.style.height="0px",p.innerHTML="";const h=document.createElement("div");h.className="tbw-filter-no-match",h.textContent="No matching values",p.appendChild(h);return}N()};x.addEventListener("scroll",()=>{T.length>0&&N()},{passive:!0}),P(a.value),e.appendChild(x);let _;a.addEventListener("input",()=>{clearTimeout(_),_=setTimeout(()=>{this.searchText.set(o,a.value),P(a.value)},this.config.debounceMs??150)});const R=document.createElement("div");R.className="tbw-filter-buttons";const F=document.createElement("button");F.className="tbw-filter-apply-btn",F.textContent="Apply",F.addEventListener("click",()=>{const n=[];for(const[d,h]of y)if(!h)if(d==="__null__")n.push(null);else{const c=t.find(s=>String(s)===d);n.push(c!==void 0?c:d)}r.applySetFilter(n)}),R.appendChild(F);const I=document.createElement("button");I.className="tbw-filter-clear-btn",I.textContent="Clear Filter",I.addEventListener("click",()=>{r.clearFilter()}),R.appendChild(I),e.appendChild(R)}applySetFilter(e,r){this.excludedValues.set(e,new Set(r)),r.length===0?this.filters.delete(e):this.filters.set(e,{field:e,type:"set",operator:"notIn",value:r}),this.cachedResult=null,this.cacheKey=null,this.emit("filter-change",{filters:[...this.filters.values()],filteredRowCount:0}),this.requestRender()}applyTextFilter(e,r,t,l){this.filters.set(e,{field:e,type:"text",operator:r,value:t,valueTo:l}),this.cachedResult=null,this.cacheKey=null,this.emit("filter-change",{filters:[...this.filters.values()],filteredRowCount:0}),this.requestRender()}getColumnState(e){const r=this.filters.get(e);if(r)return{filter:{type:r.type,operator:r.operator,value:r.value,valueTo:r.valueTo}}}applyColumnState(e,r){if(!r.filter){this.filters.delete(e);return}const t={field:e,type:r.filter.type,operator:r.filter.operator,value:r.filter.value,valueTo:r.filter.valueTo};this.filters.set(e,t),this.cachedResult=null,this.cacheKey=null}styles=B}g.FilteringPlugin=m,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(v,C){typeof exports=="object"&&typeof module<"u"?C(exports,require("../../core/internal/virtualization"),require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/virtualization","../../core/plugin/base-plugin"],C):(v=typeof globalThis<"u"?globalThis:v||self,C(v.TbwGridPlugin_filtering={},v.TbwGrid,v.TbwGrid))})(this,(function(v,C,M){"use strict";function _(w,e,t=!1){const r=w[e.field];if(e.operator==="blank")return r==null||r==="";if(e.operator==="notBlank")return r!=null&&r!=="";if(r==null)return!1;const l=String(r),n=t?l:l.toLowerCase(),a=t?String(e.value):String(e.value).toLowerCase();switch(e.operator){case"contains":return n.includes(a);case"notContains":return!n.includes(a);case"equals":return n===a;case"notEquals":return n!==a;case"startsWith":return n.startsWith(a);case"endsWith":return n.endsWith(a);case"lessThan":return Number(r)<Number(e.value);case"lessThanOrEqual":return Number(r)<=Number(e.value);case"greaterThan":return Number(r)>Number(e.value);case"greaterThanOrEqual":return Number(r)>=Number(e.value);case"between":return Number(r)>=Number(e.value)&&Number(r)<=Number(e.valueTo);case"in":return Array.isArray(e.value)&&e.value.includes(r);case"notIn":return Array.isArray(e.value)&&!e.value.includes(r);default:return!0}}function z(w,e,t=!1){return e.length?w.filter(r=>e.every(l=>_(r,l,t))):w}function B(w){return JSON.stringify(w.map(e=>({field:e.field,operator:e.operator,value:e.value,valueTo:e.valueTo})))}function A(w,e){const t=new Set;for(const r of w){const l=r[e];l!=null&&t.add(l)}return[...t].sort((r,l)=>typeof r=="number"&&typeof l=="number"?r-l:String(r).localeCompare(String(l)))}const q='.header-cell.filtered:before{content:"";position:absolute;top:4px;right:4px;width:6px;height:6px;background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));border-radius:50%}.tbw-filter-btn{display:inline-flex;align-items:center;justify-content:center;background:transparent;border:none;cursor:pointer;padding:2px;margin-left:4px;opacity:.4;transition:opacity .15s;color:inherit;vertical-align:middle}.tbw-filter-btn:hover,.tbw-filter-btn.active{opacity:1}.tbw-filter-btn.active{color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6))}',G=".tbw-filter-panel{position:fixed;background:var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));color:var(--tbw-filter-panel-fg, var(--tbw-color-fg, light-dark(#222222, #eeeeee)));border:1px solid var(--tbw-filter-panel-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-panel-radius, var(--tbw-border-radius, 4px));box-shadow:0 4px 16px var(--tbw-filter-panel-shadow, var(--tbw-color-shadow, light-dark(rgba(0, 0, 0, .1), rgba(0, 0, 0, .3))));padding:12px;z-index:10000;min-width:200px;max-width:280px;max-height:350px;display:flex;flex-direction:column;font-family:var(--tbw-font-family, system-ui, sans-serif);font-size:var(--tbw-font-size, 13px)}.tbw-filter-search{margin-bottom:8px}.tbw-filter-search-input{width:100%;padding:6px 10px;background:var(--tbw-filter-input-bg, var(--tbw-color-bg, transparent));color:inherit;border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-input-radius, 4px);font-size:inherit;box-sizing:border-box}.tbw-filter-search-input:focus{outline:none;border-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));box-shadow:0 0 0 2px rgba(from var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6)) r g b / 15%)}.tbw-filter-actions{display:flex;padding:4px 2px;margin-bottom:8px;border-bottom:1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)))}.tbw-filter-action-btn{background:transparent;border:none;color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));cursor:pointer;font-size:12px;padding:2px 0}.tbw-filter-action-btn:hover{text-decoration:underline}.tbw-filter-values{flex:1;overflow-y:auto;margin-bottom:8px;max-height:180px;position:relative}.tbw-filter-values-spacer{width:1px}.tbw-filter-values-content{position:absolute;top:0;left:0;right:0}.tbw-filter-value-item{display:flex;align-items:center;gap:8px;padding:4px 2px;cursor:pointer;border-radius:3px}.tbw-filter-value-item:hover{background:var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)))}.tbw-filter-checkbox{margin:0;cursor:pointer;accent-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6))}.tbw-filter-no-match{color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));padding:8px 0;text-align:center;font-style:italic}.tbw-filter-buttons{display:flex;gap:8px;padding-top:8px;border-top:1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)))}.tbw-filter-apply-btn{flex:1;padding:6px 12px;background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));color:var(--tbw-filter-accent-fg, var(--tbw-color-accent-fg, light-dark(#ffffff, #000000)));border:none;border-radius:4px;cursor:pointer;font-size:13px}.tbw-filter-apply-btn:hover{filter:brightness(.9)}.tbw-filter-clear-btn{flex:1;padding:6px 12px;background:transparent;color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:4px;cursor:pointer;font-size:13px}.tbw-filter-clear-btn:hover{background:var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)))}";class m extends M.BaseGridPlugin{name="filtering";version="1.0.0";get defaultConfig(){return{debounceMs:300,caseSensitive:!1,trimInput:!0,useWorker:!0}}filters=new Map;cachedResult=null;cacheKey=null;openPanelField=null;panelElement=null;searchText=new Map;excludedValues=new Map;panelAbortController=null;globalStylesInjected=!1;static LIST_ITEM_HEIGHT=28;static LIST_OVERSCAN=3;static LIST_BYPASS_THRESHOLD=50;attach(e){super.attach(e),this.injectGlobalStyles()}detach(){this.filters.clear(),this.cachedResult=null,this.cacheKey=null,this.openPanelField=null,this.panelElement&&(this.panelElement.remove(),this.panelElement=null),this.searchText.clear(),this.excludedValues.clear(),this.panelAbortController?.abort(),this.panelAbortController=null}processRows(e){const t=[...this.filters.values()];if(!t.length)return[...e];if(this.config.filterHandler)return this.cachedResult?this.cachedResult:[...e];const r=B(t);if(this.cacheKey===r&&this.cachedResult)return this.cachedResult;const l=z([...e],t,this.config.caseSensitive);return this.cachedResult=l,this.cacheKey=r,l}afterRender(){const e=this.shadowRoot;if(!e)return;e.querySelectorAll('[part~="header-cell"]').forEach(r=>{const l=r.getAttribute("data-col");if(l===null)return;const n=this.visibleColumns[parseInt(l,10)];if(!n||n.filterable===!1)return;const a=n.field;if(!a)return;const d=this.filters.has(a);let o=r.querySelector(".tbw-filter-btn");if(o){o.classList.toggle("active",d),r.classList.toggle("filtered",d);return}o=document.createElement("button"),o.className="tbw-filter-btn",o.setAttribute("aria-label",`Filter ${n.header??a}`),o.innerHTML='<svg viewBox="0 0 16 16" width="12" height="12"><path fill="currentColor" d="M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>',d&&(o.classList.add("active"),r.classList.add("filtered")),o.addEventListener("click",p=>{p.stopPropagation(),this.toggleFilterPanel(a,n,o)}),r.appendChild(o)})}setFilter(e,t){t===null?(this.filters.delete(e),this.excludedValues.delete(e)):(this.filters.set(e,{...t,field:e}),t.type==="set"&&t.operator==="notIn"&&Array.isArray(t.value)?this.excludedValues.set(e,new Set(t.value)):t.type==="set"&&this.excludedValues.delete(e)),this.cachedResult=null,this.cacheKey=null,this.emit("filter-change",{filters:[...this.filters.values()],filteredRowCount:0}),this.requestRender()}getFilter(e){return this.filters.get(e)}getFilters(){return[...this.filters.values()]}getFilterModel(){return this.getFilters()}setFilterModel(e){this.filters.clear(),this.excludedValues.clear();for(const t of e)this.filters.set(t.field,t),t.type==="set"&&t.operator==="notIn"&&Array.isArray(t.value)&&this.excludedValues.set(t.field,new Set(t.value));this.cachedResult=null,this.cacheKey=null,this.emit("filter-change",{filters:[...this.filters.values()],filteredRowCount:0}),this.requestRender()}clearAllFilters(){this.filters.clear(),this.excludedValues.clear(),this.searchText.clear(),this.applyFiltersInternal()}clearFieldFilter(e){this.filters.delete(e),this.excludedValues.delete(e),this.searchText.delete(e),this.applyFiltersInternal()}isFieldFiltered(e){return this.filters.has(e)}getFilteredRowCount(){return this.cachedResult?.length??this.rows.length}getActiveFilters(){return this.getFilters()}getUniqueValues(e){return A(this.sourceRows,e)}injectGlobalStyles(){if(this.globalStylesInjected)return;if(document.getElementById("tbw-filter-panel-styles")){this.globalStylesInjected=!0;return}const e=document.createElement("style");e.id="tbw-filter-panel-styles",e.textContent=G,document.head.appendChild(e),this.globalStylesInjected=!0}toggleFilterPanel(e,t,r){if(this.openPanelField===e){this.closeFilterPanel();return}this.closeFilterPanel();const l=document.createElement("div");if(l.className="tbw-filter-panel",this.panelElement=l,this.openPanelField=e,this.config.valuesHandler){l.innerHTML='<div class="tbw-filter-loading">Loading...</div>',document.body.appendChild(l),this.positionPanel(l,r),this.setupPanelCloseHandler(l,r),this.config.valuesHandler(e,t).then(a=>{this.openPanelField!==e||!this.panelElement||(l.innerHTML="",this.renderPanelContent(e,t,l,a))});return}const n=A(this.sourceRows,e);this.renderPanelContent(e,t,l,n),document.body.appendChild(l),this.positionPanel(l,r),this.setupPanelCloseHandler(l,r)}renderPanelContent(e,t,r,l){let n=this.excludedValues.get(e);n||(n=new Set,this.excludedValues.set(e,n));const a=this.searchText.get(e)??"",d={field:e,column:t,uniqueValues:l,excludedValues:n,searchText:a,applySetFilter:p=>{this.applySetFilter(e,p),this.closeFilterPanel()},applyTextFilter:(p,b,S)=>{this.applyTextFilter(e,p,b,S),this.closeFilterPanel()},clearFilter:()=>{this.clearFieldFilter(e),this.closeFilterPanel()},closePanel:()=>this.closeFilterPanel()};let o=!1;this.config.filterPanelRenderer&&(this.config.filterPanelRenderer(r,d),o=r.children.length>0),o||this.renderDefaultFilterPanel(r,d,l,n)}setupPanelCloseHandler(e,t){this.panelAbortController=new AbortController,setTimeout(()=>{document.addEventListener("click",r=>{!e.contains(r.target)&&r.target!==t&&this.closeFilterPanel()},{signal:this.panelAbortController?.signal})},0)}closeFilterPanel(){this.panelElement&&(this.panelElement.remove(),this.panelElement=null),this.openPanelField=null,this.panelAbortController?.abort(),this.panelAbortController=null}positionPanel(e,t){const r=t.getBoundingClientRect();e.style.position="fixed",e.style.top=`${r.bottom+4}px`,e.style.left=`${r.left}px`,requestAnimationFrame(()=>{const l=e.getBoundingClientRect();l.right>window.innerWidth-8&&(e.style.left=`${window.innerWidth-l.width-8}px`),l.bottom>window.innerHeight-8&&(e.style.top=`${r.top-l.height-4}px`)})}renderDefaultFilterPanel(e,t,r,l){const{field:n}=t,a=document.createElement("div");a.className="tbw-filter-search";const d=document.createElement("input");d.type="text",d.placeholder="Search...",d.className="tbw-filter-search-input",d.value=this.searchText.get(n)??"",a.appendChild(d),e.appendChild(a);const o=document.createElement("div");o.className="tbw-filter-actions";const p=document.createElement("label");p.className="tbw-filter-value-item",p.style.padding="0",p.style.margin="0";const b=document.createElement("input");b.type="checkbox",b.className="tbw-filter-checkbox";const S=document.createElement("span");S.textContent="Select All",p.appendChild(b),p.appendChild(S),o.appendChild(p);const I=()=>{const i=[...y.values()],u=i.every(c=>c),h=i.every(c=>!c);b.checked=u,b.indeterminate=!u&&!h};b.addEventListener("change",()=>{const i=b.checked;for(const u of y.keys())y.set(u,i);I(),L()}),e.appendChild(o);const x=document.createElement("div");x.className="tbw-filter-values";const E=document.createElement("div");E.className="tbw-filter-values-spacer",x.appendChild(E);const f=document.createElement("div");f.className="tbw-filter-values-content",x.appendChild(f);const y=new Map;r.forEach(i=>{const u=i==null?"__null__":String(i);y.set(u,!l.has(i))}),I();let T=[];const P=(i,u)=>{const h=i==null?"(Blank)":String(i),c=i==null?"__null__":String(i),s=document.createElement("label");s.className="tbw-filter-value-item",s.style.position="absolute",s.style.top=`${u*m.LIST_ITEM_HEIGHT}px`,s.style.left="0",s.style.right="0",s.style.height=`${m.LIST_ITEM_HEIGHT}px`,s.style.boxSizing="border-box";const g=document.createElement("input");g.type="checkbox",g.className="tbw-filter-checkbox",g.checked=y.get(c)??!0,g.dataset.value=c,g.addEventListener("change",()=>{y.set(c,g.checked),I()});const V=document.createElement("span");return V.textContent=h,s.appendChild(g),s.appendChild(V),s},L=()=>{const i=T.length,u=x.clientHeight,h=x.scrollTop;if(E.style.height=`${i*m.LIST_ITEM_HEIGHT}px`,C.shouldBypassVirtualization(i,m.LIST_BYPASS_THRESHOLD/3)){f.innerHTML="",f.style.transform="translateY(0px)",T.forEach((s,g)=>{f.appendChild(P(s,g))});return}const c=C.computeVirtualWindow({totalRows:i,viewportHeight:u,scrollTop:h,rowHeight:m.LIST_ITEM_HEIGHT,overscan:m.LIST_OVERSCAN});f.style.transform=`translateY(${c.offsetY}px)`,f.innerHTML="";for(let s=c.start;s<c.end;s++)f.appendChild(P(T[s],s-c.start))},H=i=>{const u=i.toLowerCase();if(T=r.filter(h=>{const c=h==null?"(Blank)":String(h);return!i||c.toLowerCase().includes(u)}),T.length===0){E.style.height="0px",f.innerHTML="";const h=document.createElement("div");h.className="tbw-filter-no-match",h.textContent="No matching values",f.appendChild(h);return}L()};x.addEventListener("scroll",()=>{T.length>0&&L()},{passive:!0}),H(d.value),e.appendChild(x);let N;d.addEventListener("input",()=>{clearTimeout(N),N=setTimeout(()=>{this.searchText.set(n,d.value),H(d.value)},this.config.debounceMs??150)});const k=document.createElement("div");k.className="tbw-filter-buttons";const F=document.createElement("button");F.className="tbw-filter-apply-btn",F.textContent="Apply",F.addEventListener("click",()=>{const i=[];for(const[u,h]of y)if(!h)if(u==="__null__")i.push(null);else{const c=r.find(s=>String(s)===u);i.push(c!==void 0?c:u)}t.applySetFilter(i)}),k.appendChild(F);const R=document.createElement("button");R.className="tbw-filter-clear-btn",R.textContent="Clear Filter",R.addEventListener("click",()=>{t.clearFilter()}),k.appendChild(R),e.appendChild(k)}applySetFilter(e,t){this.excludedValues.set(e,new Set(t)),t.length===0?this.filters.delete(e):this.filters.set(e,{field:e,type:"set",operator:"notIn",value:t}),this.applyFiltersInternal()}applyTextFilter(e,t,r,l){this.filters.set(e,{field:e,type:"text",operator:t,value:r,valueTo:l}),this.applyFiltersInternal()}applyFiltersInternal(){this.cachedResult=null,this.cacheKey=null;const e=[...this.filters.values()];if(this.config.filterHandler){const t=this.grid;t.setAttribute("aria-busy","true");const r=this.config.filterHandler(e,this.sourceRows),l=n=>{t.removeAttribute("aria-busy"),this.cachedResult=n,this.grid.rows=n,this.emit("filter-change",{filters:e,filteredRowCount:n.length}),this.requestRender()};r&&typeof r.then=="function"?r.then(l):l(r);return}this.emit("filter-change",{filters:e,filteredRowCount:0}),this.requestRender()}getColumnState(e){const t=this.filters.get(e);if(t)return{filter:{type:t.type,operator:t.operator,value:t.value,valueTo:t.valueTo}}}applyColumnState(e,t){if(!t.filter){this.filters.delete(e);return}const r={field:e,type:t.filter.type,operator:t.filter.operator,value:t.filter.value,valueTo:t.filter.valueTo};this.filters.set(e,r),this.cachedResult=null,this.cacheKey=null}styles=q}v.FilteringPlugin=m,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=filtering.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filtering.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/filtering/filter-model.ts","../../../../../libs/grid/src/lib/plugins/filtering/FilteringPlugin.ts"],"sourcesContent":["/**\n * Filter Model Core Logic\n *\n * Pure functions for filtering operations.\n */\n\nimport type { FilterModel } from './types';\n\n/**\n * Check if a single row matches a filter condition.\n *\n * @param row - The row data object\n * @param filter - The filter to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns True if the row matches the filter\n */\nexport function matchesFilter(row: Record<string, unknown>, filter: FilterModel, caseSensitive = false): boolean {\n const rawValue = row[filter.field];\n\n // Handle blank/notBlank first - these work on null/undefined/empty\n if (filter.operator === 'blank') {\n return rawValue == null || rawValue === '';\n }\n if (filter.operator === 'notBlank') {\n return rawValue != null && rawValue !== '';\n }\n\n // Null/undefined values don't match other filters\n if (rawValue == null) return false;\n\n // Prepare values for comparison\n const stringValue = String(rawValue);\n const compareValue = caseSensitive ? stringValue : stringValue.toLowerCase();\n const filterValue = caseSensitive ? String(filter.value) : String(filter.value).toLowerCase();\n\n switch (filter.operator) {\n // Text operators\n case 'contains':\n return compareValue.includes(filterValue);\n\n case 'notContains':\n return !compareValue.includes(filterValue);\n\n case 'equals':\n return compareValue === filterValue;\n\n case 'notEquals':\n return compareValue !== filterValue;\n\n case 'startsWith':\n return compareValue.startsWith(filterValue);\n\n case 'endsWith':\n return compareValue.endsWith(filterValue);\n\n // Number/Date operators (use raw numeric values)\n case 'lessThan':\n return Number(rawValue) < Number(filter.value);\n\n case 'lessThanOrEqual':\n return Number(rawValue) <= Number(filter.value);\n\n case 'greaterThan':\n return Number(rawValue) > Number(filter.value);\n\n case 'greaterThanOrEqual':\n return Number(rawValue) >= Number(filter.value);\n\n case 'between':\n return Number(rawValue) >= Number(filter.value) && Number(rawValue) <= Number(filter.valueTo);\n\n // Set operators\n case 'in':\n return Array.isArray(filter.value) && filter.value.includes(rawValue);\n\n case 'notIn':\n return Array.isArray(filter.value) && !filter.value.includes(rawValue);\n\n default:\n return true;\n }\n}\n\n/**\n * Filter rows based on multiple filter conditions (AND logic).\n * All filters must match for a row to be included.\n *\n * @param rows - The rows to filter\n * @param filters - Array of filters to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns Filtered rows\n */\nexport function filterRows<T extends Record<string, unknown>>(\n rows: T[],\n filters: FilterModel[],\n caseSensitive = false\n): T[] {\n if (!filters.length) return rows;\n return rows.filter((row) => filters.every((f) => matchesFilter(row, f, caseSensitive)));\n}\n\n/**\n * Compute a cache key for a set of filters.\n * Used for memoization of filter results.\n *\n * @param filters - Array of filters\n * @returns Stable string key for the filter set\n */\nexport function computeFilterCacheKey(filters: FilterModel[]): string {\n return JSON.stringify(\n filters.map((f) => ({\n field: f.field,\n operator: f.operator,\n value: f.value,\n valueTo: f.valueTo,\n }))\n );\n}\n\n/**\n * Extract unique values from a field across all rows.\n * Useful for populating \"set\" filter dropdowns.\n *\n * @param rows - The rows to extract values from\n * @param field - The field name\n * @returns Sorted array of unique non-null values\n */\nexport function getUniqueValues<T extends Record<string, unknown>>(rows: T[], field: string): unknown[] {\n const values = new Set<unknown>();\n for (const row of rows) {\n const value = row[field];\n if (value != null) {\n values.add(value);\n }\n }\n return [...values].sort((a, b) => {\n // Handle mixed types gracefully\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b;\n }\n return String(a).localeCompare(String(b));\n });\n}\n","/**\n * Filtering Plugin (Class-based)\n *\n * Provides comprehensive filtering functionality for tbw-grid.\n * Supports text, number, date, set, and boolean filters with caching.\n * Includes UI with filter buttons in headers and dropdown filter panels.\n */\n\nimport { computeVirtualWindow, shouldBypassVirtualization } from '../../core/internal/virtualization';\nimport { BaseGridPlugin, type GridElement } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ColumnState } from '../../core/types';\nimport { computeFilterCacheKey, filterRows, getUniqueValues } from './filter-model';\nimport styles from './filtering.css?inline';\nimport filterPanelStyles from './FilteringPlugin.css?inline';\nimport type { FilterChangeDetail, FilterConfig, FilterModel, FilterPanelParams } from './types';\n\n/**\n * Filtering Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new FilteringPlugin({ enabled: true, debounceMs: 300 })\n * ```\n */\nexport class FilteringPlugin extends BaseGridPlugin<FilterConfig> {\n readonly name = 'filtering';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<FilterConfig> {\n return {\n debounceMs: 300,\n caseSensitive: false,\n trimInput: true,\n useWorker: true,\n };\n }\n\n // #region Internal State\n private filters: Map<string, FilterModel> = new Map();\n private cachedResult: unknown[] | null = null;\n private cacheKey: string | null = null;\n private openPanelField: string | null = null;\n private panelElement: HTMLElement | null = null;\n private searchText: Map<string, string> = new Map();\n private excludedValues: Map<string, Set<unknown>> = new Map();\n private panelAbortController: AbortController | null = null; // For panel-scoped listeners\n private globalStylesInjected = false;\n\n // Virtualization constants for filter value list\n private static readonly LIST_ITEM_HEIGHT = 28;\n private static readonly LIST_OVERSCAN = 3;\n private static readonly LIST_BYPASS_THRESHOLD = 50; // Don't virtualize if < 50 items\n // #endregion\n\n // #region Lifecycle\n\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.injectGlobalStyles();\n }\n\n override detach(): void {\n this.filters.clear();\n this.cachedResult = null;\n this.cacheKey = null;\n this.openPanelField = null;\n if (this.panelElement) {\n this.panelElement.remove();\n this.panelElement = null;\n }\n this.searchText.clear();\n this.excludedValues.clear();\n // Abort panel-scoped listeners (document click handler, etc.)\n this.panelAbortController?.abort();\n this.panelAbortController = null;\n }\n // #endregion\n\n // #region Hooks\n\n override processRows(rows: readonly unknown[]): unknown[] {\n const filterList = [...this.filters.values()];\n if (!filterList.length) return [...rows];\n\n // Check cache\n const newCacheKey = computeFilterCacheKey(filterList);\n if (this.cacheKey === newCacheKey && this.cachedResult) {\n return this.cachedResult;\n }\n\n // Filter rows synchronously (worker support can be added later)\n const result = filterRows([...rows] as Record<string, unknown>[], filterList, this.config.caseSensitive);\n\n // Update cache\n this.cachedResult = result;\n this.cacheKey = newCacheKey;\n\n return result;\n }\n\n override afterRender(): void {\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n // Find all header cells (using part attribute, not class)\n const headerCells = shadowRoot.querySelectorAll('[part~=\"header-cell\"]');\n headerCells.forEach((cell) => {\n const colIndex = cell.getAttribute('data-col');\n if (colIndex === null) return;\n\n const col = this.columns[parseInt(colIndex, 10)] as ColumnConfig;\n if (!col || col.filterable === false) return;\n\n // Skip if button already exists\n if (cell.querySelector('.tbw-filter-btn')) return;\n\n const field = col.field;\n if (!field) return;\n\n // Create filter button\n const filterBtn = document.createElement('button');\n filterBtn.className = 'tbw-filter-btn';\n filterBtn.setAttribute('aria-label', `Filter ${col.header ?? field}`);\n filterBtn.innerHTML = `<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\"><path fill=\"currentColor\" d=\"M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z\"/></svg>`;\n\n // Mark button as active if filter exists\n if (this.filters.has(field)) {\n filterBtn.classList.add('active');\n cell.classList.add('filtered');\n }\n\n filterBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.toggleFilterPanel(field, col, filterBtn);\n });\n\n // Append to header cell\n cell.appendChild(filterBtn);\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Set a filter on a specific field.\n * Pass null to remove the filter.\n */\n setFilter(field: string, filter: Omit<FilterModel, 'field'> | null): void {\n if (filter === null) {\n this.filters.delete(field);\n } else {\n this.filters.set(field, { ...filter, field });\n }\n // Invalidate cache\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0, // Will be accurate after processRows\n });\n this.requestRender();\n }\n\n /**\n * Get the current filter for a field.\n */\n getFilter(field: string): FilterModel | undefined {\n return this.filters.get(field);\n }\n\n /**\n * Get all active filters.\n */\n getFilters(): FilterModel[] {\n return [...this.filters.values()];\n }\n\n /**\n * Alias for getFilters() to match functional API naming.\n */\n getFilterModel(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Set filters from an array (replaces all existing filters).\n */\n setFilterModel(filters: FilterModel[]): void {\n this.filters.clear();\n for (const filter of filters) {\n this.filters.set(filter.field, filter);\n }\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n /**\n * Clear all filters.\n */\n clearAllFilters(): void {\n this.filters.clear();\n this.excludedValues.clear();\n this.searchText.clear();\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [],\n filteredRowCount: this.rows.length,\n });\n this.requestRender();\n }\n\n /**\n * Clear filter for a specific field.\n */\n clearFieldFilter(field: string): void {\n this.filters.delete(field);\n this.excludedValues.delete(field);\n this.searchText.delete(field);\n\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n /**\n * Check if a field has an active filter.\n */\n isFieldFiltered(field: string): boolean {\n return this.filters.has(field);\n }\n\n /**\n * Get the count of filtered rows (from cache).\n */\n getFilteredRowCount(): number {\n return this.cachedResult?.length ?? this.rows.length;\n }\n\n /**\n * Get all active filters (alias for getFilters).\n */\n getActiveFilters(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Get unique values for a field (for set filter dropdowns).\n * Uses sourceRows to include all values regardless of current filter.\n */\n getUniqueValues(field: string): unknown[] {\n return getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Inject global styles for filter panel (rendered in document.body)\n */\n private injectGlobalStyles(): void {\n if (this.globalStylesInjected) return;\n if (document.getElementById('tbw-filter-panel-styles')) {\n this.globalStylesInjected = true;\n return;\n }\n const style = document.createElement('style');\n style.id = 'tbw-filter-panel-styles';\n style.textContent = filterPanelStyles;\n document.head.appendChild(style);\n this.globalStylesInjected = true;\n }\n\n /**\n * Toggle the filter panel for a field\n */\n private toggleFilterPanel(field: string, column: ColumnConfig, buttonEl: HTMLElement): void {\n // Close if already open\n if (this.openPanelField === field) {\n this.closeFilterPanel();\n return;\n }\n\n // Close any existing panel\n this.closeFilterPanel();\n\n // Create panel\n const panel = document.createElement('div');\n panel.className = 'tbw-filter-panel';\n this.panelElement = panel;\n this.openPanelField = field;\n\n // Get unique values for this field (from source rows, not filtered)\n const uniqueValues = getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n\n // Get current excluded values or initialize empty\n let excludedSet = this.excludedValues.get(field);\n if (!excludedSet) {\n excludedSet = new Set();\n this.excludedValues.set(field, excludedSet);\n }\n\n // Get current search text\n const currentSearchText = this.searchText.get(field) ?? '';\n\n // Create panel params for custom renderer\n const params: FilterPanelParams = {\n field,\n column,\n uniqueValues,\n excludedValues: excludedSet,\n searchText: currentSearchText,\n applySetFilter: (excluded: unknown[]) => {\n this.applySetFilter(field, excluded);\n this.closeFilterPanel();\n },\n applyTextFilter: (operator, value, valueTo) => {\n this.applyTextFilter(field, operator, value, valueTo);\n this.closeFilterPanel();\n },\n clearFilter: () => {\n this.clearFieldFilter(field);\n this.closeFilterPanel();\n },\n closePanel: () => this.closeFilterPanel(),\n };\n\n // Use custom renderer or default\n // Custom renderer can return undefined to fall back to default panel for specific columns\n let usedCustomRenderer = false;\n if (this.config.filterPanelRenderer) {\n const result = this.config.filterPanelRenderer(panel, params);\n // If renderer added content to panel, it handled rendering\n usedCustomRenderer = panel.children.length > 0;\n }\n if (!usedCustomRenderer) {\n this.renderDefaultFilterPanel(panel, params, uniqueValues, excludedSet);\n }\n\n // Position and append to body\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n\n // Create abort controller for panel-scoped listeners\n // This allows cleanup when panel closes OR when grid disconnects\n this.panelAbortController = new AbortController();\n\n // Add global click handler to close on outside click\n // Defer to next tick to avoid immediate close from the click that opened the panel\n setTimeout(() => {\n document.addEventListener(\n 'click',\n (e: MouseEvent) => {\n if (!panel.contains(e.target as Node) && e.target !== buttonEl) {\n this.closeFilterPanel();\n }\n },\n { signal: this.panelAbortController?.signal },\n );\n }, 0);\n }\n\n /**\n * Close the filter panel\n */\n private closeFilterPanel(): void {\n if (this.panelElement) {\n this.panelElement.remove();\n this.panelElement = null;\n }\n this.openPanelField = null;\n // Abort panel-scoped listeners (document click handler)\n this.panelAbortController?.abort();\n this.panelAbortController = null;\n }\n\n /**\n * Position the panel below the button\n */\n private positionPanel(panel: HTMLElement, buttonEl: HTMLElement): void {\n const rect = buttonEl.getBoundingClientRect();\n panel.style.position = 'fixed';\n panel.style.top = `${rect.bottom + 4}px`;\n panel.style.left = `${rect.left}px`;\n\n // Adjust if overflows right edge\n requestAnimationFrame(() => {\n const panelRect = panel.getBoundingClientRect();\n if (panelRect.right > window.innerWidth - 8) {\n panel.style.left = `${window.innerWidth - panelRect.width - 8}px`;\n }\n // Adjust if overflows bottom\n if (panelRect.bottom > window.innerHeight - 8) {\n panel.style.top = `${rect.top - panelRect.height - 4}px`;\n }\n });\n }\n\n /**\n * Render the default filter panel content\n */\n private renderDefaultFilterPanel(\n panel: HTMLElement,\n params: FilterPanelParams,\n uniqueValues: unknown[],\n excludedValues: Set<unknown>,\n ): void {\n const { field } = params;\n\n // Search input\n const searchContainer = document.createElement('div');\n searchContainer.className = 'tbw-filter-search';\n\n const searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.placeholder = 'Search...';\n searchInput.className = 'tbw-filter-search-input';\n searchInput.value = this.searchText.get(field) ?? '';\n searchContainer.appendChild(searchInput);\n panel.appendChild(searchContainer);\n\n // Select All tristate checkbox\n const actionsRow = document.createElement('div');\n actionsRow.className = 'tbw-filter-actions';\n\n const selectAllLabel = document.createElement('label');\n selectAllLabel.className = 'tbw-filter-value-item';\n selectAllLabel.style.padding = '0';\n selectAllLabel.style.margin = '0';\n\n const selectAllCheckbox = document.createElement('input');\n selectAllCheckbox.type = 'checkbox';\n selectAllCheckbox.className = 'tbw-filter-checkbox';\n\n const selectAllText = document.createElement('span');\n selectAllText.textContent = 'Select All';\n\n selectAllLabel.appendChild(selectAllCheckbox);\n selectAllLabel.appendChild(selectAllText);\n actionsRow.appendChild(selectAllLabel);\n\n // Update tristate checkbox based on checkState\n const updateSelectAllState = () => {\n const values = [...checkState.values()];\n const allChecked = values.every((v) => v);\n const noneChecked = values.every((v) => !v);\n\n selectAllCheckbox.checked = allChecked;\n selectAllCheckbox.indeterminate = !allChecked && !noneChecked;\n };\n\n // Toggle all on click\n selectAllCheckbox.addEventListener('change', () => {\n const newState = selectAllCheckbox.checked;\n for (const key of checkState.keys()) {\n checkState.set(key, newState);\n }\n updateSelectAllState();\n renderVisibleItems();\n });\n\n panel.appendChild(actionsRow);\n\n // Values container with virtualization support\n const valuesContainer = document.createElement('div');\n valuesContainer.className = 'tbw-filter-values';\n\n // Spacer for virtual height\n const spacer = document.createElement('div');\n spacer.className = 'tbw-filter-values-spacer';\n valuesContainer.appendChild(spacer);\n\n // Content container positioned absolutely\n const contentContainer = document.createElement('div');\n contentContainer.className = 'tbw-filter-values-content';\n valuesContainer.appendChild(contentContainer);\n\n // Track current check state for values (persists across virtualizations)\n const checkState = new Map<string, boolean>();\n uniqueValues.forEach((value) => {\n const key = value == null ? '__null__' : String(value);\n checkState.set(key, !excludedValues.has(value));\n });\n\n // Initialize select all state\n updateSelectAllState();\n\n // Filtered values cache\n let filteredValues: unknown[] = [];\n\n // Create a single checkbox item element\n const createItem = (value: unknown, index: number): HTMLElement => {\n const strValue = value == null ? '(Blank)' : String(value);\n const key = value == null ? '__null__' : String(value);\n\n const item = document.createElement('label');\n item.className = 'tbw-filter-value-item';\n item.style.position = 'absolute';\n item.style.top = `${index * FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n item.style.left = '0';\n item.style.right = '0';\n item.style.height = `${FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n item.style.boxSizing = 'border-box';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'tbw-filter-checkbox';\n checkbox.checked = checkState.get(key) ?? true;\n checkbox.dataset.value = key;\n\n // Sync check state on change and update tristate checkbox\n checkbox.addEventListener('change', () => {\n checkState.set(key, checkbox.checked);\n updateSelectAllState();\n });\n\n const label = document.createElement('span');\n label.textContent = strValue;\n\n item.appendChild(checkbox);\n item.appendChild(label);\n return item;\n };\n\n // Render visible items using virtualization\n const renderVisibleItems = () => {\n const totalItems = filteredValues.length;\n const viewportHeight = valuesContainer.clientHeight;\n const scrollTop = valuesContainer.scrollTop;\n\n // Set total height for scrollbar\n spacer.style.height = `${totalItems * FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n\n // Bypass virtualization for small lists\n if (shouldBypassVirtualization(totalItems, FilteringPlugin.LIST_BYPASS_THRESHOLD / 3)) {\n contentContainer.innerHTML = '';\n contentContainer.style.transform = 'translateY(0px)';\n filteredValues.forEach((value, idx) => {\n contentContainer.appendChild(createItem(value, idx));\n });\n return;\n }\n\n // Use computeVirtualWindow for real-scroll virtualization\n const window = computeVirtualWindow({\n totalRows: totalItems,\n viewportHeight,\n scrollTop,\n rowHeight: FilteringPlugin.LIST_ITEM_HEIGHT,\n overscan: FilteringPlugin.LIST_OVERSCAN,\n });\n\n // Position content container\n contentContainer.style.transform = `translateY(${window.offsetY}px)`;\n\n // Clear and render visible items\n contentContainer.innerHTML = '';\n for (let i = window.start; i < window.end; i++) {\n contentContainer.appendChild(createItem(filteredValues[i], i - window.start));\n }\n };\n\n // Filter and re-render values\n const renderValues = (filterText: string) => {\n const lowerFilter = filterText.toLowerCase();\n\n // Filter the unique values\n filteredValues = uniqueValues.filter((value) => {\n const strValue = value == null ? '(Blank)' : String(value);\n return !filterText || strValue.toLowerCase().includes(lowerFilter);\n });\n\n if (filteredValues.length === 0) {\n spacer.style.height = '0px';\n contentContainer.innerHTML = '';\n const noMatch = document.createElement('div');\n noMatch.className = 'tbw-filter-no-match';\n noMatch.textContent = 'No matching values';\n contentContainer.appendChild(noMatch);\n return;\n }\n\n renderVisibleItems();\n };\n\n // Scroll handler for virtualization\n valuesContainer.addEventListener(\n 'scroll',\n () => {\n if (filteredValues.length > 0) {\n renderVisibleItems();\n }\n },\n { passive: true },\n );\n\n renderValues(searchInput.value);\n panel.appendChild(valuesContainer);\n\n // Debounced search\n let debounceTimer: ReturnType<typeof setTimeout>;\n searchInput.addEventListener('input', () => {\n clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n this.searchText.set(field, searchInput.value);\n renderValues(searchInput.value);\n }, this.config.debounceMs ?? 150);\n });\n\n // Apply/Clear buttons\n const buttonRow = document.createElement('div');\n buttonRow.className = 'tbw-filter-buttons';\n\n const applyBtn = document.createElement('button');\n applyBtn.className = 'tbw-filter-apply-btn';\n applyBtn.textContent = 'Apply';\n applyBtn.addEventListener('click', () => {\n // Read from checkState map (works with virtualization)\n const excluded: unknown[] = [];\n for (const [key, isChecked] of checkState) {\n if (!isChecked) {\n if (key === '__null__') {\n excluded.push(null);\n } else {\n // Try to match original value type\n const original = uniqueValues.find((v) => String(v) === key);\n excluded.push(original !== undefined ? original : key);\n }\n }\n }\n params.applySetFilter(excluded);\n });\n buttonRow.appendChild(applyBtn);\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'tbw-filter-clear-btn';\n clearBtn.textContent = 'Clear Filter';\n clearBtn.addEventListener('click', () => {\n params.clearFilter();\n });\n buttonRow.appendChild(clearBtn);\n\n panel.appendChild(buttonRow);\n }\n\n /**\n * Apply a set filter (exclude values)\n */\n private applySetFilter(field: string, excluded: unknown[]): void {\n // Store excluded values\n this.excludedValues.set(field, new Set(excluded));\n\n if (excluded.length === 0) {\n // No exclusions = no filter\n this.filters.delete(field);\n } else {\n // Create \"notIn\" filter\n this.filters.set(field, {\n field,\n type: 'set',\n operator: 'notIn',\n value: excluded,\n });\n }\n\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n /**\n * Apply a text filter\n */\n private applyTextFilter(field: string, operator: FilterModel['operator'], value: string, valueTo?: string): void {\n this.filters.set(field, {\n field,\n type: 'text',\n operator,\n value,\n valueTo,\n });\n\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n // #endregion\n\n // #region Column State Hooks\n\n /**\n * Return filter state for a column if it has an active filter.\n */\n override getColumnState(field: string): Partial<ColumnState> | undefined {\n const filterModel = this.filters.get(field);\n if (!filterModel) return undefined;\n\n return {\n filter: {\n type: filterModel.type,\n operator: filterModel.operator,\n value: filterModel.value,\n valueTo: filterModel.valueTo,\n },\n };\n }\n\n /**\n * Apply filter state from column state.\n */\n override applyColumnState(field: string, state: ColumnState): void {\n // Only process if the column has filter state\n if (!state.filter) {\n this.filters.delete(field);\n return;\n }\n\n // Reconstruct the FilterModel from the stored state\n const filterModel: FilterModel = {\n field,\n type: state.filter.type,\n operator: state.filter.operator as FilterModel['operator'],\n value: state.filter.value,\n valueTo: state.filter.valueTo,\n };\n\n this.filters.set(field, filterModel);\n // Invalidate cache so filter is reapplied\n this.cachedResult = null;\n this.cacheKey = null;\n }\n // #endregion\n\n // #region Styles\n\n override readonly styles = styles;\n // #endregion\n}\n"],"names":["matchesFilter","row","filter","caseSensitive","rawValue","stringValue","compareValue","filterValue","filterRows","rows","filters","f","computeFilterCacheKey","getUniqueValues","field","values","value","a","b","FilteringPlugin","BaseGridPlugin","grid","filterList","newCacheKey","result","shadowRoot","cell","colIndex","col","filterBtn","e","style","filterPanelStyles","column","buttonEl","panel","uniqueValues","excludedSet","currentSearchText","params","excluded","operator","valueTo","usedCustomRenderer","rect","panelRect","excludedValues","searchContainer","searchInput","actionsRow","selectAllLabel","selectAllCheckbox","selectAllText","updateSelectAllState","checkState","allChecked","v","noneChecked","newState","key","renderVisibleItems","valuesContainer","spacer","contentContainer","filteredValues","createItem","index","strValue","item","checkbox","label","totalItems","viewportHeight","scrollTop","shouldBypassVirtualization","idx","window","computeVirtualWindow","i","renderValues","filterText","lowerFilter","noMatch","debounceTimer","buttonRow","applyBtn","isChecked","original","clearBtn","filterModel","state","styles"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"filtering.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/filtering/filter-model.ts","../../../../../libs/grid/src/lib/plugins/filtering/FilteringPlugin.ts"],"sourcesContent":["/**\n * Filter Model Core Logic\n *\n * Pure functions for filtering operations.\n */\n\nimport type { FilterModel } from './types';\n\n/**\n * Check if a single row matches a filter condition.\n *\n * @param row - The row data object\n * @param filter - The filter to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns True if the row matches the filter\n */\nexport function matchesFilter(row: Record<string, unknown>, filter: FilterModel, caseSensitive = false): boolean {\n const rawValue = row[filter.field];\n\n // Handle blank/notBlank first - these work on null/undefined/empty\n if (filter.operator === 'blank') {\n return rawValue == null || rawValue === '';\n }\n if (filter.operator === 'notBlank') {\n return rawValue != null && rawValue !== '';\n }\n\n // Null/undefined values don't match other filters\n if (rawValue == null) return false;\n\n // Prepare values for comparison\n const stringValue = String(rawValue);\n const compareValue = caseSensitive ? stringValue : stringValue.toLowerCase();\n const filterValue = caseSensitive ? String(filter.value) : String(filter.value).toLowerCase();\n\n switch (filter.operator) {\n // Text operators\n case 'contains':\n return compareValue.includes(filterValue);\n\n case 'notContains':\n return !compareValue.includes(filterValue);\n\n case 'equals':\n return compareValue === filterValue;\n\n case 'notEquals':\n return compareValue !== filterValue;\n\n case 'startsWith':\n return compareValue.startsWith(filterValue);\n\n case 'endsWith':\n return compareValue.endsWith(filterValue);\n\n // Number/Date operators (use raw numeric values)\n case 'lessThan':\n return Number(rawValue) < Number(filter.value);\n\n case 'lessThanOrEqual':\n return Number(rawValue) <= Number(filter.value);\n\n case 'greaterThan':\n return Number(rawValue) > Number(filter.value);\n\n case 'greaterThanOrEqual':\n return Number(rawValue) >= Number(filter.value);\n\n case 'between':\n return Number(rawValue) >= Number(filter.value) && Number(rawValue) <= Number(filter.valueTo);\n\n // Set operators\n case 'in':\n return Array.isArray(filter.value) && filter.value.includes(rawValue);\n\n case 'notIn':\n return Array.isArray(filter.value) && !filter.value.includes(rawValue);\n\n default:\n return true;\n }\n}\n\n/**\n * Filter rows based on multiple filter conditions (AND logic).\n * All filters must match for a row to be included.\n *\n * @param rows - The rows to filter\n * @param filters - Array of filters to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns Filtered rows\n */\nexport function filterRows<T extends Record<string, unknown>>(\n rows: T[],\n filters: FilterModel[],\n caseSensitive = false\n): T[] {\n if (!filters.length) return rows;\n return rows.filter((row) => filters.every((f) => matchesFilter(row, f, caseSensitive)));\n}\n\n/**\n * Compute a cache key for a set of filters.\n * Used for memoization of filter results.\n *\n * @param filters - Array of filters\n * @returns Stable string key for the filter set\n */\nexport function computeFilterCacheKey(filters: FilterModel[]): string {\n return JSON.stringify(\n filters.map((f) => ({\n field: f.field,\n operator: f.operator,\n value: f.value,\n valueTo: f.valueTo,\n }))\n );\n}\n\n/**\n * Extract unique values from a field across all rows.\n * Useful for populating \"set\" filter dropdowns.\n *\n * @param rows - The rows to extract values from\n * @param field - The field name\n * @returns Sorted array of unique non-null values\n */\nexport function getUniqueValues<T extends Record<string, unknown>>(rows: T[], field: string): unknown[] {\n const values = new Set<unknown>();\n for (const row of rows) {\n const value = row[field];\n if (value != null) {\n values.add(value);\n }\n }\n return [...values].sort((a, b) => {\n // Handle mixed types gracefully\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b;\n }\n return String(a).localeCompare(String(b));\n });\n}\n","/**\n * Filtering Plugin (Class-based)\n *\n * Provides comprehensive filtering functionality for tbw-grid.\n * Supports text, number, date, set, and boolean filters with caching.\n * Includes UI with filter buttons in headers and dropdown filter panels.\n */\n\nimport { computeVirtualWindow, shouldBypassVirtualization } from '../../core/internal/virtualization';\nimport { BaseGridPlugin, type GridElement } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ColumnState } from '../../core/types';\nimport { computeFilterCacheKey, filterRows, getUniqueValues } from './filter-model';\nimport styles from './filtering.css?inline';\nimport filterPanelStyles from './FilteringPlugin.css?inline';\nimport type { FilterChangeDetail, FilterConfig, FilterModel, FilterPanelParams } from './types';\n\n/**\n * Filtering Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new FilteringPlugin({ enabled: true, debounceMs: 300 })\n * ```\n */\nexport class FilteringPlugin extends BaseGridPlugin<FilterConfig> {\n readonly name = 'filtering';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<FilterConfig> {\n return {\n debounceMs: 300,\n caseSensitive: false,\n trimInput: true,\n useWorker: true,\n };\n }\n\n // #region Internal State\n private filters: Map<string, FilterModel> = new Map();\n private cachedResult: unknown[] | null = null;\n private cacheKey: string | null = null;\n private openPanelField: string | null = null;\n private panelElement: HTMLElement | null = null;\n private searchText: Map<string, string> = new Map();\n private excludedValues: Map<string, Set<unknown>> = new Map();\n private panelAbortController: AbortController | null = null; // For panel-scoped listeners\n private globalStylesInjected = false;\n\n // Virtualization constants for filter value list\n private static readonly LIST_ITEM_HEIGHT = 28;\n private static readonly LIST_OVERSCAN = 3;\n private static readonly LIST_BYPASS_THRESHOLD = 50; // Don't virtualize if < 50 items\n // #endregion\n\n // #region Lifecycle\n\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.injectGlobalStyles();\n }\n\n override detach(): void {\n this.filters.clear();\n this.cachedResult = null;\n this.cacheKey = null;\n this.openPanelField = null;\n if (this.panelElement) {\n this.panelElement.remove();\n this.panelElement = null;\n }\n this.searchText.clear();\n this.excludedValues.clear();\n // Abort panel-scoped listeners (document click handler, etc.)\n this.panelAbortController?.abort();\n this.panelAbortController = null;\n }\n // #endregion\n\n // #region Hooks\n\n override processRows(rows: readonly unknown[]): unknown[] {\n const filterList = [...this.filters.values()];\n if (!filterList.length) return [...rows];\n\n // If using async filterHandler, processRows becomes a passthrough\n // Actual filtering happens in applyFiltersAsync and rows are set directly on grid\n if (this.config.filterHandler) {\n // Return cached result if available (set by async handler)\n if (this.cachedResult) return this.cachedResult;\n // Otherwise return rows as-is (filtering happens async)\n return [...rows];\n }\n\n // Check cache\n const newCacheKey = computeFilterCacheKey(filterList);\n if (this.cacheKey === newCacheKey && this.cachedResult) {\n return this.cachedResult;\n }\n\n // Filter rows synchronously (worker support can be added later)\n const result = filterRows([...rows] as Record<string, unknown>[], filterList, this.config.caseSensitive);\n\n // Update cache\n this.cachedResult = result;\n this.cacheKey = newCacheKey;\n\n return result;\n }\n\n override afterRender(): void {\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n // Find all header cells (using part attribute, not class)\n const headerCells = shadowRoot.querySelectorAll('[part~=\"header-cell\"]');\n headerCells.forEach((cell) => {\n const colIndex = cell.getAttribute('data-col');\n if (colIndex === null) return;\n\n // Use visibleColumns since data-col is the index within _visibleColumns\n const col = this.visibleColumns[parseInt(colIndex, 10)] as ColumnConfig;\n if (!col || col.filterable === false) return;\n\n const field = col.field;\n if (!field) return;\n\n const hasFilter = this.filters.has(field);\n\n // Check if button already exists\n let filterBtn = cell.querySelector('.tbw-filter-btn') as HTMLElement | null;\n\n if (filterBtn) {\n // Update active state of existing button\n filterBtn.classList.toggle('active', hasFilter);\n (cell as HTMLElement).classList.toggle('filtered', hasFilter);\n return;\n }\n\n // Create filter button\n filterBtn = document.createElement('button');\n filterBtn.className = 'tbw-filter-btn';\n filterBtn.setAttribute('aria-label', `Filter ${col.header ?? field}`);\n filterBtn.innerHTML = `<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\"><path fill=\"currentColor\" d=\"M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z\"/></svg>`;\n\n // Mark button as active if filter exists\n if (hasFilter) {\n filterBtn.classList.add('active');\n (cell as HTMLElement).classList.add('filtered');\n }\n\n filterBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.toggleFilterPanel(field, col, filterBtn!);\n });\n\n // Append to header cell\n cell.appendChild(filterBtn);\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Set a filter on a specific field.\n * Pass null to remove the filter.\n */\n setFilter(field: string, filter: Omit<FilterModel, 'field'> | null): void {\n if (filter === null) {\n this.filters.delete(field);\n this.excludedValues.delete(field);\n } else {\n this.filters.set(field, { ...filter, field });\n // Sync excludedValues for set filters so the panel reflects correct state\n if (filter.type === 'set' && filter.operator === 'notIn' && Array.isArray(filter.value)) {\n this.excludedValues.set(field, new Set(filter.value));\n } else if (filter.type === 'set') {\n // Other set operators may have different semantics; clear for safety\n this.excludedValues.delete(field);\n }\n }\n // Invalidate cache\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0, // Will be accurate after processRows\n });\n this.requestRender();\n }\n\n /**\n * Get the current filter for a field.\n */\n getFilter(field: string): FilterModel | undefined {\n return this.filters.get(field);\n }\n\n /**\n * Get all active filters.\n */\n getFilters(): FilterModel[] {\n return [...this.filters.values()];\n }\n\n /**\n * Alias for getFilters() to match functional API naming.\n */\n getFilterModel(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Set filters from an array (replaces all existing filters).\n */\n setFilterModel(filters: FilterModel[]): void {\n this.filters.clear();\n this.excludedValues.clear();\n for (const filter of filters) {\n this.filters.set(filter.field, filter);\n // Sync excludedValues for set filters so the panel reflects correct state\n if (filter.type === 'set' && filter.operator === 'notIn' && Array.isArray(filter.value)) {\n this.excludedValues.set(filter.field, new Set(filter.value));\n }\n }\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n /**\n * Clear all filters.\n */\n clearAllFilters(): void {\n this.filters.clear();\n this.excludedValues.clear();\n this.searchText.clear();\n\n this.applyFiltersInternal();\n }\n\n /**\n * Clear filter for a specific field.\n */\n clearFieldFilter(field: string): void {\n this.filters.delete(field);\n this.excludedValues.delete(field);\n this.searchText.delete(field);\n\n this.applyFiltersInternal();\n }\n\n /**\n * Check if a field has an active filter.\n */\n isFieldFiltered(field: string): boolean {\n return this.filters.has(field);\n }\n\n /**\n * Get the count of filtered rows (from cache).\n */\n getFilteredRowCount(): number {\n return this.cachedResult?.length ?? this.rows.length;\n }\n\n /**\n * Get all active filters (alias for getFilters).\n */\n getActiveFilters(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Get unique values for a field (for set filter dropdowns).\n * Uses sourceRows to include all values regardless of current filter.\n */\n getUniqueValues(field: string): unknown[] {\n return getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Inject global styles for filter panel (rendered in document.body)\n */\n private injectGlobalStyles(): void {\n if (this.globalStylesInjected) return;\n if (document.getElementById('tbw-filter-panel-styles')) {\n this.globalStylesInjected = true;\n return;\n }\n const style = document.createElement('style');\n style.id = 'tbw-filter-panel-styles';\n style.textContent = filterPanelStyles;\n document.head.appendChild(style);\n this.globalStylesInjected = true;\n }\n\n /**\n * Toggle the filter panel for a field\n */\n private toggleFilterPanel(field: string, column: ColumnConfig, buttonEl: HTMLElement): void {\n // Close if already open\n if (this.openPanelField === field) {\n this.closeFilterPanel();\n return;\n }\n\n // Close any existing panel\n this.closeFilterPanel();\n\n // Create panel\n const panel = document.createElement('div');\n panel.className = 'tbw-filter-panel';\n this.panelElement = panel;\n this.openPanelField = field;\n\n // If using async valuesHandler, show loading state and fetch values\n if (this.config.valuesHandler) {\n panel.innerHTML = '<div class=\"tbw-filter-loading\">Loading...</div>';\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n this.setupPanelCloseHandler(panel, buttonEl);\n\n this.config.valuesHandler(field, column).then((values) => {\n // Check if panel is still open for this field\n if (this.openPanelField !== field || !this.panelElement) return;\n panel.innerHTML = '';\n this.renderPanelContent(field, column, panel, values);\n });\n return;\n }\n\n // Sync path: get unique values from local rows\n const uniqueValues = getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n this.renderPanelContent(field, column, panel, uniqueValues);\n\n // Position and append to body\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n this.setupPanelCloseHandler(panel, buttonEl);\n }\n\n /**\n * Render filter panel content with given values\n */\n private renderPanelContent(field: string, column: ColumnConfig, panel: HTMLElement, uniqueValues: unknown[]): void {\n // Get current excluded values or initialize empty\n let excludedSet = this.excludedValues.get(field);\n if (!excludedSet) {\n excludedSet = new Set();\n this.excludedValues.set(field, excludedSet);\n }\n\n // Get current search text\n const currentSearchText = this.searchText.get(field) ?? '';\n\n // Create panel params for custom renderer\n const params: FilterPanelParams = {\n field,\n column,\n uniqueValues,\n excludedValues: excludedSet,\n searchText: currentSearchText,\n applySetFilter: (excluded: unknown[]) => {\n this.applySetFilter(field, excluded);\n this.closeFilterPanel();\n },\n applyTextFilter: (operator, value, valueTo) => {\n this.applyTextFilter(field, operator, value, valueTo);\n this.closeFilterPanel();\n },\n clearFilter: () => {\n this.clearFieldFilter(field);\n this.closeFilterPanel();\n },\n closePanel: () => this.closeFilterPanel(),\n };\n\n // Use custom renderer or default\n // Custom renderer can return undefined to fall back to default panel for specific columns\n let usedCustomRenderer = false;\n if (this.config.filterPanelRenderer) {\n this.config.filterPanelRenderer(panel, params);\n // If renderer added content to panel, it handled rendering\n usedCustomRenderer = panel.children.length > 0;\n }\n if (!usedCustomRenderer) {\n this.renderDefaultFilterPanel(panel, params, uniqueValues, excludedSet);\n }\n }\n\n /**\n * Setup click-outside handler to close the panel\n */\n private setupPanelCloseHandler(panel: HTMLElement, buttonEl: HTMLElement): void {\n // Create abort controller for panel-scoped listeners\n // This allows cleanup when panel closes OR when grid disconnects\n this.panelAbortController = new AbortController();\n\n // Add global click handler to close on outside click\n // Defer to next tick to avoid immediate close from the click that opened the panel\n setTimeout(() => {\n document.addEventListener(\n 'click',\n (e: MouseEvent) => {\n if (!panel.contains(e.target as Node) && e.target !== buttonEl) {\n this.closeFilterPanel();\n }\n },\n { signal: this.panelAbortController?.signal },\n );\n }, 0);\n }\n\n /**\n * Close the filter panel\n */\n private closeFilterPanel(): void {\n if (this.panelElement) {\n this.panelElement.remove();\n this.panelElement = null;\n }\n this.openPanelField = null;\n // Abort panel-scoped listeners (document click handler)\n this.panelAbortController?.abort();\n this.panelAbortController = null;\n }\n\n /**\n * Position the panel below the button\n */\n private positionPanel(panel: HTMLElement, buttonEl: HTMLElement): void {\n const rect = buttonEl.getBoundingClientRect();\n panel.style.position = 'fixed';\n panel.style.top = `${rect.bottom + 4}px`;\n panel.style.left = `${rect.left}px`;\n\n // Adjust if overflows right edge\n requestAnimationFrame(() => {\n const panelRect = panel.getBoundingClientRect();\n if (panelRect.right > window.innerWidth - 8) {\n panel.style.left = `${window.innerWidth - panelRect.width - 8}px`;\n }\n // Adjust if overflows bottom\n if (panelRect.bottom > window.innerHeight - 8) {\n panel.style.top = `${rect.top - panelRect.height - 4}px`;\n }\n });\n }\n\n /**\n * Render the default filter panel content\n */\n private renderDefaultFilterPanel(\n panel: HTMLElement,\n params: FilterPanelParams,\n uniqueValues: unknown[],\n excludedValues: Set<unknown>,\n ): void {\n const { field } = params;\n\n // Search input\n const searchContainer = document.createElement('div');\n searchContainer.className = 'tbw-filter-search';\n\n const searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.placeholder = 'Search...';\n searchInput.className = 'tbw-filter-search-input';\n searchInput.value = this.searchText.get(field) ?? '';\n searchContainer.appendChild(searchInput);\n panel.appendChild(searchContainer);\n\n // Select All tristate checkbox\n const actionsRow = document.createElement('div');\n actionsRow.className = 'tbw-filter-actions';\n\n const selectAllLabel = document.createElement('label');\n selectAllLabel.className = 'tbw-filter-value-item';\n selectAllLabel.style.padding = '0';\n selectAllLabel.style.margin = '0';\n\n const selectAllCheckbox = document.createElement('input');\n selectAllCheckbox.type = 'checkbox';\n selectAllCheckbox.className = 'tbw-filter-checkbox';\n\n const selectAllText = document.createElement('span');\n selectAllText.textContent = 'Select All';\n\n selectAllLabel.appendChild(selectAllCheckbox);\n selectAllLabel.appendChild(selectAllText);\n actionsRow.appendChild(selectAllLabel);\n\n // Update tristate checkbox based on checkState\n const updateSelectAllState = () => {\n const values = [...checkState.values()];\n const allChecked = values.every((v) => v);\n const noneChecked = values.every((v) => !v);\n\n selectAllCheckbox.checked = allChecked;\n selectAllCheckbox.indeterminate = !allChecked && !noneChecked;\n };\n\n // Toggle all on click\n selectAllCheckbox.addEventListener('change', () => {\n const newState = selectAllCheckbox.checked;\n for (const key of checkState.keys()) {\n checkState.set(key, newState);\n }\n updateSelectAllState();\n renderVisibleItems();\n });\n\n panel.appendChild(actionsRow);\n\n // Values container with virtualization support\n const valuesContainer = document.createElement('div');\n valuesContainer.className = 'tbw-filter-values';\n\n // Spacer for virtual height\n const spacer = document.createElement('div');\n spacer.className = 'tbw-filter-values-spacer';\n valuesContainer.appendChild(spacer);\n\n // Content container positioned absolutely\n const contentContainer = document.createElement('div');\n contentContainer.className = 'tbw-filter-values-content';\n valuesContainer.appendChild(contentContainer);\n\n // Track current check state for values (persists across virtualizations)\n const checkState = new Map<string, boolean>();\n uniqueValues.forEach((value) => {\n const key = value == null ? '__null__' : String(value);\n checkState.set(key, !excludedValues.has(value));\n });\n\n // Initialize select all state\n updateSelectAllState();\n\n // Filtered values cache\n let filteredValues: unknown[] = [];\n\n // Create a single checkbox item element\n const createItem = (value: unknown, index: number): HTMLElement => {\n const strValue = value == null ? '(Blank)' : String(value);\n const key = value == null ? '__null__' : String(value);\n\n const item = document.createElement('label');\n item.className = 'tbw-filter-value-item';\n item.style.position = 'absolute';\n item.style.top = `${index * FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n item.style.left = '0';\n item.style.right = '0';\n item.style.height = `${FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n item.style.boxSizing = 'border-box';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'tbw-filter-checkbox';\n checkbox.checked = checkState.get(key) ?? true;\n checkbox.dataset.value = key;\n\n // Sync check state on change and update tristate checkbox\n checkbox.addEventListener('change', () => {\n checkState.set(key, checkbox.checked);\n updateSelectAllState();\n });\n\n const label = document.createElement('span');\n label.textContent = strValue;\n\n item.appendChild(checkbox);\n item.appendChild(label);\n return item;\n };\n\n // Render visible items using virtualization\n const renderVisibleItems = () => {\n const totalItems = filteredValues.length;\n const viewportHeight = valuesContainer.clientHeight;\n const scrollTop = valuesContainer.scrollTop;\n\n // Set total height for scrollbar\n spacer.style.height = `${totalItems * FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n\n // Bypass virtualization for small lists\n if (shouldBypassVirtualization(totalItems, FilteringPlugin.LIST_BYPASS_THRESHOLD / 3)) {\n contentContainer.innerHTML = '';\n contentContainer.style.transform = 'translateY(0px)';\n filteredValues.forEach((value, idx) => {\n contentContainer.appendChild(createItem(value, idx));\n });\n return;\n }\n\n // Use computeVirtualWindow for real-scroll virtualization\n const window = computeVirtualWindow({\n totalRows: totalItems,\n viewportHeight,\n scrollTop,\n rowHeight: FilteringPlugin.LIST_ITEM_HEIGHT,\n overscan: FilteringPlugin.LIST_OVERSCAN,\n });\n\n // Position content container\n contentContainer.style.transform = `translateY(${window.offsetY}px)`;\n\n // Clear and render visible items\n contentContainer.innerHTML = '';\n for (let i = window.start; i < window.end; i++) {\n contentContainer.appendChild(createItem(filteredValues[i], i - window.start));\n }\n };\n\n // Filter and re-render values\n const renderValues = (filterText: string) => {\n const lowerFilter = filterText.toLowerCase();\n\n // Filter the unique values\n filteredValues = uniqueValues.filter((value) => {\n const strValue = value == null ? '(Blank)' : String(value);\n return !filterText || strValue.toLowerCase().includes(lowerFilter);\n });\n\n if (filteredValues.length === 0) {\n spacer.style.height = '0px';\n contentContainer.innerHTML = '';\n const noMatch = document.createElement('div');\n noMatch.className = 'tbw-filter-no-match';\n noMatch.textContent = 'No matching values';\n contentContainer.appendChild(noMatch);\n return;\n }\n\n renderVisibleItems();\n };\n\n // Scroll handler for virtualization\n valuesContainer.addEventListener(\n 'scroll',\n () => {\n if (filteredValues.length > 0) {\n renderVisibleItems();\n }\n },\n { passive: true },\n );\n\n renderValues(searchInput.value);\n panel.appendChild(valuesContainer);\n\n // Debounced search\n let debounceTimer: ReturnType<typeof setTimeout>;\n searchInput.addEventListener('input', () => {\n clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n this.searchText.set(field, searchInput.value);\n renderValues(searchInput.value);\n }, this.config.debounceMs ?? 150);\n });\n\n // Apply/Clear buttons\n const buttonRow = document.createElement('div');\n buttonRow.className = 'tbw-filter-buttons';\n\n const applyBtn = document.createElement('button');\n applyBtn.className = 'tbw-filter-apply-btn';\n applyBtn.textContent = 'Apply';\n applyBtn.addEventListener('click', () => {\n // Read from checkState map (works with virtualization)\n const excluded: unknown[] = [];\n for (const [key, isChecked] of checkState) {\n if (!isChecked) {\n if (key === '__null__') {\n excluded.push(null);\n } else {\n // Try to match original value type\n const original = uniqueValues.find((v) => String(v) === key);\n excluded.push(original !== undefined ? original : key);\n }\n }\n }\n params.applySetFilter(excluded);\n });\n buttonRow.appendChild(applyBtn);\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'tbw-filter-clear-btn';\n clearBtn.textContent = 'Clear Filter';\n clearBtn.addEventListener('click', () => {\n params.clearFilter();\n });\n buttonRow.appendChild(clearBtn);\n\n panel.appendChild(buttonRow);\n }\n\n /**\n * Apply a set filter (exclude values)\n */\n private applySetFilter(field: string, excluded: unknown[]): void {\n // Store excluded values\n this.excludedValues.set(field, new Set(excluded));\n\n if (excluded.length === 0) {\n // No exclusions = no filter\n this.filters.delete(field);\n } else {\n // Create \"notIn\" filter\n this.filters.set(field, {\n field,\n type: 'set',\n operator: 'notIn',\n value: excluded,\n });\n }\n\n this.applyFiltersInternal();\n }\n\n /**\n * Apply a text filter\n */\n private applyTextFilter(field: string, operator: FilterModel['operator'], value: string, valueTo?: string): void {\n this.filters.set(field, {\n field,\n type: 'text',\n operator,\n value,\n valueTo,\n });\n\n this.applyFiltersInternal();\n }\n\n /**\n * Internal method to apply filters (sync or async based on config)\n */\n private applyFiltersInternal(): void {\n this.cachedResult = null;\n this.cacheKey = null;\n\n const filterList = [...this.filters.values()];\n\n // If using async filterHandler, delegate to server\n if (this.config.filterHandler) {\n const gridEl = this.grid as HTMLElement;\n gridEl.setAttribute('aria-busy', 'true');\n\n const result = this.config.filterHandler(filterList, this.sourceRows as unknown[]);\n\n // Handle async or sync result\n const handleResult = (rows: unknown[]) => {\n gridEl.removeAttribute('aria-busy');\n this.cachedResult = rows;\n\n // Update grid rows directly for async filtering\n (this.grid as unknown as { rows: unknown[] }).rows = rows;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: filterList,\n filteredRowCount: rows.length,\n });\n\n // Trigger afterRender to update filter button active state\n this.requestRender();\n };\n\n if (result && typeof (result as Promise<unknown[]>).then === 'function') {\n (result as Promise<unknown[]>).then(handleResult);\n } else {\n handleResult(result as unknown[]);\n }\n return;\n }\n\n // Sync path: emit event and re-render (processRows will handle filtering)\n this.emit<FilterChangeDetail>('filter-change', {\n filters: filterList,\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n // #endregion\n\n // #region Column State Hooks\n\n /**\n * Return filter state for a column if it has an active filter.\n */\n override getColumnState(field: string): Partial<ColumnState> | undefined {\n const filterModel = this.filters.get(field);\n if (!filterModel) return undefined;\n\n return {\n filter: {\n type: filterModel.type,\n operator: filterModel.operator,\n value: filterModel.value,\n valueTo: filterModel.valueTo,\n },\n };\n }\n\n /**\n * Apply filter state from column state.\n */\n override applyColumnState(field: string, state: ColumnState): void {\n // Only process if the column has filter state\n if (!state.filter) {\n this.filters.delete(field);\n return;\n }\n\n // Reconstruct the FilterModel from the stored state\n const filterModel: FilterModel = {\n field,\n type: state.filter.type,\n operator: state.filter.operator as FilterModel['operator'],\n value: state.filter.value,\n valueTo: state.filter.valueTo,\n };\n\n this.filters.set(field, filterModel);\n // Invalidate cache so filter is reapplied\n this.cachedResult = null;\n this.cacheKey = null;\n }\n // #endregion\n\n // #region Styles\n\n override readonly styles = styles;\n // #endregion\n}\n"],"names":["matchesFilter","row","filter","caseSensitive","rawValue","stringValue","compareValue","filterValue","filterRows","rows","filters","f","computeFilterCacheKey","getUniqueValues","field","values","value","a","b","FilteringPlugin","BaseGridPlugin","grid","filterList","newCacheKey","result","shadowRoot","cell","colIndex","col","hasFilter","filterBtn","e","style","filterPanelStyles","column","buttonEl","panel","uniqueValues","excludedSet","currentSearchText","params","excluded","operator","valueTo","usedCustomRenderer","rect","panelRect","excludedValues","searchContainer","searchInput","actionsRow","selectAllLabel","selectAllCheckbox","selectAllText","updateSelectAllState","checkState","allChecked","v","noneChecked","newState","key","renderVisibleItems","valuesContainer","spacer","contentContainer","filteredValues","createItem","index","strValue","item","checkbox","label","totalItems","viewportHeight","scrollTop","shouldBypassVirtualization","idx","window","computeVirtualWindow","i","renderValues","filterText","lowerFilter","noMatch","debounceTimer","buttonRow","applyBtn","isChecked","original","clearBtn","gridEl","handleResult","filterModel","state","styles"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(d,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],c):(d=typeof globalThis<"u"?globalThis:d||self,c(d.TbwGridPlugin_groupingColumns={},d.TbwGrid))})(this,(function(d,c){"use strict";function a(
|
|
1
|
+
(function(d,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],c):(d=typeof globalThis<"u"?globalThis:d||self,c(d.TbwGridPlugin_groupingColumns={},d.TbwGrid))})(this,(function(d,c){"use strict";function a(u){if(!u.length)return[];const r=new Map,e=[],t=(n,i)=>{if(!i.length)return;const l=e[e.length-1];if(l&&l.implicit&&l.firstIndex+l.columns.length===n){l.columns.push(...i);return}e.push({id:"__implicit__"+n,label:void 0,columns:i,firstIndex:n,implicit:!0})};let s=[],o=0;return u.forEach((n,i)=>{const l=n.group;if(!l){s.length===0&&(o=i),s.push(n);return}s.length&&(t(o,s.slice()),s=[]);const p=typeof l=="string"?l:l.id;let g=r.get(p);g||(g={id:p,label:typeof l=="string"?void 0:l.label,columns:[],firstIndex:i},r.set(p,g),e.push(g)),g.columns.push(n)}),s.length&&t(o,s),e.length===1&&e[0].implicit&&e[0].columns.length===u.length?[]:e}function h(u,r,e){if(!r.length||!u)return;const t=new Map;for(const o of r)for(const n of o.columns)n?.field&&t.set(n.field,o.id);const s=Array.from(u.querySelectorAll(".cell[data-field]"));s.forEach(o=>{const n=o.getAttribute("data-field")||"",i=t.get(n);i&&(o.classList.add("grouped"),o.getAttribute("data-group")||o.setAttribute("data-group",i))});for(const o of r){const n=o.columns[o.columns.length-1],i=s.find(l=>l.getAttribute("data-field")===n.field);i&&i.classList.add("group-end")}}function f(u,r){if(u.length===0)return null;const e=document.createElement("div");e.className="header-group-row",e.setAttribute("role","row");for(const t of u){const s=t.firstIndex!=null?t.firstIndex:r.findIndex(l=>t.columns.includes(l)),o=String(t.id).startsWith("__implicit__"),n=o?"":t.label||t.id,i=document.createElement("div");i.className="cell header-group-cell",o&&i.classList.add("implicit-group"),i.setAttribute("data-group",String(t.id)),i.style.gridColumn=`${s+1} / span ${t.columns.length}`,i.textContent=n,e.appendChild(i)}return e}function m(u){return u.some(r=>r.group!=null)}const b=".header-group-row{display:grid;grid-auto-flow:column;background:var(--tbw-grouping-columns-header-bg, var(--tbw-color-header-bg));border-bottom:1px solid var(--tbw-grouping-columns-border, var(--tbw-color-border))}.header-group-cell{display:flex;align-items:center;justify-content:center;padding:4px 8px;font-weight:600;font-size:.9em;text-transform:uppercase;letter-spacing:.5px;border-right:1px solid var(--tbw-grouping-columns-border, var(--tbw-color-border))}.header-group-cell:last-child{border-right:none}.header-row .cell.grouped{border-top:none}.header-row .cell.group-end{border-right:2px solid var(--tbw-grouping-columns-separator, var(--tbw-color-border-strong))}.header-row .cell.group-end:last-child{border-right:none}.header-group-row.no-borders{border-bottom:none}.header-group-row.no-borders .header-group-cell{border-right:none}.header-row.no-group-borders .cell.group-end{border-right:1px solid var(--tbw-color-border)}";class w extends c.BaseGridPlugin{name="groupingColumns";version="1.0.0";get defaultConfig(){return{showGroupBorders:!0}}groups=[];isActive=!1;detach(){this.groups=[],this.isActive=!1}static detect(r,e){const t=e?.columns;return Array.isArray(t)?m(t):!1}processColumns(r){const e=a(r);return e.length===0?(this.isActive=!1,this.groups=[],[...r]):(this.isActive=!0,this.groups=e,[...r])}afterRender(){if(!this.isActive||this.groups.length===0){const n=this.shadowRoot?.querySelector(".header")?.querySelector(".header-group-row");n&&n.remove();return}const r=this.shadowRoot?.querySelector(".header");if(!r)return;const e=r.querySelector(".header-group-row");e&&e.remove();const t=f(this.groups,this.columns);if(t){t.classList.toggle("no-borders",!this.config.showGroupBorders);const o=r.querySelector(".header-row");o?r.insertBefore(t,o):r.appendChild(t)}const s=r.querySelector(".header-row");s&&(s.classList.toggle("no-group-borders",!this.config.showGroupBorders),h(s,this.groups,this.columns))}isGroupingActive(){return this.isActive}getGroups(){return this.groups}getGroupColumns(r){const e=this.groups.find(t=>t.id===r);return e?e.columns:[]}refresh(){this.requestRender()}styles=b}d.GroupingColumnsPlugin=w,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=grouping-columns.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grouping-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/grouping-columns/grouping-columns.ts","../../../../../libs/grid/src/lib/plugins/grouping-columns/GroupingColumnsPlugin.ts"],"sourcesContent":["/**\n * Column Groups Core Logic\n *\n * Pure functions for computing and managing column header groups.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnGroup, ColumnGroupInternal } from './types';\n\n/**\n * Compute column groups from column configuration.\n * Handles explicit groups (via column.group) and creates implicit groups for ungrouped columns.\n *\n * @param columns - Array of column configurations\n * @returns Array of column groups, or empty if no meaningful groups\n */\nexport function computeColumnGroups<T>(columns: ColumnConfig<T>[]): ColumnGroup<T>[] {\n if (!columns.length) return [];\n\n const explicitMap = new Map<string, ColumnGroupInternal<T>>();\n const groupsOrdered: ColumnGroupInternal<T>[] = [];\n\n // Helper to push unnamed implicit group for a run of ungrouped columns\n const pushImplicit = (startIdx: number, cols: ColumnConfig<T>[]) => {\n if (!cols.length) return;\n // Merge with previous implicit group if adjacent to reduce noise\n const prev = groupsOrdered[groupsOrdered.length - 1];\n if (prev && prev.implicit && prev.firstIndex + prev.columns.length === startIdx) {\n prev.columns.push(...cols);\n return;\n }\n groupsOrdered.push({\n id: '__implicit__' + startIdx,\n label: undefined,\n columns: cols,\n firstIndex: startIdx,\n implicit: true,\n });\n };\n\n let run: ColumnConfig<T>[] = [];\n let runStart = 0;\n\n columns.forEach((col, idx) => {\n const g: any = (col as any).group;\n if (!g) {\n if (run.length === 0) runStart = idx;\n run.push(col);\n return;\n }\n // Close any pending implicit run\n if (run.length) {\n pushImplicit(runStart, run.slice());\n run = [];\n }\n const id = typeof g === 'string' ? g : g.id;\n let group = explicitMap.get(id);\n if (!group) {\n group = {\n id,\n label: typeof g === 'string' ? undefined : g.label,\n columns: [],\n firstIndex: idx,\n };\n explicitMap.set(id, group);\n groupsOrdered.push(group);\n }\n group.columns.push(col);\n });\n\n // Trailing implicit run\n if (run.length) pushImplicit(runStart, run);\n\n // If we only have a single implicit group covering all columns, treat as no groups\n if (\n groupsOrdered.length === 1 &&\n groupsOrdered[0].implicit &&\n groupsOrdered[0].columns.length === columns.length\n ) {\n return [];\n }\n\n return groupsOrdered as ColumnGroup<T>[];\n}\n\n/**\n * Apply CSS classes to header cells based on their group membership.\n *\n * @param headerRowEl - The header row element\n * @param groups - The computed column groups\n * @param columns - The column configurations\n */\nexport function applyGroupedHeaderCellClasses(\n headerRowEl: HTMLElement | null,\n groups: ColumnGroup[],\n columns: ColumnConfig<any>[]\n): void {\n if (!groups.length || !headerRowEl) return;\n\n const fieldToGroup = new Map<string, string>();\n for (const g of groups) {\n for (const c of g.columns) {\n if ((c as any)?.field) {\n fieldToGroup.set((c as any).field, g.id);\n }\n }\n }\n\n const headerCells = Array.from(headerRowEl.querySelectorAll('.cell[data-field]')) as HTMLElement[];\n headerCells.forEach((cell) => {\n const f = cell.getAttribute('data-field') || '';\n const gid = fieldToGroup.get(f);\n if (gid) {\n cell.classList.add('grouped');\n if (!cell.getAttribute('data-group')) {\n cell.setAttribute('data-group', gid);\n }\n }\n });\n\n // Mark group end cells for styling\n for (const g of groups) {\n const last = g.columns[g.columns.length - 1];\n const cell = headerCells.find((c) => c.getAttribute('data-field') === (last as any).field);\n if (cell) cell.classList.add('group-end');\n }\n}\n\n/**\n * Build the group header row element.\n *\n * @param groups - The computed column groups\n * @param columns - The column configurations\n * @returns The group header row element, or null if no groups\n */\nexport function buildGroupHeaderRow(\n groups: ColumnGroup[],\n columns: ColumnConfig<any>[]\n): HTMLElement | null {\n if (groups.length === 0) return null;\n\n const groupRow = document.createElement('div');\n groupRow.className = 'header-group-row';\n groupRow.setAttribute('role', 'row');\n\n for (const g of groups) {\n const startIndex =\n g.firstIndex != null\n ? g.firstIndex\n : columns.findIndex((c) => (g.columns as any[]).includes(c));\n\n const isImplicit = String(g.id).startsWith('__implicit__');\n const label = isImplicit ? '' : g.label || g.id;\n\n const cell = document.createElement('div');\n cell.className = 'cell header-group-cell';\n if (isImplicit) cell.classList.add('implicit-group');\n cell.setAttribute('data-group', String(g.id));\n cell.style.gridColumn = `${startIndex + 1} / span ${g.columns.length}`;\n cell.textContent = label;\n groupRow.appendChild(cell);\n }\n\n return groupRow;\n}\n\n/**\n * Check if any columns have group configuration.\n *\n * @param columns - The column configurations\n * @returns True if at least one column has a group\n */\nexport function hasColumnGroups(columns: ColumnConfig<any>[]): boolean {\n return columns.some((col) => (col as any).group != null);\n}\n\n/**\n * Get group ID for a specific column.\n *\n * @param column - The column configuration\n * @returns The group ID, or undefined if not grouped\n */\nexport function getColumnGroupId(column: ColumnConfig<any>): string | undefined {\n const g = (column as any).group;\n if (!g) return undefined;\n return typeof g === 'string' ? g : g.id;\n}\n","/**\n * Column Groups Plugin (Class-based)\n *\n * Enables multi-level column header grouping.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n applyGroupedHeaderCellClasses,\n buildGroupHeaderRow,\n computeColumnGroups,\n hasColumnGroups,\n} from './grouping-columns';\nimport styles from './grouping-columns.css?inline';\nimport type { ColumnGroup, GroupingColumnsConfig } from './types';\n\n/**\n * Column Groups Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new GroupingColumnsPlugin({\n * enabled: true,\n * showGroupBorders: true,\n * })\n * ```\n */\nexport class GroupingColumnsPlugin extends BaseGridPlugin<GroupingColumnsConfig> {\n readonly name = 'groupingColumns';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<GroupingColumnsConfig> {\n return {\n showGroupBorders: true,\n };\n }\n\n // #region Internal State\n private groups: ColumnGroup[] = [];\n private isActive = false;\n // #endregion\n\n // #region Lifecycle\n\n override detach(): void {\n this.groups = [];\n this.isActive = false;\n }\n // #endregion\n\n // #region Static Detection\n\n /**\n * Auto-detect column groups from column configuration.\n */\n static detect(rows: readonly any[], config: any): boolean {\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasColumnGroups(columns);\n }\n // #endregion\n\n // #region Hooks\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // Compute groups from column definitions\n const groups = computeColumnGroups(columns as ColumnConfig[]);\n\n if (groups.length === 0) {\n this.isActive = false;\n this.groups = [];\n return [...columns];\n }\n\n this.isActive = true;\n this.groups = groups;\n\n // Return columns unchanged - the afterRender hook will add the group header\n return [...columns];\n }\n\n override afterRender(): void {\n if (!this.isActive || this.groups.length === 0) {\n // Remove any existing group header\n const header = this.shadowRoot?.querySelector('.header');\n const existingGroupRow = header?.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n return;\n }\n\n const header = this.shadowRoot?.querySelector('.header');\n if (!header) return;\n\n // Remove existing group row if present\n const existingGroupRow = header.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n\n // Build and insert group header row\n const groupRow = buildGroupHeaderRow(this.groups, this.columns as ColumnConfig[]);\n if (groupRow) {\n const headerRow = header.querySelector('.header-row');\n if (headerRow) {\n header.insertBefore(groupRow, headerRow);\n } else {\n header.appendChild(groupRow);\n }\n }\n\n // Apply classes to header cells\n const headerRow = header.querySelector('.header-row') as HTMLElement;\n if (headerRow) {\n applyGroupedHeaderCellClasses(headerRow, this.groups, this.columns as ColumnConfig[]);\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Check if column groups are active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Get the computed column groups.\n * @returns Array of column groups\n */\n getGroups(): ColumnGroup[] {\n return this.groups;\n }\n\n /**\n * Get columns in a specific group.\n * @param groupId - The group ID to find\n * @returns Array of columns in the group\n */\n getGroupColumns(groupId: string): ColumnConfig[] {\n const group = this.groups.find((g) => g.id === groupId);\n return group ? group.columns : [];\n }\n\n /**\n * Refresh column groups (recompute from current columns).\n */\n refresh(): void {\n this.requestRender();\n }\n // #endregion\n\n // #region Styles\n\n override readonly styles = styles;\n // #endregion\n}\n"],"names":["computeColumnGroups","columns","explicitMap","groupsOrdered","pushImplicit","startIdx","cols","prev","run","runStart","col","idx","g","id","group","applyGroupedHeaderCellClasses","headerRowEl","groups","fieldToGroup","c","headerCells","cell","f","gid","last","buildGroupHeaderRow","groupRow","startIndex","isImplicit","label","hasColumnGroups","GroupingColumnsPlugin","BaseGridPlugin","rows","config","existingGroupRow","header","headerRow","groupId","styles"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"grouping-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/grouping-columns/grouping-columns.ts","../../../../../libs/grid/src/lib/plugins/grouping-columns/GroupingColumnsPlugin.ts"],"sourcesContent":["/**\n * Column Groups Core Logic\n *\n * Pure functions for computing and managing column header groups.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnGroup, ColumnGroupInternal } from './types';\n\n/**\n * Compute column groups from column configuration.\n * Handles explicit groups (via column.group) and creates implicit groups for ungrouped columns.\n *\n * @param columns - Array of column configurations\n * @returns Array of column groups, or empty if no meaningful groups\n */\nexport function computeColumnGroups<T>(columns: ColumnConfig<T>[]): ColumnGroup<T>[] {\n if (!columns.length) return [];\n\n const explicitMap = new Map<string, ColumnGroupInternal<T>>();\n const groupsOrdered: ColumnGroupInternal<T>[] = [];\n\n // Helper to push unnamed implicit group for a run of ungrouped columns\n const pushImplicit = (startIdx: number, cols: ColumnConfig<T>[]) => {\n if (!cols.length) return;\n // Merge with previous implicit group if adjacent to reduce noise\n const prev = groupsOrdered[groupsOrdered.length - 1];\n if (prev && prev.implicit && prev.firstIndex + prev.columns.length === startIdx) {\n prev.columns.push(...cols);\n return;\n }\n groupsOrdered.push({\n id: '__implicit__' + startIdx,\n label: undefined,\n columns: cols,\n firstIndex: startIdx,\n implicit: true,\n });\n };\n\n let run: ColumnConfig<T>[] = [];\n let runStart = 0;\n\n columns.forEach((col, idx) => {\n const g: any = (col as any).group;\n if (!g) {\n if (run.length === 0) runStart = idx;\n run.push(col);\n return;\n }\n // Close any pending implicit run\n if (run.length) {\n pushImplicit(runStart, run.slice());\n run = [];\n }\n const id = typeof g === 'string' ? g : g.id;\n let group = explicitMap.get(id);\n if (!group) {\n group = {\n id,\n label: typeof g === 'string' ? undefined : g.label,\n columns: [],\n firstIndex: idx,\n };\n explicitMap.set(id, group);\n groupsOrdered.push(group);\n }\n group.columns.push(col);\n });\n\n // Trailing implicit run\n if (run.length) pushImplicit(runStart, run);\n\n // If we only have a single implicit group covering all columns, treat as no groups\n if (\n groupsOrdered.length === 1 &&\n groupsOrdered[0].implicit &&\n groupsOrdered[0].columns.length === columns.length\n ) {\n return [];\n }\n\n return groupsOrdered as ColumnGroup<T>[];\n}\n\n/**\n * Apply CSS classes to header cells based on their group membership.\n *\n * @param headerRowEl - The header row element\n * @param groups - The computed column groups\n * @param columns - The column configurations\n */\nexport function applyGroupedHeaderCellClasses(\n headerRowEl: HTMLElement | null,\n groups: ColumnGroup[],\n columns: ColumnConfig<any>[]\n): void {\n if (!groups.length || !headerRowEl) return;\n\n const fieldToGroup = new Map<string, string>();\n for (const g of groups) {\n for (const c of g.columns) {\n if ((c as any)?.field) {\n fieldToGroup.set((c as any).field, g.id);\n }\n }\n }\n\n const headerCells = Array.from(headerRowEl.querySelectorAll('.cell[data-field]')) as HTMLElement[];\n headerCells.forEach((cell) => {\n const f = cell.getAttribute('data-field') || '';\n const gid = fieldToGroup.get(f);\n if (gid) {\n cell.classList.add('grouped');\n if (!cell.getAttribute('data-group')) {\n cell.setAttribute('data-group', gid);\n }\n }\n });\n\n // Mark group end cells for styling\n for (const g of groups) {\n const last = g.columns[g.columns.length - 1];\n const cell = headerCells.find((c) => c.getAttribute('data-field') === (last as any).field);\n if (cell) cell.classList.add('group-end');\n }\n}\n\n/**\n * Build the group header row element.\n *\n * @param groups - The computed column groups\n * @param columns - The column configurations\n * @returns The group header row element, or null if no groups\n */\nexport function buildGroupHeaderRow(\n groups: ColumnGroup[],\n columns: ColumnConfig<any>[]\n): HTMLElement | null {\n if (groups.length === 0) return null;\n\n const groupRow = document.createElement('div');\n groupRow.className = 'header-group-row';\n groupRow.setAttribute('role', 'row');\n\n for (const g of groups) {\n const startIndex =\n g.firstIndex != null\n ? g.firstIndex\n : columns.findIndex((c) => (g.columns as any[]).includes(c));\n\n const isImplicit = String(g.id).startsWith('__implicit__');\n const label = isImplicit ? '' : g.label || g.id;\n\n const cell = document.createElement('div');\n cell.className = 'cell header-group-cell';\n if (isImplicit) cell.classList.add('implicit-group');\n cell.setAttribute('data-group', String(g.id));\n cell.style.gridColumn = `${startIndex + 1} / span ${g.columns.length}`;\n cell.textContent = label;\n groupRow.appendChild(cell);\n }\n\n return groupRow;\n}\n\n/**\n * Check if any columns have group configuration.\n *\n * @param columns - The column configurations\n * @returns True if at least one column has a group\n */\nexport function hasColumnGroups(columns: ColumnConfig<any>[]): boolean {\n return columns.some((col) => (col as any).group != null);\n}\n\n/**\n * Get group ID for a specific column.\n *\n * @param column - The column configuration\n * @returns The group ID, or undefined if not grouped\n */\nexport function getColumnGroupId(column: ColumnConfig<any>): string | undefined {\n const g = (column as any).group;\n if (!g) return undefined;\n return typeof g === 'string' ? g : g.id;\n}\n","/**\n * Column Groups Plugin (Class-based)\n *\n * Enables multi-level column header grouping.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n applyGroupedHeaderCellClasses,\n buildGroupHeaderRow,\n computeColumnGroups,\n hasColumnGroups,\n} from './grouping-columns';\nimport styles from './grouping-columns.css?inline';\nimport type { ColumnGroup, GroupingColumnsConfig } from './types';\n\n/**\n * Column Groups Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new GroupingColumnsPlugin({\n * enabled: true,\n * showGroupBorders: true,\n * })\n * ```\n */\nexport class GroupingColumnsPlugin extends BaseGridPlugin<GroupingColumnsConfig> {\n readonly name = 'groupingColumns';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<GroupingColumnsConfig> {\n return {\n showGroupBorders: true,\n };\n }\n\n // #region Internal State\n private groups: ColumnGroup[] = [];\n private isActive = false;\n // #endregion\n\n // #region Lifecycle\n\n override detach(): void {\n this.groups = [];\n this.isActive = false;\n }\n // #endregion\n\n // #region Static Detection\n\n /**\n * Auto-detect column groups from column configuration.\n */\n static detect(rows: readonly any[], config: any): boolean {\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasColumnGroups(columns);\n }\n // #endregion\n\n // #region Hooks\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // Compute groups from column definitions\n const groups = computeColumnGroups(columns as ColumnConfig[]);\n\n if (groups.length === 0) {\n this.isActive = false;\n this.groups = [];\n return [...columns];\n }\n\n this.isActive = true;\n this.groups = groups;\n\n // Return columns unchanged - the afterRender hook will add the group header\n return [...columns];\n }\n\n override afterRender(): void {\n if (!this.isActive || this.groups.length === 0) {\n // Remove any existing group header\n const header = this.shadowRoot?.querySelector('.header');\n const existingGroupRow = header?.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n return;\n }\n\n const header = this.shadowRoot?.querySelector('.header');\n if (!header) return;\n\n // Remove existing group row if present\n const existingGroupRow = header.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n\n // Build and insert group header row\n const groupRow = buildGroupHeaderRow(this.groups, this.columns as ColumnConfig[]);\n if (groupRow) {\n // Toggle border visibility class\n groupRow.classList.toggle('no-borders', !this.config.showGroupBorders);\n\n const headerRow = header.querySelector('.header-row');\n if (headerRow) {\n header.insertBefore(groupRow, headerRow);\n } else {\n header.appendChild(groupRow);\n }\n }\n\n // Apply classes to header cells\n const headerRow = header.querySelector('.header-row') as HTMLElement;\n if (headerRow) {\n // Toggle border visibility on header cells\n headerRow.classList.toggle('no-group-borders', !this.config.showGroupBorders);\n applyGroupedHeaderCellClasses(headerRow, this.groups, this.columns as ColumnConfig[]);\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Check if column groups are active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Get the computed column groups.\n * @returns Array of column groups\n */\n getGroups(): ColumnGroup[] {\n return this.groups;\n }\n\n /**\n * Get columns in a specific group.\n * @param groupId - The group ID to find\n * @returns Array of columns in the group\n */\n getGroupColumns(groupId: string): ColumnConfig[] {\n const group = this.groups.find((g) => g.id === groupId);\n return group ? group.columns : [];\n }\n\n /**\n * Refresh column groups (recompute from current columns).\n */\n refresh(): void {\n this.requestRender();\n }\n // #endregion\n\n // #region Styles\n\n override readonly styles = styles;\n // #endregion\n}\n"],"names":["computeColumnGroups","columns","explicitMap","groupsOrdered","pushImplicit","startIdx","cols","prev","run","runStart","col","idx","g","id","group","applyGroupedHeaderCellClasses","headerRowEl","groups","fieldToGroup","c","headerCells","cell","f","gid","last","buildGroupHeaderRow","groupRow","startIndex","isImplicit","label","hasColumnGroups","GroupingColumnsPlugin","BaseGridPlugin","rows","config","existingGroupRow","header","headerRow","groupId","styles"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(f,_){typeof exports=="object"&&typeof module<"u"?_(exports,require("../../core/plugin/base-plugin"),require("../../core/internal/aggregators")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin","../../core/internal/aggregators"],_):(f=typeof globalThis<"u"?globalThis:f||self,_(f.TbwGridPlugin_groupingRows={},f.TbwGrid,f.TbwGrid))})(this,(function(f,_,
|
|
1
|
+
(function(f,_){typeof exports=="object"&&typeof module<"u"?_(exports,require("../../core/plugin/base-plugin"),require("../../core/internal/aggregators")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin","../../core/internal/aggregators"],_):(f=typeof globalThis<"u"?globalThis:f||self,_(f.TbwGridPlugin_groupingRows={},f.TbwGrid,f.TbwGrid))})(this,(function(f,_,b){"use strict";function w({rows:l,config:e,expanded:t}){const i=e.groupOn;if(typeof i!="function")return[];const s={key:"__root__",value:null,depth:-1,rows:[],children:new Map};if(l.forEach(o=>{let a=i(o);a==null||a===!1?a=["__ungrouped__"]:Array.isArray(a)||(a=[a]);let r=s;a.forEach((c,y)=>{const u=c==null?"∅":String(c),p=r.key==="__root__"?u:r.key+"||"+u;let g=r.children.get(u);g||(g={key:p,value:c,depth:y,rows:[],children:new Map,parent:r},r.children.set(u,g)),r=g}),r.rows.push(o)}),s.children.size===1&&s.children.has("__ungrouped__")&&s.children.get("__ungrouped__").rows.length===l.length)return[];const n=[],d=o=>{if(o===s){o.children.forEach(r=>d(r));return}const a=t.has(o.key);n.push({kind:"group",key:o.key,value:o.value,depth:o.depth,rows:o.rows,expanded:a}),a&&(o.children.size?o.children.forEach(r=>d(r)):o.rows.forEach(r=>n.push({kind:"data",row:r,rowIndex:l.indexOf(r)})))};return d(s),n}function x(l,e){const t=new Set(l);return t.has(e)?t.delete(e):t.add(e),t}function R(l){const e=new Set;for(const t of l)t.kind==="group"&&e.add(t.key);return e}function v(){return new Set}function C(l){return l.kind!=="group"?0:l.rows.length}const A='.group-row{background:var(--tbw-grouping-rows-bg, var(--tbw-color-panel-bg));font-weight:500}.group-row:hover{background:var(--tbw-grouping-rows-bg-hover, var(--tbw-color-row-hover))}.group-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin-right:4px}.group-toggle:hover{background:var(--tbw-grouping-rows-toggle-hover, var(--tbw-color-row-hover));border-radius:2px}.group-label{display:inline-flex;align-items:center;gap:8px}.group-count{color:var(--tbw-grouping-rows-count-color, var(--tbw-color-fg-muted));font-size:.85em;font-weight:400}[data-group-depth="0"] .group-label{padding-left:0}[data-group-depth="1"] .group-label{padding-left:20px}[data-group-depth="2"] .group-label{padding-left:40px}[data-group-depth="3"] .group-label{padding-left:60px}[data-group-depth="4"] .group-label{padding-left:80px}.data-grid-row.tbw-group-slide-in{animation:tbw-group-slide-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-group-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}.data-grid-row.tbw-group-fade-in{animation:tbw-group-fade-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-group-fade-in{0%{opacity:0}to{opacity:1}}';class k extends _.BaseGridPlugin{name="groupingRows";version="1.0.0";get defaultConfig(){return{defaultExpanded:!1,showRowCount:!0,indentWidth:20,aggregators:{},animation:"slide"}}expandedKeys=new Set;flattenedRows=[];isActive=!1;previousVisibleKeys=new Set;keysToAnimate=new Set;get animationStyle(){const t=this.grid.effectiveConfig?.animation?.mode??"reduced-motion";if(t===!1||t==="off")return!1;if(t!==!0&&t!=="on"){const i=this.shadowRoot?.host;if(i&&getComputedStyle(i).getPropertyValue("--tbw-animation-enabled").trim()==="0")return!1}return this.config.animation??"slide"}detach(){this.expandedKeys.clear(),this.flattenedRows=[],this.isActive=!1,this.previousVisibleKeys.clear(),this.keysToAnimate.clear()}static detect(e,t){return typeof t?.groupOn=="function"||typeof t?.enableRowGrouping=="boolean"}processRows(e){const t=this.config;if(typeof t.groupOn!="function")return this.isActive=!1,this.flattenedRows=[],[...e];const i=w({rows:e,config:t,expanded:this.expandedKeys});if(i.length===0)return this.isActive=!1,this.flattenedRows=[],[...e];this.isActive=!0,this.flattenedRows=i,this.keysToAnimate.clear();const s=new Set;return i.forEach((n,d)=>{if(n.kind==="data"){const o=`data-${d}`;s.add(o),this.previousVisibleKeys.has(o)||this.keysToAnimate.add(o)}}),this.previousVisibleKeys=s,i.map(n=>n.kind==="group"?{__isGroupRow:!0,__groupKey:n.key,__groupValue:n.value,__groupDepth:n.depth,__groupRows:n.rows,__groupExpanded:n.expanded,__groupRowCount:C(n)}:n.row)}onCellClick(e){const t=e.row;if(t?.__isGroupRow&&e.originalEvent.target?.closest(".group-toggle"))return this.toggle(t.__groupKey),!0}renderRow(e,t,i){if(!e?.__isGroupRow)return!1;const s=this.config;if(s.groupRowRenderer){const o=()=>{this.toggle(e.__groupKey)},a=s.groupRowRenderer({key:e.__groupKey,value:e.__groupValue,depth:e.__groupDepth,rows:e.__groupRows,expanded:e.__groupExpanded,toggleExpand:o});if(a)return t.className="group-row",t.__isCustomRow=!0,t.setAttribute("data-group-depth",String(e.__groupDepth)),typeof a=="string"?t.innerHTML=a:(t.innerHTML="",t.appendChild(a)),!0}const n=()=>{this.toggle(e.__groupKey)};return t.className="group-row",t.__isCustomRow=!0,t.setAttribute("data-group-depth",String(e.__groupDepth)),t.setAttribute("role","row"),t.setAttribute("aria-expanded",String(e.__groupExpanded)),t.style.paddingLeft=`${(e.__groupDepth||0)*(s.indentWidth??20)}px`,t.innerHTML="",s.fullWidth!==!1?this.renderFullWidthGroupRow(e,t,n):this.renderPerColumnGroupRow(e,t,n),!0}afterRender(){const e=this.animationStyle;if(e===!1||this.keysToAnimate.size===0)return;const t=this.shadowRoot?.querySelector(".rows");if(!t)return;const i=e==="fade"?"tbw-group-fade-in":"tbw-group-slide-in";for(const s of t.querySelectorAll(".data-grid-row:not(.group-row)")){const n=s.querySelector(".cell[data-row]"),d=n?parseInt(n.getAttribute("data-row")??"-1",10):-1,a=this.flattenedRows[d]?.kind==="data"?`data-${d}`:void 0;a&&this.keysToAnimate.has(a)&&(s.classList.add(i),s.addEventListener("animationend",()=>s.classList.remove(i),{once:!0}))}this.keysToAnimate.clear()}renderFullWidthGroupRow(e,t,i){const s=this.config,n=document.createElement("div");n.className="cell group-full",n.style.gridColumn="1 / -1",n.setAttribute("role","gridcell");const d=document.createElement("button");d.type="button",d.className=`group-toggle${e.__groupExpanded?" expanded":""}`,d.setAttribute("aria-label",e.__groupExpanded?"Collapse group":"Expand group"),this.setIcon(d,this.resolveIcon(e.__groupExpanded?"collapse":"expand")),d.addEventListener("click",r=>{r.stopPropagation(),i()}),n.appendChild(d);const o=document.createElement("span");o.className="group-label";const a=s.formatLabel?s.formatLabel(e.__groupValue,e.__groupDepth||0,e.__groupKey):String(e.__groupValue);if(o.textContent=a,n.appendChild(o),s.showRowCount!==!1){const r=document.createElement("span");r.className="group-count",r.textContent=`(${e.__groupRowCount??e.__groupRows?.length??0})`,n.appendChild(r)}t.appendChild(n)}renderPerColumnGroupRow(e,t,i){const s=this.config,n=s.aggregators??{},d=this.columns,o=e.__groupRows??[],r=this.shadowRoot?.querySelector(".body")?.style.gridTemplateColumns||"";r&&(t.style.display="grid",t.style.gridTemplateColumns=r),d.forEach((c,y)=>{const u=document.createElement("div");if(u.className="cell group-cell",u.setAttribute("data-col",String(y)),u.setAttribute("role","gridcell"),y===0){const p=document.createElement("button");p.type="button",p.className=`group-toggle${e.__groupExpanded?" expanded":""}`,p.setAttribute("aria-label",e.__groupExpanded?"Collapse group":"Expand group"),this.setIcon(p,this.resolveIcon(e.__groupExpanded?"collapse":"expand")),p.addEventListener("click",h=>{h.stopPropagation(),i()}),u.appendChild(p);const g=document.createElement("span"),m=n[c.field];if(m){const h=b.runAggregator(m,o,c.field,c);g.textContent=h!=null?String(h):String(e.__groupValue)}else{const h=s.formatLabel?s.formatLabel(e.__groupValue,e.__groupDepth||0,e.__groupKey):String(e.__groupValue);g.textContent=h}if(u.appendChild(g),s.showRowCount!==!1){const h=document.createElement("span");h.className="group-count",h.textContent=` (${o.length})`,u.appendChild(h)}}else{const p=n[c.field];if(p){const g=b.runAggregator(p,o,c.field,c);u.textContent=g!=null?String(g):""}else u.textContent=""}t.appendChild(u)})}expandAll(){this.expandedKeys=R(this.flattenedRows),this.requestRender()}collapseAll(){this.expandedKeys=v(),this.requestRender()}toggle(e){this.expandedKeys=x(this.expandedKeys,e);const t=this.flattenedRows.find(i=>i.kind==="group"&&i.key===e);this.emit("group-toggle",{key:e,expanded:this.expandedKeys.has(e),value:t?.value,depth:t?.depth??0}),this.requestRender()}isExpanded(e){return this.expandedKeys.has(e)}expand(e){this.expandedKeys.has(e)||(this.expandedKeys=new Set([...this.expandedKeys,e]),this.requestRender())}collapse(e){if(this.expandedKeys.has(e)){const t=new Set(this.expandedKeys);t.delete(e),this.expandedKeys=t,this.requestRender()}}getGroupState(){const e=this.flattenedRows.filter(t=>t.kind==="group");return{isActive:this.isActive,expandedCount:this.expandedKeys.size,totalGroups:e.length,expandedKeys:[...this.expandedKeys]}}getRowCount(){return this.flattenedRows.length}refreshGroups(){this.requestRender()}getExpandedGroups(){return[...this.expandedKeys]}getFlattenedRows(){return this.flattenedRows}isGroupingActive(){return this.isActive}setGroupOn(e){this.config.groupOn=e,this.requestRender()}styles=A}f.GroupingRowsPlugin=k,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=grouping-rows.umd.js.map
|