@toolbox-web/grid 0.0.4 → 0.0.5
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 +29 -6
- package/all.js +421 -421
- package/all.js.map +1 -1
- package/index.d.ts +28 -0
- package/index.js +774 -726
- package/index.js.map +1 -1
- package/lib/plugins/clipboard/index.js +55 -35
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js +49 -29
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js +35 -15
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/export/index.js +52 -32
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/index.js +116 -99
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/index.js +42 -22
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/index.js +20 -0
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js +50 -27
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js +25 -5
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/index.js +20 -0
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/index.js +20 -0
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js +20 -0
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/reorder/index.js +48 -28
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/selection/index.js +51 -31
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js +20 -0
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/index.js +76 -53
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js +20 -0
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js +20 -0
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +1 -1
- package/umd/grid.all.umd.js +25 -25
- 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 +3 -3
- package/umd/plugins/filtering.umd.js.map +1 -1
- package/umd/plugins/master-detail.umd.js +2 -2
- package/umd/plugins/master-detail.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/tree.umd.js +2 -2
- package/umd/plugins/tree.umd.js.map +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
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=`
|
|
2
2
|
.tbw-filter-panel {
|
|
3
3
|
position: fixed;
|
|
4
4
|
background: var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
.tbw-filter-clear-btn:hover {
|
|
140
140
|
background: var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)));
|
|
141
141
|
}
|
|
142
|
-
`;class
|
|
142
|
+
`;class m extends M.BaseGridPlugin{name="filtering";version="1.0.0";get defaultConfig(){return{enabled:!0,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(){if(!this.config.enabled)return;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=B,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=`
|
|
143
143
|
.header-cell.filtered::before {
|
|
144
144
|
content: '';
|
|
145
145
|
position: absolute;
|
|
@@ -171,5 +171,5 @@
|
|
|
171
171
|
.tbw-filter-btn.active {
|
|
172
172
|
color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));
|
|
173
173
|
}
|
|
174
|
-
`}
|
|
174
|
+
`}g.FilteringPlugin=m,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})}));
|
|
175
175
|
//# 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 { BaseGridPlugin, type GridElement } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ColumnState } from '../../core/types';\nimport { computeVirtualWindow, shouldBypassVirtualization } from '../../core/internal/virtualization';\nimport { computeFilterCacheKey, filterRows, getUniqueValues } from './filter-model';\nimport type { FilterChangeDetail, FilterConfig, FilterModel, FilterPanelParams } from './types';\n\n/** Global styles for filter panel (rendered in document.body) */\nconst filterPanelStyles = `\n.tbw-filter-panel {\n position: fixed;\n background: var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));\n color: var(--tbw-filter-panel-fg, var(--tbw-color-fg, light-dark(#222222, #eeeeee)));\n border: 1px solid var(--tbw-filter-panel-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n border-radius: var(--tbw-filter-panel-radius, var(--tbw-border-radius, 4px));\n box-shadow: 0 4px 16px var(--tbw-filter-panel-shadow, var(--tbw-color-shadow, light-dark(rgba(0,0,0,0.1), rgba(0,0,0,0.3))));\n padding: 12px;\n z-index: 10000;\n min-width: 200px;\n max-width: 280px;\n max-height: 350px;\n display: flex;\n flex-direction: column;\n font-family: var(--tbw-font-family, system-ui, sans-serif);\n font-size: var(--tbw-font-size, 13px);\n}\n\n.tbw-filter-search {\n margin-bottom: 8px;\n}\n\n.tbw-filter-search-input {\n width: 100%;\n padding: 6px 10px;\n background: var(--tbw-filter-input-bg, var(--tbw-color-bg, transparent));\n color: inherit;\n border: 1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n border-radius: var(--tbw-filter-input-radius, 4px);\n font-size: inherit;\n box-sizing: border-box;\n}\n\n.tbw-filter-search-input:focus {\n outline: none;\n border-color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n box-shadow: 0 0 0 2px rgba(from var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6)) r g b / 15%);\n}\n\n.tbw-filter-actions {\n display: flex;\n padding: 4px 2px;\n margin-bottom: 8px;\n border-bottom: 1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n}\n\n.tbw-filter-action-btn {\n background: transparent;\n border: none;\n color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n cursor: pointer;\n font-size: 12px;\n padding: 2px 0;\n}\n\n.tbw-filter-action-btn:hover {\n text-decoration: underline;\n}\n\n.tbw-filter-values {\n flex: 1;\n overflow-y: auto;\n margin-bottom: 8px;\n max-height: 180px;\n position: relative;\n}\n\n.tbw-filter-values-spacer {\n width: 1px;\n}\n\n.tbw-filter-values-content {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n}\n\n.tbw-filter-value-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 2px;\n cursor: pointer;\n border-radius: 3px;\n}\n\n.tbw-filter-value-item:hover {\n background: var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)));\n}\n\n.tbw-filter-checkbox {\n margin: 0;\n cursor: pointer;\n accent-color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n}\n\n.tbw-filter-no-match {\n color: var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));\n padding: 8px 0;\n text-align: center;\n font-style: italic;\n}\n\n.tbw-filter-buttons {\n display: flex;\n gap: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n}\n\n.tbw-filter-apply-btn {\n flex: 1;\n padding: 6px 12px;\n background: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n color: var(--tbw-filter-accent-fg, var(--tbw-color-accent-fg, light-dark(#ffffff, #000000)));\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n}\n\n.tbw-filter-apply-btn:hover {\n filter: brightness(0.9);\n}\n\n.tbw-filter-clear-btn {\n flex: 1;\n padding: 6px 12px;\n background: transparent;\n color: var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));\n border: 1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n}\n\n.tbw-filter-clear-btn:hover {\n background: var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)));\n}\n`;\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 enabled: true,\n debounceMs: 300,\n caseSensitive: false,\n trimInput: true,\n useWorker: true,\n };\n }\n\n // ===== 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 documentClickHandler: ((e: MouseEvent) => void) | null = null;\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\n // ===== 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 this.removeDocumentClickHandler();\n }\n\n // ===== 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 if (!this.config.enabled) return;\n\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\n // ===== 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\n // ===== 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 // Add global click handler to close on outside click\n const handler = (e: MouseEvent) => {\n if (!panel.contains(e.target as Node) && e.target !== buttonEl) {\n this.closeFilterPanel();\n }\n };\n this.documentClickHandler = handler;\n // Defer to next tick to avoid immediate close\n setTimeout(() => {\n document.addEventListener('click', handler);\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 this.removeDocumentClickHandler();\n }\n\n /**\n * Remove the document click handler\n */\n private removeDocumentClickHandler(): void {\n if (this.documentClickHandler) {\n document.removeEventListener('click', this.documentClickHandler);\n this.documentClickHandler = null;\n }\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\n // ===== 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\n // ===== Styles =====\n\n override readonly styles = `\n .header-cell.filtered::before {\n content: '';\n position: absolute;\n top: 4px;\n right: 4px;\n width: 6px;\n height: 6px;\n background: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n border-radius: 50%;\n }\n .tbw-filter-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 2px;\n margin-left: 4px;\n opacity: 0.4;\n transition: opacity 0.15s;\n color: inherit;\n vertical-align: middle;\n }\n .tbw-filter-btn:hover,\n .tbw-filter-btn.active {\n opacity: 1;\n }\n .tbw-filter-btn.active {\n color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n }\n `;\n}\n"],"names":["matchesFilter","row","filter","caseSensitive","rawValue","stringValue","compareValue","filterValue","filterRows","rows","filters","f","computeFilterCacheKey","getUniqueValues","field","values","value","a","b","filterPanelStyles","FilteringPlugin","BaseGridPlugin","grid","filterList","newCacheKey","result","shadowRoot","cell","colIndex","col","filterBtn","e","style","column","buttonEl","panel","uniqueValues","excludedSet","currentSearchText","params","excluded","operator","valueTo","usedCustomRenderer","handler","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"],"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 type { FilterChangeDetail, FilterConfig, FilterModel, FilterPanelParams } from './types';\n\n/** Global styles for filter panel (rendered in document.body) */\nconst filterPanelStyles = `\n.tbw-filter-panel {\n position: fixed;\n background: var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));\n color: var(--tbw-filter-panel-fg, var(--tbw-color-fg, light-dark(#222222, #eeeeee)));\n border: 1px solid var(--tbw-filter-panel-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n border-radius: var(--tbw-filter-panel-radius, var(--tbw-border-radius, 4px));\n box-shadow: 0 4px 16px var(--tbw-filter-panel-shadow, var(--tbw-color-shadow, light-dark(rgba(0,0,0,0.1), rgba(0,0,0,0.3))));\n padding: 12px;\n z-index: 10000;\n min-width: 200px;\n max-width: 280px;\n max-height: 350px;\n display: flex;\n flex-direction: column;\n font-family: var(--tbw-font-family, system-ui, sans-serif);\n font-size: var(--tbw-font-size, 13px);\n}\n\n.tbw-filter-search {\n margin-bottom: 8px;\n}\n\n.tbw-filter-search-input {\n width: 100%;\n padding: 6px 10px;\n background: var(--tbw-filter-input-bg, var(--tbw-color-bg, transparent));\n color: inherit;\n border: 1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n border-radius: var(--tbw-filter-input-radius, 4px);\n font-size: inherit;\n box-sizing: border-box;\n}\n\n.tbw-filter-search-input:focus {\n outline: none;\n border-color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n box-shadow: 0 0 0 2px rgba(from var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6)) r g b / 15%);\n}\n\n.tbw-filter-actions {\n display: flex;\n padding: 4px 2px;\n margin-bottom: 8px;\n border-bottom: 1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n}\n\n.tbw-filter-action-btn {\n background: transparent;\n border: none;\n color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n cursor: pointer;\n font-size: 12px;\n padding: 2px 0;\n}\n\n.tbw-filter-action-btn:hover {\n text-decoration: underline;\n}\n\n.tbw-filter-values {\n flex: 1;\n overflow-y: auto;\n margin-bottom: 8px;\n max-height: 180px;\n position: relative;\n}\n\n.tbw-filter-values-spacer {\n width: 1px;\n}\n\n.tbw-filter-values-content {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n}\n\n.tbw-filter-value-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 2px;\n cursor: pointer;\n border-radius: 3px;\n}\n\n.tbw-filter-value-item:hover {\n background: var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)));\n}\n\n.tbw-filter-checkbox {\n margin: 0;\n cursor: pointer;\n accent-color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n}\n\n.tbw-filter-no-match {\n color: var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));\n padding: 8px 0;\n text-align: center;\n font-style: italic;\n}\n\n.tbw-filter-buttons {\n display: flex;\n gap: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n}\n\n.tbw-filter-apply-btn {\n flex: 1;\n padding: 6px 12px;\n background: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n color: var(--tbw-filter-accent-fg, var(--tbw-color-accent-fg, light-dark(#ffffff, #000000)));\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n}\n\n.tbw-filter-apply-btn:hover {\n filter: brightness(0.9);\n}\n\n.tbw-filter-clear-btn {\n flex: 1;\n padding: 6px 12px;\n background: transparent;\n color: var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));\n border: 1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n}\n\n.tbw-filter-clear-btn:hover {\n background: var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)));\n}\n`;\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 enabled: true,\n debounceMs: 300,\n caseSensitive: false,\n trimInput: true,\n useWorker: true,\n };\n }\n\n // ===== 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\n // ===== 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\n // ===== 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 if (!this.config.enabled) return;\n\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\n // ===== 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\n // ===== 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\n // ===== 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\n // ===== Styles =====\n\n override readonly styles = `\n .header-cell.filtered::before {\n content: '';\n position: absolute;\n top: 4px;\n right: 4px;\n width: 6px;\n height: 6px;\n background: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n border-radius: 50%;\n }\n .tbw-filter-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 2px;\n margin-left: 4px;\n opacity: 0.4;\n transition: opacity 0.15s;\n color: inherit;\n vertical-align: middle;\n }\n .tbw-filter-btn:hover,\n .tbw-filter-btn.active {\n opacity: 1;\n }\n .tbw-filter-btn.active {\n color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n }\n `;\n}\n"],"names":["matchesFilter","row","filter","caseSensitive","rawValue","stringValue","compareValue","filterValue","filterRows","rows","filters","f","computeFilterCacheKey","getUniqueValues","field","values","value","a","b","filterPanelStyles","FilteringPlugin","BaseGridPlugin","grid","filterList","newCacheKey","result","shadowRoot","cell","colIndex","col","filterBtn","e","style","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"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(d,p){typeof exports=="object"&&typeof module<"u"?p(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],p):(d=typeof globalThis<"u"?globalThis:d||self,p(d.TbwGridPlugin_masterDetail={},d.TbwGrid))})(this,(function(d,p){"use strict";function f(
|
|
1
|
+
(function(d,p){typeof exports=="object"&&typeof module<"u"?p(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],p):(d=typeof globalThis<"u"?globalThis:d||self,p(d.TbwGridPlugin_masterDetail={},d.TbwGrid))})(this,(function(d,p){"use strict";function f(r,e){const t=new Set(r);return t.has(e)?t.delete(e):t.add(e),t}function x(r,e){const t=new Set(r);return t.add(e),t}function m(r,e){const t=new Set(r);return t.delete(e),t}function R(r,e){return r.has(e)}function b(r,e,t,o){const n=document.createElement("div");n.className="master-detail-row",n.setAttribute("data-detail-for",String(e)),n.setAttribute("role","row");const s=document.createElement("div");s.className="master-detail-cell",s.setAttribute("role","cell"),s.style.gridColumn=`1 / ${o+1}`;const i=t(r,e);return typeof i=="string"?s.innerHTML=i:i instanceof HTMLElement&&s.appendChild(i),n.appendChild(s),n}class E extends p.BaseGridPlugin{name="masterDetail";version="1.0.0";get defaultConfig(){return{enabled:!0,detailHeight:"auto",expandOnRowClick:!1,collapseOnClickOutside:!1,showExpandColumn:!0}}expandedRows=new Set;detailElements=new Map;detach(){this.expandedRows.clear(),this.detailElements.clear()}processColumns(e){if(!this.config.detailRenderer)return[...e];const t=[...e];if(t.length>0){const o={...t[0]},n=o.viewRenderer;if(n?.__masterDetailWrapped)return t;const s=i=>{const{value:l,row:a}=i,g=this.expandedRows.has(a),u=document.createElement("span");u.className="master-detail-cell-wrapper";const c=document.createElement("span");c.className="master-detail-toggle",c.textContent=g?"▼":"▶",c.setAttribute("aria-expanded",String(g)),c.setAttribute("aria-label",g?"Collapse details":"Expand details"),c.addEventListener("click",w=>{w.stopPropagation();const C=this.rows.indexOf(a);this.expandedRows=f(this.expandedRows,a),this.emit("detail-expand",{rowIndex:C,row:a,expanded:this.expandedRows.has(a)}),this.requestRender()}),u.appendChild(c);const h=document.createElement("span");if(n){const w=n(i);w instanceof Node?h.appendChild(w):h.textContent=String(w??l??"")}else h.textContent=String(l??"");return u.appendChild(h),u};s.__masterDetailWrapped=!0,o.viewRenderer=s,t[0]=o}return t}onRowClick(e){if(!(!this.config.expandOnRowClick||!this.config.detailRenderer))return this.expandedRows=f(this.expandedRows,e.row),this.emit("detail-expand",{rowIndex:e.rowIndex,row:e.row,expanded:this.expandedRows.has(e.row)}),this.requestRender(),!1}afterRender(){if(!this.config.detailRenderer)return;const e=this.shadowRoot?.querySelector(".rows");if(!e)return;e.querySelectorAll(".master-detail-row").forEach(n=>n.remove()),this.detailElements.clear();const t=e.querySelectorAll(".data-grid-row"),o=this.columns.length;for(const n of t){const s=n.querySelector(".cell[data-row]"),i=s?parseInt(s.getAttribute("data-row")??"-1",10):-1;if(i<0)continue;const l=this.rows[i];if(!l||!this.expandedRows.has(l))continue;const a=b(l,i,this.config.detailRenderer,o);typeof this.config.detailHeight=="number"&&(a.style.height=`${this.config.detailHeight}px`),n.appendChild(a),this.detailElements.set(l,a)}}expand(e){const t=this.rows[e];t&&(this.expandedRows=x(this.expandedRows,t),this.requestRender())}collapse(e){const t=this.rows[e];t&&(this.expandedRows=m(this.expandedRows,t),this.requestRender())}toggle(e){const t=this.rows[e];t&&(this.expandedRows=f(this.expandedRows,t),this.requestRender())}isExpanded(e){const t=this.rows[e];return t?R(this.expandedRows,t):!1}expandAll(){for(const e of this.rows)this.expandedRows.add(e);this.requestRender()}collapseAll(){this.expandedRows.clear(),this.requestRender()}getExpandedRows(){const e=[];for(const t of this.expandedRows){const o=this.rows.indexOf(t);o>=0&&e.push(o)}return e}getDetailElement(e){const t=this.rows[e];return t?this.detailElements.get(t):void 0}styles=`
|
|
2
2
|
.master-detail-cell-wrapper {
|
|
3
3
|
display: flex;
|
|
4
4
|
align-items: center;
|
|
@@ -23,5 +23,5 @@
|
|
|
23
23
|
padding: 16px;
|
|
24
24
|
overflow: auto;
|
|
25
25
|
}
|
|
26
|
-
`}d.MasterDetailPlugin=
|
|
26
|
+
`}d.MasterDetailPlugin=E,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
27
27
|
//# sourceMappingURL=master-detail.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"master-detail.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/master-detail/master-detail.ts","../../../../../libs/grid/src/lib/plugins/master-detail/MasterDetailPlugin.ts"],"sourcesContent":["/**\n * Master/Detail Core Logic\n *\n * Pure functions for managing detail row expansion state.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// Uses `any` for maximum flexibility with user-defined row types.\n\n/**\n * Toggle the expansion state of a detail row.\n * Returns a new Set with the updated state.\n */\nexport function toggleDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n if (newExpanded.has(row)) {\n newExpanded.delete(row);\n } else {\n newExpanded.add(row);\n }\n return newExpanded;\n}\n\n/**\n * Expand a detail row.\n * Returns a new Set with the row added.\n */\nexport function expandDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.add(row);\n return newExpanded;\n}\n\n/**\n * Collapse a detail row.\n * Returns a new Set with the row removed.\n */\nexport function collapseDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.delete(row);\n return newExpanded;\n}\n\n/**\n * Check if a detail row is expanded.\n */\nexport function isDetailExpanded(expandedRows: Set<object>, row: object): boolean {\n return expandedRows.has(row);\n}\n\n/**\n * Create a detail element for a given row.\n * The element spans all columns and contains the rendered content.\n */\nexport function createDetailElement(\n row: any,\n rowIndex: number,\n renderer: (row: any, rowIndex: number) => HTMLElement | string,\n columnCount: number\n): HTMLElement {\n const detailRow = document.createElement('div');\n detailRow.className = 'master-detail-row';\n detailRow.setAttribute('data-detail-for', String(rowIndex));\n detailRow.setAttribute('role', 'row');\n\n const detailCell = document.createElement('div');\n detailCell.className = 'master-detail-cell';\n detailCell.setAttribute('role', 'cell');\n detailCell.style.gridColumn = `1 / ${columnCount + 1}`;\n\n const content = renderer(row, rowIndex);\n if (typeof content === 'string') {\n detailCell.innerHTML = content;\n } else if (content instanceof HTMLElement) {\n detailCell.appendChild(content);\n }\n\n detailRow.appendChild(detailCell);\n return detailRow;\n}\n","/**\n * Master/Detail Plugin (Class-based)\n *\n * Enables expandable detail rows showing additional content for each row.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { BaseGridPlugin, RowClickEvent } from '../../core/plugin/base-plugin';\nimport {\n collapseDetailRow,\n createDetailElement,\n expandDetailRow,\n isDetailExpanded,\n toggleDetailRow,\n} from './master-detail';\nimport type { DetailExpandDetail, MasterDetailConfig } from './types';\n\n/**\n * Master/Detail Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new MasterDetailPlugin({\n * enabled: true,\n * detailRenderer: (row) => `<div>Details for ${row.name}</div>`,\n * expandOnRowClick: true,\n * })\n * ```\n */\nexport class MasterDetailPlugin extends BaseGridPlugin<MasterDetailConfig> {\n readonly name = 'masterDetail';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<MasterDetailConfig> {\n return {\n enabled: true,\n detailHeight: 'auto',\n expandOnRowClick: false,\n collapseOnClickOutside: false,\n showExpandColumn: true,\n };\n }\n\n // ===== Internal State =====\n private expandedRows: Set<any> = new Set();\n private detailElements: Map<any, HTMLElement> = new Map();\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.expandedRows.clear();\n this.detailElements.clear();\n }\n\n // ===== Hooks =====\n\n override processColumns(\n columns: readonly import('../../core/types').ColumnConfig[]\n ): import('../../core/types').ColumnConfig[] {\n if (!this.config.detailRenderer) {\n return [...columns];\n }\n\n // Wrap first column's renderer to add expand/collapse toggle\n const cols = [...columns];\n if (cols.length > 0) {\n const firstCol = { ...cols[0] };\n const originalRenderer = firstCol.viewRenderer;\n\n firstCol.viewRenderer = (renderCtx) => {\n const { value, row } = renderCtx;\n const isExpanded = this.expandedRows.has(row);\n\n const container = document.createElement('span');\n container.className = 'master-detail-cell-wrapper';\n\n // Expand/collapse toggle icon\n const toggle = document.createElement('span');\n toggle.className = 'master-detail-toggle';\n toggle.textContent = isExpanded ? '▼' : '▶';\n toggle.setAttribute('aria-expanded', String(isExpanded));\n toggle.setAttribute('aria-label', isExpanded ? 'Collapse details' : 'Expand details');\n toggle.addEventListener('click', (e) => {\n e.stopPropagation();\n const rowIndex = this.rows.indexOf(row);\n this.expandedRows = toggleDetailRow(this.expandedRows, row);\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex,\n row,\n expanded: this.expandedRows.has(row),\n });\n this.requestRender();\n });\n container.appendChild(toggle);\n\n // Cell content\n const content = document.createElement('span');\n if (originalRenderer) {\n const rendered = originalRenderer(renderCtx);\n if (rendered instanceof Node) {\n content.appendChild(rendered);\n } else {\n content.textContent = String(rendered ?? value ?? '');\n }\n } else {\n content.textContent = String(value ?? '');\n }\n container.appendChild(content);\n\n return container;\n };\n\n cols[0] = firstCol;\n }\n\n return cols;\n }\n\n override onRowClick(event: RowClickEvent): boolean | void {\n if (!this.config.expandOnRowClick || !this.config.detailRenderer) return;\n\n this.expandedRows = toggleDetailRow(this.expandedRows, event.row);\n\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex: event.rowIndex,\n row: event.row,\n expanded: this.expandedRows.has(event.row),\n });\n\n this.requestRender();\n return false;\n }\n\n override afterRender(): void {\n if (!this.config.detailRenderer) return;\n\n const body = this.shadowRoot?.querySelector('.rows');\n if (!body) return;\n\n // Remove old detail rows\n body.querySelectorAll('.master-detail-row').forEach((el) => el.remove());\n this.detailElements.clear();\n\n // Insert detail rows as last child of expanded row elements\n const dataRows = body.querySelectorAll('.data-grid-row');\n const columnCount = this.columns.length;\n\n for (const rowEl of dataRows) {\n const firstCell = rowEl.querySelector('.cell[data-row]');\n const rowIndex = firstCell ? parseInt(firstCell.getAttribute('data-row') ?? '-1', 10) : -1;\n if (rowIndex < 0) continue;\n\n const row = this.rows[rowIndex];\n if (!row || !this.expandedRows.has(row)) continue;\n\n const detailEl = createDetailElement(row, rowIndex, this.config.detailRenderer, columnCount);\n\n if (typeof this.config.detailHeight === 'number') {\n detailEl.style.height = `${this.config.detailHeight}px`;\n }\n\n rowEl.appendChild(detailEl);\n this.detailElements.set(row, detailEl);\n }\n }\n\n // ===== Public API =====\n\n /**\n * Expand the detail row at the given index.\n * @param rowIndex - Index of the row to expand\n */\n expand(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = expandDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Collapse the detail row at the given index.\n * @param rowIndex - Index of the row to collapse\n */\n collapse(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = collapseDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Toggle the detail row at the given index.\n * @param rowIndex - Index of the row to toggle\n */\n toggle(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = toggleDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Check if the detail row at the given index is expanded.\n * @param rowIndex - Index of the row to check\n * @returns Whether the detail row is expanded\n */\n isExpanded(rowIndex: number): boolean {\n const row = this.rows[rowIndex];\n return row ? isDetailExpanded(this.expandedRows, row) : false;\n }\n\n /**\n * Expand all detail rows.\n */\n expandAll(): void {\n for (const row of this.rows) {\n this.expandedRows.add(row);\n }\n this.requestRender();\n }\n\n /**\n * Collapse all detail rows.\n */\n collapseAll(): void {\n this.expandedRows.clear();\n this.requestRender();\n }\n\n /**\n * Get the indices of all expanded rows.\n * @returns Array of row indices that are expanded\n */\n getExpandedRows(): number[] {\n const indices: number[] = [];\n for (const row of this.expandedRows) {\n const idx = this.rows.indexOf(row);\n if (idx >= 0) indices.push(idx);\n }\n return indices;\n }\n\n /**\n * Get the detail element for a specific row.\n * @param rowIndex - Index of the row\n * @returns The detail HTMLElement or undefined\n */\n getDetailElement(rowIndex: number): HTMLElement | undefined {\n const row = this.rows[rowIndex];\n return row ? this.detailElements.get(row) : undefined;\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n .master-detail-cell-wrapper {\n display: flex;\n align-items: center;\n gap: 4px;\n }\n .master-detail-toggle {\n cursor: pointer;\n font-size: 10px;\n opacity: 0.7;\n user-select: none;\n }\n .master-detail-toggle:hover {\n opacity: 1;\n }\n .master-detail-row {\n grid-column: 1 / -1;\n display: grid;\n background: var(--tbw-master-detail-bg, var(--tbw-color-row-alt));\n border-bottom: 1px solid var(--tbw-master-detail-border, var(--tbw-color-border));\n }\n .master-detail-cell {\n padding: 16px;\n overflow: auto;\n }\n `;\n}\n"],"names":["toggleDetailRow","expandedRows","row","newExpanded","expandDetailRow","collapseDetailRow","isDetailExpanded","createDetailElement","rowIndex","renderer","columnCount","detailRow","detailCell","content","MasterDetailPlugin","BaseGridPlugin","columns","cols","firstCol","originalRenderer","renderCtx","value","isExpanded","container","toggle","e","rendered","event","body","el","dataRows","rowEl","firstCell","detailEl","indices","idx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"master-detail.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/master-detail/master-detail.ts","../../../../../libs/grid/src/lib/plugins/master-detail/MasterDetailPlugin.ts"],"sourcesContent":["/**\n * Master/Detail Core Logic\n *\n * Pure functions for managing detail row expansion state.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// Uses `any` for maximum flexibility with user-defined row types.\n\n/**\n * Toggle the expansion state of a detail row.\n * Returns a new Set with the updated state.\n */\nexport function toggleDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n if (newExpanded.has(row)) {\n newExpanded.delete(row);\n } else {\n newExpanded.add(row);\n }\n return newExpanded;\n}\n\n/**\n * Expand a detail row.\n * Returns a new Set with the row added.\n */\nexport function expandDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.add(row);\n return newExpanded;\n}\n\n/**\n * Collapse a detail row.\n * Returns a new Set with the row removed.\n */\nexport function collapseDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.delete(row);\n return newExpanded;\n}\n\n/**\n * Check if a detail row is expanded.\n */\nexport function isDetailExpanded(expandedRows: Set<object>, row: object): boolean {\n return expandedRows.has(row);\n}\n\n/**\n * Create a detail element for a given row.\n * The element spans all columns and contains the rendered content.\n */\nexport function createDetailElement(\n row: any,\n rowIndex: number,\n renderer: (row: any, rowIndex: number) => HTMLElement | string,\n columnCount: number\n): HTMLElement {\n const detailRow = document.createElement('div');\n detailRow.className = 'master-detail-row';\n detailRow.setAttribute('data-detail-for', String(rowIndex));\n detailRow.setAttribute('role', 'row');\n\n const detailCell = document.createElement('div');\n detailCell.className = 'master-detail-cell';\n detailCell.setAttribute('role', 'cell');\n detailCell.style.gridColumn = `1 / ${columnCount + 1}`;\n\n const content = renderer(row, rowIndex);\n if (typeof content === 'string') {\n detailCell.innerHTML = content;\n } else if (content instanceof HTMLElement) {\n detailCell.appendChild(content);\n }\n\n detailRow.appendChild(detailCell);\n return detailRow;\n}\n","/**\n * Master/Detail Plugin (Class-based)\n *\n * Enables expandable detail rows showing additional content for each row.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { BaseGridPlugin, RowClickEvent } from '../../core/plugin/base-plugin';\nimport {\n collapseDetailRow,\n createDetailElement,\n expandDetailRow,\n isDetailExpanded,\n toggleDetailRow,\n} from './master-detail';\nimport type { DetailExpandDetail, MasterDetailConfig } from './types';\n\n/**\n * Master/Detail Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new MasterDetailPlugin({\n * enabled: true,\n * detailRenderer: (row) => `<div>Details for ${row.name}</div>`,\n * expandOnRowClick: true,\n * })\n * ```\n */\nexport class MasterDetailPlugin extends BaseGridPlugin<MasterDetailConfig> {\n readonly name = 'masterDetail';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<MasterDetailConfig> {\n return {\n enabled: true,\n detailHeight: 'auto',\n expandOnRowClick: false,\n collapseOnClickOutside: false,\n showExpandColumn: true,\n };\n }\n\n // ===== Internal State =====\n private expandedRows: Set<any> = new Set();\n private detailElements: Map<any, HTMLElement> = new Map();\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.expandedRows.clear();\n this.detailElements.clear();\n }\n\n // ===== Hooks =====\n\n override processColumns(\n columns: readonly import('../../core/types').ColumnConfig[]\n ): import('../../core/types').ColumnConfig[] {\n if (!this.config.detailRenderer) {\n return [...columns];\n }\n\n // Wrap first column's renderer to add expand/collapse toggle\n const cols = [...columns];\n if (cols.length > 0) {\n const firstCol = { ...cols[0] };\n const originalRenderer = firstCol.viewRenderer;\n\n // Skip if already wrapped by this plugin (prevents double-wrapping on re-render)\n if ((originalRenderer as any)?.__masterDetailWrapped) {\n return cols;\n }\n\n const wrappedRenderer = (renderCtx: Parameters<NonNullable<typeof originalRenderer>>[0]) => {\n const { value, row } = renderCtx;\n const isExpanded = this.expandedRows.has(row);\n\n const container = document.createElement('span');\n container.className = 'master-detail-cell-wrapper';\n\n // Expand/collapse toggle icon\n const toggle = document.createElement('span');\n toggle.className = 'master-detail-toggle';\n toggle.textContent = isExpanded ? '▼' : '▶';\n toggle.setAttribute('aria-expanded', String(isExpanded));\n toggle.setAttribute('aria-label', isExpanded ? 'Collapse details' : 'Expand details');\n toggle.addEventListener('click', (e) => {\n e.stopPropagation();\n const rowIndex = this.rows.indexOf(row);\n this.expandedRows = toggleDetailRow(this.expandedRows, row);\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex,\n row,\n expanded: this.expandedRows.has(row),\n });\n this.requestRender();\n });\n container.appendChild(toggle);\n\n // Cell content\n const content = document.createElement('span');\n if (originalRenderer) {\n const rendered = originalRenderer(renderCtx);\n if (rendered instanceof Node) {\n content.appendChild(rendered);\n } else {\n content.textContent = String(rendered ?? value ?? '');\n }\n } else {\n content.textContent = String(value ?? '');\n }\n container.appendChild(content);\n\n return container;\n };\n\n // Mark renderer as wrapped to prevent double-wrapping\n (wrappedRenderer as any).__masterDetailWrapped = true;\n firstCol.viewRenderer = wrappedRenderer;\n\n cols[0] = firstCol;\n }\n\n return cols;\n }\n\n override onRowClick(event: RowClickEvent): boolean | void {\n if (!this.config.expandOnRowClick || !this.config.detailRenderer) return;\n\n this.expandedRows = toggleDetailRow(this.expandedRows, event.row);\n\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex: event.rowIndex,\n row: event.row,\n expanded: this.expandedRows.has(event.row),\n });\n\n this.requestRender();\n return false;\n }\n\n override afterRender(): void {\n if (!this.config.detailRenderer) return;\n\n const body = this.shadowRoot?.querySelector('.rows');\n if (!body) return;\n\n // Remove old detail rows\n body.querySelectorAll('.master-detail-row').forEach((el) => el.remove());\n this.detailElements.clear();\n\n // Insert detail rows as last child of expanded row elements\n const dataRows = body.querySelectorAll('.data-grid-row');\n const columnCount = this.columns.length;\n\n for (const rowEl of dataRows) {\n const firstCell = rowEl.querySelector('.cell[data-row]');\n const rowIndex = firstCell ? parseInt(firstCell.getAttribute('data-row') ?? '-1', 10) : -1;\n if (rowIndex < 0) continue;\n\n const row = this.rows[rowIndex];\n if (!row || !this.expandedRows.has(row)) continue;\n\n const detailEl = createDetailElement(row, rowIndex, this.config.detailRenderer, columnCount);\n\n if (typeof this.config.detailHeight === 'number') {\n detailEl.style.height = `${this.config.detailHeight}px`;\n }\n\n rowEl.appendChild(detailEl);\n this.detailElements.set(row, detailEl);\n }\n }\n\n // ===== Public API =====\n\n /**\n * Expand the detail row at the given index.\n * @param rowIndex - Index of the row to expand\n */\n expand(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = expandDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Collapse the detail row at the given index.\n * @param rowIndex - Index of the row to collapse\n */\n collapse(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = collapseDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Toggle the detail row at the given index.\n * @param rowIndex - Index of the row to toggle\n */\n toggle(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = toggleDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Check if the detail row at the given index is expanded.\n * @param rowIndex - Index of the row to check\n * @returns Whether the detail row is expanded\n */\n isExpanded(rowIndex: number): boolean {\n const row = this.rows[rowIndex];\n return row ? isDetailExpanded(this.expandedRows, row) : false;\n }\n\n /**\n * Expand all detail rows.\n */\n expandAll(): void {\n for (const row of this.rows) {\n this.expandedRows.add(row);\n }\n this.requestRender();\n }\n\n /**\n * Collapse all detail rows.\n */\n collapseAll(): void {\n this.expandedRows.clear();\n this.requestRender();\n }\n\n /**\n * Get the indices of all expanded rows.\n * @returns Array of row indices that are expanded\n */\n getExpandedRows(): number[] {\n const indices: number[] = [];\n for (const row of this.expandedRows) {\n const idx = this.rows.indexOf(row);\n if (idx >= 0) indices.push(idx);\n }\n return indices;\n }\n\n /**\n * Get the detail element for a specific row.\n * @param rowIndex - Index of the row\n * @returns The detail HTMLElement or undefined\n */\n getDetailElement(rowIndex: number): HTMLElement | undefined {\n const row = this.rows[rowIndex];\n return row ? this.detailElements.get(row) : undefined;\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n .master-detail-cell-wrapper {\n display: flex;\n align-items: center;\n gap: 4px;\n }\n .master-detail-toggle {\n cursor: pointer;\n font-size: 10px;\n opacity: 0.7;\n user-select: none;\n }\n .master-detail-toggle:hover {\n opacity: 1;\n }\n .master-detail-row {\n grid-column: 1 / -1;\n display: grid;\n background: var(--tbw-master-detail-bg, var(--tbw-color-row-alt));\n border-bottom: 1px solid var(--tbw-master-detail-border, var(--tbw-color-border));\n }\n .master-detail-cell {\n padding: 16px;\n overflow: auto;\n }\n `;\n}\n"],"names":["toggleDetailRow","expandedRows","row","newExpanded","expandDetailRow","collapseDetailRow","isDetailExpanded","createDetailElement","rowIndex","renderer","columnCount","detailRow","detailCell","content","MasterDetailPlugin","BaseGridPlugin","columns","cols","firstCol","originalRenderer","wrappedRenderer","renderCtx","value","isExpanded","container","toggle","e","rendered","event","body","el","dataRows","rowEl","firstCell","detailEl","indices","idx"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(a,
|
|
1
|
+
(function(a,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],g):(a=typeof globalThis<"u"?globalThis:a||self,g(a.TbwGridPlugin_reorder={},a.TbwGrid))})(this,(function(a,g){"use strict";function m(i){const r=i.sticky;if(r==="left"||r==="right")return!1;const t=i.meta??{},n=t.sticky;return n==="left"||n==="right"?!1:t.lockPosition!==!0&&t.suppressMovable!==!0}function f(i,r,t){if(r===t||r<0||r>=i.length||t<0||t>i.length)return i;const n=[...i],[e]=n.splice(r,1);return n.splice(t,0,e),n}class b extends g.BaseGridPlugin{name="reorder";version="1.0.0";get defaultConfig(){return{enabled:!0,animation:!0,animationDuration:200}}isDragging=!1;draggedField=null;draggedIndex=null;dropIndex=null;attach(r){super.attach(r),r.addEventListener("column-reorder-request",t=>{const n=t.detail;n?.field&&typeof n.toIndex=="number"&&this.moveColumn(n.field,n.toIndex)},{signal:this.disconnectSignal})}detach(){this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null}afterRender(){if(!this.config.enabled)return;const r=this.shadowRoot;if(!r)return;r.querySelectorAll(".header-row > .cell").forEach(n=>{const e=n,o=e.getAttribute("data-field");if(!o)return;const h=this.columns.find(d=>d.field===o);if(!h||!m(h)){e.draggable=!1;return}e.draggable=!0,!e.getAttribute("data-dragstart-bound")&&(e.setAttribute("data-dragstart-bound","true"),e.addEventListener("dragstart",d=>{const s=this.getColumnOrder().indexOf(o);this.isDragging=!0,this.draggedField=o,this.draggedIndex=s,d.dataTransfer&&(d.dataTransfer.effectAllowed="move",d.dataTransfer.setData("text/plain",o)),e.classList.add("dragging")}),e.addEventListener("dragend",()=>{this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null,r.querySelectorAll(".header-row > .cell").forEach(d=>{d.classList.remove("dragging","drop-target","drop-before","drop-after")})}),e.addEventListener("dragover",d=>{if(d.preventDefault(),!this.isDragging||this.draggedField===o)return;const l=e.getBoundingClientRect(),s=l.left+l.width/2,c=this.getColumnOrder().indexOf(o);this.dropIndex=d.clientX<s?c:c+1,e.classList.add("drop-target"),e.classList.toggle("drop-before",d.clientX<s),e.classList.toggle("drop-after",d.clientX>=s)}),e.addEventListener("dragleave",()=>{e.classList.remove("drop-target","drop-before","drop-after")}),e.addEventListener("drop",d=>{d.preventDefault();const l=this.draggedField,s=this.draggedIndex,u=this.dropIndex;if(!this.isDragging||l===null||s===null||u===null)return;const c=u>s?u-1:u,v=this.getColumnOrder(),p=f(v,s,c),O={field:l,fromIndex:s,toIndex:c,columnOrder:p};this.grid.setColumnOrder(p),this.emit("column-move",O),this.grid.requestStateChange?.()}))})}getColumnOrder(){return this.grid.getColumnOrder()}moveColumn(r,t){const n=this.getColumnOrder(),e=n.indexOf(r);if(e===-1)return;const o=f(n,e,t);this.grid.setColumnOrder(o),this.emit("column-move",{field:r,fromIndex:e,toIndex:t,columnOrder:o}),this.grid.requestStateChange?.()}setColumnOrder(r){this.grid.setColumnOrder(r),this.grid.requestStateChange?.()}resetColumnOrder(){const r=this.columns.map(t=>t.field);this.grid.setColumnOrder(r),this.grid.requestStateChange?.()}styles=`
|
|
2
2
|
.header-row > .cell[draggable="true"] {
|
|
3
3
|
cursor: grab;
|
|
4
4
|
position: relative;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reorder.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/reorder/column-drag.ts","../../../../../libs/grid/src/lib/plugins/reorder/ReorderPlugin.ts"],"sourcesContent":["/**\n * Column Reordering Core Logic\n *\n * Pure functions for column drag and reordering operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Check if a column can be moved.\n * Respects lockPosition, suppressMovable, and sticky properties.\n * Sticky (pinned) columns cannot be reordered as they have fixed positions.\n *\n * @param column - The column configuration to check\n * @returns True if the column can be moved\n */\nexport function canMoveColumn<TRow = unknown>(column: ColumnConfig<TRow>): boolean {\n // Check sticky directly on column config (primary location)\n const sticky = (column as ColumnConfig<TRow> & { sticky?: 'left' | 'right' }).sticky;\n if (sticky === 'left' || sticky === 'right') {\n return false;\n }\n\n // Also check meta.sticky for backwards compatibility\n const meta = column.meta ?? {};\n const metaSticky = (meta as { sticky?: 'left' | 'right' }).sticky;\n if (metaSticky === 'left' || metaSticky === 'right') {\n return false;\n }\n\n // Check for lockPosition or suppressMovable properties in the column config\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Move a column from one position to another in the order array.\n *\n * @param columns - Array of field names in current order\n * @param fromIndex - The current index of the column to move\n * @param toIndex - The target index to move the column to\n * @returns New array with updated order\n */\nexport function moveColumn(columns: string[], fromIndex: number, toIndex: number): string[] {\n if (fromIndex === toIndex) return columns;\n if (fromIndex < 0 || fromIndex >= columns.length) return columns;\n if (toIndex < 0 || toIndex > columns.length) return columns;\n\n const result = [...columns];\n const [removed] = result.splice(fromIndex, 1);\n result.splice(toIndex, 0, removed);\n return result;\n}\n\n/**\n * Calculate the drop index based on the current drag position.\n *\n * @param dragX - The current X position of the drag\n * @param headerRect - The bounding rect of the header container\n * @param columnWidths - Array of column widths in order\n * @returns The index where the column should be dropped\n */\nexport function getDropIndex(dragX: number, headerRect: DOMRect, columnWidths: number[]): number {\n let x = headerRect.left;\n\n for (let i = 0; i < columnWidths.length; i++) {\n const mid = x + columnWidths[i] / 2;\n if (dragX < mid) return i;\n x += columnWidths[i];\n }\n\n return columnWidths.length;\n}\n\n/**\n * Reorder columns according to a specified order.\n * Columns not in the order array are appended at the end.\n *\n * @param columns - Array of column configurations\n * @param order - Array of field names specifying the desired order\n * @returns New array of columns in the specified order\n */\nexport function reorderColumns<TRow = unknown>(columns: ColumnConfig<TRow>[], order: string[]): ColumnConfig<TRow>[] {\n const columnMap = new Map<string, ColumnConfig<TRow>>(columns.map((c) => [c.field as string, c]));\n const reordered: ColumnConfig<TRow>[] = [];\n\n // Add columns in specified order\n for (const field of order) {\n const col = columnMap.get(field);\n if (col) {\n reordered.push(col);\n columnMap.delete(field);\n }\n }\n\n // Add any remaining columns not in order\n for (const col of columnMap.values()) {\n reordered.push(col);\n }\n\n return reordered;\n}\n","/**\n * Column Reordering Plugin (Class-based)\n *\n * Provides drag-and-drop column reordering functionality for tbw-grid.\n * Supports keyboard and mouse interactions with visual feedback.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport { canMoveColumn, moveColumn } from './column-drag';\nimport type { ColumnMoveDetail, ReorderConfig } from './types';\n\n/** Extended grid interface with column order methods */\ninterface GridWithColumnOrder {\n setColumnOrder(order: string[]): void;\n getColumnOrder(): string[];\n requestStateChange?: () => void;\n}\n\n/**\n * Column Reordering Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ReorderPlugin({\n * enabled: true,\n * animation: true,\n * animationDuration: 200,\n * })\n * ```\n */\nexport class ReorderPlugin extends BaseGridPlugin<ReorderConfig> {\n readonly name = 'reorder';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<ReorderConfig> {\n return {\n enabled: true,\n animation: true,\n animationDuration: 200,\n };\n }\n\n // ===== Internal State =====\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n private boundReorderRequestHandler: ((e: Event) => void) | null = null;\n\n // ===== Lifecycle =====\n\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for reorder requests from other plugins (e.g., VisibilityPlugin)\n this.boundReorderRequestHandler = (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail?.field && typeof detail.toIndex === 'number') {\n this.moveColumn(detail.field, detail.toIndex);\n }\n };\n (grid as unknown as HTMLElement).addEventListener('column-reorder-request', this.boundReorderRequestHandler);\n }\n\n override detach(): void {\n // Remove event listener\n if (this.boundReorderRequestHandler && this.grid) {\n (this.grid as unknown as HTMLElement).removeEventListener(\n 'column-reorder-request',\n this.boundReorderRequestHandler\n );\n this.boundReorderRequestHandler = null;\n }\n\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n }\n\n // ===== Hooks =====\n // Note: No processColumns hook needed - we directly update the grid's column order\n\n override afterRender(): void {\n if (!this.config.enabled) return;\n\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n const headers = shadowRoot.querySelectorAll('.header-row > .cell');\n\n headers.forEach((header) => {\n const headerEl = header as HTMLElement;\n const field = headerEl.getAttribute('data-field');\n if (!field) return;\n\n const column = this.columns.find((c) => c.field === field);\n if (!column || !canMoveColumn(column)) {\n headerEl.draggable = false;\n return;\n }\n\n headerEl.draggable = true;\n\n // Remove existing listeners to prevent duplicates\n if (headerEl.getAttribute('data-dragstart-bound')) return;\n headerEl.setAttribute('data-dragstart-bound', 'true');\n\n headerEl.addEventListener('dragstart', (e: DragEvent) => {\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = orderIndex;\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n headerEl.classList.add('dragging');\n });\n\n headerEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n\n shadowRoot.querySelectorAll('.header-row > .cell').forEach((h) => {\n h.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\n });\n });\n\n headerEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging || this.draggedField === field) return;\n\n const rect = headerEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.dropIndex = e.clientX < midX ? orderIndex : orderIndex + 1;\n\n headerEl.classList.add('drop-target');\n headerEl.classList.toggle('drop-before', e.clientX < midX);\n headerEl.classList.toggle('drop-after', e.clientX >= midX);\n });\n\n headerEl.addEventListener('dragleave', () => {\n headerEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n headerEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n const draggedField = this.draggedField;\n const draggedIndex = this.draggedIndex;\n const dropIndex = this.dropIndex;\n\n if (!this.isDragging || draggedField === null || draggedIndex === null || dropIndex === null) {\n return;\n }\n\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n const currentOrder = this.getColumnOrder();\n const newOrder = moveColumn(currentOrder, draggedIndex, effectiveToIndex);\n\n const detail: ColumnMoveDetail = {\n field: draggedField,\n fromIndex: draggedIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n };\n\n // Directly update the grid's column order\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(newOrder);\n\n this.emit('column-move', detail);\n // Trigger state change after reorder\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n });\n });\n }\n\n // ===== Public API =====\n\n /**\n * Get the current column order from the grid.\n * @returns Array of field names in display order\n */\n getColumnOrder(): string[] {\n return (this.grid as unknown as GridWithColumnOrder).getColumnOrder();\n }\n\n /**\n * Move a column to a new position.\n * @param field - The field name of the column to move\n * @param toIndex - The target index\n */\n moveColumn(field: string, toIndex: number): void {\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(field);\n if (fromIndex === -1) return;\n\n const newOrder = moveColumn(currentOrder, fromIndex, toIndex);\n\n // Directly update the grid's column order\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(newOrder);\n\n this.emit<ColumnMoveDetail>('column-move', {\n field,\n fromIndex,\n toIndex,\n columnOrder: newOrder,\n });\n\n // Trigger state change after reorder\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n }\n\n /**\n * Set a specific column order.\n * @param order - Array of field names in desired order\n */\n setColumnOrder(order: string[]): void {\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(order);\n // Trigger state change after reorder\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n }\n\n /**\n * Reset column order to the original configuration order.\n */\n resetColumnOrder(): void {\n const originalOrder = this.columns.map((c) => c.field);\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(originalOrder);\n // Trigger state change after reset\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n .header-row > .cell[draggable=\"true\"] {\n cursor: grab;\n position: relative;\n }\n .header-row > .cell.dragging {\n opacity: 0.5;\n cursor: grabbing;\n }\n .header-row > .cell.drop-before::before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\n z-index: 1;\n }\n .header-row > .cell.drop-after::after {\n content: '';\n position: absolute;\n right: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\n z-index: 1;\n }\n `;\n}\n"],"names":["canMoveColumn","column","sticky","meta","metaSticky","moveColumn","columns","fromIndex","toIndex","result","removed","ReorderPlugin","BaseGridPlugin","grid","e","detail","shadowRoot","header","headerEl","field","c","orderIndex","h","rect","midX","draggedField","draggedIndex","dropIndex","effectiveToIndex","currentOrder","newOrder","order","originalOrder"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reorder.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/reorder/column-drag.ts","../../../../../libs/grid/src/lib/plugins/reorder/ReorderPlugin.ts"],"sourcesContent":["/**\n * Column Reordering Core Logic\n *\n * Pure functions for column drag and reordering operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Check if a column can be moved.\n * Respects lockPosition, suppressMovable, and sticky properties.\n * Sticky (pinned) columns cannot be reordered as they have fixed positions.\n *\n * @param column - The column configuration to check\n * @returns True if the column can be moved\n */\nexport function canMoveColumn<TRow = unknown>(column: ColumnConfig<TRow>): boolean {\n // Check sticky directly on column config (primary location)\n const sticky = (column as ColumnConfig<TRow> & { sticky?: 'left' | 'right' }).sticky;\n if (sticky === 'left' || sticky === 'right') {\n return false;\n }\n\n // Also check meta.sticky for backwards compatibility\n const meta = column.meta ?? {};\n const metaSticky = (meta as { sticky?: 'left' | 'right' }).sticky;\n if (metaSticky === 'left' || metaSticky === 'right') {\n return false;\n }\n\n // Check for lockPosition or suppressMovable properties in the column config\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Move a column from one position to another in the order array.\n *\n * @param columns - Array of field names in current order\n * @param fromIndex - The current index of the column to move\n * @param toIndex - The target index to move the column to\n * @returns New array with updated order\n */\nexport function moveColumn(columns: string[], fromIndex: number, toIndex: number): string[] {\n if (fromIndex === toIndex) return columns;\n if (fromIndex < 0 || fromIndex >= columns.length) return columns;\n if (toIndex < 0 || toIndex > columns.length) return columns;\n\n const result = [...columns];\n const [removed] = result.splice(fromIndex, 1);\n result.splice(toIndex, 0, removed);\n return result;\n}\n\n/**\n * Calculate the drop index based on the current drag position.\n *\n * @param dragX - The current X position of the drag\n * @param headerRect - The bounding rect of the header container\n * @param columnWidths - Array of column widths in order\n * @returns The index where the column should be dropped\n */\nexport function getDropIndex(dragX: number, headerRect: DOMRect, columnWidths: number[]): number {\n let x = headerRect.left;\n\n for (let i = 0; i < columnWidths.length; i++) {\n const mid = x + columnWidths[i] / 2;\n if (dragX < mid) return i;\n x += columnWidths[i];\n }\n\n return columnWidths.length;\n}\n\n/**\n * Reorder columns according to a specified order.\n * Columns not in the order array are appended at the end.\n *\n * @param columns - Array of column configurations\n * @param order - Array of field names specifying the desired order\n * @returns New array of columns in the specified order\n */\nexport function reorderColumns<TRow = unknown>(columns: ColumnConfig<TRow>[], order: string[]): ColumnConfig<TRow>[] {\n const columnMap = new Map<string, ColumnConfig<TRow>>(columns.map((c) => [c.field as string, c]));\n const reordered: ColumnConfig<TRow>[] = [];\n\n // Add columns in specified order\n for (const field of order) {\n const col = columnMap.get(field);\n if (col) {\n reordered.push(col);\n columnMap.delete(field);\n }\n }\n\n // Add any remaining columns not in order\n for (const col of columnMap.values()) {\n reordered.push(col);\n }\n\n return reordered;\n}\n","/**\n * Column Reordering Plugin (Class-based)\n *\n * Provides drag-and-drop column reordering functionality for tbw-grid.\n * Supports keyboard and mouse interactions with visual feedback.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport { canMoveColumn, moveColumn } from './column-drag';\nimport type { ColumnMoveDetail, ReorderConfig } from './types';\n\n/** Extended grid interface with column order methods */\ninterface GridWithColumnOrder {\n setColumnOrder(order: string[]): void;\n getColumnOrder(): string[];\n requestStateChange?: () => void;\n}\n\n/**\n * Column Reordering Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ReorderPlugin({\n * enabled: true,\n * animation: true,\n * animationDuration: 200,\n * })\n * ```\n */\nexport class ReorderPlugin extends BaseGridPlugin<ReorderConfig> {\n readonly name = 'reorder';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<ReorderConfig> {\n return {\n enabled: true,\n animation: true,\n animationDuration: 200,\n };\n }\n\n // ===== Internal State =====\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n\n // ===== Lifecycle =====\n\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for reorder requests from other plugins (e.g., VisibilityPlugin)\n // Uses disconnectSignal for automatic cleanup - no need for manual removeEventListener\n (grid as unknown as HTMLElement).addEventListener(\n 'column-reorder-request',\n (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail?.field && typeof detail.toIndex === 'number') {\n this.moveColumn(detail.field, detail.toIndex);\n }\n },\n { signal: this.disconnectSignal }\n );\n }\n\n override detach(): void {\n // Event listeners using eventSignal are automatically cleaned up\n // Just reset internal state\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n }\n\n // ===== Hooks =====\n // Note: No processColumns hook needed - we directly update the grid's column order\n\n override afterRender(): void {\n if (!this.config.enabled) return;\n\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n const headers = shadowRoot.querySelectorAll('.header-row > .cell');\n\n headers.forEach((header) => {\n const headerEl = header as HTMLElement;\n const field = headerEl.getAttribute('data-field');\n if (!field) return;\n\n const column = this.columns.find((c) => c.field === field);\n if (!column || !canMoveColumn(column)) {\n headerEl.draggable = false;\n return;\n }\n\n headerEl.draggable = true;\n\n // Remove existing listeners to prevent duplicates\n if (headerEl.getAttribute('data-dragstart-bound')) return;\n headerEl.setAttribute('data-dragstart-bound', 'true');\n\n headerEl.addEventListener('dragstart', (e: DragEvent) => {\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = orderIndex;\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n headerEl.classList.add('dragging');\n });\n\n headerEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n\n shadowRoot.querySelectorAll('.header-row > .cell').forEach((h) => {\n h.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\n });\n });\n\n headerEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging || this.draggedField === field) return;\n\n const rect = headerEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.dropIndex = e.clientX < midX ? orderIndex : orderIndex + 1;\n\n headerEl.classList.add('drop-target');\n headerEl.classList.toggle('drop-before', e.clientX < midX);\n headerEl.classList.toggle('drop-after', e.clientX >= midX);\n });\n\n headerEl.addEventListener('dragleave', () => {\n headerEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n headerEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n const draggedField = this.draggedField;\n const draggedIndex = this.draggedIndex;\n const dropIndex = this.dropIndex;\n\n if (!this.isDragging || draggedField === null || draggedIndex === null || dropIndex === null) {\n return;\n }\n\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n const currentOrder = this.getColumnOrder();\n const newOrder = moveColumn(currentOrder, draggedIndex, effectiveToIndex);\n\n const detail: ColumnMoveDetail = {\n field: draggedField,\n fromIndex: draggedIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n };\n\n // Directly update the grid's column order\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(newOrder);\n\n this.emit('column-move', detail);\n // Trigger state change after reorder\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n });\n });\n }\n\n // ===== Public API =====\n\n /**\n * Get the current column order from the grid.\n * @returns Array of field names in display order\n */\n getColumnOrder(): string[] {\n return (this.grid as unknown as GridWithColumnOrder).getColumnOrder();\n }\n\n /**\n * Move a column to a new position.\n * @param field - The field name of the column to move\n * @param toIndex - The target index\n */\n moveColumn(field: string, toIndex: number): void {\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(field);\n if (fromIndex === -1) return;\n\n const newOrder = moveColumn(currentOrder, fromIndex, toIndex);\n\n // Directly update the grid's column order\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(newOrder);\n\n this.emit<ColumnMoveDetail>('column-move', {\n field,\n fromIndex,\n toIndex,\n columnOrder: newOrder,\n });\n\n // Trigger state change after reorder\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n }\n\n /**\n * Set a specific column order.\n * @param order - Array of field names in desired order\n */\n setColumnOrder(order: string[]): void {\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(order);\n // Trigger state change after reorder\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n }\n\n /**\n * Reset column order to the original configuration order.\n */\n resetColumnOrder(): void {\n const originalOrder = this.columns.map((c) => c.field);\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(originalOrder);\n // Trigger state change after reset\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n .header-row > .cell[draggable=\"true\"] {\n cursor: grab;\n position: relative;\n }\n .header-row > .cell.dragging {\n opacity: 0.5;\n cursor: grabbing;\n }\n .header-row > .cell.drop-before::before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\n z-index: 1;\n }\n .header-row > .cell.drop-after::after {\n content: '';\n position: absolute;\n right: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\n z-index: 1;\n }\n `;\n}\n"],"names":["canMoveColumn","column","sticky","meta","metaSticky","moveColumn","columns","fromIndex","toIndex","result","removed","ReorderPlugin","BaseGridPlugin","grid","e","detail","shadowRoot","header","headerEl","field","c","orderIndex","h","rect","midX","draggedField","draggedIndex","dropIndex","effectiveToIndex","currentOrder","newOrder","order","originalOrder"],"mappings":"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"}
|
package/umd/plugins/tree.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(u,p){typeof exports=="object"&&typeof module<"u"?p(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],p):(u=typeof globalThis<"u"?globalThis:u||self,p(u.TbwGridPlugin_tree={},u.TbwGrid))})(this,(function(u,p){"use strict";function w(r,e,t){return r.id!==void 0?String(r.id):t?`${t}-${e}`:String(e)}function m(r,e,t,n=null,i=0){const d=e.childrenField??"children",s=[];for(let c=0;c<r.length;c++){const l=r[c],a=w(l,c,n),h=l[d],y=Array.isArray(h)&&h.length>0,g=t.has(a);if(s.push({key:a,data:l,depth:i,hasChildren:y,isExpanded:g,parentKey:n}),y&&g){const R=m(h,e,t,a,i+1);s.push(...R)}}return s}function E(r,e){const t=new Set(r);return t.has(e)?t.delete(e):t.add(e),t}function A(r,e,t=null,n=0){const i=e.childrenField??"children",d=new Set;for(let s=0;s<r.length;s++){const c=r[s],l=w(c,s,t),a=c[i];if(Array.isArray(a)&&a.length>0){d.add(l);const h=A(a,e,l,n+1);for(const y of h)d.add(y)}}return d}function T(){return new Set}function _(r,e,t,n=null,i=0){const d=t.childrenField??"children";for(let s=0;s<r.length;s++){const c=r[s],l=w(c,s,n);if(l===e)return[l];const a=c[d];if(Array.isArray(a)&&a.length>0){const h=_(a,e,t,l,i+1);if(h)return[l,...h]}}return null}function F(r,e,t,n){const i=_(r,e,t);if(!i)return n;const d=new Set(n);for(let s=0;s<i.length-1;s++)d.add(i[s]);return d}function K(r,e="children"){if(!Array.isArray(r)||r.length===0)return!1;for(const t of r)if(t&&Array.isArray(t[e])&&t[e].length>0)return!0;return!1}function C(r){if(!Array.isArray(r)||r.length===0)return null;const e=["children","items","nodes","subRows","nested"];for(const t of r)if(!(!t||typeof t!="object")){for(const n of e)if(Array.isArray(t[n])&&t[n].length>0)return n}return null}function b(r,e="children",t=0){if(!Array.isArray(r)||r.length===0)return t;let n=t;for(const i of r){if(!i)continue;const d=i[e];if(Array.isArray(d)&&d.length>0){const s=b(d,e,t+1);s>n&&(n=s)}}return n}function S(r,e="children"){if(!Array.isArray(r))return 0;let t=0;for(const n of r){if(!n)continue;t++;const i=n[e];Array.isArray(i)&&(t+=S(i,e))}return t}class M extends p.BaseGridPlugin{name="tree";version="1.0.0";get defaultConfig(){return{enabled:!0,childrenField:"children",autoDetect:!0,defaultExpanded:!1,indentWidth:20,showExpandIcons:!0}}expandedKeys=new Set;initialExpansionDone=!1;flattenedRows=[];rowKeyMap=new Map;detach(){this.expandedKeys.clear(),this.initialExpansionDone=!1,this.flattenedRows=[],this.rowKeyMap.clear()}detect(e){if(!this.config.autoDetect)return!1;const t=this.config.childrenField??C(e)??"children";return K(e,t)}processRows(e){const t=this.config.childrenField??"children";if(!K(e,t))return this.flattenedRows=[],this.rowKeyMap.clear(),[...e];this.config.defaultExpanded&&!this.initialExpansionDone&&(this.expandedKeys=A(e,this.config),this.initialExpansionDone=!0),this.flattenedRows=m(e,this.config,this.expandedKeys),this.rowKeyMap.clear();for(const n of this.flattenedRows)this.rowKeyMap.set(n.key,n);return this.flattenedRows.map(n=>({...n.data,__treeKey:n.key,__treeDepth:n.depth,__treeHasChildren:n.hasChildren,__treeExpanded:n.isExpanded}))}processColumns(e){if(this.flattenedRows.length===0)return[...e];const t=this.config.indentWidth??20,n=this.config.showExpandIcons??!0,i=[...e];if(i.length>0){const d={...i[0]},s=d.viewRenderer;if(s?.__treeWrapped)return i;const c=l=>{const{value:a,row:h,column:y}=l,g=h.__treeDepth??0,R=h.__treeHasChildren??!1,k=h.__treeExpanded??!1,f=document.createElement("span");if(f.style.display="flex",f.style.alignItems="center",f.style.paddingLeft=`${g*t}px`,R&&n){const o=document.createElement("span");o.className="tree-toggle",o.textContent=k?"▼":"▶",o.style.cursor="pointer",o.style.marginRight="4px",o.style.fontSize="10px",o.setAttribute("data-tree-key",h.__treeKey),f.appendChild(o)}else if(n){const o=document.createElement("span");o.style.width="14px",o.style.display="inline-block",f.appendChild(o)}const x=document.createElement("span");if(s){const o=s(l);o instanceof Node?x.appendChild(o):x.textContent=String(o??a??"")}else x.textContent=String(a??"");return f.appendChild(x),f};c.__treeWrapped=!0,d.viewRenderer=c,i[0]=d}return i}onCellClick(e){const t=e.originalEvent?.target;if(!t?.classList.contains("tree-toggle"))return!1;const n=t.getAttribute("data-tree-key");if(!n)return!1;const i=this.rowKeyMap.get(n);return i?(this.expandedKeys=E(this.expandedKeys,n),this.emit("tree-expand",{key:n,row:i.data,expanded:this.expandedKeys.has(n),depth:i.depth}),this.requestRender(),!0):!1}expand(e){this.expandedKeys.add(e),this.requestRender()}collapse(e){this.expandedKeys.delete(e),this.requestRender()}toggle(e){this.expandedKeys=E(this.expandedKeys,e),this.requestRender()}expandAll(){this.expandedKeys=A(this.rows,this.config),this.requestRender()}collapseAll(){this.expandedKeys=T(),this.requestRender()}isExpanded(e){return this.expandedKeys.has(e)}getExpandedKeys(){return[...this.expandedKeys]}getFlattenedRows(){return[...this.flattenedRows]}getRowByKey(e){return this.rowKeyMap.get(e)?.data}expandToKey(e){this.expandedKeys=F(this.rows,e,this.config,this.expandedKeys),this.requestRender()}styles=`
|
|
2
2
|
.tree-toggle {
|
|
3
3
|
cursor: pointer;
|
|
4
4
|
user-select: none;
|
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
.tree-toggle:hover {
|
|
8
8
|
color: var(--tbw-tree-accent, var(--tbw-color-accent));
|
|
9
9
|
}
|
|
10
|
-
`}
|
|
10
|
+
`}u.TreePlugin=M,u.countNodes=S,u.detectTreeStructure=K,u.getMaxDepth=b,u.inferChildrenField=C,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
|
|
11
11
|
//# sourceMappingURL=tree.umd.js.map
|