@beeblock/svelar-datatable 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- var m=class{_state;_listeners=new Set;_columns=[];_rowIdFn;_stateSaveKey=null;_lastSelectedId=null;_paginate=!0;constructor(t){this._columns=t.columns,this._stateSaveKey=t.stateSaveKey??null,this._paginate=t.paginate!==!1,this._rowIdFn=typeof t.rowId=="function"?t.rowId:s=>s[t.rowId??"id"];let e=this._loadState();this._state={allRows:t.data??[],filteredRows:[],sortedRows:[],paginatedRows:[],sort:e?.sort??[],filters:e?.filters??[],globalSearch:e?.globalSearch??"",pagination:{page:e?.page??1,perPage:t.perPage??15,total:0,lastPage:1},selectedIds:new Set,columnVisibility:e?.columnVisibility??Object.fromEntries(t.columns.map(s=>[s.key,s.visible!==!1])),columnOrder:e?.columnOrder??t.columns.map(s=>s.key),loading:!1,error:null,editingRowId:null,editingColumn:null,editorMode:null,formData:{},validationErrors:{},draw:0,excelFocusedCell:null,excelEditingCell:null,excelEditValue:""},this._recompute()}subscribe(t){return this._listeners.add(t),()=>this._listeners.delete(t)}getState(){return this._state}_notify(){for(let t of this._listeners)t()}_saveState(){if(this._stateSaveKey)try{let t={sort:this._state.sort,filters:this._state.filters,globalSearch:this._state.globalSearch,page:this._state.pagination.page,columnVisibility:this._state.columnVisibility,columnOrder:this._state.columnOrder};localStorage.setItem(this._stateSaveKey,JSON.stringify(t))}catch{}}_loadState(){if(!this._stateSaveKey)return null;try{let t=localStorage.getItem(this._stateSaveKey);return t?JSON.parse(t):null}catch{return null}}setData(t){this._state={...this._state,allRows:t},this._recompute()}setSort(t){this._state={...this._state,sort:t,pagination:{...this._state.pagination,page:1}},this._recompute(),this._saveState()}toggleSort(t,e=!1){let s=this._state.sort.find(i=>i.column===t),a;s?s.direction==="asc"?a=e?this._state.sort.map(i=>i.column===t?{...i,direction:"desc"}:i):[{column:t,direction:"desc"}]:a=e?this._state.sort.filter(i=>i.column!==t):[]:a=e?[...this._state.sort,{column:t,direction:"asc"}]:[{column:t,direction:"asc"}],this.setSort(a)}setGlobalSearch(t){this._state={...this._state,globalSearch:t,pagination:{...this._state.pagination,page:1}},this._recompute(),this._saveState()}setFilters(t){this._state={...this._state,filters:t,pagination:{...this._state.pagination,page:1}},this._recompute(),this._saveState()}setColumnFilter(t,e,s="like"){let a=this._state.filters.filter(i=>i.column!==t);e!==""&&e!==null&&e!==void 0&&a.push({column:t,value:e,operator:s}),this.setFilters(a)}setPage(t){let e=this._state.pagination.lastPage,s=Math.max(1,Math.min(t,e));this._state={...this._state,pagination:{...this._state.pagination,page:s}},this._recompute(),this._saveState()}setPerPage(t){this._state={...this._state,pagination:{...this._state.pagination,perPage:t,page:1}},this._recompute(),this._saveState()}toggleColumnVisibility(t){let e={...this._state.columnVisibility};e[t]=!e[t],this._state={...this._state,columnVisibility:e},this._notify(),this._saveState()}setColumnVisibility(t){this._state={...this._state,columnVisibility:t},this._notify(),this._saveState()}reorderColumns(t){this._state={...this._state,columnOrder:t},this._notify(),this._saveState()}getRowId(t){return this._rowIdFn(t)}toggleSelect(t){let e=new Set(this._state.selectedIds);e.has(t)?e.delete(t):e.add(t),this._lastSelectedId=t,this._state={...this._state,selectedIds:e},this._notify()}getLastSelectedId(){return this._lastSelectedId}selectSingle(t){this._state={...this._state,selectedIds:new Set([t])},this._notify()}selectAll(){let t=this._state.filteredRows.map(e=>this.getRowId(e));this._state={...this._state,selectedIds:new Set(t)},this._notify()}deselectAll(){this._state={...this._state,selectedIds:new Set},this._notify()}selectRange(t,e){let s=this._state.sortedRows,a=s.findIndex(n=>this.getRowId(n)===t),i=s.findIndex(n=>this.getRowId(n)===e);if(a===-1||i===-1)return;let o=Math.min(a,i),d=Math.max(a,i),r=new Set(this._state.selectedIds);for(let n=o;n<=d;n++)r.add(this.getRowId(s[n]));this._state={...this._state,selectedIds:r},this._notify()}getSelectedRows(){return this._state.allRows.filter(t=>this._state.selectedIds.has(this.getRowId(t)))}openEditor(t,e,s){let a=t!==null?this._state.allRows.find(o=>this.getRowId(o)===t):null,i=a?{...a}:{};this._state={...this._state,editingRowId:t,editingColumn:e,editorMode:s,formData:i,validationErrors:{}},this._notify()}closeEditor(){this._state={...this._state,editingRowId:null,editingColumn:null,editorMode:null,formData:{},validationErrors:{}},this._notify()}setFormField(t,e){this._state={...this._state,formData:{...this._state.formData,[t]:e}},this._notify()}setValidationErrors(t){this._state={...this._state,validationErrors:t},this._notify()}setLoading(t){this._state={...this._state,loading:t},this._notify()}setError(t){this._state={...this._state,error:t},this._notify()}setServerResponse(t){this._state={...this._state,allRows:t.data,filteredRows:t.data,sortedRows:t.data,paginatedRows:t.data,loading:!1,draw:t.draw,pagination:{...this._state.pagination,total:t.recordsFiltered,lastPage:Math.ceil(t.recordsFiltered/this._state.pagination.perPage)||1}},this._notify()}resetState(){if(this._state={...this._state,sort:[],filters:[],globalSearch:"",pagination:{...this._state.pagination,page:1},selectedIds:new Set},this._recompute(),this._stateSaveKey)try{localStorage.removeItem(this._stateSaveKey)}catch{}}focusCell(t,e){this._state={...this._state,excelFocusedCell:{rowIndex:t,columnKey:e}},this._notify()}startCellEdit(){let t=this._state.excelFocusedCell;if(!t)return;let e=this._state.paginatedRows[t.rowIndex];if(!e)return;let s=this._columns.find(a=>a.key===t.columnKey);!s||s.editable===!1||(this._state={...this._state,excelEditingCell:{...t},excelEditValue:e[t.columnKey]!=null?String(e[t.columnKey]):""},this._notify())}setExcelEditValue(t){this._state={...this._state,excelEditValue:t}}commitCellEdit(){let t=this._state.excelEditingCell;if(!t)return null;let e=this._state.paginatedRows[t.rowIndex];if(!e)return this.cancelCellEdit(),null;let s=this._columns.find(o=>o.key===t.columnKey),a=e[t.columnKey],i=this._state.excelEditValue;return s?.type==="number"?i=i===""?null:Number(i):s?.type==="boolean"?i=i==="true"||i==="1":i===""&&(i=null),this._state={...this._state,excelEditingCell:null,excelEditValue:""},i===a||i===null&&a==null?(this._notify(),null):(e[t.columnKey]=i,this._notify(),{row:e,columnKey:t.columnKey,oldValue:a,newValue:i})}cancelCellEdit(){this._state={...this._state,excelEditingCell:null,excelEditValue:""},this._notify()}clearExcelFocus(){this._state={...this._state,excelFocusedCell:null,excelEditingCell:null,excelEditValue:""},this._notify()}getVisibleColumns(){return this._state.columnOrder.filter(t=>this._state.columnVisibility[t]!==!1).map(t=>this._columns.find(e=>e.key===t)).filter(Boolean)}navigateCell(t){let e=this._state.excelFocusedCell,s=this._state.paginatedRows,a=this.getVisibleColumns();if(a.length===0)return null;if(!e){let r=a.find(n=>n.editable!==!1);return r&&s.length>0&&this.focusCell(0,r.key),null}let{rowIndex:i,columnKey:o}=e,d=a.findIndex(r=>r.key===o);if(d===-1)return null;switch(t){case"left":{for(let r=d-1;r>=0;r--)if(a[r].editable!==!1)return this.focusCell(i,a[r].key),null;for(let r=a.length-1;r>=0;r--)if(a[r].editable!==!1)return i>0?(this.focusCell(i-1,a[r].key),null):"page-prev";return null}case"right":{for(let r=d+1;r<a.length;r++)if(a[r].editable!==!1)return this.focusCell(i,a[r].key),null;for(let r=0;r<a.length;r++)if(a[r].editable!==!1)return i<s.length-1?(this.focusCell(i+1,a[r].key),null):"page-next";return null}case"up":return i>0?(this.focusCell(i-1,o),null):"page-prev";case"down":return i<s.length-1?(this.focusCell(i+1,o),null):"page-next"}}_recompute(){let t=[...this._state.allRows];if(this._state.globalSearch){let n=this._state.globalSearch.toLowerCase(),u=this._columns.filter(l=>l.searchable!==!1);t=t.filter(l=>u.some(p=>{let h=l[p.key];return h!=null&&String(h).toLowerCase().includes(n)}))}for(let n of this._state.filters)t=t.filter(u=>{let l=u[n.column];switch(n.operator){case"=":return l==n.value;case"!=":return l!=n.value;case">":return l>n.value;case"<":return l<n.value;case">=":return l>=n.value;case"<=":return l<=n.value;case"like":return l!=null&&String(l).toLowerCase().includes(String(n.value).toLowerCase());case"not_like":return l==null||!String(l).toLowerCase().includes(String(n.value).toLowerCase());case"in":return Array.isArray(n.value)&&n.value.includes(l);case"between":return Array.isArray(n.value)&&l>=n.value[0]&&l<=n.value[1];case"null":return l==null;case"not_null":return l!=null;default:return!0}});let e=t;this._state.sort.length>0&&(t=[...t].sort((n,u)=>{for(let l of this._state.sort){let p=this._columns.find(E=>E.key===l.column),h=n[l.column],g=u[l.column],f=0;if(h==null?f=-1:g==null?f=1:p?.type==="number"?f=Number(h)-Number(g):p?.type==="date"?f=new Date(h).getTime()-new Date(g).getTime():p?.type==="boolean"?f=(h?1:0)-(g?1:0):f=String(h).localeCompare(String(g)),f!==0)return l.direction==="desc"?-f:f}return 0}));let s=t,a=e.length,i=this._state.pagination.perPage,o,d,r;if(this._paginate){r=Math.ceil(a/i)||1,d=Math.min(this._state.pagination.page,r);let n=(d-1)*i;o=s.slice(n,n+i)}else o=s,d=1,r=1;this._state={...this._state,filteredRows:e,sortedRows:s,paginatedRows:o,pagination:{page:d,perPage:i,total:a,lastPage:r}},this._notify()}};function F(c="XSRF-TOKEN"){if(typeof document>"u")return null;let t=c.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),e=document.cookie.match(new RegExp(`(?:^|;\\s*)${t}=([^;]*)`));return e?decodeURIComponent(e[1]):null}var y=class extends m{_serverUrl;_serverMethod;_drawCounter=0;_abortController=null;_fetchDebounceTimer=null;_serverColumns;_csrfCookieName;_csrfHeaderName;constructor(t){super(t),this._serverUrl=t.serverUrl,this._serverMethod=t.serverMethod??"GET",this._csrfCookieName=t.csrfCookieName??"XSRF-TOKEN",this._csrfHeaderName=t.csrfHeaderName??"X-CSRF-Token",this._serverColumns=t.columns.map(e=>({data:e.key,name:e.key,searchable:e.searchable!==!1,orderable:e.sortable!==!1}))}_buildHeaders(t={}){let e={...t},s=F(this._csrfCookieName);return s&&(e[this._csrfHeaderName]=s),e}setSort(t){let e=this.getState();this._state={...e,sort:t,pagination:{...e.pagination,page:1}},this._debouncedFetch()}setGlobalSearch(t){let e=this.getState();this._state={...e,globalSearch:t,pagination:{...e.pagination,page:1}},this._debouncedFetch()}setFilters(t){let e=this.getState();this._state={...e,filters:t,pagination:{...e.pagination,page:1}},this._debouncedFetch()}setPage(t){let e=this.getState();this._state={...e,pagination:{...e.pagination,page:t}},this._fetchFromServer()}setPerPage(t){let e=this.getState();this._state={...e,pagination:{...e.pagination,perPage:t,page:1}},this._fetchFromServer()}_debouncedFetch(){this._fetchDebounceTimer&&clearTimeout(this._fetchDebounceTimer),this._fetchDebounceTimer=setTimeout(()=>this._fetchFromServer(),300)}async _fetchFromServer(){this._abortController&&this._abortController.abort(),this._abortController=new AbortController;let t=this.getState();this.setLoading(!0),this.setError(null);let e=++this._drawCounter,s={draw:e,start:(t.pagination.page-1)*t.pagination.perPage,length:t.pagination.perPage,search:{value:t.globalSearch,regex:!1},order:t.sort.map(a=>({column:this._serverColumns.findIndex(i=>i.data===a.column),dir:a.direction})),columns:this._serverColumns.map(a=>({...a,search:{value:t.filters.find(i=>i.column===a.data)?.value??"",regex:!1}}))};try{let a;if(this._serverMethod==="POST")a=await fetch(this._serverUrl,{method:"POST",headers:this._buildHeaders({"Content-Type":"application/json"}),body:JSON.stringify(s),signal:this._abortController.signal});else{let o=new URLSearchParams;o.set("draw",String(s.draw)),o.set("start",String(s.start)),o.set("length",String(s.length)),o.set("search[value]",s.search.value),o.set("search[regex]",String(s.search.regex)),s.order.forEach((r,n)=>{o.set(`order[${n}][column]`,String(r.column)),o.set(`order[${n}][dir]`,r.dir)}),s.columns.forEach((r,n)=>{o.set(`columns[${n}][data]`,r.data),o.set(`columns[${n}][name]`,r.name),o.set(`columns[${n}][searchable]`,String(r.searchable)),o.set(`columns[${n}][orderable]`,String(r.orderable)),o.set(`columns[${n}][search][value]`,r.search.value),o.set(`columns[${n}][search][regex]`,String(r.search.regex))});let d=`${this._serverUrl}?${o.toString()}`;a=await fetch(d,{signal:this._abortController.signal})}if(!a.ok)throw new Error(`Server responded with ${a.status}`);let i=await a.json();i.draw===e&&this.setServerResponse(i)}catch(a){if(a.name==="AbortError")return;this.setLoading(!1),this.setError(a.message??"Failed to fetch data")}}async initialFetch(){await this._fetchFromServer()}destroy(){this._abortController&&this._abortController.abort(),this._fetchDebounceTimer&&clearTimeout(this._fetchDebounceTimer)}};function v(c){if(c==null)return"";let t=String(c);return t.includes(",")||t.includes('"')||t.includes(`
2
- `)||t.includes("\r")?'"'+t.replace(/"/g,'""')+'"':t}function C(c,t){let e=t.filter(i=>i.visible!==!1),s=e.map(i=>v(i.header)).join(","),a=c.map(i=>e.map(o=>v(i[o.key])).join(","));return"\uFEFF"+[s,...a].join(`\r
3
- `)}function x(c,t="export.csv"){let e=new Blob([c],{type:"text/csv;charset=utf-8;"});S(e,t)}function S(c,t){let e=URL.createObjectURL(c),s=document.createElement("a");s.href=e,s.download=t,document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(e)}async function T(c,t){let e=t.filter(o=>o.visible!==!1),s=e.map(o=>o.header).join(" "),a=c.map(o=>e.map(d=>{let r=o[d.key];return r==null?"":String(r)}).join(" ")),i=[s,...a].join(`
4
- `);await navigator.clipboard.writeText(i)}function b(c,t,e){let s=t.filter(r=>r.visible!==!1),a=s.map(r=>`<th style="border:1px solid #ddd;padding:8px 12px;text-align:left;background:#f5f5f5;font-weight:600;">${_(r.header)}</th>`).join(""),i=c.map(r=>`<tr>${s.map(u=>{let l=r[u.key];return`<td style="border:1px solid #ddd;padding:8px 12px;">${_(l==null?"":String(l))}</td>`}).join("")}</tr>`).join(""),o=`<!DOCTYPE html>
1
+ var g=class{_state;_listeners=new Set;_columns=[];_rowIdFn;_stateSaveKey=null;_lastSelectedId=null;_paginate=!0;constructor(t){this._columns=t.columns,this._stateSaveKey=t.stateSaveKey??null,this._paginate=t.paginate!==!1,this._rowIdFn=typeof t.rowId=="function"?t.rowId:a=>a[t.rowId??"id"];let e=this._loadState();this._state={allRows:t.data??[],filteredRows:[],sortedRows:[],paginatedRows:[],sort:e?.sort??[],filters:e?.filters??[],globalSearch:e?.globalSearch??"",pagination:{page:e?.page??1,perPage:t.perPage??15,total:0,lastPage:1},selectedIds:new Set,columnVisibility:e?.columnVisibility??Object.fromEntries(t.columns.map(a=>[a.key,a.visible!==!1])),columnOrder:e?.columnOrder??t.columns.map(a=>a.key),loading:!1,error:null,editingRowId:null,editingColumn:null,editorMode:null,formData:{},validationErrors:{},draw:0,excelFocusedCell:null,excelEditingCell:null,excelEditValue:""},this._recompute()}subscribe(t){return this._listeners.add(t),()=>this._listeners.delete(t)}getState(){return this._state}_notify(){for(let t of this._listeners)t()}_saveState(){if(this._stateSaveKey)try{let t={sort:this._state.sort,filters:this._state.filters,globalSearch:this._state.globalSearch,page:this._state.pagination.page,columnVisibility:this._state.columnVisibility,columnOrder:this._state.columnOrder};localStorage.setItem(this._stateSaveKey,JSON.stringify(t))}catch{}}_loadState(){if(!this._stateSaveKey)return null;try{let t=localStorage.getItem(this._stateSaveKey);return t?JSON.parse(t):null}catch{return null}}setData(t){this._state={...this._state,allRows:t},this._recompute()}setSort(t){this._state={...this._state,sort:t,pagination:{...this._state.pagination,page:1}},this._recompute(),this._saveState()}toggleSort(t,e=!1){let a=this._state.sort.find(s=>s.column===t),r;a?a.direction==="asc"?r=e?this._state.sort.map(s=>s.column===t?{...s,direction:"desc"}:s):[{column:t,direction:"desc"}]:r=e?this._state.sort.filter(s=>s.column!==t):[]:r=e?[...this._state.sort,{column:t,direction:"asc"}]:[{column:t,direction:"asc"}],this.setSort(r)}setGlobalSearch(t){this._state={...this._state,globalSearch:t,pagination:{...this._state.pagination,page:1}},this._recompute(),this._saveState()}setFilters(t){this._state={...this._state,filters:t,pagination:{...this._state.pagination,page:1}},this._recompute(),this._saveState()}setColumnFilter(t,e,a="like"){let r=this._state.filters.filter(s=>s.column!==t);e!==""&&e!==null&&e!==void 0&&r.push({column:t,value:e,operator:a}),this.setFilters(r)}setPage(t){let e=this._state.pagination.lastPage,a=Math.max(1,Math.min(t,e));this._state={...this._state,pagination:{...this._state.pagination,page:a}},this._recompute(),this._saveState()}setPerPage(t){this._state={...this._state,pagination:{...this._state.pagination,perPage:t,page:1}},this._recompute(),this._saveState()}toggleColumnVisibility(t){let e={...this._state.columnVisibility};e[t]=!e[t],this._state={...this._state,columnVisibility:e},this._notify(),this._saveState()}setColumnVisibility(t){this._state={...this._state,columnVisibility:t},this._notify(),this._saveState()}reorderColumns(t){this._state={...this._state,columnOrder:t},this._notify(),this._saveState()}getRowId(t){return this._rowIdFn(t)}toggleSelect(t){let e=new Set(this._state.selectedIds);e.has(t)?e.delete(t):e.add(t),this._lastSelectedId=t,this._state={...this._state,selectedIds:e},this._notify()}getLastSelectedId(){return this._lastSelectedId}selectSingle(t){this._state={...this._state,selectedIds:new Set([t])},this._notify()}selectAll(){let t=this._state.filteredRows.map(e=>this.getRowId(e));this._state={...this._state,selectedIds:new Set(t)},this._notify()}deselectAll(){this._state={...this._state,selectedIds:new Set},this._notify()}selectRange(t,e){let a=this._state.sortedRows,r=a.findIndex(i=>this.getRowId(i)===t),s=a.findIndex(i=>this.getRowId(i)===e);if(r===-1||s===-1)return;let c=Math.min(r,s),o=Math.max(r,s),n=new Set(this._state.selectedIds);for(let i=c;i<=o;i++)n.add(this.getRowId(a[i]));this._state={...this._state,selectedIds:n},this._notify()}getSelectedRows(){return this._state.allRows.filter(t=>this._state.selectedIds.has(this.getRowId(t)))}openEditor(t,e,a){let r=t!==null?this._state.allRows.find(c=>this.getRowId(c)===t):null,s=r?{...r}:{};this._state={...this._state,editingRowId:t,editingColumn:e,editorMode:a,formData:s,validationErrors:{}},this._notify()}closeEditor(){this._state={...this._state,editingRowId:null,editingColumn:null,editorMode:null,formData:{},validationErrors:{}},this._notify()}setFormField(t,e){this._state={...this._state,formData:{...this._state.formData,[t]:e}},this._notify()}setValidationErrors(t){this._state={...this._state,validationErrors:t},this._notify()}setLoading(t){this._state={...this._state,loading:t},this._notify()}setError(t){this._state={...this._state,error:t},this._notify()}setServerResponse(t){this._state={...this._state,allRows:t.data,filteredRows:t.data,sortedRows:t.data,paginatedRows:t.data,loading:!1,draw:t.draw,pagination:{...this._state.pagination,total:t.recordsFiltered,lastPage:Math.ceil(t.recordsFiltered/this._state.pagination.perPage)||1}},this._notify()}resetState(){if(this._state={...this._state,sort:[],filters:[],globalSearch:"",pagination:{...this._state.pagination,page:1},selectedIds:new Set},this._recompute(),this._stateSaveKey)try{localStorage.removeItem(this._stateSaveKey)}catch{}}focusCell(t,e){this._state={...this._state,excelFocusedCell:{rowIndex:t,columnKey:e}},this._notify()}startCellEdit(){let t=this._state.excelFocusedCell;if(!t)return;let e=this._state.paginatedRows[t.rowIndex];if(!e)return;let a=this._columns.find(r=>r.key===t.columnKey);!a||a.editable===!1||(this._state={...this._state,excelEditingCell:{...t},excelEditValue:e[t.columnKey]!=null?String(e[t.columnKey]):""},this._notify())}setExcelEditValue(t){this._state={...this._state,excelEditValue:t}}commitCellEdit(){let t=this._state.excelEditingCell;if(!t)return null;let e=this._state.paginatedRows[t.rowIndex];if(!e)return this.cancelCellEdit(),null;let a=this._columns.find(c=>c.key===t.columnKey),r=e[t.columnKey],s=this._state.excelEditValue;return a?.type==="number"?s=s===""?null:Number(s):a?.type==="boolean"?s=s==="true"||s==="1":s===""&&(s=null),this._state={...this._state,excelEditingCell:null,excelEditValue:""},s===r||s===null&&r==null?(this._notify(),null):(e[t.columnKey]=s,this._notify(),{row:e,columnKey:t.columnKey,oldValue:r,newValue:s})}cancelCellEdit(){this._state={...this._state,excelEditingCell:null,excelEditValue:""},this._notify()}clearExcelFocus(){this._state={...this._state,excelFocusedCell:null,excelEditingCell:null,excelEditValue:""},this._notify()}getVisibleColumns(){return this._state.columnOrder.filter(t=>this._state.columnVisibility[t]!==!1).map(t=>this._columns.find(e=>e.key===t)).filter(Boolean)}navigateCell(t){let e=this._state.excelFocusedCell,a=this._state.paginatedRows,r=this.getVisibleColumns();if(r.length===0)return null;if(!e){let n=r.find(i=>i.editable!==!1);return n&&a.length>0&&this.focusCell(0,n.key),null}let{rowIndex:s,columnKey:c}=e,o=r.findIndex(n=>n.key===c);if(o===-1)return null;switch(t){case"left":{for(let n=o-1;n>=0;n--)if(r[n].editable!==!1)return this.focusCell(s,r[n].key),null;for(let n=r.length-1;n>=0;n--)if(r[n].editable!==!1)return s>0?(this.focusCell(s-1,r[n].key),null):"page-prev";return null}case"right":{for(let n=o+1;n<r.length;n++)if(r[n].editable!==!1)return this.focusCell(s,r[n].key),null;for(let n=0;n<r.length;n++)if(r[n].editable!==!1)return s<a.length-1?(this.focusCell(s+1,r[n].key),null):"page-next";return null}case"up":return s>0?(this.focusCell(s-1,c),null):"page-prev";case"down":return s<a.length-1?(this.focusCell(s+1,c),null):"page-next"}}_recompute(){let t=[...this._state.allRows];if(this._state.globalSearch){let i=this._state.globalSearch.toLowerCase(),d=this._columns.filter(l=>l.searchable!==!1);t=t.filter(l=>d.some(p=>{let h=l[p.key];return h!=null&&String(h).toLowerCase().includes(i)}))}for(let i of this._state.filters)t=t.filter(d=>{let l=d[i.column];switch(i.operator){case"=":return l==i.value;case"!=":return l!=i.value;case">":return l>i.value;case"<":return l<i.value;case">=":return l>=i.value;case"<=":return l<=i.value;case"like":return l!=null&&String(l).toLowerCase().includes(String(i.value).toLowerCase());case"not_like":return l==null||!String(l).toLowerCase().includes(String(i.value).toLowerCase());case"in":return Array.isArray(i.value)&&i.value.includes(l);case"between":return Array.isArray(i.value)&&l>=i.value[0]&&l<=i.value[1];case"null":return l==null;case"not_null":return l!=null;default:return!0}});let e=t;this._state.sort.length>0&&(t=[...t].sort((i,d)=>{for(let l of this._state.sort){let p=this._columns.find(E=>E.key===l.column),h=i[l.column],m=d[l.column],f=0;if(h==null?f=-1:m==null?f=1:p?.type==="number"?f=Number(h)-Number(m):p?.type==="date"?f=new Date(h).getTime()-new Date(m).getTime():p?.type==="boolean"?f=(h?1:0)-(m?1:0):f=String(h).localeCompare(String(m)),f!==0)return l.direction==="desc"?-f:f}return 0}));let a=t,r=e.length,s=this._state.pagination.perPage,c,o,n;if(this._paginate){n=Math.ceil(r/s)||1,o=Math.min(this._state.pagination.page,n);let i=(o-1)*s;c=a.slice(i,i+s)}else c=a,o=1,n=1;this._state={...this._state,filteredRows:e,sortedRows:a,paginatedRows:c,pagination:{page:o,perPage:s,total:r,lastPage:n}},this._notify()}};function F(u="XSRF-TOKEN"){if(typeof document>"u")return null;let t=u.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),e=document.cookie.match(new RegExp(`(?:^|;\\s*)${t}=([^;]*)`));return e?decodeURIComponent(e[1]):null}var y=class extends g{_serverUrl;_serverMethod;_serverParams;_drawCounter=0;_abortController=null;_fetchDebounceTimer=null;_serverColumns;_csrfCookieName;_csrfHeaderName;constructor(t){super(t),this._serverUrl=t.serverUrl,this._serverMethod=t.serverMethod??"GET",this._serverParams=t.serverParams,this._csrfCookieName=t.csrfCookieName??"XSRF-TOKEN",this._csrfHeaderName=t.csrfHeaderName??"X-CSRF-Token",this._serverColumns=t.columns.map(e=>({data:e.key,name:e.key,searchable:e.searchable!==!1,orderable:e.sortable!==!1}))}_buildHeaders(t={}){let e={...t},a=F(this._csrfCookieName);return a&&(e[this._csrfHeaderName]=a),e}_resolveServerParams(){return this._serverParams?typeof this._serverParams=="function"?this._serverParams():this._serverParams:{}}setSort(t){let e=this.getState();this._state={...e,sort:t,pagination:{...e.pagination,page:1}},this._debouncedFetch()}setGlobalSearch(t){let e=this.getState();this._state={...e,globalSearch:t,pagination:{...e.pagination,page:1}},this._debouncedFetch()}setFilters(t){let e=this.getState();this._state={...e,filters:t,pagination:{...e.pagination,page:1}},this._debouncedFetch()}setPage(t){let e=this.getState();this._state={...e,pagination:{...e.pagination,page:t}},this._fetchFromServer()}setPerPage(t){let e=this.getState();this._state={...e,pagination:{...e.pagination,perPage:t,page:1}},this._fetchFromServer()}_debouncedFetch(){this._fetchDebounceTimer&&clearTimeout(this._fetchDebounceTimer),this._fetchDebounceTimer=setTimeout(()=>this._fetchFromServer(),300)}async _fetchFromServer(){this._abortController&&this._abortController.abort(),this._abortController=new AbortController;let t=this.getState(),e=this._resolveServerParams();this.setLoading(!0),this.setError(null);let a=++this._drawCounter,r={draw:a,start:(t.pagination.page-1)*t.pagination.perPage,length:t.pagination.perPage,search:{value:t.globalSearch,regex:!1},customFilters:e,order:t.sort.map(s=>({column:this._serverColumns.findIndex(c=>c.data===s.column),dir:s.direction})),columns:this._serverColumns.map(s=>({...s,search:{value:t.filters.find(c=>c.column===s.data)?.value??"",regex:!1}}))};try{let s;if(this._serverMethod==="POST")s=await fetch(this._serverUrl,{method:"POST",headers:this._buildHeaders({"Content-Type":"application/json"}),body:JSON.stringify(r),signal:this._abortController.signal});else{let o=new URLSearchParams;o.set("draw",String(r.draw)),o.set("start",String(r.start)),o.set("length",String(r.length)),o.set("search[value]",r.search.value),o.set("search[regex]",String(r.search.regex)),Object.entries(e).forEach(([i,d])=>{d!=null&&d!==""&&o.set(`filters[${i}]`,String(d))}),r.order.forEach((i,d)=>{o.set(`order[${d}][column]`,String(i.column)),o.set(`order[${d}][dir]`,i.dir)}),r.columns.forEach((i,d)=>{o.set(`columns[${d}][data]`,i.data),o.set(`columns[${d}][name]`,i.name),o.set(`columns[${d}][searchable]`,String(i.searchable)),o.set(`columns[${d}][orderable]`,String(i.orderable)),o.set(`columns[${d}][search][value]`,i.search.value),o.set(`columns[${d}][search][regex]`,String(i.search.regex))});let n=`${this._serverUrl}?${o.toString()}`;s=await fetch(n,{signal:this._abortController.signal})}if(!s.ok)throw new Error(`Server responded with ${s.status}`);let c=await s.json();c.draw===a&&this.setServerResponse(c)}catch(s){if(s.name==="AbortError")return;this.setLoading(!1),this.setError(s.message??"Failed to fetch data")}}async initialFetch(){await this._fetchFromServer()}destroy(){this._abortController&&this._abortController.abort(),this._fetchDebounceTimer&&clearTimeout(this._fetchDebounceTimer)}};function w(u){if(u==null)return"";let t=String(u);return t.includes(",")||t.includes('"')||t.includes(`
2
+ `)||t.includes("\r")?'"'+t.replace(/"/g,'""')+'"':t}function C(u,t){let e=t.filter(s=>s.visible!==!1),a=e.map(s=>w(s.header)).join(","),r=u.map(s=>e.map(c=>w(s[c.key])).join(","));return"\uFEFF"+[a,...r].join(`\r
3
+ `)}function x(u,t="export.csv"){let e=new Blob([u],{type:"text/csv;charset=utf-8;"});S(e,t)}function S(u,t){let e=URL.createObjectURL(u),a=document.createElement("a");a.href=e,a.download=t,document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(e)}async function T(u,t){let e=t.filter(c=>c.visible!==!1),a=e.map(c=>c.header).join(" "),r=u.map(c=>e.map(o=>{let n=c[o.key];return n==null?"":String(n)}).join(" ")),s=[a,...r].join(`
4
+ `);await navigator.clipboard.writeText(s)}function b(u,t,e){let a=t.filter(n=>n.visible!==!1),r=a.map(n=>`<th style="border:1px solid #ddd;padding:8px 12px;text-align:left;background:#f5f5f5;font-weight:600;">${_(n.header)}</th>`).join(""),s=u.map(n=>`<tr>${a.map(d=>{let l=n[d.key];return`<td style="border:1px solid #ddd;padding:8px 12px;">${_(l==null?"":String(l))}</td>`}).join("")}</tr>`).join(""),c=`<!DOCTYPE html>
5
5
  <html><head>
6
6
  <title>${_(e??"Data Export")}</title>
7
7
  <style>
@@ -13,7 +13,7 @@ var m=class{_state;_listeners=new Set;_columns=[];_rowIdFn;_stateSaveKey=null;_l
13
13
  </head><body>
14
14
  ${e?`<h1>${_(e)}</h1>`:""}
15
15
  <table>
16
- <thead><tr>${a}</tr></thead>
17
- <tbody>${i}</tbody>
16
+ <thead><tr>${r}</tr></thead>
17
+ <tbody>${s}</tbody>
18
18
  </table>
19
- </body></html>`,d=window.open("","_blank");d&&(d.document.write(o),d.document.close(),d.focus(),setTimeout(()=>d.print(),250))}function _(c){return c.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function R(c,t,e="export.xlsx"){let s;try{s=await import("exceljs")}catch{throw new Error("exceljs is required for Excel export. Install it: npm install exceljs")}let a=new s.Workbook,i=a.addWorksheet("Data"),o=t.filter(u=>u.visible!==!1);i.columns=o.map(u=>({header:u.header,key:u.key,width:20}));let d=i.getRow(1);d.font={bold:!0},d.fill={type:"pattern",pattern:"solid",fgColor:{argb:"FFF0F0F0"}};for(let u of c){let l={};for(let p of o)l[p.key]=u[p.key]??"";i.addRow(l)}let r=await a.xlsx.writeBuffer(),n=new Blob([r],{type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});S(n,e)}function D(c,t,e="export.pdf"){b(c,t,e.replace(".pdf",""))}var w=class{async export(t,e,s,a){switch(t){case"csv":{let i=C(e,s);x(i,a??"export.csv");break}case"excel":await R(e,s,a??"export.xlsx");break;case"pdf":D(e,s,a??"export.pdf");break;case"clipboard":await T(e,s);break;case"print":b(e,s);break}}};export{m as DataTableStore,w as ExportManager,y as ServerDataTableStore};
19
+ </body></html>`,o=window.open("","_blank");o&&(o.document.write(c),o.document.close(),o.focus(),setTimeout(()=>o.print(),250))}function _(u){return u.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function R(u,t,e="export.xlsx"){let a;try{a=await import("exceljs")}catch{throw new Error("exceljs is required for Excel export. Install it: npm install exceljs")}let r=new a.Workbook,s=r.addWorksheet("Data"),c=t.filter(d=>d.visible!==!1);s.columns=c.map(d=>({header:d.header,key:d.key,width:20}));let o=s.getRow(1);o.font={bold:!0},o.fill={type:"pattern",pattern:"solid",fgColor:{argb:"FFF0F0F0"}};for(let d of u){let l={};for(let p of c)l[p.key]=d[p.key]??"";s.addRow(l)}let n=await r.xlsx.writeBuffer(),i=new Blob([n],{type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});S(i,e)}function D(u,t,e="export.pdf"){b(u,t,e.replace(".pdf",""))}var v=class{async export(t,e,a,r){switch(t){case"csv":{let s=C(e,a);x(s,r??"export.csv");break}case"excel":await R(e,a,r??"export.xlsx");break;case"pdf":D(e,a,r??"export.pdf");break;case"clipboard":await T(e,a);break;case"print":b(e,a);break}}};export{g as DataTableStore,v as ExportManager,y as ServerDataTableStore};
package/dist/plugin.js CHANGED
@@ -1 +1 @@
1
- import{Plugin as s}from"@beeblock/svelar/plugins";import{fileURLToPath as o}from"url";import{dirname as r,join as i}from"path";var a={prefix:"/api",defaults:{perPage:15,perPageOptions:[10,15,25,50,100],searchDebounceMs:300,sortable:!0,searchable:!0,paginate:!0,selectable:"none",striped:!0,hover:!0,bordered:!1,compact:!1,responsive:!0,virtualScroll:!1,virtualRowHeight:48}},l=r(o(import.meta.url)),n=r(l),u=i(n,"src","publishable"),e=class extends s{name="svelar-datatable";version="0.1.4";description="Full-featured DataTable plugin for Svelar \u2014 sorting, searching, pagination, inline editing, export, and server-side processing";_config;constructor(t={}){super(),this._config={...a,...t,defaults:{...a.defaults,...t.defaults}}}get datatableConfig(){return this._config}static defaults(){return{...a.defaults}}publishables(){return{routes:[{source:i(u,"routes/datatable.ts"),dest:"src/routes/api/datatable/+server.ts",type:"asset"}]}}};var m=e;export{e as SvelarDatatablePlugin,m as default};
1
+ import{Plugin as f}from"@beeblock/svelar/plugins";import{fileURLToPath as c}from"url";import{dirname as o,join as r}from"path";import{readFileSync as u}from"fs";var a={prefix:"/api",defaults:{perPage:15,perPageOptions:[10,15,25,50,100],searchDebounceMs:300,sortable:!0,searchable:!0,paginate:!0,selectable:"none",striped:!0,hover:!0,bordered:!1,compact:!1,responsive:!0,virtualScroll:!1,virtualRowHeight:48}},g=o(c(import.meta.url)),s=o(g),p=r(s,"src","publishable");function d(){let i=u(r(s,"package.json"),"utf8"),e=JSON.parse(i);return{name:e.name??"@beeblock/svelar-datatable",version:e.version??"0.0.0"}}var l=d(),n=l.name,b=l.version,t=class extends f{name=n;version=b;description="Full-featured DataTable plugin for Svelar \u2014 sorting, searching, pagination, inline editing, export, and server-side processing";_config;constructor(e={}){super(),this._config={...a,...e,defaults:{...a.defaults,...e.defaults}}}get datatableConfig(){return this._config}static defaults(){return{...a.defaults}}config(){return{key:"datatable",defaults:{prefix:this._config.prefix,defaults:{...this._config.defaults}}}}async register(e){e.instance("datatable.config",this.datatableConfig),e.instance(`${n}.config`,this.datatableConfig)}publishables(){return{routes:[{source:r(p,"routes/datatable.ts"),dest:"src/routes/api/datatable/+server.ts",type:"asset"}]}}};var x=t;export{t as SvelarDatatablePlugin,x as default};
@@ -1,10 +1,18 @@
1
1
  import type { DataTableRequest, DataTableResponse } from '../types.js';
2
+ export type DataTableServerFilterHandler = (query: any, value: any, context: {
3
+ request: DataTableRequest;
4
+ filter: string;
5
+ }) => any | void;
2
6
  export interface DataTableServiceOptions {
3
7
  searchable?: string[];
4
8
  orderable?: string[];
5
9
  baseQuery?: (query: any) => any;
6
10
  scopes?: Record<string, (query: any) => any>;
7
11
  computedColumns?: Record<string, string>;
12
+ filters?: Record<string, DataTableServerFilterHandler>;
13
+ searchDriver?: 'database' | 'meilisearch' | 'auto';
14
+ meilisearchFilter?: (filters: Record<string, any>, request: DataTableRequest) => string | string[] | undefined;
15
+ meilisearchSort?: (request: DataTableRequest) => string[] | undefined;
8
16
  }
9
17
  export declare class DataTableService<T = any> {
10
18
  private _model;
@@ -14,6 +22,11 @@ export declare class DataTableService<T = any> {
14
22
  private _scopes;
15
23
  private _computedColumns;
16
24
  private _activeScopes;
25
+ private _filters;
26
+ private _activeFilters;
27
+ private _searchDriver;
28
+ private _meilisearchFilter?;
29
+ private _meilisearchSort?;
17
30
  constructor(model: any, options?: DataTableServiceOptions);
18
31
  searchable(columns: string[]): this;
19
32
  orderable(columns: string[]): this;
@@ -21,5 +34,13 @@ export declare class DataTableService<T = any> {
21
34
  addScope(name: string, fn: (query: any) => any): this;
22
35
  applyScope(name: string): this;
23
36
  addComputedColumn(name: string, sqlExpr: string): this;
37
+ addFilter(name: string, fn: DataTableServerFilterHandler): this;
38
+ applyFilter(name: string, value: any): this;
39
+ applyFilters(filters?: Record<string, any>): this;
40
+ private searchOperator;
41
+ private shouldUseMeilisearch;
42
+ private sortableColumns;
43
+ private meilisearchSort;
44
+ private processWithMeilisearch;
24
45
  process(request: DataTableRequest): Promise<DataTableResponse<T>>;
25
46
  }
@@ -1 +1 @@
1
- var u=class{_model;_searchable=[];_orderable=[];_baseQueryFn=null;_scopes={};_computedColumns={};_activeScopes=[];constructor(e,r){this._model=e,r?.searchable&&(this._searchable=r.searchable),r?.orderable&&(this._orderable=r.orderable),r?.baseQuery&&(this._baseQueryFn=r.baseQuery),r?.scopes&&(this._scopes=r.scopes),r?.computedColumns&&(this._computedColumns=r.computedColumns)}searchable(e){return this._searchable=e,this}orderable(e){return this._orderable=e,this}setBaseQuery(e){return this._baseQueryFn=e,this}addScope(e,r){return this._scopes[e]=r,this}applyScope(e){return this._activeScopes.push(e),this}addComputedColumn(e,r){return this._computedColumns[e]=r,this}async process(e){try{let r=this._model.query();this._baseQueryFn&&this._baseQueryFn(r);let n=await r.count(),a=this._model.query();this._baseQueryFn&&this._baseQueryFn(a);for(let t of this._activeScopes){let o=this._scopes[t];o&&o(a)}for(let[t,o]of Object.entries(this._computedColumns))a=a.selectRaw(`(${o}) as ${t}`);if(e.search.value){let t=e.search.value,o=this._searchable.length>0?this._searchable:e.columns.filter(l=>l.searchable).map(l=>l.data);o.length>0&&(a=a.whereNested(l=>{for(let c=0;c<o.length;c++){let m=o[c];c===0?l.where(m,"LIKE",`%${t}%`):l.orWhere(m,"LIKE",`%${t}%`)}}))}for(let t of e.columns)t.search.value&&t.searchable&&(a=a.where(t.data,"LIKE",`%${t.search.value}%`));let i=await a.clone().count();for(let t of e.order){let o=e.columns[t.column];o&&(this._orderable.length>0?this._orderable:e.columns.filter(c=>c.orderable).map(c=>c.data)).includes(o.data)&&(a=a.orderBy(o.data,t.dir))}a=a.offset(e.start).limit(e.length);let h=await a.get();return{draw:e.draw,recordsTotal:n,recordsFiltered:i,data:h}}catch(r){return{draw:e.draw,recordsTotal:0,recordsFiltered:0,data:[],error:r.message??"Server error"}}}};import{Controller as g}from"@beeblock/svelar/routing";function p(s){let e=parseInt(s.get("draw")??"1",10),r=parseInt(s.get("start")??"0",10),n=parseInt(s.get("length")??"15",10),a={value:s.get("search[value]")??"",regex:s.get("search[regex]")==="true"},d=[],i=0;for(;s.has(`order[${i}][column]`);)d.push({column:parseInt(s.get(`order[${i}][column]`)??"0",10),dir:s.get(`order[${i}][dir]`)??"asc"}),i++;let h=[],t=0;for(;s.has(`columns[${t}][data]`);)h.push({data:s.get(`columns[${t}][data]`)??"",name:s.get(`columns[${t}][name]`)??"",searchable:s.get(`columns[${t}][searchable]`)!=="false",orderable:s.get(`columns[${t}][orderable]`)!=="false",search:{value:s.get(`columns[${t}][search][value]`)??"",regex:s.get(`columns[${t}][search][regex]`)==="true"}}),t++;return{draw:e,start:r,length:n,search:a,order:d,columns:h}}async function b(s){return await s.json()}var y=class extends g{_model;_options;constructor(e,r){super(),this._model=e,this._options=r}async query(e){try{let r=new u(this._model,this._options),n;e.request.method==="POST"?n=await b(e.request):n=p(e.url.searchParams);let a=await r.process(n);return this.json(a)}catch(r){let n={draw:0,recordsTotal:0,recordsFiltered:0,data:[],error:r.message??"Internal server error"};return this.json(n,500)}}};export{y as DataTableController,u as DataTableService,p as parseDataTableRequest,b as parseDataTableRequestFromBody};
1
+ import{Connection as v}from"@beeblock/svelar/database";var m=class{_model;_searchable=[];_orderable=[];_baseQueryFn=null;_scopes={};_computedColumns={};_activeScopes=[];_filters={};_activeFilters={};_searchDriver="auto";_meilisearchFilter;_meilisearchSort;constructor(e,r){this._model=e,r?.searchable&&(this._searchable=r.searchable),r?.orderable&&(this._orderable=r.orderable),r?.baseQuery&&(this._baseQueryFn=r.baseQuery),r?.scopes&&(this._scopes=r.scopes),r?.computedColumns&&(this._computedColumns=r.computedColumns),r?.filters&&(this._filters=r.filters),r?.searchDriver&&(this._searchDriver=r.searchDriver),r?.meilisearchFilter&&(this._meilisearchFilter=r.meilisearchFilter),r?.meilisearchSort&&(this._meilisearchSort=r.meilisearchSort)}searchable(e){return this._searchable=e,this}orderable(e){return this._orderable=e,this}setBaseQuery(e){return this._baseQueryFn=e,this}addScope(e,r){return this._scopes[e]=r,this}applyScope(e){return this._activeScopes.push(e),this}addComputedColumn(e,r){return this._computedColumns[e]=r,this}addFilter(e,r){return this._filters[e]=r,this}applyFilter(e,r){return r!=null&&r!==""&&(this._activeFilters[e]=r),this}applyFilters(e={}){for(let[r,i]of Object.entries(e))this.applyFilter(r,i);return this}searchOperator(){try{return v.getDriver(this._model.connection)==="postgres"?"ILIKE":"LIKE"}catch{return"LIKE"}}shouldUseMeilisearch(e){return!e.search.value||this._searchDriver==="database"?!1:typeof this._model.search=="function"}sortableColumns(e){return this._orderable.length>0?this._orderable:e.columns.filter(r=>r.orderable).map(r=>r.data)}meilisearchSort(e){let r=this._meilisearchSort?.(e);if(r)return r;let i=this.sortableColumns(e),l=e.order.map(t=>{let n=e.columns[t.column];return!n||!i.includes(n.data)?null:`${n.data}:${t.dir}`}).filter(Boolean);return l.length>0?l:void 0}async processWithMeilisearch(e,r,i){let l={limit:e.length,offset:e.start},t=this._meilisearchFilter?.(i,e);t&&(l.filter=t);let n=this.meilisearchSort(e);n&&(l.sort=n);let h=await this._model.search(e.search.value,l);return{draw:e.draw,recordsTotal:r,recordsFiltered:h.estimatedTotalHits??h.hits.length,data:h.hits}}async process(e){try{let r=this._model.query();this._baseQueryFn&&this._baseQueryFn(r);let i=await r.count(),l={...e.customFilters,...this._activeFilters};if(this.shouldUseMeilisearch(e))try{return await this.processWithMeilisearch(e,i,l)}catch(a){if(this._searchDriver==="meilisearch")throw a}let t=this._model.query();this._baseQueryFn&&this._baseQueryFn(t);for(let a of this._activeScopes){let o=this._scopes[a];o&&o(t)}for(let[a,o]of Object.entries(l)){let d=this._filters[a];if(d&&o!==void 0&&o!==null&&o!==""){let c=d(t,o,{request:e,filter:a});c&&(t=c)}}for(let[a,o]of Object.entries(this._computedColumns))t=t.selectRaw(`(${o}) as ${a}`);if(e.search.value){let a=e.search.value,o=this.searchOperator(),d=this._searchable.length>0?this._searchable:e.columns.filter(c=>c.searchable).map(c=>c.data);d.length>0&&(t=t.whereNested(c=>{for(let f=0;f<d.length;f++){let _=d[f];f===0?c.where(_,o,`%${a}%`):c.orWhere(_,o,`%${a}%`)}}))}for(let a of e.columns)a.search.value&&a.searchable&&(t=t.where(a.data,this.searchOperator(),`%${a.search.value}%`));let h=await t.clone().count();for(let a of e.order){let o=e.columns[a.column];o&&this.sortableColumns(e).includes(o.data)&&(t=t.orderBy(o.data,a.dir))}t=t.offset(e.start).limit(e.length);let u=await t.get();return{draw:e.draw,recordsTotal:i,recordsFiltered:h,data:u}}catch(r){return{draw:e.draw,recordsTotal:0,recordsFiltered:0,data:[],error:r.message??"Server error"}}}};import{Controller as T}from"@beeblock/svelar/routing";function b(s,e,r=0){let i=Number.parseInt(s??"",10);return Number.isFinite(i)?Math.max(r,i):e}function D(s){return s==="desc"?"desc":"asc"}function p(s){let e=b(s.get("draw"),1,1),r=b(s.get("start"),0,0),i=b(s.get("length"),15,1),l={value:s.get("search[value]")??"",regex:s.get("search[regex]")==="true"},t=[],n=0;for(;s.has(`order[${n}][column]`);)t.push({column:b(s.get(`order[${n}][column]`),0,0),dir:D(s.get(`order[${n}][dir]`))}),n++;let h=[],u=0;for(;s.has(`columns[${u}][data]`);)h.push({data:s.get(`columns[${u}][data]`)??"",name:s.get(`columns[${u}][name]`)??"",searchable:s.get(`columns[${u}][searchable]`)!=="false",orderable:s.get(`columns[${u}][orderable]`)!=="false",search:{value:s.get(`columns[${u}][search][value]`)??"",regex:s.get(`columns[${u}][search][regex]`)==="true"}}),u++;let a={};for(let[o,d]of s.entries()){let c=o.match(/^filters\[(.+)\]$/);c&&d!==""&&(a[c[1]]=d)}return{draw:e,start:r,length:i,search:l,order:t,columns:h,...Object.keys(a).length>0?{customFilters:a}:{}}}async function y(s){return await s.json()}var g=class extends T{_model;_options;constructor(e,r){super(),this._model=e,this._options=r}async query(e){try{let r=new m(this._model,this._options),i;if(e.request.method==="POST"?i=await y(e.request):i=p(e.url.searchParams),this._options?.filters&&e.url?.searchParams){let t={};for(let n of Object.keys(this._options.filters)){let h=e.url.searchParams.get(`filters[${n}]`)??e.url.searchParams.get(n);h!==null&&(t[n]=h)}r.applyFilters(t)}let l=await r.process(i);return this.json(l)}catch(r){let i={draw:0,recordsTotal:0,recordsFiltered:0,data:[],error:r.message??"Internal server error"};return this.json(i,500)}}};export{g as DataTableController,m as DataTableService,p as parseDataTableRequest,y as parseDataTableRequestFromBody};
@@ -3,6 +3,7 @@ import { DataTableStore } from './DataTableStore.js';
3
3
  export declare class ServerDataTableStore<T = any> extends DataTableStore<T> {
4
4
  private _serverUrl;
5
5
  private _serverMethod;
6
+ private _serverParams?;
6
7
  private _drawCounter;
7
8
  private _abortController;
8
9
  private _fetchDebounceTimer;
@@ -11,6 +12,7 @@ export declare class ServerDataTableStore<T = any> extends DataTableStore<T> {
11
12
  private _csrfHeaderName;
12
13
  constructor(config: DataTableConfig<T>);
13
14
  private _buildHeaders;
15
+ private _resolveServerParams;
14
16
  setSort(sort: SortState[]): void;
15
17
  setGlobalSearch(search: string): void;
16
18
  setFilters(filters: FilterState[]): void;
package/dist/types.d.ts CHANGED
@@ -51,6 +51,7 @@ export interface DataTableRequest {
51
51
  column: number;
52
52
  dir: 'asc' | 'desc';
53
53
  }[];
54
+ customFilters?: Record<string, any>;
54
55
  columns: {
55
56
  data: string;
56
57
  name: string;
@@ -136,6 +137,7 @@ export interface DataTableConfig<T = any> {
136
137
  data?: T[];
137
138
  serverUrl?: string;
138
139
  serverMethod?: 'GET' | 'POST';
140
+ serverParams?: Record<string, any> | (() => Record<string, any>);
139
141
  csrfCookieName?: string;
140
142
  csrfHeaderName?: string;
141
143
  columns: ColumnDef<T>[];
package/package.json CHANGED
@@ -1,76 +1,82 @@
1
- {
2
- "name": "@beeblock/svelar-datatable",
3
- "version": "0.1.7",
4
- "description": "Full-featured DataTable plugin for Svelar — sorting, searching, pagination, inline editing, export, and server-side processing",
5
- "type": "module",
6
- "keywords": [
7
- "svelar-plugin",
8
- "datatable",
9
- "svelte",
10
- "sveltekit",
11
- "data-grid"
12
- ],
13
- "license": "MIT",
14
- "main": "dist/index.js",
15
- "types": "dist/index.d.ts",
16
- "svelte": "./src/ui/index.ts",
17
- "exports": {
18
- "./package.json": "./package.json",
19
- ".": {
20
- "types": "./dist/index.d.ts",
21
- "default": "./dist/index.js"
22
- },
23
- "./plugin": {
24
- "types": "./dist/plugin.d.ts",
25
- "default": "./dist/plugin.js"
26
- },
27
- "./server": {
28
- "types": "./dist/server/index.d.ts",
29
- "default": "./dist/server/index.js"
30
- },
31
- "./types": {
32
- "types": "./dist/types.d.ts",
33
- "default": "./dist/types.js"
34
- },
35
- "./ui": {
36
- "types": "./src/ui/index.ts",
37
- "svelte": "./src/ui/index.ts",
38
- "import": "./src/ui/index.ts"
39
- },
40
- "./ui/*": {
41
- "svelte": "./src/ui/*",
42
- "import": "./src/ui/*"
43
- }
44
- },
45
- "files": [
46
- "dist",
47
- "src/ui",
48
- "src/state",
49
- "src/export",
50
- "src/types.ts",
51
- "src/index.ts",
52
- "src/publishable",
53
- "LICENSE"
54
- ],
55
- "scripts": {
56
- "build": "tsup && (tsc --emitDeclarationOnly || echo 'Warning: tsc declaration emit had errors')",
57
- "dev": "tsup --watch"
58
- },
59
- "peerDependencies": {
60
- "@beeblock/svelar": ">=0.4.0",
61
- "svelte": "^5.0.0"
62
- },
63
- "peerDependenciesMeta": {
64
- "exceljs": {
65
- "optional": true
66
- },
67
- "@beeblock/svelar": {
68
- "optional": false
69
- }
70
- },
71
- "devDependencies": {
72
- "tsup": "^8.5.0",
73
- "typescript": "^5.7.0",
74
- "svelte": "^5.0.0"
75
- }
76
- }
1
+ {
2
+ "name": "@beeblock/svelar-datatable",
3
+ "version": "0.2.0",
4
+ "description": "Full-featured DataTable plugin for Svelar — sorting, searching, pagination, inline editing, export, and server-side processing",
5
+ "type": "module",
6
+ "keywords": [
7
+ "svelar-plugin",
8
+ "datatable",
9
+ "svelte",
10
+ "sveltekit",
11
+ "data-grid"
12
+ ],
13
+ "license": "MIT",
14
+ "main": "dist/index.js",
15
+ "types": "dist/index.d.ts",
16
+ "svelte": "./src/ui/index.ts",
17
+ "exports": {
18
+ "./package.json": "./package.json",
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ },
23
+ "./plugin": {
24
+ "types": "./dist/plugin.d.ts",
25
+ "default": "./dist/plugin.js"
26
+ },
27
+ "./server": {
28
+ "types": "./dist/server/index.d.ts",
29
+ "default": "./dist/server/index.js"
30
+ },
31
+ "./types": {
32
+ "types": "./dist/types.d.ts",
33
+ "default": "./dist/types.js"
34
+ },
35
+ "./ui": {
36
+ "types": "./src/ui/index.ts",
37
+ "svelte": "./src/ui/index.ts",
38
+ "import": "./src/ui/index.ts"
39
+ },
40
+ "./ui/*": {
41
+ "svelte": "./src/ui/*",
42
+ "import": "./src/ui/*"
43
+ }
44
+ },
45
+ "files": [
46
+ "dist",
47
+ "src/ui",
48
+ "src/state",
49
+ "src/export",
50
+ "src/types.ts",
51
+ "src/index.ts",
52
+ "src/publishable",
53
+ "LICENSE"
54
+ ],
55
+ "scripts": {
56
+ "build": "tsup && tsc --emitDeclarationOnly",
57
+ "dev": "tsup --watch",
58
+ "lint": "tsc --noEmit",
59
+ "test": "vitest run",
60
+ "prepublishOnly": "npm run build && npm run test"
61
+ },
62
+ "peerDependencies": {
63
+ "@beeblock/svelar": ">=0.6.7",
64
+ "exceljs": ">=4.4.0",
65
+ "svelte": "^5.0.0"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "exceljs": {
69
+ "optional": true
70
+ },
71
+ "@beeblock/svelar": {
72
+ "optional": false
73
+ }
74
+ },
75
+ "devDependencies": {
76
+ "@types/node": "^22.0.0",
77
+ "tsup": "^8.5.0",
78
+ "typescript": "^5.7.0",
79
+ "vitest": "^4.1.8",
80
+ "svelte": "^5.0.0"
81
+ }
82
+ }
@@ -11,6 +11,7 @@ function getCsrfToken(cookieName = 'XSRF-TOKEN'): string | null {
11
11
  export class ServerDataTableStore<T = any> extends DataTableStore<T> {
12
12
  private _serverUrl: string;
13
13
  private _serverMethod: 'GET' | 'POST';
14
+ private _serverParams?: Record<string, any> | (() => Record<string, any>);
14
15
  private _drawCounter = 0;
15
16
  private _abortController: AbortController | null = null;
16
17
  private _fetchDebounceTimer: ReturnType<typeof setTimeout> | null = null;
@@ -22,6 +23,7 @@ export class ServerDataTableStore<T = any> extends DataTableStore<T> {
22
23
  super(config);
23
24
  this._serverUrl = config.serverUrl!;
24
25
  this._serverMethod = config.serverMethod ?? 'GET';
26
+ this._serverParams = config.serverParams;
25
27
  this._csrfCookieName = config.csrfCookieName ?? 'XSRF-TOKEN';
26
28
  this._csrfHeaderName = config.csrfHeaderName ?? 'X-CSRF-Token';
27
29
  this._serverColumns = config.columns.map((c) => ({
@@ -41,6 +43,11 @@ export class ServerDataTableStore<T = any> extends DataTableStore<T> {
41
43
  return headers;
42
44
  }
43
45
 
46
+ private _resolveServerParams(): Record<string, any> {
47
+ if (!this._serverParams) return {};
48
+ return typeof this._serverParams === 'function' ? this._serverParams() : this._serverParams;
49
+ }
50
+
44
51
  override setSort(sort: SortState[]) {
45
52
  const state = this.getState();
46
53
  // Update sort in state without recomputing locally
@@ -82,6 +89,7 @@ export class ServerDataTableStore<T = any> extends DataTableStore<T> {
82
89
  this._abortController = new AbortController();
83
90
 
84
91
  const state = this.getState();
92
+ const customFilters = this._resolveServerParams();
85
93
  this.setLoading(true);
86
94
  this.setError(null);
87
95
 
@@ -91,6 +99,7 @@ export class ServerDataTableStore<T = any> extends DataTableStore<T> {
91
99
  start: (state.pagination.page - 1) * state.pagination.perPage,
92
100
  length: state.pagination.perPage,
93
101
  search: { value: state.globalSearch, regex: false },
102
+ customFilters,
94
103
  order: state.sort.map((s) => ({
95
104
  column: this._serverColumns.findIndex((c) => c.data === s.column),
96
105
  dir: s.direction,
@@ -121,6 +130,11 @@ export class ServerDataTableStore<T = any> extends DataTableStore<T> {
121
130
  params.set('length', String(request.length));
122
131
  params.set('search[value]', request.search.value);
123
132
  params.set('search[regex]', String(request.search.regex));
133
+ Object.entries(customFilters).forEach(([key, value]) => {
134
+ if (value !== undefined && value !== null && value !== '') {
135
+ params.set(`filters[${key}]`, String(value));
136
+ }
137
+ });
124
138
 
125
139
  request.order.forEach((o, i) => {
126
140
  params.set(`order[${i}][column]`, String(o.column));
package/src/types.ts CHANGED
@@ -55,6 +55,7 @@ export interface DataTableRequest {
55
55
  length: number;
56
56
  search: { value: string; regex: boolean };
57
57
  order: { column: number; dir: 'asc' | 'desc' }[];
58
+ customFilters?: Record<string, any>;
58
59
  columns: {
59
60
  data: string;
60
61
  name: string;
@@ -144,6 +145,7 @@ export interface DataTableConfig<T = any> {
144
145
  data?: T[];
145
146
  serverUrl?: string;
146
147
  serverMethod?: 'GET' | 'POST';
148
+ serverParams?: Record<string, any> | (() => Record<string, any>);
147
149
  // CSRF token config (defaults to Svelar conventions)
148
150
  csrfCookieName?: string;
149
151
  csrfHeaderName?: string;
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import type { DataTableConfig, DataTableClassNames, ColumnDef, EditorFieldDef } from '../types.js';
4
- import { DataTableStore } from '../state/DataTableStore.js';
5
- import { ServerDataTableStore } from '../state/ServerDataTableStore.js';
3
+ import type { DataTableConfig, DataTableClassNames, ColumnDef, EditorFieldDef } from '../types.ts';
4
+ import { DataTableStore } from '../state/DataTableStore.ts';
5
+ import { ServerDataTableStore } from '../state/ServerDataTableStore.ts';
6
6
  import DataTableToolbar from './DataTableToolbar.svelte';
7
7
  import DataTableHead from './DataTableHead.svelte';
8
8
  import DataTableBody from './DataTableBody.svelte';
@@ -24,6 +24,7 @@
24
24
  data,
25
25
  serverUrl,
26
26
  serverMethod,
27
+ serverParams,
27
28
  // Columns
28
29
  columns,
29
30
  // Features
@@ -84,7 +85,7 @@
84
85
 
85
86
  // Build config object
86
87
  let config: DataTableConfig = $derived({
87
- data, serverUrl, serverMethod, columns, sortable, searchable, paginate,
88
+ data, serverUrl, serverMethod, serverParams, columns, sortable, searchable, paginate,
88
89
  selectable, perPage, perPageOptions, searchDebounceMs, stateSaveKey,
89
90
  rowId, rowClass, buttons, editorMode, editorFields,
90
91
  onSort, onFilter, onPageChange, onSelect, onRowClick, onEdit, onCellEdit, onCreate, onDelete,
@@ -93,6 +94,7 @@
93
94
  });
94
95
 
95
96
  // Create store
97
+ // svelte-ignore state_referenced_locally
96
98
  let store: DataTableStore = $state(
97
99
  serverUrl
98
100
  ? new ServerDataTableStore(config)
@@ -104,6 +106,8 @@
104
106
  storeRef = store;
105
107
  });
106
108
 
109
+ // svelte-ignore state_referenced_locally
110
+
107
111
  let state = $state(store.getState());
108
112
 
109
113
  // Subscribe to store changes
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { ColumnDef, DataTableStore, SelectionMode, EditorMode, DataTableClassNames } from '../index.js';
2
+ import type { ColumnDef, DataTableStore, SelectionMode, EditorMode, DataTableClassNames } from '../index.ts';
3
3
  import type { Snippet } from 'svelte';
4
4
  import DataTableRow from './DataTableRow.svelte';
5
5
  import DataTableLoading from './DataTableLoading.svelte';
@@ -52,6 +52,8 @@
52
52
  containerHeight = 500,
53
53
  }: Props = $props();
54
54
 
55
+ // svelte-ignore state_referenced_locally
56
+
55
57
  let state = $state(store.getState());
56
58
  $effect(() => {
57
59
  return store.subscribe(() => {
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { EditorFieldDef, DataTableStore, DataTableClassNames } from '../index.js';
2
+ import type { EditorFieldDef, DataTableStore, DataTableClassNames } from '../index.ts';
3
3
  import DataTableEditorForm from './DataTableEditorForm.svelte';
4
4
  import { tick } from 'svelte';
5
5
 
@@ -12,6 +12,8 @@
12
12
  }
13
13
  let { fields, store, anchorEl = null, classNames = {}, onsubmit }: Props = $props();
14
14
 
15
+ // svelte-ignore state_referenced_locally
16
+
15
17
  let state = $state(store.getState());
16
18
  $effect(() => {
17
19
  return store.subscribe(() => {
@@ -57,6 +59,7 @@
57
59
 
58
60
  {#if isOpen}
59
61
  <!-- svelte-ignore a11y_no_static_element_interactions -->
62
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
60
63
  <div class="sdt-bubble-backdrop" onclick={close}></div>
61
64
  <div class="sdt-bubble-editor" style={popoverStyle}>
62
65
  <DataTableEditorForm
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import type { ButtonDef, ExportFormat, DataTableStore, ColumnDef } from '../index.js';
3
- import { ExportManager } from '../export/ExportManager.js';
2
+ import type { ButtonDef, ExportFormat, DataTableStore, ColumnDef } from '../index.ts';
3
+ import { ExportManager } from '../export/ExportManager.ts';
4
4
 
5
5
  interface Props {
6
6
  buttons: (ButtonDef | ExportFormat)[];
@@ -10,6 +10,14 @@
10
10
  let { buttons, store, columns }: Props = $props();
11
11
 
12
12
  const exportManager = new ExportManager();
13
+ let state = $state(store.getState());
14
+ let selectedRows = $derived(state.allRows.filter((row) => state.selectedIds.has(store.getRowId(row))));
15
+
16
+ $effect(() => {
17
+ return store.subscribe(() => {
18
+ state = store.getState();
19
+ });
20
+ });
13
21
 
14
22
  const exportLabels: Record<ExportFormat, string> = {
15
23
  csv: 'CSV',
@@ -32,15 +40,13 @@
32
40
 
33
41
  async function handleAction(btn: ButtonDef) {
34
42
  if (typeof btn.action === 'function') {
35
- const selected = store.getSelectedRows();
36
- const state = store.getState();
37
- await btn.action(selected, state.allRows);
43
+ await btn.action(selectedRows, state.allRows);
38
44
  }
39
45
  }
40
46
 
41
47
  function isDisabled(btn: ButtonDef): boolean {
42
48
  if (typeof btn.disabled === 'function') {
43
- return btn.disabled(store.getSelectedRows());
49
+ return btn.disabled(selectedRows);
44
50
  }
45
51
  return btn.disabled ?? false;
46
52
  }
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { ColumnDef, DataTableStore } from '../index.js';
2
+ import type { ColumnDef, DataTableStore } from '../index.ts';
3
3
  import type { Snippet } from 'svelte';
4
4
 
5
5
  interface Props {
@@ -21,6 +21,8 @@
21
21
 
22
22
  let value = $derived(row[column.key]);
23
23
 
24
+ // svelte-ignore state_referenced_locally
25
+
24
26
  let state = $state(store.getState());
25
27
  $effect(() => {
26
28
  return store.subscribe(() => {
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { ColumnDef, DataTableStore } from '../index.js';
2
+ import type { ColumnDef, DataTableStore } from '../index.ts';
3
3
 
4
4
  interface Props {
5
5
  store: DataTableStore;
@@ -8,6 +8,7 @@
8
8
  let { store, columns }: Props = $props();
9
9
 
10
10
  let open = $state(false);
11
+ // svelte-ignore state_referenced_locally
11
12
  let state = $state(store.getState());
12
13
 
13
14
  $effect(() => {
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { EditorFieldDef, DataTableStore, DataTableClassNames } from '../index.js';
2
+ import type { EditorFieldDef, DataTableStore, DataTableClassNames } from '../index.ts';
3
3
  import DataTableModalEditor from './DataTableModalEditor.svelte';
4
4
  import DataTableBubbleEditor from './DataTableBubbleEditor.svelte';
5
5
 
@@ -12,6 +12,8 @@
12
12
  }
13
13
  let { fields, store, classNames = {}, onsubmit, anchorEl = null }: Props = $props();
14
14
 
15
+ // svelte-ignore state_referenced_locally
16
+
15
17
  let state = $state(store.getState());
16
18
  $effect(() => {
17
19
  return store.subscribe(() => {