@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/README.md +347 -154
- package/dist/SvelarDatatablePlugin.d.ts +57 -2
- package/dist/index.js +7 -7
- package/dist/plugin.js +1 -1
- package/dist/server/DataTableService.d.ts +21 -0
- package/dist/server/index.js +1 -1
- package/dist/state/ServerDataTableStore.d.ts +2 -0
- package/dist/types.d.ts +2 -0
- package/package.json +82 -76
- package/src/state/ServerDataTableStore.ts +14 -0
- package/src/types.ts +2 -0
- package/src/ui/DataTable.svelte +8 -4
- package/src/ui/DataTableBody.svelte +3 -1
- package/src/ui/DataTableBubbleEditor.svelte +4 -1
- package/src/ui/DataTableButtons.svelte +12 -6
- package/src/ui/DataTableCell.svelte +3 -1
- package/src/ui/DataTableColumnToggle.svelte +2 -1
- package/src/ui/DataTableEditor.svelte +3 -1
- package/src/ui/DataTableEditorField.svelte +3 -1
- package/src/ui/DataTableEditorForm.svelte +1 -1
- package/src/ui/DataTableFooter.svelte +3 -1
- package/src/ui/DataTableHead.svelte +3 -1
- package/src/ui/DataTableModalEditor.svelte +4 -1
- package/src/ui/DataTablePagination.svelte +3 -1
- package/src/ui/DataTableRow.svelte +22 -3
- package/src/ui/DataTableSearch.svelte +3 -1
- package/src/ui/DataTableToolbar.svelte +3 -1
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(
|
|
3
|
-
`)}function x(
|
|
4
|
-
`);await navigator.clipboard.writeText(
|
|
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>${
|
|
17
|
-
<tbody>${
|
|
16
|
+
<thead><tr>${r}</tr></thead>
|
|
17
|
+
<tbody>${s}</tbody>
|
|
18
18
|
</table>
|
|
19
|
-
</body></html>`,
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}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
|
|
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
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
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.
|
|
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 &&
|
|
57
|
-
"dev": "tsup --watch"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"exceljs":
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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;
|
package/src/ui/DataTable.svelte
CHANGED
|
@@ -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.
|
|
4
|
-
import { DataTableStore } from '../state/DataTableStore.
|
|
5
|
-
import { ServerDataTableStore } from '../state/ServerDataTableStore.
|
|
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.
|
|
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.
|
|
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.
|
|
3
|
-
import { ExportManager } from '../export/ExportManager.
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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(() => {
|